mupdf不支持直接的多线程.multithread.c我也没看明白,android直接调用多线程就会莫名其妙的事,要么崩溃,要么渲染出的页面不对.
pdfium倒是可以直接多线程.但是解码速度要慢不少.平均速度,100页,多个pdf对比,要慢50-200%,这个差距是明显的.
多线程,我最后发现,即使这方面已经做到极致,缓存也尽量最大化,渲染块的速度在最终的体验上,依然不尽如人意.可能是本人的能力问题吧.研究过radaee的sdk源码,商业产品效率与稳定性都极高,它已经加强了cjk的支持,原来不能显示的pdf也正常了,渲染速度之快,我比不了.静读天下就是用它的sdk.
解码优化
解码优化有好多方面可以做;
队列优化,把当前显示中间的项,优先进行解码.
如果有缩略图,要优化解码.
解码前判断是否可见,因为队列是单线程的,当轮到它解码时,可能页面已经滑动过去
队列优化
用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
通常有两种方法,一种是mat