Android - Activity

5s执行不完就ANR异常

一、生命周期

Android - Activity_第1张图片

1.1 生命周期方法

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() 运行完。

1.2 生命周期变化

1.2.1 界面跳转时

打开甲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。

1.2.2 弹出对话框时

开启dialog Activity不会执行生命周期的方法。
跳转不是全屏的Activity

给activity配置android:theme="@android:style/Theme.Dialog"。

前一个Activity执行onPause,不会再执行onStop,否则就显示不出来了。

1.2.3 横竖屏切换时

不设置 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

标准模式

  • 每启动一个Activity都会创建一个新实例,按返回键会依次退出。
  • 从Service、Application中启动Activity不存在任务栈,要intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 
默认就是这个模式,适用于需要按顺序返回的情景。

singleTop

栈顶复用

  • 如果该Activity在栈顶,不会创建新实例而是复用它。会调用onNewIntent()→onRestart()→ onStart()→onResume()。
  • 如果不在就创建即便栈内已经存在实例。
适合接收通知启动的内容显示页面,例如通知栏不停接收到消息,不能每次点击就新建一个实例。

singleTask

栈内复用

  • 不存在该实例就创建,有则销毁该Activity之上的其它实例并复用它。会调用onNewIntent()→onRestart()→onStart()→onResume()。
  • 若是在其他APP中启动它,会创建一个新的任务栈,它再启动其它的Activity也会是存在于这个任务栈中。
  • 在Manifest中可以通过taskAffinity节点指定加载到哪个任务栈中。Activity如果没有显式设置该节点,默认是Application中该节点指定的,也没有显示显式指定的话就默认是包名。

        适合作为程序入口(主页)。第一次进入主界面之后,主界面位于栈底,以后不管启动多少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)

三、临时数据的保存和恢复

3.1 内存不足被回收

当系统内存不足时, 调用 onPause() 和 onStop() 方法后的 activity 可能会被系统销毁,此时内存中就不会存有该 Activity 的实例了。如果之后这个 Activity 重新回到前台,该 Activity 在这种情况下被重新创建了一次,之前所作的改变就会消失。

3.1.1 保存 onSavaInstanceState()

API28以后在 onStop() 后执行,之前的话它的调用和 onPause() 没有严格先后关系,但肯定在 onStop() 之前。以下场景系统会触发该函数的调用:

  1. 按下HOME键:系统不知道你按下HOME后要运行多少其他的程序,自然也不知道 acitvity 是否会被销毁,所以系统会触发让我们有机会保存某些临时数据。
  2. 按下多任务键:因为你完全有可能选择其他程序然后玩很久,这个程序就被冷落好久了就有可能被 gc 回收,所以要保存数据很正常。
  3. 从 Activity 中启动一个新的 Activity。
  4. 横竖屏切换:会销毁当前Activity。
  5. 锁屏。

3.1.2  恢复 onRestoreInstanceState()

Activity重建时保存的 Bundle 对象会被传递给 onRestoreInstanceState() 和 onCreate(),区别在于该生命周期被调用说明 Bundle 对象非空不用做判断。在 onResume() 之前执行:

  1. Activity被异常销毁后打开Activity。
  2. 横竖屏切换后打开Activity。
  3. 或者说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");
}

3.2  配置更改、横竖屏切换

屏幕方向、键盘是否隐藏、语言等都是系统的一个属性。当配置发生变化时 Activity 会销毁后重建,对于临时数据的处理办法:

  1. 推荐使用 ViewModel 处理。
  2. 可以利用 Manifest 里面的 android:configChanges 来监听变化并不让 Activity 重建,然后重写 onConfigurationChanged() 进行处理。
android:configChanges="orientation|screenSize"

四、Intent

4.1 启动Activity

4.1.1 使用 Intent 主动启动(指定具体的)

直接代码指定需要跳转的下一个Activity。

Intent(Context packageContext, Class cls)
形参 packageContext要求提供一个启动Activity的上下文;形参cls用于指定想要启动的目标Activity。
button.setOnClickListener {
    //写法相当于Java中SecondActivity.class
    val intent = Intent(this, SecondActivity::class.java)
    startActivity(intent)
}

4.1.2 配置 IntentFilter 被动启动(指定符合条件的)

指定一些条件,符合条件的 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)

4.2 传递数据

Intent可携带的数据大小在 1m 之内(1020kb左右)。

4.2.1 传递数据给下一个Activity

//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")
}

4.2.2 返回数据给上一个Activity

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()
}

4.3 回传 onNewIntent( )

  • 触发条件:当 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中的一些配置属性

六、修改转场动画(进入退出)

通过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)

七、一些开发中的应用技巧

7.1 当前界面是哪个Activity

开发中查看别人的代码有时候很费劲,通过在 BaseActivity 中日志打印信息来快速锁定当前界面对应的Activity。(javaClass表示获取当前实例的Class对象,相当于在Java中调用

getClass()方法;而Kotlin中的BaseActivity::class.java表示获取BaseActivity类的
Class对象,相当于在Java中调用BaseActivity.class)

Log.d("当前Activity", javaClass.simpleName)

7.2 随时直接退出程序

在不考虑进后台划掉APP的情况下,由于回退栈的原因,退出程序可能需要多次按下返回键:

  1. 使用一个专门的集合对所有 Activity 进行管理。
  2. 使用代码关闭当前进程: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) }
}

7.3 跳转Activity写法

实际开发中会存在对接问题,对于跳转到其它 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)
    }
}

你可能感兴趣的:(Android,android)