前段时间在项目中要添加用PDF显示合同的功能,发现Andorid本身并不支持显示PDF,后来在网上搜索使用pdfView来显示,本身pdfView项目包挺大的,我使用了旧的版本自己优化了一下,使得自己的项目包只增加了1M左右的大小,本来以为就这样解决了,结果后来项目经理跟我说:APP的PDF上没有电子章,合同没有章怎么行.....然后又在网上各种找资料,在百度上搜愣是没有搜到任何关于android不显示PDF电子章的问题,后来还是IOS给了我MuPDF,(他IOS自带的也没办法显示电子章)让我自己去研究。我在网上找了下资料,发现都是只有一段一段的,在我完成嵌入之后就想把我嵌入的过程从头记录下来。
我在网上搜过集成好的项目,发现很多都是需要积分下载的,在github上找的包有点大,后面我看到有源码可以自己编译,我就想着干脆自己编译吧,随便也让自己熟悉下linux,这里有个参考的文章,我基本是按照他的步骤来的:
1、安装make build工具:
sudo apt-get install build-essential
2、下载 解压NDK,我是在http://www.androiddevtools.cn/ 里面下载的,下载14的版本
然后解压,从上面网站下载的是 zip格式的,使用命令:
unzip android-ndk-r14b-linux-x86_64.zip
如果是tar.gz格式的使用:
tar -xvf xxxxx.tar.gz
3、配置环境变量:
先切换到ndk解压的目录,使用pwd命令查询到目录地址,然后复制:
然后使用以下命令打开文件进行配置
sudo vim ~/.bashrc
然后在文件最底部配置以下参数:
export NDK_HOME=/home/wzl/bin/andorid-ndk-r14b export PATH=$PATH:$NDK_HOME
最后使用命令:
ndk-build -v
验证是否安装成功,显示以下内容表示已经安装成功:
1、下载解压源码:
https://mupdf.com/downloads/ 这是源码的下载地址,最新的是1.12,它的大小有49M,我下载的是1.6版本的,大小只有13M(如果想使用它自带的so包,可以下载APK然后解压,里面有编译好的so包,如果使用它编译好的so包可以跳过这一段,直接看下面放入项目中使用的部分):
下载完成后,解压代码:
tar -xvf mupdf-1.6-source.tar.gz
2、然后进入解压后的目录,使用以下命令,执行完成后项目中会有一个generted文件夹 (
注意一定要执行这个命令,我之前因为没有执行导致编译失败
)
make generate
3、修改编译前的文件配置:
进入platform目录,里面有个android文件夹,进入android文件夹,里面文件内容:
把里面的local.properties.sample文件改成 local.properties,使用命令:
sudo mv local.properties.sample local.properties
使用命令打开文件:
sudo vim local.properties
网上说要配置SDK和NDK目录,我自己只配置了NDK目录,不过我后来测试不配置也是可以编译的:
ndk.dir=/home/wzl/bin/andorid-ndk-r14b
配置好了保存退出,然后进入jni目录,打开Application.mk文件(我使用的是root用户,如果不是记得在命令前加sudo):
里面可以选择你想编译哪个架构的so包,每次编译一个,我打开注释的是我需要编译的armeabi:
4、编译
回到android目录执行 ndk-build命令:
第一次编译大概在一分钟左右,显示以下内容说明编译成功:
最后有文件存放的目录,在当前文件夹下的/libs/armeabi/下,进入文件夹应该可以看到我们刚刚编译成功的so包:
如果使用虚拟机编译的话,可以使用共享把so包共享到windows上,不知道怎么共享的可以点击这里
编译好的so文件大概在9M左右
(这个补充来自这里),这个补充可以把它默认的包名改成自己项目的包名,到自己使用的时候可能会方便一些
使用上面的步骤得到的so文件的的包名是com.artifex.mupdfdemo,
因为src里面的类是的包名就是这个。如果我们想要改变这个包名,
当然有人这么问过我,我就回答不可以,因为我不会,早知道回答我不会了(%>_<%)。既然编译了源码,那么这个问题也就应该解决一下。
首先来看一下com.artifex.mupdfdemo这个是在哪出现的。
我们来看一下jni目录下的mupdf.c文件,里面的内容很多,需要看的内容如下:
#define JNI_FN(A) Java_com_artifex_mupdfdemo_ ## A
#define PACKAGENAME "com/artifex/mupdfdemo"
可以看到,包名就是在这里定义的,所以我们在这个把包名改了,如下:
#define JNI_FN(A) Java_com_app_mupdf_ ## A
#define PACKAGENAME "com/app/mupdf"
当然这个随意,根据自己的需要来改,改完之后就可以ndk-build了,接下来使用类的时候把类的包名也改一下就可以了。
如何在项目中使用so包,这里就不介绍了,详细的可以百度,下面说的是如何使用我们编译好的libmupdf.so包里面的方法,使它能够显示我们的PDF文件。
首先如果你是直接编译没有修改编译的包名,那么你需要在你的项目中新建一个Module,包名用com.artifex.mupdfdemo,
必须要使用这个包名,除非你的项目的包名和这个一样;然后把so包放进Module里面。如果你是用自己的包名编译的就不用新建Module项目了,把它和自己项目中使用到的so包放到一起就行。使用so包的代码可以看它官方提供的github地址:https://github.com/muennich/mupdf 从这里面找到1.6的版本进入它的demo,把它的代码拷贝到module项目中,详细流程可以看截图:
进入到platform/andorid目录
把圈起来的目录下的代码全部复制进去,注意res下的代码只需要复制自己需要的就行,可以先复制src下的,然后看项目需要哪些就去res下去找,最后要运行的话还要记得注册AndroidManifest.xml文件,把里面注册Acitity的代码复制到我们的module中
官方代码中的默认启动的Activity是ChoosePDFActivity,在这个activity的onListItemClick方法里面有下面几行代码:
Uri uri = Uri.parse(mFiles[position].getAbsolutePath());
Intent intent = new Intent(this,MuPDFActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.setData(uri);
从代码中可以看到这个ChoosePDFActivity只是并不最重要的activity,MuPDFActivity才是,如果可以接受官方自带的界面的话,只要把上面的代码复制到自己Activity要跳转的地方,把Uri里面的参数改成我们的自己PDF文件的路径就行了,注意是要本地的文件路径,MuPDF好像不支持下载PDF,所以需要我们先把PDF下载到本地,然后再使用MuPDF显示;如果要自定义界面的话主要的调用代码可以去MuPDFActivity找,下面是我自己项目中使用的代码:
public class PDFActivity extends BaseActivity implements FilePicker.FilePickerSupport{
@Bind(R.id.pageNumber)
TextView mPageNumberView;
@Bind(R.id.rela_layout)
RelativeLayout mLayout;
private String mFileName;
private MuPDFCore core;
private MuPDFReaderView mDocView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pdf);
ButterKnife.bind(this);
setSwipeBackEnable(false);
initData();
}
@Override
protected void initData() {
String url = getIntent().getStringExtra(Constants.KEY);
if (!TextUtils.isEmpty(url)) {
//判断文件是否存在 如果存在直接从文件读取 如果不存在去服务器下载
String fileName = url.substring(url.lastIndexOf("=") + 1) + ".pdf";// 解析fileName
final String mFile = Environment.getExternalStorageDirectory().getPath() + "/" + Environment.DIRECTORY_DOWNLOADS + "/PDFFile";
File file = new File(mFile);
if (!file.exists()){
file.mkdirs();
}
String path = mFile +"/"+fileName;
File filePath = new File(path);
if (filePath.exists()){
showPDFView(filePath);
}else {
showLoading();
downloadPDF(url,path);
}
}
}
/**
* 先下载PDF然后再设置到PDFview
* @param mUrl
*/
private void downloadPDF(final String mUrl,final String path) {
new Thread(new Runnable() {
@Override
public void run() {
InputStream input = null;
OutputStream output = null;
try {
URL url = new URL(mUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
Set cookies = SharePerferUtil.getPreference().getCookie();
for (String cookie : cookies) {
connection.setRequestProperty(Constants.COOKIE, cookie);
}
//设置超时间为3秒
connection.setConnectTimeout(3 * 1000);
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept-Encoding", "identity");
connection.connect();
input = new BufferedInputStream(connection.getInputStream());
output = new FileOutputStream(path);
byte data[] = new byte[1024];
int count;
while ((count = input.read(data)) != -1) {
output.write(data, 0, count);
}
final String code = parseJsonResultByBytes(data);
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
if (TextUtils.isEmpty(code)){
File file = new File(path);
showPDFView(file);
}else {
//code不为空,说明请求PDF失败
showInfo("未登录!请先登录");
}
hideLoading();
}catch (Exception e){}
}
});
} catch (Exception e) {
e.printStackTrace();
}finally {
if (output != null){
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (input != null){
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
/**
* 显示PDF文件
* @param file
*/
private void showPDFView(File file) {
Uri uri = Uri.parse(file.getAbsolutePath());
core = openFile(Uri.decode(uri.getEncodedPath()));
if (core == null){
showInfo("显示PDF失败");
return;
}
mDocView = new MuPDFReaderView(PDFActivity.this) {
@Override
protected void onMoveToChild(int i) {
if (core == null)
return;
mPageNumberView.setText(String.format("%d / %d", i + 1,
core.countPages()));
super.onMoveToChild(i);
}
@Override
protected void onTapMainDocArea() {
if (mPageNumberView.getVisibility() == GONE){
mPageNumberView.setVisibility(VISIBLE);
}else {
mPageNumberView.setVisibility(GONE);
}
}
};
mDocView.setAdapter(new MuPDFPageAdapter(PDFActivity.this, PDFActivity.this, core));
mPageNumberView.setText(String.format("%d / %d", 1, core.countPages()));
mLayout.addView(mDocView);
}
/**
* 打开PDF文件
* @param path
* @return
*/
private MuPDFCore openFile(String path)
{
int lastSlashPos = path.lastIndexOf('/');
mFileName = new String(lastSlashPos == -1
? path
: path.substring(lastSlashPos+1));
System.out.println("Trying to open "+path);
try
{
core = new MuPDFCore(this, path);
// New file: drop the old outline data
OutlineActivityData.set(null);
}
catch (Exception e)
{
System.out.println(e);
return null;
}
return core;
}
//将byte解析成json
private String parseJsonResultByBytes(byte[] bytes){
String jsonString = getStringByBytes(bytes);
try {
JSONObject obje = new JSONObject(jsonString);
String code = obje.optString("code");
return code;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//读取响应头
private int getResponseHeaderStatus(HttpURLConnection conn) {
Map> responseHeaderMap = conn.getHeaderFields();
int size = responseHeaderMap.size();
int status = -1;
for(int i = 0; i < size; i++){
String responseHeaderKey = conn.getHeaderFieldKey(i);
if (responseHeaderKey.equals("X-Android-Response-Source")){
String responseHeaderValue = conn.getHeaderField(i);
int index = responseHeaderValue.indexOf(" ");
String value = responseHeaderValue.substring(index+1);
status = Integer.parseInt(value);
Log.e("TAG",value);
break;
}
// sbResponseHeader.append(responseHeaderKey);
// sbResponseHeader.append(":");
// sbResponseHeader.append(responseHeaderValue);
// sbResponseHeader.append("\n");
}
return status;
}
//根据字节数组构建UTF-8字符串
private String getStringByBytes(byte[] bytes) {
String str = "";
try {
str = new String(bytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return str;
}
@Override
protected void addListener() {}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
public void performPickFor(FilePicker picker) {
}
}
MuPDF这个第三方控件的功能非常多,我只使用了显示功能,他默认的横向滑动我也没有改,由于我项目展示的是公司和客户的合同,所以就不展示我配置好的效果了。