Android四大组件有:Activity、Service、BroadcastRecevier、ContentProvider.
Activity正常生命周期
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的调用模式有两种,显式调用和隐式调用。显式调用需要明确指定组件信息,包括包名和类名。隐式调用需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配则无法启动目标Activity。
Activity完全匹配规则:一个intent-fileter可以有多个action、category、data信息。只有完全匹配才能成功启动目标Activity,一个Activity可以有多个intent-filter,一个Intent只要能和其中任意一组intent-filter匹配即可成功启动对应的Activity。
://: / [] | | ]
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是后台处理的一种组件,它不需要界面。
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方法则直接结束。
1.按运行地点分类:本地服务和远程服务。
本地服务
远程服务
2.按运行类型分类:前台服务和后台服务 服务使用时
前台服务
使用场景:离线下载
使用过程:首先,定义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
问题
总结
通过HandlerThread 单独开启1个工作线程:IntentService
创建1个内部 Handler :ServiceHandler
绑定 ServiceHandler 与 IntentService
通过 onStartCommand() 传递服务intent 到ServiceHandler 、依次插入Intent到工作队列中 & 逐个发送给 onHandleIntent()
通过onHandleIntent() 依次处理所有Intent对象所对应的任务
所以我们可以通过复写onHandleIntent() 这个方法,根据Intent的不同进行不同线程操作即可。
IntentService与其他线程的区别
Service分析过程主要借鉴和整理于以下链接:
Service生命周期 完全解析
Android 多线程 解析:IntentService(含源码解析)
Android四大组件:Service史上最全面解析
它是一个全局监听器,是android四大组件之一。它分为广播接收者和广播发送者两种。
作用
监听/接收app发出的请求,并做出处理响应广播。
应用场景
1.Android不同组件间通信。(分应用内和应用之间)
2.多线程间通信。
3.与Android系统在特定情况下通信,比如电话呼入、网络可用时。
实现原理
主要采用的是观察者模式:基于消息的发布/订阅模型。所以Android广播的接收者和发布者是解耦的,使得系统方便集成,易于扩展,也就是往大的发展。
模型讲解
模型有三个角色:广播发送者,广播接收者,消息中心。
图片来自于:https://www.jianshu.com/p/ca3d87a4cdf3
使用
先注册一个广播接收者:分两种方式——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)
具体使用1 - 将全局广播设置成局部广播
1.注册广播时将exported属性设置为false,使得非本App内部发出的此广播不被接收;
2.在广播发送和接收时,增设相应权限permission,用于权限验证;
3.发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
通过intent.setPackage(packageName)指定包名
具体使用2 - 使用封装好的LocalBroadcastManager类
具体:https://www.jianshu.com/p/ca3d87a4cdf3