android 沉淀 - 四大组件

Activity

大家最熟悉的吧,但是熟悉并不代表了解一切,但往往越是被熟知的越是被问及的越深,面试时看=有的面试官可以喜欢在这里难住面试者的

1. 生命周期
  • onCreate - Activity 被创建,一些初始化工作在这里(加载布局资源,初始化所需要的数据等),如果有耗时任务需开异步线程
  • onStart - Activity 启动中,个状态下 Activity 还在加载其他资源,用户还无法看到,不能交互
  • onResume - Activity创建完成,用户可看见界面,可交互
  • onPause - Activity 正在暂停,正常情况下接着会执行 onStop(),这时可以做数据的存储、动画停止的操作,尤其是不能太耗时,新的 Activity 必须要等当前 Activity 的 onPause 执行完才能开始创建的
  • onStop - Activity 即将停止,这时可以做一些回收工作,一样不能太耗时
  • onDestory - Activity即将被销毁,可以做一些工作和资源的回收,Service、BroadCastReceiver、Map、Bitmap、动画、handle、rxjava 回收
  • onRestart - Activity 正在重新启动,一般时当前 Activity 从不可见到可见状态时会执行这个方法,例如:用户按下 Home 键(锁屏)或者打开新 Activity 再返回这个 Activity

以下的操作会触发 Activity 的生命周期函数:

2. 启动模式

启动模式这个一定会问,但是必会的

  • standard - 标准默认启动模式,Activity 可以有多个实例,每次启动 Activity,无论任务栈中是否已经有这个Activity 的实例,系统都会创建一个新的 Activity 实例
  • singleTop - 栈顶复用模式,当一个 singleTop 模式的 Activity 已经位于任务栈的栈顶,再去启动它时,不会再创建新的实例,如果不位于栈顶,就会创建新的实例
  • SingleTask - 栈复用模式,整个 Activity 栈只能有一个 SingleTask 的 Activity 实例存在,若是再启动相同的 Activity,会把栈中的该 Activity 实例置于栈顶,简单说就是该 Activity 实例上面的所有 Activity都会出栈,也就是退出了,之后该 Activity 实例自然就在栈顶了,注意栈的操作
  • singleInstance - Activity 实例复用模式,singleInstance 的 Activity 会自动生成一个 Activity 任务栈,该栈中只能切只有该 Activity 一个Activity,整个 app 的进程中只能使用这一个 Activity 实例,类似于 static 的概念

除了 standard 之外其他3个模式,复用所在 Activity 时都会触发 onNewIntent 方法,这个方法接受一个 intent 参数,可以拿到用户传递过来的数据

调用顺序如下:

  • onNewIntent() -> onRestart() -> onStart() -> onResume()
    需要特别注意的是, 如果在 onNewIntent(Intent) 中,不调用 setIntent(Intent) 方法对 Intent 进行更新的话,那么之后在调用 getIntent() 方法时得到的依然是最初的值
3. 数据保存与恢复

2 个方法:

调用时机 : 注意是 Activity 容易被销毁的时候调用, 是容易被销毁, 但是也可能没有销毁就调用了

onSaveInstanceState 方法总是在 onStop 之前调用,但是不去确定是在 onPause 之前或之后调用。另外 onSaveInstanceState 保存的数据有有效期和 app 进程相同,app 进程要是被销毁了那么保存的数据也就没了

4. 系统 kill 时的生命周期

非常郁闷的是当系统因为内存不足要回收 Activity 占用的资源时,有时 Activity 在 onPause() 之后就会被销毁,onStop(),onDestory() 根本不会执行,所以很多时候我们要在 onResume()注册监听,onPause() 注销监听也是没办法的事,尤其是对于广播接收器来说,这可能造成内存泄露问题


Service

Service 既服务,被认为是没有 UI 的 Activity,非常相似

Service 的特点(可能被问及的点):

  • Service 同 Activity 一样都是运行在主线程上的
  • Service 的作用就是用来在可不见的后台默默执行一些任务
  • Service 有2种启动模式:StartService / bindService
  • StartService 启动的 Service,无法与外置直接通信(如传递接口),只能通过广播,handle,evenBus 等间接通讯方式。声明周期同 app 进程,比 app 本身要长,即便 app 退出了,该 Service 也会一直运行,直到内存不足时被系统 kill,并根据 Service 中 onStartCommand 方法的返回值采取不同的策略(比如重新启动该 Service )。可以多次启动,在该 Service 已经启动的情况下只会触发 onStartCommand 方法,可以在外部和 Service 内部关闭 Service
  • bindService 启动的 Service,可以与外界直接通信,可以通过 IBinder 接口获取 Service 实例,bindService 的生命周期和启动 Service 的 UI 页面等同,UI 页面关闭时,bindService 也会同步销毁,当然也可以在 UI 内主动解绑销毁 Service,当没有人与 bindService 捆绑时,bindService 也会自行销毁
  • 当前最常见的 Service 绑定方式:先 StartService 启动该 Service,在需要的时候 bindService,这样即可以让 Service 服务一直运行,也可以和 Service 进行通讯。
  • 设置为前台模式的 Service 会一直存活,理论上系统不会主动销毁该服务,除非用户手动 kill 该进程

2种 Service 启动方式分别对应的生命周期:


如何保证Service不被杀死:

  • onStartCommand方式中,返回START_STICKY,这样即便被系统 kill 也有东山再起的机会
  • 提高Service的优先级,AndroidManifest.xml 中可以通过 android:priority = "1000" 这个属性设置最高优先级,1000是最高值,如果数字越小则优先级越低,同时适用于广播
  • 提升Service进程的优先级,比如前台进程
  • 在onDestroy方法里重启Service,onDestroy() 时发送一个自定义广播,重新启动service
  • 系统广播监听 Service 状态,利用时间广播 Intent.ACTION_TIME_TICK 该广播每分钟发送一次,如果已经被结束了,就重新启动 Service
  • 将APK安装到/system/app,变身为系统级应用

更多更详细请看:


广播

1. 静态注册参数
<receiver 
    android:enabled=["true" | "false"]
//此broadcastReceiver能否接收其他App的发出的广播
//默认值是由receiver中有无intent-filter决定的:如果有intent-filter,默认值为true,否则为false
    android:exported=["true" | "false"]
    android:icon="drawable resource"
    android:label="string resource"
//继承BroadcastReceiver子类的类名
    android:name=".mBroadcastReceiver"
//具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收;
    android:permission="string"
//BroadcastReceiver运行所处的进程
//默认为app的进程,可以指定独立的进程
//注:Android四大基本组件都可以通过此属性指定自己的独立进程
    android:process="string" >

//用于指定此广播接收器将接收的广播类型
//本示例中给出的是用于接收网络状态改变时发出的广播
 <intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
</receiver>
2. 继承 BroadcastReceiver

BroadcastReceiver 是官方组件,自然是要像 Activity 一样去继承的

public class SdCardBroadcastReceiver extends BroadcastReceiver {
    
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction ();
        if ("android.intent.action.MEDIA_MOUNTED".equals(action)) {
            System.out.println("sd卡已挂载");
        } else if ("android.intent.action.MEDIA_UNMOUNTED".equals(action)) {
            System.out.println("sd卡已卸载");
        }

       String data = intent.getStringExtra("key");
    }
}

从 intent 中可以拿到 action 和 bundle 传递的数据

3. 广播的类型
  • 普通广播(Normal Broadcast)
  • 系统广播(System Broadcast)
  • 有序广播(Ordered Broadcast)
  • 粘性广播(Sticky Broadcast)
  • App应用内广播(Local Broadcast)

3.1 发送无序广播

public void startBroadcast(View view){
        //开启广播
        //创建一个意图对象
        Intent intent = new Intent();
        //指定发送广播的频道
        intent.setAction("com.example.BROADCAST");
        //发送广播的数据
        intent.putExtra("key", "发送无序广播,顺便传递的数据");
        //发送
        sendBroadcast(intent);
    }

3.2 发送有序广播

    public void sendOrderedBroad(View view) {
        Intent intent = new Intent();
        intent.setAction("com.example.ORDERED");
        // 发送无序广播
        sendOrderedBroadcast(intent,//意图动作,指定action动作
                null, //receiverPermission,接收这条广播具备什么权限
                new FinalReceiver(),//resultReceiver,最终的广播接受者,广播一定会传给他
                null, //scheduler,handler对象处理广播的分发
                0,//initialCode,初始代码
                "每人发10斤大米,不得有误!", //initialData,初始数据
                null//initialExtras,额外的数据,如果觉得初始数据不够,可以通过bundle来指定其他数据
                );
    }

有序广播的特点是按照 xml 中的优先级从高到底开始接受,先接受的 recriver 可以把数据处理之后再交给下一级或者不在传递,很想网络里的拦截器

priority 越大优先级越高


public class ShengReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent)        

        // 获取广播的数据
        String data = getResultData();       
 
        // 修改之后再给下一级
        setResultData("中央下达福利,每人5斤大米");

        // 终止广播,权限小的接收者就接收不到广播了
        abortBroadcast();
    }
}
4. 动态注册方法

注意广播注册注销的最佳时机在 onResume、onPause,因为系统回收 Activity 时后面的生命周期可能就不会走了

上面在 AndroidManifest 中声明的都叫做静态广播,动态注册的广播就是不在AndroidManifest 中声明,用代码启动,解绑,但是要注意自 8.0 开始,不在允许静态注册的广播,必须手动代码注册

// 选择在Activity生命周期方法中的onResume()中注册
@Override
  protected void onResume(){
      super.onResume();

        // 手动注册广播
        var intentFilter = IntentFilter()
        intentFilter.addAction("AAA")
        registerReceiver(receiver, intentFilter)

        // 发送给广播
        var intent = Intent("AAA")
        sendBroadcast(intent)
 }

 @Override
 protected void onPause() {
     super.onPause();
      // 解绑广播
     unregisterReceiver(mBroadcastReceiver);
     }
}

重复注册、重复注销也不允许,不注销会有内存泄露问题的

5. 不同注册方式的广播回调 OnReceive

对于不同注册方式的广播接收器回调 OnReceive(Context context,Intent intent)中的 context 返回值是不一样的:

  • 对于静态注册(全局+应用内广播),回调onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext
  • 对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity Context
  • 对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context
  • 对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context

进程

android 的进程主要了解有优先级,用来配合 Service 保活策略

android 进程优先级5个档:

  • 前台进程
  • 可见进程
  • 服务进程
  • 后台进程
  • 空进程

怎么理解呢,简单来说:

  • 前台进程 - 需要指定设置;
  • 可见进程 - Activity 可见时就是
  • 服务进程 - Service 启动时就是
  • 后台进程 - app 按 home 就是
  • 空进程 - 所有的页面都退出了,但 app 所在的进程不会第一时间就回收,为了放置用户短时间内再启动有一个缓冲,但是非常容易被回收

上面是简单理解,详细请看:

但是和理论不同的是,Service 优先级首先参考的不是自己的优先级,而是首先参考所在 app 进程的页面活动

  • 比如我们在一个页面中启动一个 Service 播放音乐,我们按 home 切到后台,按照理论此时 app 所在进程是服务进程的优先级,属于不容易被回收的那种,但是实际不是,app 进程是后台进程,优先级比服务进程第一个档次,属于容易被回收的进程,只有 Service 设置为前台时除外
  • 若是这个 Service 单独运行在一个进程中,那么就和理论情况一样

具体可以参考:


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,377评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,390评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,967评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,344评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,441评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,492评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,497评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,274评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,732评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,008评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,184评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,837评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,520评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,156评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,407评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,056评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,074评论 2 352

推荐阅读更多精彩内容