内存泄露:实质应用程序不能及时释放内存或者加载到内存上的数据太大而导致的OOM问题
窗体泄露:是指Activity或者Fragment在Destory的情况下启用了窗体API
StackOverflowError:应用程序调用中,导致栈空间无限延长,超过了虚拟机的承载能力
消息队列遵循先进先出(First in First out)的原则来讲某些信息或者任务进行排队等待。android中有自己的消息队列,如Handler和Broadcastreceiver。
andriod提供了 Handler 和 Looper 来满足线程间的通信。Handler 先进先出原则。Looper类用来管理特定线程内对象之间的消息交换(Message Exchange)。 1)Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的Message Queue(消息队列)。 2)Handler: 你可以构造Handler对象来与Looper沟通,以便push新消息到Message Queue里;或者接收Looper从Message Queue取出)所送来的消息。 3) Message Queue(消息队列):用来存放线程放入的消息。 4)线程:UI thread 通常就是main thread,而Android启动程序时会替它建立一个Message Queue。
Handler的集中创建方式和用法
Handler mHandler = new Handler() { public void handleMessage(Message msg) { switch (msg.what) { case TestHandler.GUIUPDATEIDENTIFIER: myBounceView.invalidate(); break; } super.handleMessage(msg); } };
在工作线程中创建(异步)Handler
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { } }; Looper.loop(); }
使用HandlerThread创建(异步)Handler
HandlerThread handlerThread = new HandlerThread("my.handlerthread"); handlerThread.start(); Handler mHandler = new Handler(handlerThread.getLooper()){ public void handleMessage(Message msg) { } };
Handler的用法很多,这里只贴出线程通信的用法,读者可以自行深入研究其他用法
class myThread implements Runnable { public void run() { while (!Thread.currentThread().isInterrupted()) { //这里可以使用 new,因为主线程中的消息队列只有一条 Message message = new Message(); message.what = 1024; TmHandler.sendMessage(message); try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } }
当我们这样写在一个Activity中时,Android Lint会提示我们这样一个 warning: In Android, Handler classes should be static or leaks might occur.。 意思说:在Android中,Handler 类应该是静态的否则可能发生泄漏 同样在Eclipse代码编辑区域的 Handler定义行也会出现类似的警告,一般都会加上supresslint的注解
先来看看,为什么会出现这种问题:
1.当Android程序第一次创建的时候,在主线程同时会创建一个Looper对象。Looper实现了一个简单的消息队列,一个接着一个处理Message对象。 程序框架所有主要的事件(例如:屏幕上的点击时间,Activity生命周期的方法等等)都包含在Message对象中,然后添加到Looper的消息队列中, 一个一个处理。主线程的Looper存在整个应用程序的生命周期内。
2.当一个Handler对象在主线程中创建的时候,它会关联到Looper的 message queue 。Message添加到消息队列中的时候Message 会持有当前Handler引用,当Looper处理到当前消息的时候,会调用Handler#handleMessage(Message).
3.在java中,no-static的内部类会 隐式的 持有当前类的一个引用。static的类则没有。
综上三点可知,这种泄露非常危险,Activity或者被持有者(宿主)不能及时释放内存,Looper也在不断循环,是导致内存泄露原因之一,
原因之二是 Activity未被回收,当Activity处于非活动状态时,如果handlerMessage的处理导致UI的改变,将会导致窗体泄露,比如弹框,UI改变等。
/** * 使用静态的内部类,不会持有当前对象的引用 */ private static class MyHandler extends Handler { private SoftReference<Activity> mActivityReference = null; public MyHandler(SampleActivity activity) { mActivityReference = new SoftReference<Activity>(activity) } @Override public void handleMessage(Message msg) { SampleActivity activity = (SampleActivity )mActivityReference.get(); if (activity != null) { // ... } } public void release() { removeCallbacksAndMessages(null); mActivityReference.clear(); } } private final MyHandler mHandler = new MyHandler(this);
在Activity的OnDestroy中调用
public void onDestroy() { mHandler.release(); }
有些时候,Handler无需自己定义,我们可以使用View自身提供的Handler进行操作
myView.post()
myView.postDelay()
myView.getHandler()
runOnUiThread()
.............................
LocalBroadcastReceiver
BroadcastReceiver
或者使用开源方案EventBus
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
对于广播,或者一些Runnable,CallBacks甚至普通内部类,我们要避免全局化,导致变量不能被释放,从而对象也不能释放
这里所说的全局化是被静态对象持有,比如单例,全局静态List,Map等
add——remove
addAll——clearAll
register——unregister
bind——unbind
这里相关的主要有View,ViewTreeObserver,ContentObserver,static Map,static List, static SparseArray,singleObject如
View.addOnAttachStateChangeListener
View.addOnLayoutChangeListener
ViewTreeObserver.add
registerBroadcast
bindService
registerContentObserver
........
建议使其持有ApplicationContext对象,而不是Service,BroadCastReceiver或者Activity
举个栗子
android.support.v4.content.LocalBroadcastManager的getInstance
private static LocalBroadcastManager mInstance; public static LocalBroadcastManager getInstance(Context context) { if(mInstance == null) mInstance = new LocalBroadcastManager(context.getApplicationContext()); return mInstance; }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Android中图片有四种属性,分别是:
ALPHA_8:每个像素占用1byte内存
ARGB_4444:每个像素占用2byte内存
ARGB_8888:每个像素占用4byte内存 (默认)
RGB_565:每个像素占用2byte内存
Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。 所以在对图片效果不是特别高的情况下使用RGB_565(565没有透明度属性)
publicstaticBitmapreadBitMap(Contextcontext, intresId) { BitmapFactory.Optionsopt = newBitmapFactory.Options(); opt.inPreferredConfig = Bitmap.Config.RGB_565; opt.inPurgeable = true; opt.inInputShareable = true; //获取资源图片 InputStreamis = context.getResources().openRawResource(resId); returnBitmapFactory.decodeStream(is, null, opt); } // 先判断是否已经回收 if(bitmap != null && !bitmap.isRecycled()){ // 回收并且置为null bitmap.recycle(); bitmap = null; }
具体用法请参考http://my.oschina.net/ososchina/blog/355721
内部类隐式持有主类的当前对象,对于内部类需不需要和Handler一样进行静态化,完全取决于内部类会不会被全局对象引用,如果被全局对象引用,那么static,否则no-static
参考:Android postDelay+Dialog引起的窗体泄露
findViewById本身不需要缓存,因为每次都是树形遍历,而LayoutInflater每次都是创建一个新的View,所以必要时使用ViewHolder或者ViewModel进行缓存
this.field = null; --------------------------------------------------------------------------------------------------- ViewGroup container= getWindow().getDecorView().findViewById(android.R.id.content); container.removeAllViews(); --------------------------------------------------------------------------------------------------- Iterator it = mImageList.iterator(); while(it.hasNext()) { Bitmap bmp = it.next(); bmp.recycle(); it.remove(); } --------------------------------------------------------------------------------------------------- mList.clear();