ANDROID | 工具类

一、生命周期


整个生命周期来说,成对出现的。onCreate和onDestroy是成对的,代表着创建与销毁;onStart和onStop表示Activity对于程序是否可见(后台);onResume和onPause表示Activity对于用户是否可见(前台)。

正常生命周期

1、启动ActivityA:onCreate() -> onStart() -> onResume()
2、按了back键返回:onPause() -> onStop() -> onDestroy()
3、用户按了Home键:onPause() -> onStop()
4、启动ActivityB:A.onPause() -> B.onCreate() -> B.onStart() -> B.onResume() -> A.onStop()
5、ActivityB返回:B.onPause() -> A.onRestart() -> A.onStart() -> A.onResume() -> B.onStop()
-> B.onDestroy()

切换横竖屏幕

targetSdkVersion 25,没有设置android:configChanges:
1、竖屏切横屏:onConfigurationChanged() -> onPause() -> onSaveInstanceState() -> onStop() -> onDestroy() -> onCreate() -> onStart() -> onRestoreInstanceState() -> onResume()
2、横屏切竖屏:onConfigurationChanged() -> onPause() -> onSaveInstanceState() -> onStop() -> onDestroy() -> onCreate() -> onStart() -> onRestoreInstanceState() -> onResume()

本人使用的是模拟器(minSdkVersion 16 / targetSdkVersion 25)测试,无论是否设置Activity的android:configChanges,或者属性值设为orientation或orientation|keyboardHidden,无论是竖屏切横屏还是横屏切竖屏,流程都如上所述,当android:configChanges=”orientation|keyboardHidden|screenSize”时,只会调用onConfigurationChanged()。经过查资料,android:targetSdkVersion这个属性会影响横竖屏切换的生命周期。详见xiaoQLud的博客

异常生命周期

1、系统配置发生改变后,默认情况下Activity会被销毁然后重新创建,生命周期参考横竖屏切换。常用的系统配置只有local、orientation和keyboardHidden。
2、资源内存不足导致低优先级的Activity被杀死,生命周期参考横竖屏切换。
3、运行时发生了异常,程序直接崩溃。例如:

1
2
for (int i = 10; i >= 0; i++)
System.out.println(10 / i)

二、数据存储

当Activity在异常情况下被终止,在onStop之前,系统会调用onSaveInstanceState方法,来保存当前Activity状态。当Activity被重新创建后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceState和onCreate方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
String name = savedInstanceState.getString("name");
Log.i(tag, "onCreate() name : " + name);
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.i(tag, "onSaveInstanceState()");
outState.putString("name", "xianxiaotao");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
String name = savedInstanceState.getString("name");
Log.i(tag, "onRestoreInstanceState() name : " + name);
}

android提供的一些控件也实现了onSaveInstanceState和onRestoreInstanceState方法,如TextView。

三、启动模式

ActivityManagerService(AMS)内部维护着一个ActivityStack栈,系统会将Activity实例一一放入栈内(先进后出),默认情况下创建一个实例压入栈内。如果多次创建同一个Activity,栈内将压入多个实例。为此,Android提供了启动模式来修改系统的默认行为。目前有四种启动模式:standard、singleTop、singleTask和singleInstance:

standard

标准模式,也是系统默认模式。每次启动一个Activity都会重新创建一个新的实例,不管这个实例是否已经存在。谁启动了这个Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。ABCBB

singleTop

栈顶复用模式。如果新Activity已经位于任务栈的栈顶,那么只调用它的onNewIntent方法实现复用,而不会创建新的实例。如果新的Activity的实例已存在但不是位于栈顶,仍然重新创建。假设目前栈内情况为ABCD(ABCD为四个Activity,A位于栈底),此时再次启动,如果D的启动模式为singleTop,那么栈内仍然是ABCD;如果D的启动模式为standard,D将被重新创建,栈内情况变为ABCDD。

singleTask

栈内复用模式。在同一个栈内,它是单实例模式。在这种模式下,启动一个Activity A,系统首先会寻找是否存在A想要的任务栈,如果不存在,就重新创建一个任务栈,然后创建A的实例并压入栈内;如果存在所需的任务栈,这时要看A是否在栈中有实例存在,如果有,那么系统就会把A调到栈顶并调用它的onNewIntent方法,不存在就创建并压入栈中。
1、目前任务栈栈S1(ABC),此时Activity D以singleTask模式请求启动,其所需的任务栈为S2,由于S2和D的实例均不存在,所以系统先创建任务栈S2,然后再创建D的实例并将其放入栈S2中。
2、另外一种情况,假设D所需的任务栈为S1,上述S1已存在,所以系统会直接创建D的实例并压入栈S1内。
3、如果D所需的任务栈为S1,其中为ADBC,此时系统将D上面的Activity出栈,把D切换到栈顶并调用onNewIntent方法,最终S1栈内为AD。

singleInstance:

该模式具备singleTask模式的所有特性外,与它的区别就是,这种模式下的Activity会单独占用一个Task栈,具有全局唯一性,即整个系统中就这么一个实例,由于栈内复用的特性,后续的请求均不会创建新的Activity实例,除非这个特殊的任务栈被销毁了。以singleInstance模式启动的Activity在整个系统中是单例的,如果在启动这样的Activiyt时,已经存在了一个实例,那么会把它所在的任务调度到前台,重用这个实例。
以下代码是用于测试启动模式的BaseActivity,具体的Activity继承BaseActivity:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.king.bester.demo;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
public class BaseActivity extends AppCompatActivity {
private static final String TAG = "Xian Xiaotao";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 区别类名,区别同类名是否同实例,区别任务栈
Log.i(TAG, "onCreate:" + getClass().getSimpleName() + " hasCode:" + this.hashCode() + " TaskId: " + getTaskId());
// 任务栈名称,默认为包名相关
dumpTaskAffinity();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.i(TAG, "onNewIntent:" + getClass().getSimpleName() + " hasCode:" + this.hashCode() + " TaskId: " + getTaskId());
dumpTaskAffinity();
}
protected void dumpTaskAffinity(){
try {
ActivityInfo info = this.getPackageManager().getActivityInfo(getComponentName(), PackageManager.GET_META_DATA);
Log.i(TAG, "taskAffinity:" + info.taskAffinity);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}

配置形式:

1
2
3
4
<activity
android:name=".ActivitySingleTop"
android:launchMode="singleTop"
android:taskAffinity="com.king.bester.demo"/>

启动模式参考博客

四、意图调用

启动Activity分为两种,显示调用和隐式调用。显示调用通过指定Intent组件名称来实现的,它一般用在知道目标组件名称的前提下,一般是在相同的应用程序内部实现的。隐式调用通过Intent Filter来实现的,它一般用在没有明确指出目标组件名称的前提下,一般是用于在不同应用程序之间。

显示调用

1
2
3
Intent intent=new Intent(MainActivity.this,SettingActivity.class);
intent.putExtra(EXTRA,contentEditText.getText().toString());
startActivity(intent);

而Manifest文件描述为:

1
2
3
4
5
6
7
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SettingActivity" />

隐式调用

隐式不明确指定启动哪个Activity,而是在Intent Filter中设置Action、Data、Category,让系统来筛选出合适的Activity。清单文件配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.king.bester.myapplication">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".TargetActivity"
android:launchMode="singleTask"
android:taskAffinity="com.king.bester.myapplication" >
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain"/>
</intent-filter>
<intent-filter>
<action android:name="com.king.bester.demo" />
<action android:name="com.king.bester.DEMO" />
<category android:name="com.king.bester.a" />
<category android:name="com.king.bester.b" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain"/>
</intent-filter>
</activity>
</application>
</manifest>

代码调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 匹配第一组intent-filter
public void startA(View view) {
Intent intent = new Intent();
intent.setAction("android.intent.action.SEND");
intent.setType("text/plain");
startActivity(intent);
}
// 匹配第二组intent-filter
public void startB(View view) {
Intent intent = new Intent();
intent.setAction("com.king.bester.demo");
intent.addCategory("com.king.bester.a");
intent.setType("text/plain");
startActivity(intent);
}

1、startA()中没有设置category,是因为系统在调用startActivity或者startActivityForResult的时候会默认为Intent加上“android.intent.category.DEFAULT”这个category。所以,为了我们的Activity能接收隐式调用,必须在intent-filter中指定这个category。
2、action区分大小写
3、一个Activity可以有多个intent-filter,一个intent只要能匹配任何一组intent-filter即可启动对应的Activity。