Android:使用MuPDF显示PDF文件,解决不显示电子章问题

1,594 阅读8分钟
原文链接: blog.csdn.net

    前段时间在项目中要添加用PDF显示合同的功能,发现Andorid本身并不支持显示PDF,后来在网上搜索使用pdfView来显示,本身pdfView项目包挺大的,我使用了旧的版本自己优化了一下,使得自己的项目包只增加了1M左右的大小,本来以为就这样解决了,结果后来项目经理跟我说:APP的PDF上没有电子章,合同没有章怎么行.....然后又在网上各种找资料,在百度上搜愣是没有搜到任何关于android不显示PDF电子章的问题,后来还是IOS给了我 MuPDF,(他IOS自带的也没办法显示电子章)让我自己去研究。我在网上找了下资料,发现都是只有一段一段的,在我完成嵌入之后就想把我嵌入的过程从头记录下来。

    我在网上搜过集成好的项目,发现很多都是需要积分下载的,在github上找的包有点大,后面我看到有源码可以自己编译,我就想着干脆自己编译吧,随便也让自己熟悉下linux,这里有个参考的文章,我基本是按照他的步骤来的:

我使用的系统是ubuntu,首先是安装NDK:

sudo apt-get install build-essential

2、下载 解压NDK,我是在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

验证是否安装成功,显示以下内容表示已经安装成功:


然后我们要下载MuPDF的源码,把它编译成so文件:

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地址: github.com/muennich/mu…

进入到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<String> 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<String, List<String>> 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这个第三方控件的功能非常多,我只使用了显示功能,他默认的横向滑动我也没有改,由于我项目展示的是公司和客户的合同,所以就不展示我配置好的效果了。