你的位置:首页 > 操作系统

[操作系统]探究Activity的各回调方法


刚毕业那会儿,一心想朝着java web的方向进军,却岂料实习的时候阴差阳错地踏入了Android的大门,自此人生跌宕起伏、坎坎坷坷,在一家外企参与了几个需要越过GFW才能使用的有关于体育赛事的项目,之后跳了个槽跟另外一个哥们做了好几个没见到阳光的充斥着浓浓果味儿的App。无数的努力付之东流会让一个人厌倦某一件事情,所以我要开始写写能让自己看懂的文章,借此找点乐子当然也是为了记录记录自己的学习历程。

探究activity的各回调方法之前,首先插入一张官方的生命周期图,然后用适合自己的语言记录下简化的生命周期中各回调方法的涵义。

 

onCreate()

一般人认为的activity的入口(然而不是),当activity第一次created之后会回调这个方法。如果把activity比作房子的话,回调这个方法之前activity还只是一个毛坯房,我们要在这个方法里边对它进行装修,这样它随后显示的效果就跟我们所预想的一样了。

onstart() : 

这个时候我们可以看到activity了。

onResume() : 

这个时候我们可以跟activity进行交互了。

onPause() : 

这个时候我们还是能看到activity,但是不能进行交互了。举个栗子,假设我们当前的activity为A,有一个启动activity B的意图,这个时候A会回调onPause(),当A的onPause()回调完成之后,B开始onCreate()->onStart()->onResume()...完了之后B就处于可见可交互的状态了。那么A呢?如果此时A看不见了,A就会回调onStop()方法;另一种情况是此时A还是部分可见的(比如Activity B的主题是@android:style/Theme.Dialog),A就不会回调onStop();  从上面的分析可以知道,onPause()方法里面不允许做耗时的操作,不然B等了半天都启动不了。

有一点需要注意的是,不是说A处于部分可见但是不可交互的状态就一定会回调onPause()的,不信你show一个Dialog,show一个DialogFragment,或者show一个设置焦点为true的PopupWindow试试看。

onStop():

这个时候activity已经一丁点儿都看不到了。

onDestroy():

activity的临终遗言就在这里面写了,因为回调完它就被摧毁了。activity被摧毁有两种情况,一是someone调用了finish()方法,二是系统要节省内存空间而临时干掉它. 如何区分呢?官方文档里面说的是通过isFinishing()这个方法来判断。 如果是someone调用了finish()方法,isFinishing()毫无疑问是return true的。如果是系统为了节省空间,isFinishing()=false?

onRestart():

activity准备重新出来见人了。典型的栗子是启动一个能完全遮挡住前一个activity的新的activity之后,再按back键返回到前一个actvity,这样前一个activity就会onRestart()->onStart()->onResume();另一个栗子是按下电源键,熄灭屏幕,再打开,点亮屏幕的时候;还有一个是按下home键,再从最近任务栏或者点击应用图标重新进去的时候....

 


 

到此为止,简化的activity生命周期就大致掌握了,但是将近七千行的activity源码可不止这几个回调方法。当然,我们不必要去追究里面每个方法每个变量的意义与作用,我觉得那样是一种浪费时间的表现,还不如去多看几个用得上的api或者研究下目前一些流行的开源框架怎么使用(好吧,实际上是老子看不懂那一堆fucking source code~~),话虽如此,一些有用或者有意思的回调方法我们还是需要了解了解的,要不然怎么提升自己的编程逼格呢!!!!!!!!!!

onApplyThemeResource():

一般来说在AndroidManifest.

这个方法顾名思义就是activity应用主题资源的,当然并不是说activity直接就调用onApplyThemeResource()了,我们可以稍微追踪下它的调用路线。

我们知道要启动一个activity,会调用ActivityThread的performLaunchActivity()方法来创建这个activity,下面我们点进去找到关于设置主题的几行代码。

private Activity performLaunchActivity(......) { ......   if (activity != null) {     Context appContext = createBaseContextForActivity(r, activity);     ......     activity.attach(appContext, this,......);     ......     int theme = r.activityInfo.getThemeResource();     //如果在AndroidManifest.

然后去activity的setTheme()里面看看,看之前先了解下activity的继承关系,如下图。activity是直接继承自ContextThemeWrapper类的,所以才具有了变换主题的能力,实际上activity的setTheme()方法即是ContextThemeWrapper的setTheme()方法。

点进去ContextThemeWrapper的setTheme()方法:

public void setTheme(int resid) {  if (mThemeResource != resid) {    mThemeResource = resid;    initializeTheme();  }}

然后initializeTheme():

private void initializeTheme() {   final boolean first = mTheme == null;   if (first) {     mTheme = getResources().newTheme();     Resources.Theme theme = getBaseContext().getTheme();     if (theme != null) {       mTheme.setTo(theme);     }   }   //找到目标   onApplyThemeResource(mTheme, mThemeResource, first);}

以上用语言来表示就是,activity在创建的时候,如果有在AndroidManifest.

public class ContextWrapper extends Context {  Context mBase;  ......  protected void attachBaseContext(Context base) {    if (mBase != null) {      throw new IllegalStateException("Base context already set");    }    mBase = base;  }    public Context getBaseContext() {    return mBase;  }  @Override  public AssetManager getAssets() {    return mBase.getAssets();  }  @Override  public Resources getResources()  {    return mBase.getResources();  }  ......} 

所以initializeTheme()方法调用的getBaseContext()方法取的就是这个名为mBase的Context类型的成员变量,但是实际上Context类是一个抽象类,里面根本没有任何实现,这个时候就要重新回到上面的performLaunchActivity(),看看那多出来的两行代码到底干了什么。

首先是createBaseContextForActivity()方法,只贴重要的代码,其它的不想看:

private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {   ......   ContextImpl appContext = ContextImpl.createActivityContext(       this, r.packageInfo, displayId, r.overrideConfig);   appContext.setOuterContext(activity);   Context baseContext = appContext;   ......   return baseContext;} 

然后是Activity的attch()方法,就不贴代码了,点进去发现调用了attachBaseContext()方法,对了,实则调用的就是上面ContextWrapper类的attachBaseContext(),由此,分析得知,activity在创建的时候通过createBaseContextForActivity()得到一个Context的实现类ContextImpl类的实例,然后在attch()方法里面通过调用attachBaseContext()方法让ContextWrapper类的mBase成员变量指向这个实例.

综上所述,前面initializeTheme()方法里面的getBaseContext()就说得通了,它实际得到的是一个ContextImpl类的实例,Activity第一次设置主题的时候,ContextImpl类的getTheme()方法会根据你的targetSdkVersion版本返回一个对应的默认的主题对象。以后有时间再去看看ContextImpl类,现在先跳过这个实际开发没什么用的onApplyThemeResource()方法。

onContentChanged():

当屏幕的内容视图发生改变时会调用,官方文档上的这句话反正我是看不大明白,只能看看源码了。这里以AppCompatActivity为例,发现AppCompatActivity的setContentView()或者addContentView()方法都是通过AppCompatDelegate来委托调用的,于是我们找到它的实现类AppCompatDelegateImplV7,然后把这两个方法贴出来如下:

......final Window.Callback mOriginalWindowCallback;......  @Override  public void setContentView(View v) {    ensureSubDecor();    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);    contentParent.removeAllViews();    contentParent.addView(v);    mOriginalWindowCallback.onContentChanged();  }  @Override  public void setContentView(int resId) {    ensureSubDecor();    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);    contentParent.removeAllViews();    LayoutInflater.from(mContext).inflate(resId, contentParent);    mOriginalWindowCallback.onContentChanged();  }......

所以,Activity在onCreate()里边调用setContentView()方法或者addContentView()方法的时候,都会紧接着回调onContentChanged()方法,上面代码mOriginalWindowCallback变量指向的就是委托的Activity对象。根据上述分析,onContentChanged()方法什么时候会回调呢?就是当Activity的根视图中id为android.R.id.content的ViewGroup的子View发生改变时,这种改变指的是子View的替换。这样来看的话,我们在onCreate()里边就不用写个initView()的方法来findViewById()了,直接把这些操作丢在onContentChanged()方法里边,感觉吊吊的。遗憾的是,有了butterknife、AndroidAnnotations、Dagger亦或是其它的注解框架,谁还会用findViewById()?

当然,也可以直接看看Activity里边的setContentView()方法,然后看看PhoneWindow类里边的setContentView()方法,原理是一样的。那么这个id为android.R.id.content的ViewGroup到底是什么呢?以后有时间的话要单独记录下有关DecorView的知识😄。

onPostCreate():

照例把官方文档翻译一下,当activity已经完全启动的时候会回调这个方法,系统会在里边做一些最终的初始化,我们的应用程序通常不用重写这个方法。 嗯,就这样,但是总感觉少了点什么,去ActivityThread类的performLaunchActivity()方法里面看看:

....../** *activity通过这个mCalled变量来判断你activity的回调方法有木有调用父类对应的方法, *如果没有会抛SuperNotCalledException异常,原理非常简单,先把mCalled置为false, *然后在父类的方法里面再置为true,由此判断... */activity.mCalled = false;//调用onCreate()...if (r.isPersistable()) {  mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);}else {  mInstrumentation.callActivityOnCreate(activity, r.state);}if (!activity.mCalled) {  throw new SuperNotCalledException(     "Activity " + r.intent.getComponent().toShortString() +       " did not call through to super.onCreate()");}r.activity = activity;r.stopped = true;/** *记得前面说的isFinishing()方法吗,实际上它返回的就是这个mFinished变量,someone  *调用finish()时候会把mfinished置为true...so理论上,activity启动的时候光速般按下返  *回键,这样onStart()就不会调了,当然,这只是理论。onStart()调完之后,将stopped置 *为false,说明activity已经不在后台了。 */if (!r.activity.mFinished) {  activity.performStart();  r.stopped = false;}//略过...if (!r.activity.mFinished) {  if (r.isPersistable()) {    if (r.state != null || r.persistentState != null) {      mInstrumentation.callActivityOnRestoreInstanceState(activity,r.state, r.persistentState);    }  }else if (r.state != null) {    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);  }}//这里就是说要去调onPostCreate()了...if (!r.activity.mFinished) {  activity.mCalled = false;  if (r.isPersistable()) {    mInstrumentation.callActivityOnPostCreate(activity, r.state,r.persistentState);  } else {    mInstrumentation.callActivityOnPostCreate(activity, r.state);  }  if (!activity.mCalled) {    throw new SuperNotCalledException(       "Activity " + r.intent.getComponent().toShortString() +          " did not call through to super.onPostCreate()");  } }}r.paused = true;......

看到这,我突然发现不管是onPostCreate()还是onPostResume(),Activity的默认实现操作都是跟标题栏有关,或者说跟ActionBar有关,我觉得这不是一个巧合,你看ActionBarDrawerToggle也是。所以,我觉得当设备状态变化时,比如横竖屏转换的时候,如果我们没有在AndroidManifest.

onUserInteraction()、onUserLeaveHint():

两个长的很像的方法,onUserInteraction()比较好理解,只要用户与Activity有交互就会调用,比如说按了个键、触了个屏、滚了个轨迹球...专业来讲就是只要有事件分发给Activity的时候就会首先调用onUserInteraction(),所以你去看Activity的源码可以发现,在dispatchXXXEvent()的方法体里面,首先就是调onUserInteraction()。哦,除了dispatchPopulateAccessibilityEvent(),这个好像是android系统设置里面有个什么辅助功能相关的交互吧。

onUserLeaveHint(),因为用户的选择从而让当前的Activity进入后台的时候就会回调这个方法,一定要注意,"用户的选择"和"进入后台"。比如,在当前Acitivity按下home键会回调onUserLeaveHint()方法;启动一个新的Activity(包括Dialog或者透明风格的Activity),前一个Activity会回调onUserLeaveHint()方法,其实这个情况这种说法不完全正确啊 ,如果你前一个Activity在startActivity()之前先调用了finish()方法,onUserLeaveHint()是不会调用的,因为此时前一个Activity就不仅仅是进入后台了,而是要被摧毁了...当然如果你的finish()方法写在startActivity()之后的话,还是会调用onUserLeaveHint()的;第三种情况,因为系统的调用而让你的Activity进入后台是不会走onUserLeaveHint()的,比如突然一个电话打进来的时候。以上三种情况的话特别要注意第二种,最后记录一句,假若onUserLeaveHint()要回调的话是在onPause()之前的。

分析了这么多,那onUserLeaveHint()能做什么呢,捕获home点击事件?那启动一个新的Activity也会被调用啊,这个时候我们就要问了,系统是怎么判断当前的Activity进入后台是不是用户的选择呢,实际上,Intent有个FLAG叫做FLAG_ACTIVITY_NO_USER_ACTION。

所以啊,前一个Activity调用startActivity(Intent intent)启动一个新的Activity的时候,如果intent设置了这个FLAG,前一个Activity的onUserLeaveHint()方法就会被阻止调用了,因为这代表不是USER的ACTION. 到此,我们就知道了,想要大致捕获home点击事件,应用内的Activity跳转的时候加上这个FLAG就OK了。 那我们能在这个方法里面做什么呢,典型的,发送一个Notification告诉用户,你的app现在跑在后台...

dispatchXXXEvent():

事件分发进Activity时被调用,这个方法里边一般首先调用onUserInteraction();

"Event事件是首先到了 PhoneWindow 的 DecorView 的 dispatchTouchEvent 方法,此方法通过 CallBack 调用了 Activity 的 dispatchTouchEvent 方法,在 Activity 这里,我们可以重写 Activity 的dispatchTouchEvent 方法阻断 touch事件的传播。接着在Activity里的dispatchTouchEvent 方法里,事件又再次传递到DecorView,DecorView通过调用父类(ViewGroup)的dispatchTouchEvent 将事件传给父类处理,也就是我们下面要分析的方法,这才进入网上大部分文章讲解的touch事件传递流程"

以上这段话出自 Android 事件分发机制详解

onSaveInstanceState()、onRestoreInstanceState():

首先,当内存不足时,为了保证前台进程的正常运行,Android系统会kill掉一些后台进程来释放内存。当某一个Activity存在一种被系统kill掉的可能性时,或者当一个Activty被系统摧毁但是又马上重新创建时,onSaveInstanceState()就会回调,因为Activity在这种情况下被kill掉并不是我们的本意,所以系统给出这么一个地方让我们保存自己的数据,等下次再进入这个Acticity的时候可以复原。Activity的onSaveInstanceState()方法里面,默认给我们保存了视图层次与Fragmets的相关状态,所以我们override这个方法的时候,先super调一下,然后再保存一些其它的临时数据就是了(通过我们熟悉的Bundle对象来保存)。列举一下ononSaveInstanceState()会被回调的情况如下:

  1. 用户按下home键 
  2. 在最近任务切换到其它应用 (一般是长按home键)  
  3. 启动一个新的Actvity
  4. 按电源键熄灭屏幕
  5. 设备的配置信息发生改变时,比如横竖屏切换、系统语言切换、调整设置里面的字体大小等

前四种情况都是由于Activity进入后台了,当内存不足时存在被系统kill掉的可能性,所以会回调onSaveInstanceState()。最后一种情况比较特殊,如果没有在AndroidManifest.

onRestoreInstanceState()与onSaveInstanceState()并不是成对出现的,只有当上述的可能性变成现实的时候才会回调,因为没有摧毁就没有复原。由此可见,对上述的第五种情况来说,onSaveInstanceState()与onRestoreInstanceState()是一定会成双成对得出现的。

onSaveInstanceState()如果被回调的话,在onStop()之前,有可能在onPause()之前也有可能在onPause()之后;onRestoreInstanceState()如果被回调的话,在onStart()与onPostCreate()之间。

onConfigurationChanged():

这个方法是跟设备的配置信息改变有关的,如果在AndroidManifest.Configuration的说明。

mcc : SIM卡唯一标识IMSI(国际移动用户识别码)中的国家代码,由三位数字组成,中国为460。此项标识mcc代码发生了改变

mnc:SIM卡唯一标识IMSI(国际移动用户识别码)中的运营商代码,由两位数字组成,中国移动TD系统为00,中国联通为01,中国电信为03。此项标识mnc发生了改变

locale:设备的本地位置发生了改变,一般指切换了系统语言

touchscreen:触摸屏发生了改变,这个很费解,正常情况下无法发生,可以忽略它

keyboard:键盘类型发生了改变,比如用户使用了外插键盘

keyboardHide:键盘的可访问性发生了改变,比如用户调出了键盘

navigation:系统导航方式发生了改变,比如采用了轨迹球导航,这个有点费解,很难发生,可以忽略它

screenLayout:屏幕布局发生了改变,很可能是用户激活了另外一个显示设备

fontScale:系统字体缩放比例发生了改变,比如用户选择了一个新字号

uiMode:用户界面模式发生了改变,比如是否开启了夜间模式(API 8添加)

orientation:屏幕方向发生了改变,这个是最常用的,比如旋转了手机屏幕

screenSize:当屏幕的尺寸信息发生了改变,当旋转设备屏幕时,屏幕尺寸会发生变化,这个选项比较特殊,它和编译选项有关,当编译选项中的minSdkVersion和TargetVersion均低于13时,此选项不会导致Activity重启,否则会导致Activity重启(API 13新添加)

smallestScreenSize:设备的物理屏幕尺寸发生改变,这个项目和屏幕的方向没有关系,仅仅表示在实际的物理屏幕的尺寸改变的时候发生,比如用户切换到了外部的显示设备,这个选项和screenSize一样,当编译选项中的minSdkVersion和TargetVersion均低于13时,此选项不会导致Activity重启,否则会导致Activity重启(API 13新添加)

layoutDirection:当布局方向发生改变,这个属性用的比较少,正常情况下无须修改布局的layoutDirection属性(API 17新添加)

为什么要一定要抄一遍,因为可以加深印象。

onAttachedToWindow(),onDetachedFromWindow():

官方要我们去看View的这两个对应的方法。。。那就再说吧。。。

onWindowFocusChanged():

当Activity的窗口获取或失去焦点的时候就会回调这个方法。官方文档上说,这个方法是判断Activity是否对用户可见的最好的标志了,个人理解的意思就是,当走到这个方法的时候,Activity里面的视图都已经测量过了,我们的肉眼也确实能看到这个Activity了,但是可能还没有绘制没有渲染,整个界面是黑的还是灰的,我也不知道,我只知道在这个方法里面可以获取到View的宽高。

列举几种Activity焦点变化的情况。

  1. 弹出/消失一个Dialog
  2. 上/下拉状态栏
  3. 启动一个新的Activity & 返回到前一个Activity
  4. 略......

最后,附一张所谓的Activity的完整的生命周期图:

 

activity完整生命周期图