Android四大组件-2018.11/12.x

Android四大组件有:Activity、Service、BroadcastRecevier、ContentProvider.

一.Activity

1.Activity生命周期

Activity正常生命周期

  • onCreate: 表示Activity开始创建,并进行一些布局资源的初始化,比如setContentView去初始化Activity界面资源并初始化Activity的所需数据。
  • onStart:表示Activity正在被启动,这时Activity可见但未在前台,所以不能交互。
  • onResume: 表示Activity已经可见,并在前台活动。onStart和onResume时Activity都已经可见,只是与onStart相比,onResume时Activity出现在前台。
  • onPause: 表示Activity正在暂停,正常情况下,onStop紧接着会被调用,如果此时快速回到当前Activity,那么onResume会被调用。此时可以做一些存储数据、停止动画等工作,但注意不能太耗时,因为onPause必须执行完,新Activity的onResume才会执行(耗时会影响新Activity的创建)。
  • onStop:表示Activity即将停止,可以做一些稍重量级的回收工作,同样不能太耗时。
  • onDestory:表示Activity正在销毁,此时可以做一些资源回收和释放。
  • onRestart: 表示Activity正在重新启动,一般情况下当Activity从不可见到可见状态时onRestart就会调用。这种情形发生一般是由用户导致的:比如用户按了Home键回到桌面然后又打开一个新的Activity,这时当前Activity会执行onPause和onStop,接着用户又回到了这个Activity就会调用出现这种情况。

Activity异常生命周期

  • 情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建
    场景:横竖屏转换
    当发生横竖屏转换的时候,由于系统配置发生改变,在默认情况下,我们的Activity也会重建,但是系统会在onStop之前调用onSaveInstanceState来保存当前状态和在Activity创建之后调用onRestoreInstanceState,并且会把销毁时onSaveInstanceState方法保存的Bundle对象传递给onRestoreInstanceState和onCreate方法。因此我们可以通过onRestoreInstanceState和onCreate方法判断Activity是否重建了,如果重建则恢复数据,从时许上说,onRestoreInstanceState方法调用时机在onStart之后。系统会自动为我们恢复一些数据,比如TextView的内容、ListView的滚动位置等等一些View相关的状态,具体要看View源码实现。
  • 情况2:资源内存不足导致低优先级的Activity被杀死
    Activity按优先级从高到低分为如下三种:
    1)前台Activity——正在与用户交互的Activity
  1. 可见非前台(不可交互)——比如Activity谈了一个对话框,导致Activity可见不可交互。
    3)后台Activity——已经被暂停的Activity,比如执行了onStop,优先级最低。
    当系统内存不足时,就会按照Activity的优先级来杀死Activity,一个进程没有四大组件在运行那么它很快就会被杀死,比较好的做法是把后台任务放到Servcie里执行提高进程的优先级。
    还有一点,在系统配置发生改变的时候如果不想Activity发生重建,就给Activity设置configChanges属性。比如不想让Activity在屏幕旋转的时候重建,就可以给configChanges属性添加orientation这个值(其实还有一个系统配置也会在屏幕旋转的时候会使Activity重建,screenSize这个属性,它在minSdkVersion和targetSdkVersion均低于13且屏幕尺寸发生变化时会导致Activity重启),如果我们向指定多个值可以使用“|”连接。

2.Activity的启动模式

四种模式

  • standard(标准模式):系统默认为这个模式。每次启动一个Activity都会新建一个Activity,没有任何优化。但注意有一个FLAG_ACTIVITY_NEW_TASK标记位,这样启动的时候就会为它创建一个新的任务栈,但这个时候Activity的启动实际上是singleTask模式启动。
  • singleTop(栈顶复用模式):当需要创建的Activity已经位于任务栈的栈顶,那么Activity不会创建新的,而是使用栈顶那个Activity.比如任务栈目前为ABCD,D为栈顶,然后需要一个D,这时不会创建D,会复用栈顶D,即调用onNewIntent方法,任务栈此时情况为ABCD,但如果是标准模式任务栈此时就是ABCDD。
  • singleTask(栈内复用模式):当我们需要的Activity任务栈中存在的话,就会复用那一个Activity实例。举个例子,ABCD,D为栈顶,现在我们需要B,则任务栈会出栈D,出栈C,然后复用B(调用onNewIntent方法),此时任务栈的情况为AB。
  • singleInstance(单例模式):它是singleTask的加强模式,它除了具有singleTask的特性以外还有一点,那就是具有此种模式的Activity只能单独地位于一个任务栈中。

TaskAffinity属性:在AndroidMenifest.xml中Activity的属性,翻译任务相关性,这个参数表示了一个Activity需要的任务栈名称,默认为应用的包名。这个属性和singleTask启动模式或者allowTaskReparenting属性配对使用,否则无意义

  • 当TaskAffinity属性和SingleTask启动模式配对使用的时候,它是具有该模式的Activity的目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈。
  • 当TaskAffinity属性和allowTaskReparenting结合的时候,会产生特殊的效果。当一个应用A启动了应用B的一个Activity C后,如果这个Activity C的allowTaskReparenting为true时,(然后按Home键回到桌面,再点击B应用的桌面图标)那么当应用B被启动后,(这时不是启动了B的主Activity,而是显示了被应用A启动的Activity C)此Activity C会直接从应用A的任务栈移到应用B的任务栈中。

这里需要注意一种情况:假设我们有2个任务栈A,前台任务栈的情况为AB,而后台任务栈的情况为CD,假设CD的启动模式均为singleTask。现在请求启动D,那么整个后台任务栈都会被切换到前台,这个时候后退列表成了ABCD,当用户按back键的时候,列表中的Activity会一一出栈。也就是D,C,B,A的顺序出栈。
但是当我们请求启动的是C,那么当用户按back键的时候,后退列表为CBA,也就是说D在启动C的时候由于C是singleTask启动模式而出栈了。

再看一个例子:三个ActivityA,B,C,其中A为standard启动模式并在默认任务栈,记作task0,B,C为singleTask启动模式并在相同任务栈,记作task1。这时A启动B,B启动C,C启动A,A再启动B,再按两次back,然后看到的是哪个Activity?答案是:回到桌面。
分析过程(自己画图看):首先A启动B,此时A在task0,新B在task1;下来B启动C,此时新C在task1;再下来C启动A,由于A是标准模式,所以新A在task1,此时我们来看一下两个任务栈情况task0:A,task1:BCA。继续,A启动B,由于B为singleTask模式,所以C,A出栈,这时task1只有B。再按两次back,先退出了B并销毁了task1任务栈(无Activity了),然后后台任务栈task0切换到前台,再退出了A,此时task0也销毁了(也无Activity了),所以回到了桌面。

指定启动模式的两种方法:
方式一:通过AndroidMenifest指定。
我这里设置的是 栈顶复用模式。
在这里插入图片描述
方式二:在Intent中设置标志位来为Activity指定启动模式。

	    Intent intent = new Intent(); //1
        intent.setClass(MainActivity.this, SecondActivity.class); //2
        //上面1,2两句其实等价于:
        //Intent intent = new Intent(MainActivity.this, SecondActivity.class);
        //设置Flags ↓
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 
        startActivity(intent1);

Activity的Flags

Activity的Flags有很多,这里只列出常用的标记位。

  • FLAG_ACTIVITY_NEW_TASK
    这个标志位的作用是为Activity指定“singleTask”启动模式,其效果和在XML指定该启动模式相同。
  • FLAG_ACTIVITY_SINGLE_TOP
    这个标志位的作用是为Activity指定“singleTask”启动模式,其效果和在XML指定该启动模式相同。
  • FLAG_ACTIVITY_CLEAR_TOP
    具有此标志位的Activity,当它启动时,在同一个任务栈位于该Activity之上的Activity都要出栈。这个标志位一般会和singleTask启动模式一起出现,在这种情况下,被启动的Actvity实例如果已经存在,那么系统将会调用onNewIntent方法。如果被启动的Activity采用的是standard模式,那么它连同它上面的Activity都出栈,然后创建一个新的Activity实例放到栈顶。singleTask默认具有此效果。
  • FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
    这个标志位的作用是不会出现历史Activity记录,如果不想用户通过历史记录回到我们的Activity时用这个。它等同于在XML中设置android:excludeFromRecents=“true”。

3.IntentFilter的匹配规则 (activity隐式调用)

Activity的调用模式有两种,显式调用和隐式调用。显式调用需要明确指定组件信息,包括包名和类名。隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配则无法启动目标Activity。

Activity完全匹配规则:一个intent-fileter可以有多个action、category、data信息。只有完全匹配才能成功启动目标Activity,一个Activity可以有多个intent-filter,一个Intent只要能和其中任意一组intent-filter匹配即可成功启动对应的Activity。

  • action的匹配规则
    它区分大小写,要求Intent中的action存在且必须和过滤规则中的其中一个action相同。
  • category的匹配规则
    可以没有,但是一旦给Intent设置了,不管有几个都要能够和过滤规则中的任何一个匹配。
  • data的匹配规则
    data由两部分组成:mimeType和URI。mimeType指媒体类型,比如image/jpeg、audio/mpeg4-generic和video/*等,可以表示图片,文本、视频等不同的媒体格式。URI的结构如下
	://: / [] |  | ]
  • Scheme:URI的模式,比如http、file、content等,如果URI没有指定scheme,那么整个URI的其他参数无效,也意味着URI无效。(必要
  • Host:URI的主机名,比如www.baidu.com,如果host未指定,那么整个URI的其他参数无效,也意味着URI无效。(必要
  • Port:URI的端口号,比如80,仅当URI指定了Scheme和Host参数的时候post参数才是有意义的。
  • Path、pathPattern和pathPrefix:这三个参数表述路径信息。其中path表示完整的路径信息;pathPattern也表示完整的路径信息,但是它里面可以包含通配符“”,“”表示0个或多个任意字符,需要注意的是,由于正则表达式的规范,如果想表示真实的字符串,那么“”要写成“\”,“\”要写成“\\”;pathPrefix表示路径的前缀信息。

data的匹配规则:和action类似,intent必须有data数据,并且data数据能够完全匹配过滤规则的某一个data。

URI的默认值为content和file才能匹配。虽然没有指定URI,但是Intent中的URI部分的schema必须为file或者content才能匹配。示例如下:
intent.setDataAndType(Uri.parse("file://abc"), "image/png")
需要注意一点,如果要制定完整data必须使用setDataAndType方法不能使用setData再使用setType方法,因为这两个方法会互相清理对方的值。

判断是否有Activity能够匹配我们的隐式Intent,否则会报:无法找到Activity异常。
判断方法:
1.采用Packagemanager的resolveActivity方法或者采用Intent的resolveActivity方法,这个方法如果匹配不到就会返回null,根据返回值规避上述错误。
2.采用Packagemanager的queryIntentActivities方法,与上面不同,不是返回最佳匹配的Activity信息而是返回所有成功匹配的Activity信息。

public abstract List queryIntentActivities(Intent intent, int flags);
public abstract ResolveInfo resolveActivity(Intent intent, int flags);

这两个方法中第二个参数,我们使用MATCH_DEFAULT_ONLY这个标记位。这个标记位的意义在于,只要上述两个方法不返回null,那么startActivity一定可以成功。如果不用这个标记位,那么这些Activity是无法接收隐式的Intent的,所以可以把intent-filter中category不含DEFAULT的那些Activity给匹配出来,所以可能会导致startActivity失败。

二、Service

Service是后台处理的一种组件,它不需要界面。

两种工作方式:

startService(启动方式)和bindService(绑定方式)。

startService
生命周期:onCreate->onStartCommand->Running…->onDestory。

启动过程:手动调用startService方法,内部判断是否第一次调用startSevice方法,是调用onCreate方法,再调用onSTartConmmand方法,不是第一次调用直接调用onStartConmmand方法,然后Service处于运行状态,直到手动调用stopService结束。

注意: 一个Service被startService启动n次,那么onCreate也只调用一次,onStartConmmand会调用n次。

结束过程:手动调用stopService方法,内部判断是否调用过onStartConmmand方法,是则再判断是否绑定过服务(调用过bindService方法)是则接着判断是否已经接绑服务,是则调用onDestory方法销毁service,如果没有解绑服务则直接结束,如果没有绑定服务则调用onDestory方法销毁service然后结束。

注意:
如果一个Service被启动且绑定,则没解绑服务去调用stopService是不能停止服务的。

bindService
生命周期:调用bindService->onCreate->onBind->Runnning…->onUnbind->onDestory。

启动过程:手动调用bindService方法,内部判断是否调用过onCreate方法,是则判断是否调用过onbind方法,是则判断是否已经绑定服务,是则启动成功结束,如果没有调用过onCreate方法则调用onCreat方法,然后调用onBind方法然后启动结束。

结束过程:手动调用unBindService方法,内部判断是否已经调用过onBind方法,是则判断是否调用过onStartCommand方法,是则调用onUnbind方法然后结束,不是则调用onUnbind方法然后调用onDestory方法再结束。如果没有调用过onBind方法则直接结束。

Service 分类

1.按运行地点分类本地服务远程服务
本地服务

  • 运行在主线程
  • 主进程被终止后,服务也会终止
  • 优点:①节约资源,不需要创建新进程②通信方便,不需要跨进程通信
  • 缺点:限制性大,主进程终止后服务也会终止。
  • 常见场景:需要依附某个进程的服务如音乐播放器。

远程服务

  • 运行在其他进程
  • 服务常驻在后台,不受其他Activity的影响。
  • 优点:灵活性强,不受其他Activity影响
  • 缺点:①消耗资源比较多,需要创建新的进程②需要跨进程通信,通信复杂。
  • 常见场景:系统级别服务。

2.按运行类型分类前台服务后台服务 服务使用时

前台服务

IntentService(Android多线程)

使用场景:离线下载
使用过程:首先,定义IntentService子类,注意需要常人线程的名称、复写onHandleIntent方法;其次,在Manifest.xml中注册服务,然后,在Activity中开启Servcie服务。
详细过程如下:
第一步,定义IntentService子类,注意需要常人线程的名称、复写onHandleIntent方法。

public class MyIntentService extends IntentServcie{
	public MyIntentService(){
	    //在调用父类构造函数时传入工作线程的名字
		super("MyIntentService");
	}

	/** 
     * 复写onHandleIntent()方法
     * 根据 Intent实现 耗时任务 操作
     **/  
    @Override
    protected void onHandleIntent(Intent intent) {

        // 根据 Intent的不同,进行不同的事务处理
        String taskName = intent.getExtras().getString("taskName");
        switch (taskName) {
            case "task1":
                Log.i("MyIntentService", "do task1");
                break;
            case "task2":
                Log.i("MyIntentService", "do task2");
                break;
            default:
                break;
        }
    }
    
    @Override
    public void onCreate() {
        Log.i("MyIntentService", "onCreate");
        super.onCreate();
    }
   /** 
     * 复写onStartCommand()方法
     * 默认实现 = 将请求的Intent添加到工作队列里
     **/  
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("MyIntentService", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.i("MyIntentService", "onDestroy");
        super.onDestroy();
    }
}

作者:Carson_Ho
链接:https://www.jianshu.com/p/8a3c44a9173a
來源:简书

第二步,在Manifest.xml中注册服务。


            
                
            

第三步,在Activity中开启Servcie服务。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

            // 同一服务只会开启1个工作线程
            // 在onHandleIntent()函数里,依次处理传入的Intent请求
            // 将请求通过Bundle对象传入到Intent,再传入到服务里

            // 请求1
            Intent i = new Intent("com.nby.service");
            Bundle bundle = new Bundle();
            bundle.putString("taskName", "task1");
            i.putExtras(bundle);
            startService(i);

            // 请求2
            Intent i2 = new Intent("com.nby.service");
            Bundle bundle2 = new Bundle();
            bundle2.putString("taskName", "task2");
            i2.putExtras(bundle2);
            startService(i2);

            startService(i);  //多次启动
        }
    }

结果发现:onCreate只执行了一次,onStartCommand执行了三次,任务的执行顺序为:task1->task2->task1,说明是按照它加入任务队列的顺序执行的。并且在执行完最后一个任务后调用了onDestory结束了service。

使用好处:第一,不需要我们自己在Service里创建线程;第二,当操作完成时,Service会自动停止。

注意:

  • 若启动IntentService 多次,那么 每个耗时操作 则 以队列的方式(工作任务队列 = 顺序执行) 在 IntentService的 onHandleIntent回调方法中依次执行,执行完自动结束
  • 由于onCreate只会调用一次,所以只会创建1个工作线程。
  • 不建议通过 bindService() 启动 IntentService。因为这种启动方式:
    // 在IntentService中,onBind()默认返回null
    @Override
    public IBinder onBind(Intent intent) {
    return null;
    }
    并且它的生命周期中并不会调用onStart() 或 onStartcommand(),故不会将消息发送到消息队列,那么onHandleIntent()将不会回调,即无法实现多线程的操作 。此时,你应该使用Service,而不是IntentService

问题

  • IntentService如何单独开启1个新的工作线程:使用HandlerThread。
  • IntentService 如何通过onStartCommand() 将Intent 传递给服务 & 依次插入到工作队列中:使用ServiceHandler。

总结

  • IntentService本质 = Handler + HandlerThread:
    • 通过HandlerThread 单独开启1个工作线程:IntentService

    • 创建1个内部 Handler :ServiceHandler

    • 绑定 ServiceHandler 与 IntentService

    • 通过 onStartCommand() 传递服务intent 到ServiceHandler 、依次插入Intent到工作队列中 & 逐个发送给 onHandleIntent()

    • 通过onHandleIntent() 依次处理所有Intent对象所对应的任务

所以我们可以通过复写onHandleIntent() 这个方法,根据Intent的不同进行不同线程操作即可。

对比
IntentService与Service的区别
Android四大组件-2018.11/12.x_第1张图片

IntentService与其他线程的区别
Android四大组件-2018.11/12.x_第2张图片
Service分析过程主要借鉴和整理于以下链接:
Service生命周期 完全解析
Android 多线程 解析:IntentService(含源码解析)
Android四大组件:Service史上最全面解析

三、BroadcastRecevier

它是一个全局监听器,是android四大组件之一。它分为广播接收者和广播发送者两种。
作用
监听/接收app发出的请求,并做出处理响应广播。

应用场景
1.Android不同组件间通信。(分应用内和应用之间)
2.多线程间通信。
3.与Android系统在特定情况下通信,比如电话呼入、网络可用时。

实现原理
主要采用的是观察者模式:基于消息的发布/订阅模型。所以Android广播的接收者和发布者是解耦的,使得系统方便集成,易于扩展,也就是往大的发展。

模型讲解
模型有三个角色:广播发送者,广播接收者,消息中心。
图片来自于:https://www.jianshu.com/p/ca3d87a4cdf3
Android四大组件-2018.11/12.x_第3张图片
使用
先注册一个广播接收者:分两种方式——xml静态注册和代码动态注册。
xml静态注册:

 
            
                
            
        

代码动态注册:

        IntentFilter filter = new IntentFilter();
        filter.addAction("com.nby.receiver.PRINT");
        registerReceiver(new MyReceiver(),filter);

这里需要注意的是,动态注册广播接收者没什么问题,但是静态注册广播接收者在8.0会出现接收不到的情况,解决方法是在发送广播时:

 intent.setPackage(getPackageName());

具体原因及解决方法参考:https://blog.csdn.net/u011386173/article/details/82889275

但动态注册广播接收者需要注意的是要解注册,否则会导致内存泄漏,一般在Activity onPause里解注册,在onResume里注册。

这样就发送了一个简单的广播,当然在发送广播的时候还可以传递数据,也就是利用intent放入数据,然后在onReceive方法中去取数据,但是不能在这个方法中进行耗时操作,否则可能会导致anr(广播10s)。

广播的类型还有很多:粘性广播,有序广播,系统广播,普通广播,私有广播(应用内部的广播)等等。
1.普通广播
自定义的intent发送广播,广播接收者使用intentFilter与intent添加的action去匹配,匹配成功即可接收到广播,若intent发送广播时添加了权限,则广播接收者也必须添加响应权限才可以接收到广播。
发送:

 Intent intent = new Intent();
//对应BroadcastReceiver中intentFilter的action
intent.setAction(BROADCAST_ACTION);
//发送广播
sendBroadcast(intent);

接收:

 
    //用于接收网络状态改变时发出的广播
    
        
    

2.系统广播
安卓手机系统内部的广播,例如开启,网络状态变化,拍照等等都会发出系统广播,并且每个广播都有特殊的IntentFilter。如下所示:

第一行:系统操作
第二行:对应的action

监听网络变化
android.net.conn.CONNECTIVITY_CHANGE

关闭或打开飞行模式
Intent.ACTION_AIRPLANE_MODE_CHANGED

充电时或电量发生变化
Intent.ACTION_BATTERY_CHANGED

电池电量低
Intent.ACTION_BATTERY_LOW

电池电量充足(即从电量低变化到饱满时会发出广播
Intent.ACTION_BATTERY_OKAY

系统启动完成后(仅广播一次)
Intent.ACTION_BOOT_COMPLETED

按下照相时的拍照按键(硬件按键)时
Intent.ACTION_CAMERA_BUTTON

屏幕锁屏
Intent.ACTION_CLOSE_SYSTEM_DIALOGS

设备当前设置被改变时(界面语言、设备方向等)
Intent.ACTION_CONFIGURATION_CHANGED

插入耳机时
Intent.ACTION_HEADSET_PLUG

未正确移除SD卡但已取出来时(正确移除方法:设置--SD卡和设备内存--卸载SD卡)
Intent.ACTION_MEDIA_BAD_REMOVAL

插入外部储存装置(如SD卡)
Intent.ACTION_MEDIA_CHECKING

成功安装APK
Intent.ACTION_PACKAGE_ADDED

成功删除APK
Intent.ACTION_PACKAGE_REMOVED

重启设备
Intent.ACTION_REBOOT

屏幕被关闭
Intent.ACTION_SCREEN_OFF

屏幕被打开
Intent.ACTION_SCREEN_ON

关闭系统时
Intent.ACTION_SHUTDOWN

重启设备
Intent.ACTION_REBOOT

3.有序广播
发送出去的广播被广播接收者按照先后顺序接收。
注意:有序指的是广播接收者接收有序

有序接收规则:
1.按照Priority属性值大-小
2.Priority属性值相同的,动态注册的优先。

特点
1.广播接收者按照接收规则,有序的接收广播
2.先接收到广播的广播接收者可以对广播进行修改,导致后面的广播接收者接收到的广播是修改过后的
3.先接收到广播的广播接收者可以对广播进行截断,后面的广播接收者将接收不到广播。

具体使用
还是通过intent发送广播,主要区别是广播接收者的接收有序了。

4.App应用内广播
背景
Android中的广播可以跨App直接通信(exported对于有intent-filter情况下默认值为true)

冲突
可能出现的问题:

  • 其他App针对性发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收广播并处理;
  • 其他App注册与当前App一致的intent-filter用于接收广播,获取广播具体信息;
    即会出现安全性 & 效率性的问题。

解决方案
使用App应用内广播(Local Broadcast)

  • App应用内广播可理解为一种局部广播,广播的发送者和接收者都同属于一个App。
  • 相比于全局广播(普通广播),App应用内广播优势体现在:安全性高 & 效率高

具体使用1 - 将全局广播设置成局部广播
1.注册广播时将exported属性设置为false,使得非本App内部发出的此广播不被接收;
2.在广播发送和接收时,增设相应权限permission,用于权限验证;
3.发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。

通过intent.setPackage(packageName)指定包名

具体使用2 - 使用封装好的LocalBroadcastManager类

具体:https://www.jianshu.com/p/ca3d87a4cdf3

你可能感兴趣的:(Android)