mupdf不支持直接的多线程.multithread.c我也没看明白,android直接调用多线程就会莫名其妙的事,要么崩溃,要么渲染出的页面不对.
pdfium倒是可以直接多线程.但是解码速度要慢不少.
解码优化
解码优化有好多方面可以做;
队列优化,把当前显示中间的项,优先进行解码.
如果有缩略图,要优化解码.
解码前判断是否可见,因为队列是单线程的,当轮到它解码时,可能页面已经滑动过去
队列优化
用recyclerview实现的页面,不需要这么复杂,直接切边,然后解码页面,甚至不需要缩略图,滑动起来效果还是不错的,前提是把额外空间设置一个页面的高度,利用它的预加载能力.
recyelerview已经处理了页面的布局,可见性,回收,预加载等操作,而自定义的view是没有这些的.
定义两个队列,一个是解码缩略图的.另一个解码具体node的.
private final LinkedHashMap<String, DecodeTask> nodeTasks = new LinkedHashMap<>(32, 0.75f, false); private final LinkedHashMap<String, DecodeTask> pageTasks = new LinkedHashMap<>(32, 0.75f, false);
使用handlerthread来处理,因为单线程,所以也不开线程池了.
具体的消息调度:
public boolean handleMessage(Message msg) {int what = msg.what;if (what == MSG_DECODE_START) {addDecodeTask(msg);} else if (what == MSG_DECODE_SELECT) {selectDecodeTask(msg);} else if (what == MSG_DECODE_CANCEL) {cancelDecodeTask(msg);}return true;}
外部调用:
public void decodePage(String decodeKey, PageTreeNode node, int pageNumber, final DecodeCallback decodeCallback, float zoom, RectF pageSliceBounds, String thumbKey) {final DecodeTask decodeTask = new DecodeTask(node, pageNumber, decodeCallback, zoom, decodeKey, pageSliceBounds, thumbKey);Message message = Message.obtain();message.obj = decodeTask;message.what = MSG_DECODE_START;mHandler.sendMessage(message);}
这样就把任务添加进队列了.然后就是队列的循环去判断是否有缩略图的任务
添加任务的方法比较简单,就是判断有没有任务在.
private void addDecodeTask(Message msg) {final DecodeTask decodeTask = (DecodeTask) msg.obj;if (decodeTask.type == DecodeTask.TYPE_PAGE) {DecodeTask old = pageTasks.put(decodeTask.thumbKey, decodeTask);if (old != null) {Log.d(TAG, String.format("old page task:%s-%s", pageTasks.size(), old));}} else {DecodeTask old = nodeTasks.put(decodeTask.decodeKey, decodeTask);if (old != null) {Log.d(TAG, String.format("old node task:%s-%s", nodeTasks.size(), old));}}mHandler.sendEmptyMessage(MSG_DECODE_SELECT);}
选任务:
private void selectDecodeTask(Message msg) {if (isRecycled) {return;}DecodeTask selectTask = null;if (!pageTasks.isEmpty()) {selectTask = pageTasks.entrySet().iterator().next().getValue();pageTasks.remove(selectTask.thumbKey);}if (selectTask == null) {if (!nodeTasks.isEmpty()) {selectTask = nodeTasks.entrySet().iterator().next().getValue();nodeTasks.remove(selectTask.decodeKey);}}if (selectTask == null) {//mHandler.sendEmptyMessageDelayed(MSG_DECODE_SELECT, 5000L);Log.d(TAG, String.format("no task:%s-%s", pageTasks.size(), nodeTasks.size()));} else {Log.d(TAG, String.format("add task:%s-%s", selectTask.pageNumber, selectTask.type));try {performDecode(selectTask);} catch (IOException e) {Log.e(TAG, String.format("decode error:%s-%s", selectTask.pageNumber, selectTask.node));} finally {mHandler.sendEmptyMessage(MSG_DECODE_SELECT);}}}
先判断缩略图的队列,如果不为空,则运行它,如果为空,判断node队列.最后都要发消息去进行下一轮选择.
原来的任务是否死亡的判断就要修改了:
private boolean isTaskDead(DecodeTask task) {boolean isPage = false;if (task.node == null) {isPage = true;}if (skipInvisible(task, isPage)) {return true;}return false;}
缩略图优化
涉及到体验,如果图片未解码,显示的是黑白色,看画布的颜色了.这体验是不好的.
所以优先设置一张缩略图,然后把这图先放上去,每一块解码时,把这些填充上,就会有一个渐变的过程,从模糊到清晰的过程,这种体验比较好.尤其在缩放值比较大的时候.比如放大3倍了.滑动的时候,每一块解码耗时都不小.
设置缩略图.就要先解析缩略图.
所以当node进行解码时,先判断有没有缩略图.没有的话,先解码缩略图.然后放入缓存中,再画出来.在vudroid的page中去画.pagetreenode中的decode,添加进参数.缩略图的key生成一个与page相关的唯一值.
绘制缩略图
protected String getKey() {
return String.format("%s-%s", index, documentView.decodeService);
}
这时的draw就要修改了
public void draw(Canvas canvas) {if (!isVisible()) {return;}//canvas.drawRect(bounds, fillPaint);Bitmap thumb = BitmapCache.getInstance().getBitmap(getKey());Log.d("", String.format("index:%s,%s, %s", index, getKey(), thumb));if (null != thumb) {Rect src = new Rect(0, 0, thumb.getWidth(), thumb.getHeight());Rect dst = new Rect((int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom);canvas.drawBitmap(thumb, src, dst, null);} else {canvas.drawText("Page:" + (index + 1), bounds.centerX(), bounds.centerY(), textPaint);}node.draw(canvas);canvas.drawLine(bounds.left, bounds.bottom, bounds.right / 5, bounds.bottom, strokePaint);drawPageLinks(canvas);}
把小图画到大的范围,drawbitmap
通常有两种方法,一种是matrix,一种就是drawbitmap(bmp,src,dst,paint)
src取bitmap的大小生成一个框,dst就是目标的区域.这里使用的是第二种画法,比较简单.如果高宽比不对,图片会变形.