android中打开查看pdf文件可用mupdf

来源:互联网 发布:pop3 端口号 编辑:程序博客网 时间:2024/05/17 02:33

项目中用到过mupdf第三方库来开发android应用直接打开pdf文件的功能,为了更多的了解mupdf库上网找资料发现一篇个人文章写的还不错,拿来记录一下:

一时兴起

因为自己前段时间一直在做故事会的一个客户端,当然是非官方版,主要是由于自己的兴趣所致。以前就挺喜欢看故事会的,所以就希望做一个故事会的客户端,在手机上随时随地地看。因为故事会的官方APP的体验实在是太差了,而且资源还不全(而且还收费),所以我就打算自己做一个,然后去收集网上的资源。因为网上的故事会是以PDF文件的形式出现的,刚开始我想调用手机上的软件例如WPS等来阅读,后来一想反正做那就做PDF文件阅读功能吧,反正也没做过,正好试试呗。

一波三折

说做就做,刚开始使用的开源库是PDFView,就结果来说,软件体积太大了,而且使用的时候加载有点慢了,所以当时就不是太想使用这个库。后来没办法妥协呗,想了一个折中的办法,在开始的时候,利用PDFView提供的方法将PDF文件转换成一张张的图片保存到手机里,等加载的时候直接使用Picasso等加载图片的库来加载本地图片,这样总体来说还是不错的,直接加载图片比直接解析PDF文件要快很多,所以就出现了第一个版本,有兴趣的可以下载看一看。

后来,又随便在网上搜了搜,又找到一个不错的开源库,也就是MuPdf,这个和PDFView相比体积小,只有原来的一半,这已经是一个很大的诱惑了,没什么比体积小更让人扛不住的了。然后就开始找一些实现代码,最后找到了一个Eclipse版本的,它的阅读方式是横屏滑动阅读,我更喜欢竖屏阅读,所以就改成用ListView来显示,而且重要的是可以直接去显示,而不需要生成图片在手机里(因为这个做了实现封装),所以我就改了改,然后使用LruCache来保证不会出现内存溢出的情况,Demo运行还是不错的。

因为我的那个故事会客户端是使用AndroidStudio开发的,所以必须把MuPdf移植到AndroidStudio上。因为MuPdf需要调用so文件,所以我就先做了一个Demo,看看能不能运行出来,结果没问题再移植,然后悲催就开始了!!

本来so接触的就不多(其实很少%>_<%),所以怎么导入就是一个大问题,去网上搜有的说是建立jniLibs文件夹,然后放在里面,然而并无卵用;还有的说将so文件压缩成jar文件,然后放在libs里面,同样无卵用,最后真是要崩溃了。折腾了一下午,出去吃个晚饭,路上又搜了一种解决方法,告诉自己回去再试最后一次,不行我就放弃了,吃完饭回来试一下居然可以了,终于不在停止运行了。

问题出在哪我想暂时是找不到了,大概就是因为运行是没有正确的加载so文件,所以一些底层方法不能调用而崩溃了。

解决方法

    task nativeLibsToJar(type: Zip, description: "create a jar archive of the native libs"){        destinationDir file("$projectDir/libs")        baseName "Native_Libs2"        extension "jar"        from fileTree(dir: "libs", include: "**/*.so")        into "lib"    }    tasks.withType(JavaCompile){        compileTask -> compileTask.dependsOn(nativeLibsToJar)    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

将这一段代码加在build.gradle里,同时将libmupdf.so文件放在如下位置:




图中的Native_Libs2.jar是编译之后出现的,如果编译之后没有出现这个文件,那么可能就不会运行成功了。

哦,对了还要注意的是MuPdf的那些类的包名必须是com.artifex.mupdf,如下:




不然好像也不能运行成功。

柳暗花明

因为在AndroidStudio上可以运行成功了,所以我就打算将原来的客户端重新改写一下,将PdfViewer替换成MuPdf,一来可以减小APK的体积,二来也不用将PDF文件转换成图片来显示,很方便,等完成之后再将软件上传上来让有兴趣的看看。

下载

MuPdf的Demo-Eclipse版

MuPdf的Demo-AndroidStudio版

故事会客户端-PDFView版

故事会客户端-MuPdf版


附:经朋友测试,发现如果打开的PDF文件如果是损坏的话,那么应用就直接崩溃了,其次如果快速的关闭和打开应用,那么应用也会崩溃。这里我上传一份解决上述问题的版本(AndroidStudio版),它解决了上述问题,其实也就是换了一个so库,不过确实不会崩溃就是了。还需要注意的是新版的加载so库的方法和旧版不一样,大家在build.gradle中可以看到。

新版下载


看了作者写的上述过程感觉作者很用心在学习。但其中gradle文件中加的那个生成jar包的task我不太明白作者的目的,难道没有jar包就不能使用mupdf吗?我们项目里就没有其对应的jar包只有so文件啊。当然我们项目里直接将用so文件编写的一个demo引入到项目中了。





另一关于此知识的文章:

一、基本实现

       1,导入so库;


        2,声明库文件中方法

[java] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. </pre><pre name="code" class="java">public class MuPDFCore {  
  2.     /* load our native library */  
  3.     static {  
  4.         System.loadLibrary("mupdf");  
  5.     }  
  6.   
  7.     /* Readable members */  
  8.     private int pageNum = -1;;  
  9.     private int numPages = -1;  
  10.     public float pageWidth;  
  11.     public float pageHeight;  
  12.   
  13.     /* The native functions */  
  14.     private static native int openFile(String filename);  
  15.   
  16.     private static native int countPagesInternal();  
  17.   
  18.     private static native void gotoPageInternal(int localActionPageNum);  
  19.   
  20.     private static native float getPageWidth();  
  21.   
  22.     private static native float getPageHeight();  
  23.   
  24.     public static native void drawPage(Bitmap bitmap, int pageW, int pageH,  
  25.             int patchX, int patchY, int patchW, int patchH);  
  26.   
  27.     public static native RectF[] searchPage(String text);  
  28.   
  29.     public static native int getPageLink(int page, float x, float y);  
  30.   
  31.     public static native boolean hasOutlineInternal();  
  32.   
  33.     public static native boolean needsPasswordInternal();  
  34.   
  35.     public static native boolean authenticatePasswordInternal(String password);  
  36.   
  37.     public static native void destroying();  
  38.   
  39.     public MuPDFCore(String filename) throws Exception {  
  40.         if (openFile(filename) <= 0) {  
  41.             throw new Exception("Failed to open " + filename);  
  42.         }  
  43.     }  
  44.   
  45.     public int countPages() {  
  46.         if (numPages < 0)  
  47.             numPages = countPagesSynchronized();  
  48.         return numPages;  
  49.     }  
  50.   
  51.     private synchronized int countPagesSynchronized() {  
  52.         return countPagesInternal();  
  53.     }  
  54.   
  55.     /* Shim function */  
  56.     public void gotoPage(int page) {  
  57.         if (page > numPages - 1)  
  58.             page = numPages - 1;  
  59.         else if (page < 0)  
  60.             page = 0;  
  61.         if (this.pageNum == page)  
  62.             return;  
  63.         gotoPageInternal(page);  
  64.         this.pageNum = page;  
  65.         this.pageWidth = getPageWidth();  
  66.         this.pageHeight = getPageHeight();  
  67.     }  
  68.   
  69.     public synchronized PointF getPageSize(int page) {  
  70.         gotoPage(page);  
  71.         return new PointF(pageWidth, pageHeight);  
  72.     }  
  73.   
  74.     public synchronized void onDestroy() {  
  75.         destroying();  
  76.     }  
  77.   
  78.     public synchronized void drawPage(int page, Bitmap bitmap, int pageW,int pageH, int patchX, int patchY, int patchW, int patchH) {  
  79.         gotoPage(page);  
  80.         drawPage(bitmap, pageW, pageH, patchX, patchY, patchW, patchH);  
  81.     }  
  82.   
  83.     public synchronized int hitLinkPage(int page, float x, float y) {  
  84.         return getPageLink(page, x, y);  
  85.     }  
  86.   
  87.     public synchronized RectF[] searchPage(int page, String text) {  
  88.         gotoPage(page);  
  89.         return searchPage(text);  
  90.     }  
  91.   
  92.     public synchronized boolean hasOutline() {  
  93.         return hasOutlineInternal();  
  94.     }  
  95.   
  96.     public synchronized boolean needsPassword() {  
  97.         return needsPasswordInternal();  
  98.     }  
  99.   
  100.     public synchronized boolean authenticatePassword(String password) {  
  101.         return authenticatePasswordInternal(password);  
  102.     }  
  103. }  

            3,使用

[java] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. public class MuPDFActivity extends BaseActivity {  
  2.     private MuPDFCore core;  
  3.     private String mFileName;  
  4.     private ListView mDocListView;  
  5.     private View mButtonsView;  
  6.     private boolean mButtonsVisible;  
  7.     private EditText mPasswordView;  
  8.     private TextView mFilenameView;  
  9.     private SeekBar mPageSlider;  
  10.     private TextView mPageNumberView;  
  11.     private ViewSwitcher mTopBarSwitcher;  
  12.   
  13.     private ProgressBar loadingPB;  
  14.   
  15.     private MyMuPDFPageAdapter pdfPageAdapter;  
  16.   
  17.     @Override  
  18.     public void onCreate(Bundle savedInstanceState) {  
  19.         super.onCreate(savedInstanceState);  
  20.   
  21.         if (core == null) {  
  22.             File sdcardDir = Environment.getExternalStorageDirectory();  
  23.             // 得到一个路径,内容是sdcard的文件夹路径和名字  
  24.             String path = sdcardDir.getPath() + "/MyMobileDownlod/test.pdf";  
  25.   
  26.             int lastSlashPos = path.lastIndexOf('/');  
  27.             mFileName = new String(lastSlashPos == -1 ? path  
  28.                     : path.substring(lastSlashPos + 1));  
  29.             System.out.println("Trying to open " + path);  
  30.             try {  
  31.                 core = new MuPDFCore(path);  
  32.             } catch (Exception e) {  
  33.             }  
  34.   
  35.             if (core != null && core.needsPassword()) {  
  36.                 return;  
  37.             }  
  38.         }  
  39.         if (core == null) {  
  40.             AlertDialog alert = new AlertDialog.Builder(this).create();  
  41.             alert.setTitle(R.string.open_failed);  
  42.             alert.setButton(AlertDialog.BUTTON_POSITIVE, "Dismiss",  
  43.                     new DialogInterface.OnClickListener() {  
  44.                         public void onClick(DialogInterface dialog, int which) {  
  45.                             finish();  
  46.                         }  
  47.                     });  
  48.             alert.show();  
  49.             return;  
  50.         }  
  51.   
  52.         createUI(savedInstanceState);  
  53.     }  
  54.   
  55.   
  56.     public void createUI(Bundle savedInstanceState) {  
  57.         mDocListView = new ListView(this);  
  58.         pdfPageAdapter = new MyMuPDFPageAdapter(this, core);  
  59.         mDocListView.setAdapter(pdfPageAdapter);  
  60.   
  61.         mButtonsView = getLayoutInflater().inflate(R.layout.buttons, null);  
  62.         mFilenameView = (TextView) mButtonsView.findViewById(R.id.docNameText);  
  63.         loadingPB = (ProgressBar) mButtonsView.findViewById(R.id.loadingPB);  
  64.         mTopBarSwitcher = (ViewSwitcher) mButtonsView  
  65.                 .findViewById(R.id.switcher);  
  66.         mTopBarSwitcher.setDisplayedChild(0);  
  67.         mTopBarSwitcher.setVisibility(View.VISIBLE);  
  68.         loadingPB.setVisibility(View.INVISIBLE);  
  69.   
  70.         mFilenameView.setText("Name:" + mFileName  
  71.                 + String.format("    SumPage:%d", core.countPages()));  
  72.   
  73.         RelativeLayout layout = new RelativeLayout(this);  
  74.         layout.addView(mDocListView);  
  75.         layout.addView(mButtonsView);  
  76.         layout.setBackgroundResource(R.drawable.tiled_background);  
  77.         setContentView(layout);  
  78.     }  
  79.   
  80.     public void onDestroy() {  
  81.         if (core != null)  
  82.             core.onDestroy();  
  83.         core = null;  
  84.         super.onDestroy();  
  85.     }  
  86.   
  87.     @Override  
  88.     protected void dispatchMsgOP(Message msg) {  
  89.         super.dispatchMsgOP(msg);  
  90.         if (1 == msg.what) {  
  91.             Toast.makeText(MuPDFActivity.this"loading", Toast.LENGTH_SHORT)  
  92.                     .show();  
  93.             loadingPB.setVisibility(View.VISIBLE);  
  94.         } else {  
  95.             loadingPB.setVisibility(View.INVISIBLE);  
  96.         }  
  97.     }  
  98. }  
         4,展示效果【实际应用】


实际使用说明:

        当前PDF阅读器的实质是一个ListView,从而可以实现当前页面的重新布局,以达到实际需求。如:title栏变更效果,增加“分享”等后续操作。


二、so库的使用

1,导入so库源文件

so库分包有armeabi,arm64-v8a,armeabi-v7a等,是针对不同的ARM设备的包。armeabi,armeabi-v7a是32位ARM设备,arm64-v8a是64位ARM设备。arm64-v8a是向下兼容的,每个包有自己的优化和处理,最理想的条件是每个设备类型都有对应的so库文件。

注意事项是arm64-v8a中一定要有全部的armeabi的源文件,否则在调用到armeabi中独有的就会出错。当存在arm64-v8a中没有armeabi的so库文件时,只能删除整个包,保证程序能够正常运行。

2,关联库文件

[java] view plain copy print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:18px;">    static {  
  2.         System.loadLibrary("mupdf");//加载库文件  
  3.     }</span>  
生命so库文件中存在的方法,从而能够正常使用。后续调用,只是类本身之间的调用,遵循类的规则即可。


三、使用细节注意

        1,包名一致【JNI调用规则】

Java_ + 包名(com.lucyfyr) 类名(HelloWorld) + 接口名(printJNI):必须要按此JNI规范来操作;

so库文件中方法在使用时,必须保证使用环境的包名与so库文件编译生成的包名一致。


        2,so库的注意事项

注意事项:arm64-v8a中一定要有全部的armeabi的源文件,否则在调用到armeabi中独有文件方法时就会出错。当存在arm64-v8a中没有armeabi的so库文件时,只能删除整个包,保证程序能够正常运行。或者重新编译生成so库文件。


这里是源码
0 0
原创粉丝点击