在上一篇文章中我们学习了 Activity 的生命周期以及用 Intent 打开系统的方法相关方面的知识,分析了单个 Activity 生命周期从创建到销毁的过程,以及多个 Activity 之间互相跳转的生命周期的创建过程,如果你还不是很清楚,可以先阅读 Android Activity完全解析(上),今天我们接着来学习一下 Activity 其它的相关方面的知识,为了让大家看起来一目了然今天分享的内容,绘制如下脑图:
一、Activity 之间的数据交互
1)简单的数据传递
在我们的项目开发过程中经常需要从上一页向下一页传递数据,以减少再请求流量的损耗,下面我们来看一下简单数据的传递,首先我们创建一个MainActivity,并实现一个 onClick 方法,用于点击跳转,然后再创建一个 ThirdActivity,在 MainActivity 中发送数据,在 ThirdActivity 中接受数据,代码如下:
MainActivity 中通过 onClick 方法发送数据:
@Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this,ThirdActivity.class); intent.putExtra("name","name"); intent.putExtra("age","age"); startActivity(intent); } ThirdActivity 中接受数据:这里没什么需要多说的,相信大家一看就懂 , 稍微需要注意的就是对 Intent 进行判 null 处理 /** * 数据传递测试 */ public class ThirdActivity extends AppCompatActivity { private static final String TAG = "ThirdActivity"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); if (intent != null) { String name =intent.getStringExtra("name"); String age = intent.getStringExtra("age"); Log.i(TAG, "name="+name); Log.i(TAG, "age="+age); } } } 这样我们就完成了简单的数据传递,我们看一下控制台 Log 的打印:03-12 19:28:16.834 22772-22772/com.example.qiudengjiao.activitytest I/ThirdActivity: name=name 03-12 19:28:16.834 22772-22772/com.example.qiudengjiao.activitytest I/ThirdActivity: age=age
可以看到,我们的数据成功传递过来
2)传递 Bundle 对象
同样我们还是在 MainActivity 中发送数据:
@Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this,ThirdActivity.class); //这里传递Bundle对象 Bundle bundle = new Bundle(); bundle.putString("name","姓名"); bundle.putString("age","0"); intent.putExtras(bundle); startActivity(intent); } 在 ThirdActivity 中接受数据:这里接受数据和1)的接受方法是一样的,不用修改 /** * 数据传递测试 */ public class ThirdActivity extends AppCompatActivity { private static final String TAG = "ThirdActivity"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_third); TextView textView = (TextView) findViewById(R.id.tv_textView); Intent intent = getIntent(); if (intent != null) { String name = intent.getStringExtra("name"); String age = intent.getStringExtra("age"); Log.i(TAG, "name=" + name); Log.i(TAG, "age=" + age); textView.setText(name + age); } } }3)传递 JavaBean
当我们传递的数据比较大的时候,这时候我们可以直接传递一个 JavaBean,这里注意要实现 Serializable 接口
首先我们先创建一个 Person 类, 并生成它的构造方法和 toString 方法,代码如下:
/** * Person实体类 */ public class Person implements Serializable { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } } 接下来我们开始在 MainActivity 中去发送数据,代码如下: public void onClick(View v) { Intent intent = new Intent(MainActivity.this, ThirdActivity.class); Person person = new Person("姓名", 0); Bundle bundle = new Bundle(); bundle.putSerializable("person", person); intent.putExtras(bundle); startActivity(intent); } ThirdActivity 中的代码和上面的不太一样,写法如下: Intent intent = getIntent(); if (intent != null) { Person person = (Person) intent.getSerializableExtra("person"); Log.i(TAG, "person=" + person); } 这里我们也成功接收到了传递过来的数据,控制台打印情况如下:03-12 20:48:25.021 5314-5314/com.example.qiudengjiao.activitytest I/ThirdActivity: person=Person{name='姓名', age=0}
4)传递 BitMap 对象(这里注意不能太大)
我们还是按照上面的惯例,先在 MainActivity 中发送数据:
@Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, ThirdActivity.class); Bundle bundle = new Bundle(); Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher); bundle.putParcelable("bitmap",bitmap); intent.putExtras(bundle); startActivity(intent); }在 ThirdActivity 中接收:这样我们就可以接收到传递的 BitMap 对象
/** * 数据传递测试 */ public class ThirdActivity extends AppCompatActivity { private static final String TAG = "ThirdActivity"; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_third); TextView textView = (TextView) findViewById(R.id.tv_textView); Intent intent = getIntent(); if (intent != null) { Parcelable bitmap= intent.getParcelableExtra("bitmap"); } } }好,常见的 Activity 之间的数据传递我们就一起探讨完了,接下来我们来看一下 Activity 之间传递大数据发生的问题
二、Activity 传递大数据的时候遇到的问题
首先我们还是在 MainActivity 的点击事件里写下如下代码:
这里我们试着来传递一些大一点的数据,看有什么效果
@Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, ThirdActivity.class); Bundle bundle = new Bundle(); int[] data = new int[1024*1024*8]; bundle.putIntArray("name",data); intent.putExtras(bundle); startActivity(intent); } 在 ThirdActivity 中接收代码如下: Intent intent = getIntent(); if (intent != null) { int[] data = intent.getIntArrayExtra("name"); Log.i(TAG, "data=" + data); } 我们点击按钮跳转,发现程序崩溃,我们来看一下控制台打印的日志:Caused by: android.os.TransactionTooLargeException: data parcel size 33554940 bytes
TransactionTooLargeException:我们看到报了交易数据太大的异常信息,这就说明我们数据之间的数据是不能太大的,这里建议不要超过 0.5 M,还望看到这样的提示异常,大家可以知道该去如何解决
三、任务和返回栈
1)Tasks 和 back stack
任务是指在执行特定作业时与用户进行交互的一些列 Activity,这些 Activity 按照各自的打开顺序排列在堆栈(即返回栈)中,设备的主屏幕是大多数任务的起点,当用户触摸到应用启动器中的图标(或屏幕上的快捷方式),该应用的任务出现在前台,如果应用不存在任务(应用最近未曾使用),则会创建一个新任务,并且该应用的“主” Activity 将作为堆栈中的根 Activity 打开。
当前 Activity 启动另一个 Activity 时,该新的 Activity 会被推送到栈顶,成为焦点所在,前一个 Activity 任然保留在堆栈中,但是处于停滞状态,Activity 停滞时,系统会保留其用户界面的当前状态,用户按“返回”按钮时,当前 Activity 会从堆栈顶部弹出( Activity 被销毁),而前一个 Activity 恢复执行(恢复其UI 的前一状态),堆栈中的 Activity 永远不会重新排列,仅推入和弹出堆栈:由当前的 Activity 启动时推入堆栈,用户返回按钮时弹出堆栈,因此返回栈以“后进先出”对象结构运行,下图通过时间线显示 Activity 之间的进度以及每个时间点的当前返回栈,形象的呈现了这种行为:
如果用户继续按"返回",堆栈中的相应 Activity 就会弹出,以显示前一个 Activity,直到用户返回主屏幕为止,当所有 Activity 均从堆栈中移除后,任务不复存在
Activity 和任务的默认行为总结如下:
当前 Activity A 启动 Activity B 时,Activity A 将会停止,但系统会保留其状态,如果用户处在 Activity B 时按“返回”按钮,则 Activity A 将恢复其状态,继续执行用户按“主页”按钮离开任务时,当前 Activity 将停止,其任务会进入后台,系统将保留任务中每个 Activity 状态,如果用户稍后通过选择开始任务的启动器图标来恢复任务,则当前任务出现在前台,并恢复执行堆栈顶部的 Activity如果用户按“返回”按钮,则当前的 Activity 会被销毁,堆栈中的前一个 Activity 恢复执行,被销毁的 Activity 不会保留该 Activity 的状态即使来自其他任务,Activity 也可以多次实例化 2)adb 查看 Activity 的命令 我们可以使用 adb 命令来查看 Activity 的创建情况,这样可以很清楚的看到我们打开的每个 Activity 的情况,这里就不演示了,希望大家可以去亲自看一下 命令为:adb shell dumpsys activity3) LaunchMode
接下来我们来看比较重要的 Activity 的启动模式,Activity 的启动模式在面试中也是经常遇到的,即便面试问不到,我们也需要好好去搞明白,不说废话了,我们开始来说 Activity 的启动模式:
standard:
标准模式,也是系统的默认模式,每次启动一个 Activity 都会重新创建一个新的实例,不管这个实例是否存在,被创建的实例的生命周期符合我们之前所说的正常情况下的生命周期的过程
singleTop:
栈顶复用模式,在这种模式下,如果新 Activity 已经位于任务栈的栈顶,那么此 Activity 不会被重新创建,同时它的 onNewIntent 方法会被调用,通过此方法的参数,我们可以取出当前的请求信息,需要注意的是这个 Activity 的 onCreate、onStart 不会被系统调用,因为它并没有发生改变,如果新的 Activity 已经存在,但是并没有处在栈顶,那么这个 Activity 任然会被创建,这里举个例子,假设目前栈内的情况是 ABCD,其中 ABCD 为 4 个 Activity,A位于栈低,D 位于栈顶,这个时候要再次启动 D,如果 D 的启动模式为 singleTop, 那么栈内的情况仍然为 ABCD,如果 D 的启动模式为 standard,那么由于 D 被重新创建,导致栈内的情况就变为 ABCDD
singleTask:
栈内复用模式,这是一种单实例模式,在这种模式下,只要 Activity 在一个栈中存在,那么多次启动此 Activity 都不会再重新创建实例,和 singleTop 一样,系统也会回调它的 onNewIntent 方法,具体一点,当一个 Activity 的启动模式是 singleTask 模式时,比如 Activity A,系统会首先去找是否存在 A 想要的任务栈,如果不存在,就重新创建一个任务栈,然后创建 A 的实例并放到栈中,如果存在 A 所需要的任务栈,这时要看 A 实例在栈中是否存在,如果不存在,就创建 A 实例,如果存在,就把 A 调到栈顶,并调用 onNewIntent 方法,下面举个例子:
1)比如目前任务栈 S1 的情况为 ABC,这个时候 D 以 singleTask 的方式请求启动,它所需要的任务栈为 S2,由于 S2 和 D 都不存在,所以会先创建S2,然后创建 D 的实例并放入栈中
2)另一种情况,假如 D 所需要的任务栈为 S1,其他情况如上,那么由于 S1 已经存在,系统就会直接创建 D 的实例并将其入栈到 S1
3)如果 D 所需要的任务栈为 S1,并且当前任务栈的情况为 ADBC,根据栈内复用的原则,系统会把 D 切换到栈顶并调用其 onNewIntent 方法,同时singleTask 模式具有 clearTop 的效果,会导致栈内所有在 D 上面的 Activity 全部出栈,即全部销毁,于是最后的情况为 AD
singleInstance:
单实例模式,这是一种加强的 singleTask 模式,它除了具有 singleTask 的全部属性外,那就是这种模式的 Activity 只能单独的位于一个任务栈中