当前位置: 代码迷 >> Android >> 2.结束Activity的4个阶段
  详细解决方案

2.结束Activity的4个阶段

热度:63   发布时间:2016-04-24 12:03:15.0
深入剖析Android四大组件(八)——结束Activity的4个阶段

上一篇博文介绍了启动Activity请求的流程以及对相关数据结构的处理,那么当我们试图结束Activity的时候,ActivityManagerService的行为将会是怎样的呢?这一节将介绍结束Activity的3种主要方法和4个阶段。


1.结束Activity的3种主要方法


结束Activity时,我们通常采用如下3种主要方法。


①以编程的方式结束Activity


该方法即在代码中显式调用Activity的finish()方法。一般来说,我们经常会遇到这样的需求——点击某个按钮退出界面,此时只需要在按钮的点击事件中添加finish()方法即可。finish()方法的代码如下所示,finish()内部默认调用finish(false):


private void finish(boolean finishTask) {    if (mParent == null) {        int resultCode;        Intent resultData;        synchronized (this) {            resultCode = mResultCode;            resultData = mResultData;        }        if (false) Log.v(TAG, "Finishing self: token=" + mToken);        try {            if (resultData != null) {                resultData.prepareToLeaveProcess();            }            if (ActivityManagerNative.getDefault()                    .finishActivity(mToken, resultCode, resultData, finishTask)) {                mFinished = true;            }        } catch (RemoteException e) {            // Empty        }    } else {        mParent.finishFromChild(this);    }}


以上红色标注的为主要方法。


②按键盘(硬键盘或者软键盘)上Back键来结束Activity


这种情况下,不需要添加任何代码就可以结束Activity,但需要注意的是,并不是所有的设备都会有Back键。在未加定制的Android代码中,它为每个Activity界面提供了软键盘。


当单击此按钮的时候,系统将会通过回调onBackPressed()方法告知Activity Back按键已经按下。onBackPressed()方法的代码如下所示:


public void onBackPressed() {    if (mActionBar != null && mActionBar.collapseActionView()) {        return;    }    if (!mFragments.getFragmentManager().popBackStackImmediate()) {        finishAfterTransition();    }}


finishAfterTransition()代码如下:


public void finishAfterTransition() {    if (!mActivityTransitionState.startExitBackTransition(this)) {        finish();    }}


通过上面的代码可知,onBackPressed()方法的本质还是一个finish()方法。当然,也可以屏蔽这种行为,只需要在我们自行实现的Activity的onBackPressed()方法中取消调用super.onBackPressed()即可,但是我们不建议这样做。


③使用Home键使当前显示的Activity消失,回到Launcher首页


与Back键一样,并不是所有的设备都会提供硬Home按键。在未加定制的Android代码中,它为每个Activity界面提供了软键盘。


通常,应用程序无法捕获Home键,除非强行捕获,但不建议这么做。这个键将会由PhoneWindowManager处理,具体代码如下:


void startDockOrHome(){    Intent dock=createHomeDockIntent();    if(dock!=null){        try{            mContext.startActivity(dock);            return;        }catch(ActivityNotFoundException e){        }    }    mContext.startActivity(mHomeIntent);}


最终,Android会以mHomeIntent去启动Launcher从而使得当前Activity退居后台,Launcher被重新显示出来。mHomeIntent是这样定义的:


mHomeIntent=new Intent(Intent.ACTION_MAIN,null);

mHomeIntent.addCateGory(Intent.CATEGORY_HOME);

mHomeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);


2.结束Activity的4个阶段


同启动Activity一样,结束Activity也有4个阶段,下面我们将对其进行详细讲解。


①第一阶段——参数初始化以及参数传递


与启动Activity相同,结束Activity同样需要ActivityManagerProxy将命令转发出去的。当按下Back键时,将会执行下面的这行代码:


ActivityManagerNative.getDefault().finishActivity(mToken,resultCode,resultData);


这时,ActivityManagerProxy会调用它的finishActivity()方法将参数写入Parcel中并转发出去。finishActivity()方法代码如下:


public boolean finishActivity(IBinder token,int resultCode,Intent resultData)throws RemoteException{    Parcel data=Parcel.obtain();    Parcel reply=Parcel.obtain();    ......    //转发指令    mRemote.transact(FINISH_ACTIVITY_TRANSACTION,data,reply,0);    ......    return res;}


调用finishActivity()时,Android会回调ActivityManagerService的onTransact()方法,转而执行其基类(也就是ActivityManagerNative类)的onTransact()方法来向ActivityManagerService()发送请求:


@Overridepublic boolean onTransact(int code,Parcel data,Parcel reply,int flags)throws RemoteException{    ......    case FINISH_ACTIVITY_TRANSACTION:{        data.enforceInterface(IActivityManager.descriptor);        IBinder token=data.readStrongBinder();        Intent resultData=null;        int resultCode=data.readInt();        if(data.readInt()!=0){            resultData=Intent.CREATOR.createFromParcel(data);        }        boolean res=finishActivity(token,resultCode,resultData);        reply.writeNoException();;        reply.writeInt(res ? 1 : 0);        return true;    }    ......}


至此,参数处理以及指令发送的前驱工作已经完成,接下来的工作将由ActivityManagerService完成。


②第二阶段——获取需要结束的Activity的记录信息


在第二阶段,首先要做的是用ActivityManagerService调用ActivityStack的requestFinishActivityLocked()方法执行信息手机工作,具体代码如下所示:


public final boolean finishActivity(IBinder token,int resultCode,Intent resultData){    ......    final long origId= Binder.clearCallingIdentity();    boolean res=mMainStack.requestFinishActivityLocked(token,resultCode,resultData,"app-request");    Binder.restoreCallingIdentity(origId);    return  res;}


而在requestFinishActivityLocked()方法中,首先将使用indexOfTokenLocked()方法获取该Activity在启动Activity的历史记录(mHistory)中的偏移量,然后从启动历史记录中获取该Activity的记录信息(ActivityRecord),具体代码如下所示:


final boolean requestFinishActivityLocked(IBinder token,int resultCode,Intent resultData,String reason){    .......    //获取索引    int index=indexOfTokenLocked(token);    ......        //从历史记录中获取Activity记录信息    ActivityRecord r=mHistory.get(index);        //启动结束流程的下一个阶段    finishActivityLocked(r,index,resultCode,resultData,reason);    return  true;}


其中indexOfTokenLocked()方法的关键代码如下所示:


final int indexOfTokenLocked(IBinder token){    ActivityRecord r=(ActivityRecord)token;    return mHistory.indexOf(r);//获取需要结束的Activity在mHistory的所引值    ......}


③第三阶段——处理需要结束的Activity信息


在第三阶段中,我们已经获取到需要结束的Activity的记录信息,这里需要对它们进行一些处理,这通过finishActivityLocked()方法完成。finishActivityLocked()方法的流程下图所示:



该图有一下几点需要特别说明一下。


Ⅰ代码中r.makeFinishing()的作用是将Activity的正在结束标志置为true,并且将该Activity所在的Activity栈的Activity数量减一,这就为了后续操作做好了准备。makeFinishing()方法的代码如下所示:


void makeFinishing(){    if(!finishing){        finishing=true;//标识正在结束        if(task!=null && inHistory){            task.numActivities--;//同一个栈的Activity数量减一        }    }}


Ⅱ由于当前的Activity即将结束,它至少会被另一个Activity覆盖,这时当前Activity窗口则不应该继续将按键消息分发到当前Activity上。为完成这个需求,ActivityManagerService会调用pauseKeyDispatchingLocked()方法,该方法的代码如下所示:


void pauseKeyDispatchingLocked(){    if(!keyPaused){        keysPaused=true;        service.mWindowManager.pauseKeyDispatching(this);    }}


Ⅲ假设使用startActivityForResult的方式启动当前Activity,那么结束此Activity时需要给调用的Activity传送处理的结果。这里使用如下代码完成:


resultTo.addResultLocked(r,r.resultWho,r.requestCode,resultCode,resultData);


下面我们来看看addResultLocked()方法的行为,具体如下代码所示:


void addResultLocked(ActivityRecord from,String resultWho,int requestCode,int resultCode,Intent resultData){    Instrumentation.ActivityResult r=new Instrumentation.ActivityResult(from,resultWho,requestCode,resultCode,resultData);    if(results==null){        results=new ArrayList();    }    results.add(r);}


④第四阶段——Activity间调度准备


在第三阶段中,我们完成了对一些Activity栈以及窗口的处理,为Activity调度做了一些准备工作,然后启动了ActivityThread的调度流程:


startPausingLocked(false,false);


其中startPausingLocked()方法的关键代码如下所示:


private final void startPausingLocked(boolean userLeaving,boolean uiSleeping){    .....    mResumedActivity=null;    mPausingActivity=prev;    mLastPausedActivity=prev;    prev.state=ActivityState.PAUSING;    prev.task.touchActiveTime();    prev.updateThumbnail(screenshotActivities(prev),null);    .....    prev.app.thread.schedulePauseActivity(prev,prev.finishing,userleaving,pre.configChangeFlags);}


通过学习启动Activity的4个阶段,我们知道ActivityStack启动了ActivityThread的Activity间的调度,这里就要讲解当Activity将要被结束时Activity之间的调度行为。schedulePauseActivity()方法用于完成这个任务,其代码如下所示:


public final void schedulePauseActivity(IBinder token, boolean finished,boolean userLeaving,int configChanges){    queueOrSendMessage(            finished? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,token,            (userLeaving?1:0),            configChanges);}


此处,queueOrSendMessage()方法拼装了一个消息,发往用于处理消息的handler。在Handler中,最终调用handlePauseActivity()方法处理这个消息。handlePauseActivity()方法的代码如下所示:


private void handlePauseActivity(IBinder token,boolean finished,                                 boolean userLeaving,int configChanges){    ActivityClientRecord r=mActivities.get(token);    ......    //暂停当前的Activity    performPauseActivity(token,finished,r.isPreHoneycomb());        //通知服务操作完成    ActivityManagerNative.getDefault().activityPaused(token);}


该方法主要完成以下两件事情。


Ⅰ调用performPauseActivity()方法回调Activity的onPause等回调接口,并设置Activity的状态,具体流程如下图所示:



Ⅱ调用代理的activityPaused()方法,转发Activity的一个pause指令到Activity管理服务,代码如下所示:


public boolean onTransact(int code,Parcel data,Parcel reply,int flags)throws RemoteException{    case ACTIVITY_PAUSED_TRANSACTION:{        ......        IBinder token=data.readStrongBinder();        activityPaused(token);        ......        return true;    }}


Activity管理服务的activityPaused()方法主要完成该Activity其他生命周期的调度以及恢复前一个Activity,其中的部分关键代码如下所示:


private final void completePauseLocked(){    ActivityRecord prev=mPausingActivity;    ......    prev=finishCurrentActivityLocked(prev,FINISH_AFTER_VISIBLE);    .....    destroyActivityLocked(prev,true,false);    .....    resumeTopActivityLocked(prev);//恢复一个Activity        if(prev !=null){        //允许窗口分发按键消息到此Activity(prev)        prev.resumeKeyDispatchingLocked();    }    .....    prev.cpuTimeAtResume=0;//重置}


当前一个Activity被重新显示出来的时候,它需要具有捕获按键消息的能力,因此这里调用了resumeKeyDispatchingLocked()方法来完成这个需求。resumeKeyDispatchingLocked()方法的作用是恢复对这个Activity的按键分发,具体代码如下所示:


void resumeKeyDispatchingLocked(){    if(keysPaused){        keysPaused=false;        service.mWindowManager.resumeKeyDispatching(false);    }}


至此,原来显示的Activity由于按下了Back键而消失,而覆盖在它下面的那个Activity则被重新显示出来了。


注意1:按Home键与按下Back键或以Activity的finish()方法结束Activity不同是的,按Home键是强制显示Launcher而使得其他Activity被Launcher覆盖,而按下Back键或以Activity的finish()方法结束Activity,则是因为当前的Activity消失而导致在它下面的Activity被显示出来。它们有着本质的区别,请注意这个注意。


注意2:只要应用程序启动,进程将会被保留,除非应用程序发生了严重的异常或者使用别的工具(比如DDMS)杀掉进程,就算我们按下Back键结束Activity了,其应用程序进程依然一直存在于设备中。

??
  相关解决方案