1 Activity是什么?
Activity是Android中与用户直接进行交互(滑动、触摸、点击等)的组件,也就是Android系统提供给用户操作的UI组件。
2 Activity的生命周期
2.1 Activity的4种状态
- running(运行状态)
Activity运行于“前台任务栈”的栈顶,是与用户直接进行交互的Activity。 - paused
Activity失去焦点,就是有其他Activity以透明、非全屏的状态“覆盖”于此Activity之上时。 - stoped
Activity处于任务栈内,保存了所有运行时的数据,但无法与用户交互。 - killed
Activity被释放掉,不存在与内存之中。
2.2 Activity各生命周期方法的调用意义、作用
- onCreate()
表示Activity正在被创建。在这里可以做一些初始化的工作。 - onStart()
表示Activity正在被启动。已经可见,但不在前台,无法交互。 - onResume()
表示Activity已经可见,并且出现在前台可以交互。 - onPause()
表示Activity正在停止。在这里可以做一些储存数据、保存用户状态(必须在此处保存,因为onPause方法在Activity即将销毁或将到后台前必定执行)、停止动画等工作,但不能太耗时,因为必须onPause执行完成之后新的Activity才能Resume。 - onStop()
表示Activity即将停止。可以进行一些稍微重量级的回收工作,不能太耗时。在内存严重不足时将不会执行,因此保存用户状态等操作最好在onPause()方法中进行 - onRestart()
表示Activity正在重新启动。当前Activity从不可见重新变成可见状态。 - onDestory()
表示Activity即将被销毁。可以进行一些回收工作和最终的资源释放。
2.3 Activity在不同场景下经历的生命周期方法
常见情况
- Activity正常启动:
onCreate()-> onStart()-> onResume()
在正常情况下,一个Activity从启动到结束会以如下顺序经历整个生命周期: - 当用户按下back键之后(Activity正常销毁):
onPause()-> onStop()-> onDestory() - 当有一个透明、非全屏的Activity覆盖在此Activity之上时:
onPause() - 打开了另外一个Activity时(此Activity不处于前台任务栈栈顶)
onPause()-> onStop() - 用户按下Home键时
onPause()-> onStop()
特殊情况
- 调用finish()方法后:
- 在Activity的onCreate()中调用finish(),则执行的生命周期方法顺序为:
onCreate() -> onDestroy() - 在Activity的onStart()中调用finish(),则执行的生命周期方法顺序为:
onCreate() -> onStart() -> onStop() -> onDestroy() - 在Activity的onResume()或onPostResume()中调用finish(),则执行的生命周期方法顺序为:
onCreate() -> onStart() -> onResume() -> onPostResume() -> onPause() -> onStop() -> onDestroy() - 在Activity的onPause()、onStop()、onDestory()中调用finish(),则执行的什么周期方法顺序为:
按照正常的生命周期顺序执行。
- 在Activity的onCreate()中调用finish(),则执行的生命周期方法顺序为:
- 横竖屏切换
onPause()-> onSaveInstanceState()-> onStop()-> onDestroy()->onCreate()-> onStart()-> onRestoreInstanceState()-> onResume()
PS:1.横竖屏切换过程中,Activity进行被销毁然后重建的过程,因此才有了生命周期的改变。
2.在Activity因异常情况下终止时,系统会调用onSaveInstanceState()来保存当前Activity的状态。这个方法的调用是在onStop()之前,但它和onPause没有既定的时序关系,该方法只在Activity被异常终止的情况下调用。
3.异常终止的Activity被重建后,系统会调用onRestoreInstanceState(),并把Activity销毁时onSaveInstanceState()方法所保存的Bundle对象参数同时传递给onRestoreInstanceState()和onCreate()方法,因此,可通过onRestoreInstanceState()方法来恢复Activity的状态,该方法的调用时机是在onStart之后。
4.onCreate()和onRestoreInstanceState()方法恢复Activity的状态的区别:onRestoreInstanceState回调则表明其中Bundle对象非空,不用加非空判断。onCreate()需要非空判断。建议使用onRestoreInstanceState()**。
5.避免横竖屏切换时,Activity的销毁和重建,只需在AndroidManifest文件中指定如下属性即可:
android:configChanges = “orientation| screenSize”
避免重建后,Activity横竖屏切换后会调用如下方法
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
2.4 Android进程优先级
前台进程 > 可见进程 > 服务进程 > 后台进程 > 空进程
前台进程(running状态):正在与用户交互的Activity或与前台Activity绑定的Service
可见进程(paused状态):当Activity可见但不是处于前台进程,也就是不可交互的进程。
服务进程:在后台开启的Service服务的进程
后台进程(stoped状态):如按下Home键后的当前Activity就处于后台进程,此时后台进程不会立即被kill掉,系统会根据当前的内存状况来进行判断是否kill。
空进程 :表示不活跃的组件,系统出于缓存的目的而保留。
3 Android任务栈
任务栈是什么?
-
Task又称为Android任务栈,是一个栈结构,具有后进先出的特性,用于存放我们的Activity,Android中用任务栈与启动模式结合来达到节省内存资源的目的。
-
我们每次打开一个新Activity或退出当前Activity都会在任务栈中添加或减少一个Activity,因此一个任务栈包含了一个Activity的集合,只有栈顶Activity才能与用户交互,,Android系统通过Task有序地管理每个Activity,并决定哪个Activity与用户交互。
-
我们退出应用程序时,必须把任务栈中所有的Activity清除出栈时,任务栈才 会被销毁。当然任务栈也可以移动到后台,并且保留了每一个Activity的状态,可以有序的给用户列出它们的任务,同时也不会丢失Activity的状态信息。
-
需要注意的是,一个App中可能不止一个任务栈,某些特殊情况下,单独一个Actvity可以独享一个任务栈。还有一点就是一个Task中的Actvity可以来自不同的App,同一个App的Activity也可能不在一个Task中。
4 Activity四种启动模式
4.1 Standard (标准模式)
说明: 假设没有为Activity设置启动模式的话,默认标准模式。无论这个实例是否存在,每次启动一个Activity都会又一次创建一个新的实例入栈。
举例:此时Activity 栈中以此有A、B、C三个Activity,此时C处于栈顶,启动模式为Standard 模式。若在C Activity中加入点击事件,须要跳转到还有一个同类型的C Activity。结果是还有一个C Activity进入栈中,成为栈顶。
特殊情况:
如果在Service或Application中启动一个Activity,其并没有所谓的任务栈,可以使用标记位Flag来解决。解决办法:为待启动的Activity指定FLAG_ACTIVITY_NEW_TASK标记位,创建一个新栈。
绝大多数Activity。如果以这种方式启动的Activity被跨进程调用,在5.0之前新启动的Activity实例会放入发送Intent的Task的栈的顶部,尽管它们属于不同的程序,这似乎有点费解看起来也不是那么合理,所以在5.0之后,上述情景会创建一个新的Task,新启动的Activity就会放入刚创建的Task中,这样就合理的多了。
4.2 SingleTop (栈顶复用模式)
说明:(分两种处理情况)
1.须要创建的Activity已经处于栈顶时,此时会直接复用栈顶的Activity,不会再创建新的Activity。
2.若须要创建的Activity不处于栈顶,此时会又一次创建一个新的Activity入栈,同Standard模式一样。
若情况一中栈顶的Activity被直接复用时,它的onCreate()、onStart()不会被系统调用,由于它并没有发生改变。可是一个新的方法 onNewIntent()会被回调(Activity被正常创建时不会回调此方法)。
举例:此时Activity 栈中以此有A、B、C三个Activity,此时C处于栈顶,启动模式为SingleTop 模式。
情况一:在C Activity中加入点击事件,须要跳转到还有一个同类型的C Activity。结果是直接复用栈顶的C Activity,并调用如下方法。
//由于不会重建一个Activity实例,则不会回调其他生命周期方法。
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
}
情况二:在C Activity中加入点击事件,须要跳转到还有一个A Activity。结果是创建一个新的Activity入栈。成为栈顶。
应用场景:
在通知栏点击收到的通知,然后需要启动一个Activity,这个Activity就可以用SingleTop,否则每次点击都会新建一个Activity。
当然实际的开发过程中,测试妹纸没准给你提过这样的bug:某个场景下连续快速点击,启动了两个Activity。如果这个时候待启动的Activity使用 SingleTop模式也是可以避免这个Bug的。
同Standard模式,如果是外部程序启动SingleTop的Activity,在Android 5.0之前新创建的Activity会位于调用者的Task中,5.0及以后会放入新的Task中。
4.3 SingleTask (栈内复用模式)
说明:若须要创建的Activity已经处于栈中时,此时不会创建新的Activity,而是将存在栈中的Activity上面的其他Activity所有销毁,使它成为栈顶,并调用onNewintent()方法。
PS:该模式是一种单例模式,即一个栈内只有一个该Activity实例。该模式,可以通过在AndroidManifest文件的Activity中指定该Activity需要加载到那个栈中,即SingleTask的Activity可以指定想要加载的目标栈。SingleTask和taskAffinity配合使用,指定开启的Activity加入到哪个栈中。
<activity android:name=".Activity_A"
android:launchMode="singleTask"
android:taskAffinity="com.lvr.task"
android:label="@string/app_name">
</activity>
PS:
1.关于taskAffinity的值:
每个Activity都有taskAffinity属性,这个属性指出了它希望进入的Task。如果一个Activity没有显式的指明该Activity的taskAffinity,那么它的这个属性就等于Application指明的taskAffinity,如果Application也没有指明,那么该taskAffinity的值就等于包名。2.执行逻辑:
在这种模式下,如果Activity指定的栈不存在,则创建一个栈,并把创建的Activity压入栈内。如果Activity指定的栈存在,如果其中没有该Activity实例,则会创建Activity并压入栈顶,如果其中有该Activity实例,则把该Activity实例之上的Activity杀死清除出栈,重用并让该Activity实例处在栈顶,然后调用onNewIntent()方法。
**举例:**此时Activity 栈中依次有A、B、C三个Activity。此时C处于栈顶,启动模式为SingleTask 模式。
情况一:在C Activity中加入点击事件,须要跳转到还有一个同类型的C Activity。结果是直接用栈顶的C Activity。
情况二:在C Activity中加入点击事件,须要跳转到还有一个A Activity。结果是将A Activity上面的B、C所有销毁,使A Activity成为栈顶。
情况三:目标任务栈不存在,则创建一个任务栈,并压入栈顶。
应用场景:
应用于大多数App的主页。对于大部分应用,当我们在主界面点击回退按钮的时候都是退出应用,那么当我们第一次进入主界面之后,主界面位于栈底,以后不管我们打开了多少个Activity,只要我们再次回到主界面,都应该使用将主界面Activity上所有的Activity移除的方式来让主界面Activity处于栈顶,而不是往栈顶新加一个主界面Activity的实例。
通过这种方式能够保证退出应用时所有的Activity都能报销毁。在跨应用Intent传递时,如果系统中不存在singleTask Activity的实例,那么将创建一个新的Task,然后创建SingleTask Activity的实例,将其放入新的Task中。
特殊情景:
特殊情景一:
现在我们假设有如下两个Task栈,分别为前台任务栈和后台任务栈
从图中我们看出前台任务栈分别为AB两个Activity,后台任务栈分别为CD两个任务栈,而且其启动模式均为singleTask,此时我们先启动CD,然后再启动AB,再有B启动D,此时后台任务栈便会被切换到前台,而且这个时候整个***后退列表***就变成了ABCD,请注意我们这里强调的是后退列表,而非栈合并。因此当用户点击back键时,列表中的Activity会依次按DCBA顺序出栈,如下图所示:
这里我们通过两个应用ActivityTask和ActivityTask2来测试重现这个现象。因为两个是不同的应用所以启动时所在的栈也是不同。我们先启动ActivityTask2的应用,其ActivityC和ActivityD都是singleTask模式,然后再启动应用ActivityTask,此时ActivityC和ActivityD所在任务栈会被退居后台,而打开的ActivityA和ActivityB会在前台,而且都是默认模式。我们通过 adb shell dumpsys activity activities 命令查看此时栈的情况:
我们可以看到由两个栈,分别为id=222且栈名为“com.cmcm.activitytask”的任务栈其包含ActivityA和ActivityB(下面简称AB,栈名一般默认和包名相同),另外一个任务栈,id=221,栈名为“com.cmcm.activitytask2”,其包含ActivityC和ActivityD(下面检测CD)。现在我们通过ActivityB去启动ActivityD,然后按back键回退。B调用D代码如下:
public class ActivityB extends Activity {
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
btn= (Button) findViewById(R.id.main);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
ComponentName cn = new ComponentName("com.cmcm.activitytask2", "com.cmcm.activitytask2.ActivityD");
intent.setComponent(cn);
startActivity(intent);
}
});
}
}
运行结果如下:
我们可以看到包含CD的任务栈被提前的,虽然CD隔开了,但是我们从id和栈名可以发现他们是同一个栈,而AB所在的栈则在CD所在栈的后面,所以此时我们按back回退时,退出顺序是这样的D->C->B->A,动态图如下:
到这里我们就应该更加清晰的了解情景一的现象了。了解这点有什么用呢,这可以使用我们更好地去管理我们的任务栈,而不会导致栈混乱是进入一些用户本来就不需要界面,影响用户体验。
特殊情景二:
如果上面B不是请求启动D而是请求启动C,那么又会是什么情况呢?其实这个时候任务栈退出列表变成C->B->A,其实原因很简单,singleTask模式的ActivityC切换到栈顶时会导致在他之上的栈内的Activity出栈。同样我们还是使用上面的代码,把B启动D改为B启动C,那么此时B未启动C时任务栈的情况如下:
我们仍然可以看到两个任务栈,分别为id=242,栈名“com.cmcm.activitytask”的Task,包含ActivityA和ActivityB;id=241,栈名“com.cmcm.activitytask2”的Task,包含ActivityC和ActivityD。此时我们通过B启动C后栈的情况变成如下情况 :
因此,栈的退出列表就变成了C->B->A了,如下图所示:
动态图如下:
4.4 SingleInstance (单例模式)
说明 SingleInstance比较特殊,具有此模式的Activity仅仅能单独位于一个任务栈中。这个经常使用于系统中的应用,比如Launch、锁屏键的应用等等,整个系统中仅仅有一个!
举例:比方 A Activity是该模式,启动A后。系统会为它创建一个单独的任务栈,由于栈内复用的特性,新的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁。
4.5 Activity启动模式的使用方式
1.通过AndroidMenifest.xml文件为Activity指定启动模式,代码如下:
<activity android:name=".ActivityC"
android:launchMode="singleTask" />
2.通过在Intent中设置标志位(addFlags方法)来为Activity指定启动模式,示例代码如下:
Intent intent = new Intent();
intent.setClass(ActivityB.this,ActivityA.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
那么标志位是是什么呢?接下来我们就来了解一些常用的标志位
1.3 Activity的Flags
**说明:**这里我们主要介绍一下一些常用的Activity的Flag,因为Activity的Flag比较多,我们知道一些常用的就够了,遇到比较特殊的还是查查官网文档吧。
Intent.FLAG_ACTIVITY_NEW_TASK
该标志位表示使用一个新的Task来启动一个Activity,相当于在清单文件中给Activity指定“singleTask”启动模式。通常我们在Service启动Activity时,由于Service中并没有Activity任务栈,所以必须使用该Flag来创建一个新的Task。我们来重现一下这个错误,创建一个Service服务,并在onCreate方法中启动Activity,代码如下:
public class ServiceT extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Intent i =new Intent(getApplicationContext(),ActivityD.class);
startActivity(i);
}
}
启动应用并启动Service服务,后报错如下:
从异常信息我们可以看出,提示我们添加Intent.FLAG_ACTIVITY_NEW_TASK标志位,所以我们代码必须改成如下:
public class ServiceT extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Intent i =new Intent(getApplicationContext(),ActivityD.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(i);
}
}
Intent.FLAG_ACTIVITY_SINGLE_TOP
该标志位表示使用singleTop模式来启动一个Activity,与在清单文件指定android:launchMode="singleTop"效果相同。
Intent.FLAG_ACTIVITY_CLEAR_TOP
该标志位表示使用singleTask模式来启动一个Activity,与在清单文件指定android:launchMode="singleTask"效果相同。
Intent.FLAG_ACTIVITY_NO_HISTORY
使用该模式来启动Activity,当该Activity启动其他Activity后,该Activity就被销毁了,不会保留在任务栈中。如A-B,B中以这种模式启动C,C再启动D,则任务栈只有ABD。
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
使用该标识位启动的Activity不添加到最近应用列表,也即我们从最近应用里面查看不到我们启动的这个activity。与属性android:excludeFromRecents="true"
效果相同。
5 URL Scheme跳转协议
5.1 什么URL Scheme?
Scheme是Android中的一种页面跳转协议,是一种非常好的实现机制,通过定义自己的scheme协议,可以非常方便跳转app中的各个页面;通过scheme协议,服务器可以定制化告诉App跳转那个页面,可以通过通知栏消息定制化跳转页面,可以通过H5页面跳转页面等。
5.2 URL Scheme的应用场景
客户端应用可以向操作系统注册一个 URL scheme,该 scheme 用于从浏览器或其他应用中启动本应用。通过指定的 URL 字段,可以让应用在被调起后直接打开某些特定页面,比如商品详情页、活动详情页等等。也可以执行某些指定动作,如完成支付等。也可以在应用内通过 html 页来直接调用显示 app 内的某个页面。综上URL Scheme使用场景大致分以下几种:
- 服务器下发跳转路径,客户端根据服务器下发跳转路 径跳转相应的页面
- H5页面点击锚点,根据锚点具体跳转路径APP端跳转具体的页面
- APP端收到服务器端下发的PUSH通知栏消息,根据消息的点击跳转路径跳转相关页面
- APP根据URL跳转到另外一个APP指定页面
5.3 URL Scheme协议格式
完整的URL Scheme协议格式:
xl://goods:8888/goodsDetail?goodsId=10011002
通过上面的路径 Scheme、Host、port、path、query全部包含,基本上平时使用路径就是这样子的。
- xl代表该Scheme 协议名称
- goods代表Scheme作用于哪个地址域
- goodsDetail代表Scheme指定的页面
- goodsId代表传递的参数
- 8888代表该路径的端口号
5.4 URL Scheme如何使用?
- 在AndroidManifest.xml中对标签增加设置Scheme。
<activity
android:name=".GoodsDetailActivity"
android:theme="@style/AppTheme">
<!--要想在别的App上能成功调起App,必须添加intent过滤器-->
<intent-filter>
<!--协议部分,随便设置-->
<data android:scheme="xl" android:host="goods" android:path="/goodsDetail" android:port="8888"/>
<!--下面这几行也必须得设置-->
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
- 获取Scheme跳转的参数
Uri uri = getIntent().getData();
if (uri != null) {
// 完整的url信息
String url = uri.toString();
Log.e(TAG, "url: " + uri);
// scheme部分
String scheme = uri.getScheme();
Log.e(TAG, "scheme: " + scheme);
// host部分
String host = uri.getHost();
Log.e(TAG, "host: " + host);
//port部分
int port = uri.getPort();
Log.e(TAG, "host: " + port);
// 访问路劲
String path = uri.getPath();
Log.e(TAG, "path: " + path);
List<String> pathSegments = uri.getPathSegments();
// Query部分
String query = uri.getQuery();
Log.e(TAG, "query: " + query);
//获取指定参数值
String goodsId = uri.getQueryParameter("goodsId");
Log.e(TAG, "goodsId: " + goodsId);
}
- 调用方式
在网页中:
<a href="xl://goods:8888/goodsDetail?goodsId=10011002">打开商品详情</a>
在原生应用中:
Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("xl://goods:8888/goodsDetail?goodsId=10011002"));
startActivity(intent);
- 如何判断一个URL Scheme是否有效
PackageManager packageManager = getPackageManager();
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("xl://goods:8888/goodsDetail?goodsId=10011002"));
List<ResolveInfo> activities = packageManager.queryIntentActivities(intent, 0);
boolean isValid = !activities.isEmpty();
if (isValid) {
startActivity(intent);
}
注:以上内容是由自己从互联网收集整理、自己写、及看书、看视频等总结出来的笔记,如果借鉴的内容需要标识出来请私信我。