最近开发中,使用 AsyncTask + ProgressDialog 显示进度信息,但在AsyncTask停止,Activity finish 后该Activity的实例始终不会被gc,多次运行程序后,会存在多个activity,造成内存泄漏。
下面详细分析一下:
一份显示进度条的测试代码:
-
publicclassMainextendsActivity{
-
-
-
@Override
-
protectedvoidonCreate(BundlesavedInstanceState){
-
super.onCreate(savedInstanceState);
-
-
TextViewtv=newTextView(this);
-
tv.setText("InitState");
-
setContentView(tv);
-
-
tv.setOnClickListener(newOnClickListener(){
-
-
@Override
-
publicvoidonClick(Viewv){
-
showProgress(Main.this);
-
}
-
});
-
}
-
-
publicvoidshowProgress(finalActivityactivity){
-
newAsyncTask<Void,Void,Void>(){
-
ProgressDialogprogressDial;
-
-
protectedvoidonPreExecute(){
-
progressDial=newProgressDialog(activity);
-
progressDial.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
-
progressDial.show();
-
};
-
-
@Override
-
protectedVoiddoInBackground(Void...params){
-
doSomeHeavyWork(progressDial);
-
returnnull;
-
}
-
-
protectedvoidonPostExecute(Voidresult){
-
progressDial.dismiss();
-
};
-
-
}.execute();
-
}
-
-
voiddoSomeHeavyWork(ProgressDialogprogress){
-
try{
-
for(inti=1;i<=10;++i){
-
progress.setProgress(i);
-
Thread.sleep(1000);
-
}
-
}catch(Exceptione){
-
}
-
}
-
}
上述代码发生内存泄漏的地方在 doSomeHeavyWork() 的 progress.setProgress(i);部分;我们看一下setProgress()的实现,最终会调用ProgressBar 类的如下方法:
-
privatesynchronizedvoidrefreshProgress(intid,intprogress,booleanfromUser){
-
if(mUiThreadId==Thread.currentThread().getId()){
-
doRefreshProgress(id,progress,fromUser,true);
-
}else{
-
RefreshProgressRunnabler;
-
if(mRefreshProgressRunnable!=null){
-
-
r=mRefreshProgressRunnable;
-
-
mRefreshProgressRunnable=null;
-
r.setup(id,progress,fromUser);
-
}else{
-
-
r=newRefreshProgressRunnable(id,progress,fromUser);
-
}
-
post(r);
-
}
-
}
-
privateclassRefreshProgressRunnableimplementsRunnable{
-
-
privateintmId;
-
privateintmProgress;
-
privatebooleanmFromUser;
-
-
RefreshProgressRunnable(intid,intprogress,booleanfromUser){
-
mId=id;
-
mProgress=progress;
-
mFromUser=fromUser;
-
}
-
-
publicvoidrun(){
-
doRefreshProgress(mId,mProgress,mFromUser,true);
-
-
mRefreshProgressRunnable=this;
-
}
-
-
publicvoidsetup(intid,intprogress,booleanfromUser){
-
mId=id;
-
mProgress=progress;
-
mFromUser=fromUser;
-
}
-
}
if 语句表明当调用的该方法的线程是UI线程时,则直接执行doRefreshProgress() 方法以刷新界面;否则,创建一个RefreshProgressRunnable,并通过调用View.pos()方法将其插入到UI线程的消息队列中。
View.post()实现如下:
-
publicbooleanpost(Runnableaction){
-
Handlerhandler;
-
AttachInfoattachInfo=mAttachInfo;
-
if(attachInfo!=null){
-
handler=attachInfo.mHandler;
-
}else{
-
-
ViewRootImpl.getRunQueue().post(action);
-
returntrue;
-
}
-
-
returnhandler.post(action);
-
}
在post() 函数注释中,明确写着:This method can be invoked from outside of the UI threadonly when this View is attached to a window.
当ProgressDialog还没有attach到当前window时(ProgressDialog.show() 方法是异步执行的),mAttachInfo 值为 null,故而执行 else语句,再看一下getRunQueue()和其post()
方法:
-
staticfinalThreadLocal<RunQueue>sRunQueues=newThreadLocal<RunQueue>();
-
-
staticRunQueuegetRunQueue(){
-
RunQueuerq=sRunQueues.get();
-
if(rq!=null){
-
returnrq;
-
}
-
rq=newRunQueue();
-
sRunQueues.set(rq);
-
returnrq;
-
}
-
……
-
staticfinalclassRunQueue{
-
privatefinalArrayList<HandlerAction>mActions=newArrayList<HandlerAction>();
-
-
voidpost(Runnableaction){
-
postDelayed(action,0);
-
}
-
-
voidpostDelayed(Runnableaction,longdelayMillis){
-
HandlerActionhandlerAction=newHandlerAction();
-
handlerAction.action=action;
-
handlerAction.delay=delayMillis;
-
-
synchronized(mActions){
-
mActions.add(handlerAction);
-
}
-
}
-
-
voidexecuteActions(Handlerhandler){
-
synchronized(mActions){
-
finalArrayList<handleraction>actions=mActions;
-
finalintcount=actions.size();
-
-
for(inti=0;i<count;i++){
-
finalHandlerActionhandlerAction=actions.get(i);
-
handler.postDelayed(handlerAction.action,handlerAction.delay);
-
}
-
-
actions.clear();
-
}
-
}
-
……
-
}
-
andleraction>
这样会把ProgressBar的RefreshProgressRunnable 插入到一个静态的ThreadLocal的RunQueue队列里,针对本文开头给出的例子,刷新进度的Runnable被插入到了AsyncTask 所在线程的RunQueue里;
那么插入的Runnable什么时候得到执行呢?
调用RunQueue.executeActions()方法只有一处,即在ViewRootImpl类的如下非静态方法中
-
privatevoidperformTraversals(){
-
……
-
if(mLayoutRequested&&!mStopped){
-
-
-
getRunQueue().executeActions(attachInfo.mHandler);
-
……
-
}
-
-
……
-
}
该方法是在UI线程执行的(见ViewRootImpl.handleMessage()), 故当UI线程执行到该performTraversals() 里的 getRunQueue() 时,得到的是UI线程中的RunQueue,这样AsyncTask 线程中的
RunQueue永远不会被执行到, 并且AsyncTask的是用线程池实现的,AsyncTask启动的线程会长期存在,造成如下引用关系:
AsyncTask线程 => 静态的ThreadLocal的RunQueue => Runnable => ProgressBar => Activity;
如此即使activity finish 了,确始终存在一个静态引用链引用这该activity,而 Activity一般又引用着很多资源,比如图片等,最终造成严重资源泄漏。
另外,上述问题不限与ProgressBar,凡是在非UI线程使用view.post()方法,如果view没有被attach,则均存在潜在的内存泄漏的问题!
针对本文给出的ProgressBar例子,一个简单fix方法实在 AsyncTask的doInbackground() 开始处sleep(500) 即可。 更为精准的方式可使用如下循环测试:
-
Viewv=progressBar.getWindow().getDecorView();
-
while(v.getWindowToken()==null){
-
Thread.sleep(10);
-
}
上述ProgressBar例子,并不是总能再现内存泄漏的情况的(因为异步执行的不缺定性),下面再给出一个更容易再现类似问题的例子:
-
publicclassMainextendsActivity{
-
-
publicstaticinta=0;
-
-
@Override
-
protectedvoidonCreate(BundlesavedInstanceState){
-
super.onCreate(savedInstanceState);
-
-
TextViewtv=newTextView(this);
-
tv.setText("InitState");
-
setContentView(tv);
-
-
-
if(a++>3){
-
return;
-
}
-
newAsyncTask<TextView,Void,Void>(){
-
-
@Override
-
protectedVoiddoInBackground(TextView...params){
-
try{
-
TextViewtv=params[0];
-
-
for(inti=1;i<=3;++i){
-
doSomeHeavyWork(tv,"AsyncTask:"+i);
-
Thread.sleep(1000);
-
}
-
}catch(Exceptione){
-
}
-
returnnull;
-
}
-
-
protectedvoidonPostExecute(Voidresult){
-
recreate();
-
};
-
-
}.execute(tv);
-
}
-
-
voiddoSomeHeavyWork(finalTextViewtv,finalStringtext){
-
tv.post(newRunnable(){
-
@Override
-
publicvoidrun(){
-
tv.setText(text);
-
}
-
});
-
}
现象是: TextView 很大概率不会显示 "AsyncTask: 1“ 文字; 而把 Thread.sleep(500)注释掉后,一切ok!
Log.e("Token:",tv.getWindowToken()+"");//输出的Token为null,这时候还没有和Activity绑定
Thread.sleep(500);
Log.e("Token:",tv.getWindowToken()+"");//输出的Token不为null,这时候已经和Activity绑定
分享到:
相关推荐
Xamarin.Android 非UI线程更新UI
[Android.UI基础教程].Jason.Ostrander.扫描版
[Android.UI基础教程].Jason.Ostrander.扫描版,关于android的UI设计方面的知识,学习过程的经典书
EX_UI界面库201702.27版EX_UI界面库201702.27版EX_UI界面库201702.27版EX_UI界面库201702.27版EX_UI界面库201702.27版EX_UI界面库201702.27版EX_UI界面库201702.27版EX_UI界面库201702.27版EX_UI界面库201702.27版EX...
Android_UI_组件介绍.pdf Android_UI_组件介绍.pdf
Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面。
[Android.UI基础教程].Jason.Ostrander.扫描版[电子书www.minxue.net].part4
ndroid异步处理一:使用Thread+Handler实现非UI线程更新UI界面
010_android 之UI线程阻塞及其优化视频教材,讲解的比较详细,有兴趣的可以学习下哦。
Thread 达到跨线程更新UI 虽然使用Dispatcher.Invoke 和模拟winform 里面的DoEvent 但是运行中关闭还是会有异常,而且耗资源高; 第二种 : DispatcherTimer 失败:UI还是会卡顿; 第三种 : Timer 建议使用、资源...
Android中UI线程与后台线程交互的探讨.pdf
XUI Android原生UI框架 v1.2.1.zip
dialog库,可以在任意类内调用,子线程或ui线程内均可显示
WPF开发桌面软件具有天然优势,能快速漂亮的界面程序。 Newbeecoder.UI是一款简单易用漂亮的UI控件库,融合多个开源框架组件,为企业和个人定制开发提供支持。
[Android.UI基础教程].Jason.Ostrander.扫描版[电子书www.minxue.net].part3
EX_UI界面库201702.27#1版(静态版exui.fne)EX_UI界面库201702.27#1版(静态版exui.fne)
安卓Android源码——UI界面源码.zip
老二牛车教育 程矢AndroidUI之常用控件.ppt
Android源码——UI界面源码.rar
在C#中,由于使用线程和调用UI的线程属于两个不同的线程,如果在线程中直接设置UI元素的属性,此时就会出现跨线程错误。 下面介绍两种解决方案 第一种:使用控件自带的Invoke或者BeginInvoke方法。 Task....