5s执行不完就ANR异常
onCreate () |
当 activity 被创建时调用: 用来做一些初始化操作,例如加载布局、绑定事件。该方法提供了一个 Bundle 类型的参数,携带着该 activity 被系统销毁前的数据,前提是手动保存过。 若在 onCreate() 中调用 finish() 的话,接下来运行 -> onDestroy()。 |
onStart () |
当 activity 对用户可见时会调用: 此时界面可见但无法与用户交互,可以做一些数据加载、动画的初始化操作。 若在 onStart() 中调用 finish() 的话,接下来运行 -> onStop() -> onDestroy()。 |
onResume () |
当 activity 与用户交互时调用: 此时该 activity 位于栈顶,属于运行状态(Running),获取了焦点能与用户进行交互操作,可以做一些资源的开始(播放音乐、动画)。 此时可能该 activity 是从 onPause() 恢复显示(例如关闭Dialog、退出状态栏下拉的控制/通知中心),可以做一些资源的恢复(音乐播放)。 |
onPause () |
当 activity 被部分遮挡时调用: 此时 Activity 虽被遮挡无法交互但是部分可见(例如弹出Dialog、下拉状态栏进入控制/通知中心),可以停止不必要的动画等消耗CPU的资源(广播接收器、传感器、耗电的、音乐播放)。 此时系统准备启动或恢复另一个Activity,可以保存一些临时数据,不能放耗时操作否则影响界面切换速度。 |
onStop () |
当 activity 完全看不见时调用: 对资源进行释放以保证回退栈中不再显示的 Activity 不会占用过多性能,此时容易因为内存不足而被销毁。 以下三种情况都会触发该生命周期:①启动一个新的activity;②另一个已经存在的 activity 被切换到栈顶;③这个 activity 要被销毁的时候。 |
onDestroy () |
当 activity 销毁时调用: 比如发生如下情况:activity 调用了 finish() 方法来结束这个 activity,或者因为系统为了节省空间而临时销毁这个 activity,这两个情况可以通过 isFinishing() 方法判断。 |
onRestart () |
当 activity 恢复到栈顶时调用: 从回退栈中恢复到栈顶,运行完 onRestart() 之后运行 onStart()。 若在 onRestart() 中调用 finish() 的话,则还是会继续运行 onStart() 及后面的状态方法直到 onDestroy() 运行完。 |
打开甲Activity,然后打开乙Activity,接着按下返回键。 | 甲onPause → 乙onCreate → 乙onStart → 乙onResume → 甲onStop → 按下返回键 → 乙onPause → 甲onRestart→ 甲onStart → 甲onResume → 乙onStop → 乙onDestroy。 |
按下多任务键,再从后台进入。 | 按下多任务键 → onPause → onStop → 选择这个app → onRestart → onStart → onResume。 |
按主页键,再从桌面进入。 | 按下主页键 → onPause → onStop → 再次回到APP → onRestart → onStart → onResume。 |
锁屏再解锁。 | 锁屏 → onPause → onStop → 解锁 → onRestart → onStart → onResume。 |
startActivityForResult开启一个Activity并返回。 | 甲onPause → 乙onActivityResult → 乙onRestart → 乙onStart → 乙onResume →甲onStop → 甲onDestroy。 |
开启dialog | Activity不会执行生命周期的方法。 |
跳转不是全屏的Activity | 给activity配置android:theme="@android:style/Theme.Dialog"。 前一个Activity执行onPause,不会再执行onStop,否则就显示不出来了。 |
不设置 android:configChanges | 切换横屏执行一次,切换竖屏执行一次。 onPause -> onStop -> onSaveInstanceState -> onDestroy ->onCreate -> onStart -> onRestoreInstanceState -> onResume |
android:configChanges="orientation|keyboardHidden" | |
android:configChanges="orientation|keyboardHidden|screenSize" | 切屏只执行onConfigurationChanged。 |
Acndroid使用任务(task)来管理Activity,一个任务就是一组存放在栈里的Activity集合,因此也被称作返回栈(back task),栈是先进后出的结构。
模式 | 说明 | 使用场景 |
standard 标准模式 |
|
默认就是这个模式,适用于需要按顺序返回的情景。 |
singleTop 栈顶复用 |
|
适合接收通知启动的内容显示页面,例如通知栏不停接收到消息,不能每次点击就新建一个实例。 |
singleTask 栈内复用 |
|
适合作为程序入口(主页)。第一次进入主界面之后,主界面位于栈底,以后不管启动多少Activity,回到主界面都应该将它们清除掉,而不是又在栈顶新建一个主界面实例。主界面按返回键都是退出APP,保证了退出时所有Activity都能被销毁,而不是回退到某个界面。 在启动Activtiy时,如果希望在onCreate()不被触发的的情况下依然可以对Intent进行操作,这就需要使用onNewIntent()。在一个应用的Activity供多种方式调用启动的情况,多个调用希望只有一个Activity的实例存在,这就需要onNewIntent()方法了。只要在Activity中加入自己的onNewIntent(intent)的实现加上Manifest中对Activity设置lanuchMode=“singleTask”就可以。 当调用到onNewIntent()的时候,需要在中使用setIntent(intent)赋值给Activity的Intent.否则,后续的getIntent()都是得到老的Intent。 |
singleInstance 单例模式 |
新建一个任务栈并创建该实例,栈中不允许有其它实例。 |
当我们的程序中有一个Activity是允许其它程序调用时(来点呼叫界面)。每个应用程序都有自己的任务栈,同一个Activity在不同任务栈中入栈时必然是创建了新实例,而singleInstance会有单独的任务栈来管理,这样不管是哪个应用来访问这个Activity都是共用的同一个任务栈的实例,也就解决了共享Activity实例的问题,退出后又能回到原本的任务栈中。 singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A → B (singleInstance) → C,完全退出后再次启动首先打开的是B,例如某个应用中用到了google地图,当退出该应用后进入google地图还是刚才的界面。 |
设置方式(若两者同时存在,动态指定的优先级更高) | 说明 |
Manifest 静态指定 | 无法设置 FLAG_ACRIVITY_CLEAR_TOP标识。 |
Intent 动态指定 | 无法设置 singleInstance 模式。 |
//xml
//代码
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
当系统内存不足时, 调用 onPause() 和 onStop() 方法后的 activity 可能会被系统销毁,此时内存中就不会存有该 Activity 的实例了。如果之后这个 Activity 重新回到前台,该 Activity 在这种情况下被重新创建了一次,之前所作的改变就会消失。
API28以后在 onStop() 后执行,之前的话它的调用和 onPause() 没有严格先后关系,但肯定在 onStop() 之前。以下场景系统会触发该函数的调用:
- 按下HOME键:系统不知道你按下HOME后要运行多少其他的程序,自然也不知道 acitvity 是否会被销毁,所以系统会触发让我们有机会保存某些临时数据。
- 按下多任务键:因为你完全有可能选择其他程序然后玩很久,这个程序就被冷落好久了就有可能被 gc 回收,所以要保存数据很正常。
- 从 Activity 中启动一个新的 Activity。
- 横竖屏切换:会销毁当前Activity。
- 锁屏。
Activity重建时保存的 Bundle 对象会被传递给 onRestoreInstanceState() 和 onCreate(),区别在于该生命周期被调用说明 Bundle 对象非空不用做判断。在 onResume() 之前执行:
- Activity被异常销毁后打开Activity。
- 横竖屏切换后打开Activity。
- 或者说gc回收后打开Activity。
//存
protected void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
outState.putInt("age",100);
}
//取(方式一)
//推荐,因为如果没执行存储的操作,就不会调用这个方法,里面不必做非null判断
protected void onRestoreInstanceState(Bundle saveInstanceState){
super.onRestoreInstanceState(saveInstanceState);
var age = saveInstanceState.getInt("age");
}
//取(方式二)
//不推荐,如果没执行存,那参数可能为null,判断起来麻烦
protected void onCreate(Bundle saveInstanceState){
var age = saveInstanceState?.getInt("age");
}
屏幕方向、键盘是否隐藏、语言等都是系统的一个属性。当配置发生变化时 Activity 会销毁后重建,对于临时数据的处理办法:
- 推荐使用 ViewModel 处理。
- 可以利用 Manifest 里面的 android:configChanges 来监听变化并不让 Activity 重建,然后重写 onConfigurationChanged() 进行处理。
android:configChanges="orientation|screenSize"
直接代码指定需要跳转的下一个Activity。
Intent(Context packageContext, Class> cls)
形参 packageContext要求提供一个启动Activity的上下文;形参cls用于指定想要启动的目标Activity。
|
button.setOnClickListener {
//写法相当于Java中SecondActivity.class
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
}
指定一些条件,符合条件的 Activity 都会响应(用户手动选择跳转到哪个)。(利用scheme使得服务器可以定制化跳转,H5页面可以跳转)
action 动作 |
只能指定一个。 | ||
category 分类 |
可以指定多个(相当于用来细分)。可以不手动添加 Intent.addCategory("android.intent.DEFAULT"),因为启动会默认添加 ,也因此 IntentFilter 必须添加 |
||
data 数据 |
mineType 指定可以处理的类型,允许使用通配符。 |
image | 图片类型。 |
audio | 音频类型。 | ||
video | 视频类型。 | ||
URI 用来标识数据。 |
https://www.baidu.com:8080/hello/word.html?id=123&name=sb | ||
scheme | 数据协议(https、file、content、tel、geo...) | ||
host | 主机名(www.baidu.com) | ||
port | 端口(:8080) | ||
path | 资源路径(端口之后?之前,hello/word.html) | ||
query | 查询部分(?之后,id=123&name=sb) | ||
pathPattern | 完整路径信息(可以包含通配符,*写成\\*,\写成\\\\)。 | ||
pathPrefix | 路径的前缀。 |
//解析Uri
val uri = Uri.parse("http://www.baidu.com:8080/wenku/jiatiao.html?id=123456&name=jack")
//Uri各部分都可以通过代码拿到
uri.scheme
uri.authority
匹配不到会报错,可以使用函数先判断。 | PackageManager.resolveActivity( ) |
PackageManager.queryIntentActivities( ) | |
Intent.resolveActivity( ) |
//自定义category
val intent = Intent("com.example.activitytest.ACTION_START")
intent.addCategory("com.example.activitytest.MY_CATEGORY") //指定自定义的category
startActivity(intent)
//跳转到浏览器
val intent = Intent(Intent.ACTION_VIEW) //系统内置动作,值为android.intent.action.VIEW
intent.data = Uri.parse("https://www.baidu.com") //指定传递的数据,将网址解析成字符串并传入
startActivity(intent)
//跳转到拨号
val intent = Intent(Intent.ACTION_DIAL)
intent.data = Uri.parse("tel:10086")
startActivity(intent)
Intent可携带的数据大小在 1m 之内(1020kb左右)。
//ActivityA
button1.setOnClickListener {
val data = "Hello SecondActivity"
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("extra_data", data)
startActivity(intent)
}
//ActivityB
override fun onCreate(savedInstanceState: Bundle?) {
val extraData = intent.getStringExtra("extra_data")
}
startActivityForResult()
第二个参数是请求码(唯一值)用于在之后的回调中判断数据的来源。
|
onActivityResult 第一个参数 requestCode ,即我们在启动 Activity 时传入的请求码;第二个参数resultCode ,即我们在返回数据时传入的处理结果;第三个参 数 data ,即携带着返回数据的 Intent。
|
setResult()
第一 个参数用于向上一个Activity 返回处理结果,一般只使用 RESULT_OK 或 RESULT_CANCELED 这
两个值;第二个参数则把带有数据的 Intent 传递回去。
|
//ActivityA
button.setOnClickListener {
val intent = Intent(this, SecondActivity::class.java)
startActivityForResult(intent, 1)
}
//由于在一个Activity中有可能启动很多不同的Activity,每一个返回的数据都会回调到这个方法中。
//因此要通过检查requestCode来判断数据来源,再通过resultCode来判断处理结果是否成功。
//最后从data中取值。
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
1 -> if (resultCode == RESULT_OK) {
val returnedData = data?.getStringExtra("data_return")
}
}
}
//ActivityB
//点击自定义按钮返回的处理情况
button.setOnClickListener {
val intent = Intent()
intent.putExtra("data_return", "Hello FirstActivity")
setResult(RESULT_OK, intent)
finish()
}
//点击自带返回键的处理情况
override fun onBackPressed() {
val intent = Intent()
intent.putExtra("data_return", "Hello FirstActivity")
setResult(RESULT_OK, intent)
finish()
}
- 触发条件:当 Activity 配置了 singleTop 或 singleTask 模式时,任务栈中已有该 Activity 的实例再启动该 Activity 就会进行复用,生命周期会执行 onNewIntent() -> onRestart() -> onStart() -> onResume()。
- 数据处理:注意此时不会再执行用于初始化的 onCreate(),如果 Activity 展示的界面与跳转的数据有关,需要在 onNewIntent() 中通过 setIntent() 来更新携带的数据,这样就能反复跳转同一个 Activity 并显示不同内容。
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
setIntent(intent)
initView()
initData()
}
通过Manifest | 通过编辑 style.xml,再在 Manifest 中给 activity 设置 theme。 |
通过代码 | 调用overridePendingTransition(进入动画, 退出动画)。 |
//淡入淡出效果
overridePendingTransition(R.anim.fade, R.anim.hold)
//放大淡出效果
overridePendingTransition(R.anim.my_scale_action,R.anim.my_alpha_action)
//转动淡出效果
overridePendingTransition(R.anim.scale_rotate,R.anim.my_alpha_action)
//转动淡出效果
overridePendingTransition(R.anim.scale_translate_rotate,R.anim.my_alpha_action)
//左上角展开淡出效果
overridePendingTransition(R.anim.scale_translate,R.anim.my_alpha_action)
//压缩变小淡出效果
overridePendingTransition(R.anim.hyperspace_in,R.anim.hyperspace_out)
//右往左推出效果
overridePendingTransition(R.anim.push_left_in,R.anim.push_left_out)
//下往上推出效果
overridePendingTransition(R.anim.push_up_in,R.anim.push_up_out)
//左右交错效果
overridePendingTransition(R.anim.slide_left,R.anim.slide_right)
//放大淡出效果
overridePendingTransition(R.anim.wave_scale,R.anim.my_alpha_action)
//缩小效果
overridePendingTransition(R.anim.zoom_enter,R.anim.zoom_exit)
//上下交错效果
overridePendingTransition(R.anim.slide_up_in,R.anim.slide_down_out)
开发中查看别人的代码有时候很费劲,通过在 BaseActivity 中日志打印信息来快速锁定当前界面对应的Activity。(javaClass表示获取当前实例的Class对象,相当于在Java中调用
getClass()方法;而Kotlin中的BaseActivity::class.java表示获取BaseActivity类的
Class对象,相当于在Java中调用BaseActivity.class)
Log.d("当前Activity", javaClass.simpleName)
在不考虑进后台划掉APP的情况下,由于回退栈的原因,退出程序可能需要多次按下返回键:
- 使用一个专门的集合对所有 Activity 进行管理。
使用代码关闭当前进程:android.os.Process.killProcess(android.os.Process.myPid())
//创建管理的集合
object ActivityCollector { //使用单例类因为全局只需要一个Activity集合
private val activities = ArrayList()
fun addActivity(activity: Activity) {
activities.add(activity)
}
fun removeActivity(activity: Activity) {
activities.remove(activity)
}
fun finishAll() {
for (activity in activities) {
if (!activity.isFinishing) { //判断是否销毁中,因为存在按返回键销毁的情况
activity.finish()
}
}
activities.clear()
}
}
//在BaseActivity中配置
open class BaseActivity : AppCompatActivity() {
onCreate() { ActivityCollector.addActivity(this) }
onDestroy() { ActivityCollector.removeActivity(this) }
}
实际开发中会存在对接问题,对于跳转到其它 Activity 需要携带哪些数据,自己摸索或咨询别人都很麻烦。伴生对象中的函数可以像 Java 中的静态方法那样调用,在被跳转的 ActivityB 的伴生对象中封装跳转函数,在 ActivityA 中直接调用该函数传入所需数据。
//ActivityA
ActivityB.jumpTo(this, "张三" ,18)
//ActivityB
companion object {
fun jumpTo(context: Context, name: String, age: Int) {
val intent = Intent(context, SecondActivity::class.java)
intent.putString("name", name)
intent.putInt("age", age)
context.startActivity(intent)
}
}