下面简要介绍一下项目中的一些经验。
技术总结:
1.缓存系统
缓存系统的原理很简单。这里就不具体介绍。
public class Cache {
CacheData[] cachePool=null;
int size=10;
public Cache(int dataSize)
{
size=dataSize;
cachePool=new CacheData[size];//仅仅分配size个cacheData指针
int i;
for(i=0;i<size;i++)
{
cachePool[i]=new CacheData();
}
}
public Bitmap getBitmap(int currentRes,String fileName)
{
int i;
for(i=0;i<size;i++)
{
//把不在当前比例尺的缓存清空
if(cachePool[i].Resolution != currentRes && cachePool[i].data != null)
{
Log.d("xiaoxiao", "clear "+i+"res:"+currentRes);
cachePool[i].clear();
}
}
for(i=0;i<size;i++)
{
if(fileName.equals(cachePool[i].fileName))
{
cachePool[i].usedCount+=1;
//Log.d("xiaoxiao", "cache!");
int j=0;
for(j=0;j<size;j++)
{
if(j!=i)
cachePool[j].usedCount--;
}
return cachePool[i].data;
}
}
//Log.d("xiaoxiao", "can not find "+fileName);
//未找到
File file=new File(fileName);
if(!file.exists())
Log.d("xiaoxiao", "cachesys: file is not exists "+fileName);
//文件存在 也有可能加载图片失败
Bitmap map=BitmapFactory.decodeFile(fileName);
if(map==null)
{
//尝试10次重新加载
int j;
int times=0;
for(j=0;j<times;j++)
{
Log.d("xiaoxiao", "try "+j+" decode file error:"+fileName);
map=BitmapFactory.decodeFile(fileName);
if(map!=null)
break;
}
if(map==null)
{
Log.d("xiaoxiao", "decode file after "+times+" times even error:"+fileName);
return null;
}
}
int targetId=-1;
for(i=0;i<size;i++)
{
if(cachePool[i].data==null)
{
targetId=i;
break;
}
}
if(i>=size)
{
targetId=0;
for(i=1;i<size;i++)
{
if(cachePool[targetId].usedCount > cachePool[i].usedCount)
targetId=i;
}
}
//Log.d("xiaoxiao","cache in "+targetId+fileName);
cachePool[targetId].fillData(fileName, map, currentRes);
//cachePool[targetId].debug();
return map;
}
}
2.android jni的使用
贴再这里,算是复习吧。
public class FpdfembActivity extends Activity {
/** Called when the activity is first created. */
static
{
System.loadLibrary("fpdfemb");
}
public native static int openPDF(String pdfPath);
public native static int closePDF();
.....
}
3.实现自己的View之刷新界面
在自己实现的view里面,定义一个handler即可实现消息的响应。注意,view要刷新界面,只有自己在ui线程中调用invalidate()。工作线程要让view刷新界面,最好用发消息的形式,不建议直接调用invalidate函数。
要响应消息,在view里面定义一个handler并实现handlerMessage函数即可。
mHandler=new Handler(){
//接收到消息后处理
public void handleMessage(Message msg)
{
switch (msg.what)
{
case PDFView.REFRESH:
PDFView.this.invalidate(); //刷新界面
break;
}
super.handleMessage(msg);
}
};
注:PDFView.REFRESH是一个宏定义,值为1。
4.实现自己的View之发送刷新消息
在工作线程中要给UI线程发送消息,很简单。只有三句话:
Message message=new Message();
message.what=PDFView.REFRESH;
PDFView.this.mHandler.sendMessage(message);
5.工作线程
对于解析PDF这样需要很长时间需要很多计算量的任务来说,有必要交给工作线程来做。工作线程的实现,推荐以下模型:
public class PageThread extends Thread{
private Vector<PageData> msgVector=new Vector<PageData>();
public void reset()
{
msgVector.clear();
}
public synchronized void sendMessage(PageData msg){
//msgVector.add(msg);
Log.d("xiaoxiao","msg imgPath: "+msg.imgPath);
msgVector.add(0,msg);
}
public void run(){
while(true){.......}
msgVector是任务队列。reset函数可以清空任务队列。sendMessage函数负责填充任务队列。在while循环中,不停的从队列中取出任务并完成任务即可。
经验总结:
1.每次编译完ndk后,记得清空工程,不然难保证工程中用的是最新的编译库。
2.嵌入式设备上,最忌讳内存泄露,所以,写完代码后,最后仔细检查一下看看有无内存泄露的地方。最后,把关键的函数用for循环跑上1000遍,如果内存增加不多,说明代码质量不错。
3.写嵌入式上的程序,忌讳内存平凡申请和释放。最好自己做内存管理,即一上来就申请一个很大的内存块,以后每次需要内存,就从自己申请到的大内存中分一块出来用。
4.在嵌入式设备上,为了提高效率,减少计算量,常用缓存机制。例如,在本项目中,为了不减少绘图次数,将渲染后的bitmap存为png图片,下次需要显示的时候,直接贴图即可。
5.加载自己写的ndk库只能在activety里面。(在其他地方加载没有成功)
6.想要在自己实现的view里面拿到view的大小,必须在onDraw函数里面获取。因为view必须在初始化结束后才知道自己的尺寸。
7.在缓存中,一般用的是最少使用替换策略。在更新每个缓存元素的使用次数的时候,最好访问一次,当前元素使用次数加一,同时,其他的使用次数减一。
8.对于使用第三方库,每次调用函数后,最后都判断返回值,否则,程序会出现很多的安全隐患。
9.java中的线程安全,不光是访问冲突那么简单。例如线程安全的vector,如果有两个线程一个遍历vector,另外一个clear vertor,会出现很多意想不到的情况,最容易出现的是访问越界。这就是所谓的“逻辑冲突”。
10.android的刷新机制的理解:每一次invalidate(),不一定会触发一次刷新操作。10次连续的invalidate,可能刷新其中的6次。这是操作系统为了减少刷新次数而优化设计的机制。
11.在ondraw函数和onTouchEvent等消息响应函数,能够做尽量少的事情尽量少做,这样能够保证很好的响应速度。