探究Activity的各回调方法

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

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

 

onCreate()

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

onstart() : 

这个时候咱们能够看到activity了。编程

onResume() : 

这个时候咱们能够跟activity进行交互了。api

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等了半天都启动不了。app

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

onStop():

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

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.xml的application标签下会全局设置一个theme属性,或者单独为每一个activity设置也能够,这样onApplyThemeResource()方法会先于onCreate()调用,固然你如果不在AndroidManifest.xml设置,硬是单单在onCreate()里面调用setTheme()方法也是能够的,这样onApplyThemeResource()就会在onCreate()后调用了。

这个方法顾名思义就是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.xml里面设置了有效的theme属性,则调用setTheme()
         if (theme != 0) {
             activity.setTheme(theme);
         }
     }
}   

而后去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.xml里面设置有效的主题资源id,就会在onCreate()以前调用setTheme()方法,而后调用initializeTheme()方法,进而回调onApplyThemeResource()方法;其实前面看ActivityThread的performLaunchActivity()方法的时候,除了关于设置主题的代码外,上面还多了几行代码,为何要把它提出来呢,由于initializeTheme()方法有个getBaseContext()的方法,那多出来的几行代码就是为了此刻来讲明下的。看看Contextwrapper类的源码,发现它有一个名为mBase的Context类型的成员变量,这个mBase变量委托Contextwrapper类来调用本身的全部方法,大概就是下面这样:

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.xml配置configChanges属性,当Activity从新回调各生命周期方法的时候,咱们在这两个方法里面同步ActionBar的状态是否会比较符合官方的思想。

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对象来保存)。列举一下onSaveInstanceState()会被回调的状况以下:

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

前四种状况都是因为Activity进入后台了,当内存不足时存在被系统kill掉的可能性,因此会回调onSaveInstanceState()。最后一种状况比较特殊,若是没有在AndroidManifest.xml配置相应的configChanges属性,系统会摧毁这个Activity并立马从新建立。须要注意的,是存在被摧毁的可能性才会回调,因此像点击back键,显示地调用finish(),这种百分之一百会被摧毁的时候是不会给你保存数据的,由于这是你本身想要它去死的。

onRestoreInstanceState()与onSaveInstanceState()并非成对出现的,只有当上述的可能性变成现实的时候才会回调,由于没有摧毁就没有复原。因而可知,对上述的第五种状况来讲,onSaveInstanceState()与onRestoreInstanceState()是必定会成双成对得出现的。

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

onConfigurationChanged():

这个方法是跟设备的配置信息改变有关的,若是在AndroidManifest.xml给Activity配置了相应的configChanges属性,这个时候Activity就不会先被摧毁而后立马从新建立了,而会只是回调这个方法。那么设备都有哪些配置呢?在此摘抄下《Android开发艺术探索》的有关于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完整生命周期图

相关文章
相关标签/搜索