项目中用到过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文件转换成图片来显示,很方便,等完成之后再将软件上传上来让有兴趣的看看。
下载
附:经朋友测试,发现如果打开的PDF文件如果是损坏的话,那么应用就直接崩溃了,其次如果快速的关闭和打开应用,那么应用也会崩溃。这里我上传一份解决上述问题的版本(AndroidStudio版),它解决了上述问题,其实也就是换了一个so库,不过确实不会崩溃就是了。还需要注意的是新版的加载so库的方法和旧版不一样,大家在build.gradle中可以看到。
看了作者写的上述过程感觉作者很用心在学习。但其中gradle文件中加的那个生成jar包的task我不太明白作者的目的,难道没有jar包就不能使用mupdf吗?我们项目里就没有其对应的jar包只有so文件啊。当然我们项目里直接将用so文件编写的一个demo引入到项目中了。
另一关于此知识的文章:
一、基本实现
1,导入so库;
2,声明库文件中方法
- </pre><pre name="code" class="java">public class MuPDFCore {
- /* load our native library */
- static {
- System.loadLibrary("mupdf");
- }
- /* Readable members */
- private int pageNum = -1;;
- private int numPages = -1;
- public float pageWidth;
- public float pageHeight;
- /* The native functions */
- private static native int openFile(String filename);
- private static native int countPagesInternal();
- private static native void gotoPageInternal(int localActionPageNum);
- private static native float getPageWidth();
- private static native float getPageHeight();
- public static native void drawPage(Bitmap bitmap, int pageW, int pageH,
- int patchX, int patchY, int patchW, int patchH);
- public static native RectF[] searchPage(String text);
- public static native int getPageLink(int page, float x, float y);
- public static native boolean hasOutlineInternal();
- public static native boolean needsPasswordInternal();
- public static native boolean authenticatePasswordInternal(String password);
- public static native void destroying();
- public MuPDFCore(String filename) throws Exception {
- if (openFile(filename) <= 0) {
- throw new Exception("Failed to open " + filename);
- }
- }
- public int countPages() {
- if (numPages < 0)
- numPages = countPagesSynchronized();
- return numPages;
- }
- private synchronized int countPagesSynchronized() {
- return countPagesInternal();
- }
- /* Shim function */
- public void gotoPage(int page) {
- if (page > numPages - 1)
- page = numPages - 1;
- else if (page < 0)
- page = 0;
- if (this.pageNum == page)
- return;
- gotoPageInternal(page);
- this.pageNum = page;
- this.pageWidth = getPageWidth();
- this.pageHeight = getPageHeight();
- }
- public synchronized PointF getPageSize(int page) {
- gotoPage(page);
- return new PointF(pageWidth, pageHeight);
- }
- public synchronized void onDestroy() {
- destroying();
- }
- public synchronized void drawPage(int page, Bitmap bitmap, int pageW,int pageH, int patchX, int patchY, int patchW, int patchH) {
- gotoPage(page);
- drawPage(bitmap, pageW, pageH, patchX, patchY, patchW, patchH);
- }
- public synchronized int hitLinkPage(int page, float x, float y) {
- return getPageLink(page, x, y);
- }
- public synchronized RectF[] searchPage(int page, String text) {
- gotoPage(page);
- return searchPage(text);
- }
- public synchronized boolean hasOutline() {
- return hasOutlineInternal();
- }
- public synchronized boolean needsPassword() {
- return needsPasswordInternal();
- }
- public synchronized boolean authenticatePassword(String password) {
- return authenticatePasswordInternal(password);
- }
- }
3,使用
- public class MuPDFActivity extends BaseActivity {
- private MuPDFCore core;
- private String mFileName;
- private ListView mDocListView;
- private View mButtonsView;
- private boolean mButtonsVisible;
- private EditText mPasswordView;
- private TextView mFilenameView;
- private SeekBar mPageSlider;
- private TextView mPageNumberView;
- private ViewSwitcher mTopBarSwitcher;
- private ProgressBar loadingPB;
- private MyMuPDFPageAdapter pdfPageAdapter;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (core == null) {
- File sdcardDir = Environment.getExternalStorageDirectory();
- // 得到一个路径,内容是sdcard的文件夹路径和名字
- String path = sdcardDir.getPath() + "/MyMobileDownlod/test.pdf";
- 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(path);
- } catch (Exception e) {
- }
- if (core != null && core.needsPassword()) {
- return;
- }
- }
- if (core == null) {
- AlertDialog alert = new AlertDialog.Builder(this).create();
- alert.setTitle(R.string.open_failed);
- alert.setButton(AlertDialog.BUTTON_POSITIVE, "Dismiss",
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- finish();
- }
- });
- alert.show();
- return;
- }
- createUI(savedInstanceState);
- }
- public void createUI(Bundle savedInstanceState) {
- mDocListView = new ListView(this);
- pdfPageAdapter = new MyMuPDFPageAdapter(this, core);
- mDocListView.setAdapter(pdfPageAdapter);
- mButtonsView = getLayoutInflater().inflate(R.layout.buttons, null);
- mFilenameView = (TextView) mButtonsView.findViewById(R.id.docNameText);
- loadingPB = (ProgressBar) mButtonsView.findViewById(R.id.loadingPB);
- mTopBarSwitcher = (ViewSwitcher) mButtonsView
- .findViewById(R.id.switcher);
- mTopBarSwitcher.setDisplayedChild(0);
- mTopBarSwitcher.setVisibility(View.VISIBLE);
- loadingPB.setVisibility(View.INVISIBLE);
- mFilenameView.setText("Name:" + mFileName
- + String.format(" SumPage:%d", core.countPages()));
- RelativeLayout layout = new RelativeLayout(this);
- layout.addView(mDocListView);
- layout.addView(mButtonsView);
- layout.setBackgroundResource(R.drawable.tiled_background);
- setContentView(layout);
- }
- public void onDestroy() {
- if (core != null)
- core.onDestroy();
- core = null;
- super.onDestroy();
- }
- @Override
- protected void dispatchMsgOP(Message msg) {
- super.dispatchMsgOP(msg);
- if (1 == msg.what) {
- Toast.makeText(MuPDFActivity.this, "loading", Toast.LENGTH_SHORT)
- .show();
- loadingPB.setVisibility(View.VISIBLE);
- } else {
- loadingPB.setVisibility(View.INVISIBLE);
- }
- }
- }
实际使用说明:
当前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,关联库文件
- <span style="font-size:18px;"> static {
- System.loadLibrary("mupdf");//加载库文件
- }</span>
三、使用细节注意
1,包名一致【JNI调用规则】
Java_ + 包名(com.lucyfyr) + 类名(HelloWorld) + 接口名(printJNI):必须要按此JNI规范来操作;
so库文件中方法在使用时,必须保证使用环境的包名与so库文件编译生成的包名一致。
2,so库的注意事项
注意事项:arm64-v8a中一定要有全部的armeabi的源文件,否则在调用到armeabi中独有文件方法时就会出错。当存在arm64-v8a中没有armeabi的so库文件时,只能删除整个包,保证程序能够正常运行。或者重新编译生成so库文件。