最近开发了一款和LBS相结合的android软件。项目虽小,但在开发过程中遇到的问题却是不少。今天写此博文,一为总结,再次理一下在解决问题时的思考过程;二为分享,希望遇到同样问题的童鞋们有一个参考;三为交流学习,虽然有些解决方法没有bug,但性能上本人还不甚满意,如果您有更好的解决办法或发现任何问题,欢迎批评指正,多多交流学习。
下面列举出我所遇到的问题及解决方法(注:所有测试都是基于android 4.0.4系统)。
1).百度地图刷新得不到及时响应,出现灰色区域
解决方法:在manifest文件中的application标签下添加 android:hardwareAccelerated="false",但添加之后还是不行。最后在 非主activity(主activity:启动软件显示的第一个activity)中添加 android:hardwareAccelerated="true",启用硬件加速,然后地图响应就很流畅了。
2).用腾讯微博开放平台发表带图片的微博时抛NetWorkOnMainThreadException
解决方法:在onCreate方法中添加如下代码就OK了:
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads().detectDiskWrites().detectNetwork()
.penaltyLog().build());
在2.3以前的系统中不会出现此问题。或者通过异步方式进行此操作,因为它说的是**OnMainThread**,那么不在主线程中进行这种耗时的网络操作,就不会有问题了。
3).Context的使用问题
大家都知道,context容易引发内存溢出,而且很隐蔽,所以大多数情况都尽量使用Application context,因为这种context和Application有相同的生命周期,不会引起内存泄露。但当我要显示一个AlertDialog时,使用Application context就会抛出异常:android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application,必须使用Activity context。查看google提供的文档,Activity->ContextThemeWrapper->ContextWrapper->Context;Application->ContextWrapper->Context,可以看出Context是Activity和Application的父类,通过Activity和Application都可以获得Context对象。但问题就是,显示dialog相当于显示一个自定义窗口,Application context是无法获得Window对象的,而Activity可以通过getWindow()方法获得Window对象。为了防止context引发的内存溢出,请记住以下3点:
- 不要让生命周期长的对象引用activity context,即保证引用activity的对象要与activity本身生命周期是一样的
- 对于生命周期长的对象,可以使用application context
- 避免非静态的内部类,尽量使用静态内部类,避免生命周期问题,注意内部类对外部对象引用导致的生命周期变化
这是我遇到的最头疼的一个问题,在网上也查了很多资料,大多数都一样。如弱引用(WeakReference),软引用(SoftReference)等,这些在一定程度上的确可以优化,但并不能彻底解决问题,图片很多的时候,还是会出现内存溢出。最主要的还是要及时回收bitmap,这个及时,,如何及时?在网上看到一些方法:
//前面还有部分代码未贴出
originalImage = BitmapFactory.decodeFile(path, options);
width = originalImage.getWidth();
height = originalImage.getHeight();
Bitmap bitmapWithReflection = Bitmap.createBitmap(dw,
dh, Config.ARGB_8888);
Canvas canvas = new Canvas(bitmapWithReflection);
canvas.drawBitmap(originalImage, 0, 0, null);
ImageView imageView = new ImageView(mContext);
imageView.setImageBitmap(originalImage);
imageView.setLayoutParams(new GalleryFlow.LayoutParams(width, height));
mImages[index++]=imageView;
bitmapWithReflection.recycle();
bitmapWithReflection = null;
System.gc();
这样如果马上回收originalImage,图片就无法显示;显示后再回收?由于每初始化一张图片,originalImage对象就会指向另一块内存,而指向原来的bitmap内存的对象则被覆盖了,当我们要回收该bitmap时,却发现找不到指向该内存的对象,自然也就无法回收了。
我的解决方法是:延迟加载图片,每初始化一张图片,就把bitmap对象存储在一个Bitmap数组中,ImageView再从该数组中取得对应的bitmap。需要回收的时候,可以通过数组中bitmap对象来释放所占用的内存。不管有多少图片,都可以保证在内存中的图片数量不超过M张(M可由自己控制,如8张。通过option参数可以控制加载到内存的图片的大小,一般800*600*4=1920000byte=1.8M,8张一般不会超出android单个程序内存限制)。部分代码如下:
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView==null){
if(mImages[position]==null && bitmaps[position]==null){
int high = position+mem_size;
/**
* 当图片的索引位置大于或等于mem_size时,要考虑回收两头的bitmap内存;
* 否则只需考虑回收后面的内存。
*/
if(position>=mem_size){
int low=position-mem_size;
if(bitmaps[low]!=null){
this.destory(low);
}
if(high<size && bitmaps[high]!=null){
this.destory(high);
}
}else{
if(high<size && bitmaps[high]!=null){
this.destory(high);
}
}
System.gc();
try {
ImageView imageView = new ImageView(mContext);
bitmaps[position]=this.createView(paths.get(position));
imageView.setImageBitmap(bitmaps[position]);
imageView.setLayoutParams(new GalleryFlow.LayoutParams(width, height));
mImages[position]=imageView;
} catch (Exception e) {
e.printStackTrace();
}finally{
convertView=mImages[position];
}
}else{
convertView=mImages[position];
}
}
return convertView;
}
private void destory(int index){
bitmaps[index].recycle();
bitmaps[index]=null;
mImages[index]=null;
}
算是解决了bitmap内存溢出问题,但是性能上还有待优化。