Android--四大组件 (万字详细解读!!!)
作者:互联网
四大组件的生命周期
目录
Activity
用于表现功能。
生命周期:这个结合它的各个方法来看
方法名称 | 作用 | 描述 |
---|---|---|
onCreate() | 创建Activity,对Activity做一些初始化工作 | 是生命周期的第一个方法,可以在这个方法里面加载布局、初始化控件等。此时Activity还在后台,不可见。 |
onStart() | 启动Activity,也可以将初始化工作放在这里面执行 | 生命周期的第二个方法,此时Activity可见,但是没有出现在前台,所以用户是看不见的 |
onResume() | 继续重新开始这个Activity,可以打开独占设备 | 此时Activity已经在出现在前台并且可见了 |
onPause() | 当前Activity暂停运行 | 此时Activity依然在前台可见,这个方法会在应用退出或者跳转到另一个Activity时执行,当只有一个Activity的onPause方法执行完后,下一个Activity才会启动,在Android中如果onPause方法在0.5秒内没有执行完毕的话,就会强制关闭这个Activity,可以在这里快速重启这个Activity。 |
onStop() | 停止这个Activity | 此时Activity已经不可见了,但是Activity对象还在内存中,没有被销毁,此方法中做的工作一般是一些资源的回收工作 |
onDestroy() | 销毁这个Activity | 不可见,我们可以将还没释放的资源释放,以及进行一些回收工作。 |
onRestart() | 重启Activity | Activity在这时可见,当用户按Home键切换到桌面后又切回来或者从后一个Activity切回前一个Activity就会触发这个方法。这里一般不做什么操作。 |
方法间的区别
- onCreate和onStart的区别
- 可见与不可见
- 执行次数
- 能在onStart里面做的是不一定能在onCreate里面做,但是能在onCreate里面做的一定可以在onStart里面做(比如动画的初始化只能在onStart里面做)
- onStart和onResume的区别
- 是否在前台
- 职责不同,前者一般用于初始化,后者一般用于独占设备的操作
- onPause和onStop的区别
- 是否可见
- 内存不足可能不会执行onStop方法,所以程序状态的保存、独占设备和动画的关闭、以及一些数据的保存最好在onPause中进行,但要注意不能太耗时(0.5m)
Activity的切换
正常次序是(A)onPause→(B)onCreate→(B)onStart→(B)onResume→(A)onStop
可以发现A在B启动完成后才启动的onStop方法,这里有两个原因:
- onPause方法会释放掉这个Activity的很多占用的系统资源,为了保证切换的流畅性没有必要再多等一个阶段
- 如果用户在切换的途中快速的切回原Activity就会直接调用onResume方法,会快很多。
Activity的四种启动模式
- Standard:标准的启动模式,如果需要启动一个activity就会创建该activity的实例。也是activity的默认启动模式。
- SingeTop:如果启动的activity已经位于栈顶,那么就不会重新创建一个新的activity实例。而是复用位于栈顶的activity实例对象。如果不位于栈顶仍旧会重新创建activity的实例对象。
- SingleTask:设置了singleTask启动模式的activity在启动时,如果位于activity栈中,就会复用该activity,这样的话,在该实例之上的所有activity都依次进行出栈操作,即执行对应的onDestroy()方法,直到当前要启动的activity位于栈顶。一般应用在网页的图集,一键退出当前的应用程序。
- singleInstance:如果使用singleInstance启动模式的activity在启动的时候会复用已经存在的activity实例。不管这个activity的实例是位于哪一个应用当中,都会共享已经启动的activity的实例对象。使用了singlestance的启动模式的activity会单独的开启一个共享栈,这个栈中只存在当前的activity实例对象。
- Activity所需的任务栈
TaskAffinity:任务相关性,标识了一个Activity所需要的任务栈的名字,默认情况下是包名。必须和singleTask启动模式或者allowTaskReparenting属性配对使用,否则没有意义。
任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity处于暂停状态,用户可以通过切换将后台任务栈调到前台。
TaskAffinity和allowTaskReparenting结合使用:现在有2个应用A和B,A启动了B的Activity B1,然后按Home键回到桌面,然后单击桌面图标启动B,此时并不是启动B的主Activity,而是重新显示了Activity B1或者说,B1从A的任务栈转移到了B的任务栈。可以这么理解,由于A启动了B1,这个B1只能运行在A所在的任务栈中,但是B1是属于B应用的,正常情况下,它的TaskAffinity应该是B的包名。所以,B被启动之后,B会创建自己的任务栈,此时系统发现B1原本想要的任务栈已经有了,就会把B1从A的任务栈中转移过。
- 指定启动模式的两种方式
第一种:通过AndroidMenifest.xml指定android:launchMode="singTask"
第二种:通过Intent的addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
Activity的Flags:
Flags有很多,有些可以设定Activity的启动模式,有些可以影响Activity的运行状态。
FLAG_ACTIVITY_NEW_TASK | 为Activity指定“singleTask”启动模式 |
---|---|
FLAG_ACTIVITY_CLEAR_TOP | 具有此标记的Activity在启动时,同一个任务栈中位于它上面的都要出栈。与singleTask一起使用,若实例已经存在,会调用newIntent方法;若被启动的Activity是standard,则它自己也会出栈,然后重新创建一个新的Activity实例入栈。 |
FLAG_ACTIVITY_SINGLE_TOP | 为Activity指定“singleTop”启动模式 |
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | 等价于android:excludeFromRecents="true" ,表明此Activity不会出现在历史Activity列表中 |
当按下Home键时,引起的变化
不管是按下home键还是查看任务列表或者是横屏切换到竖屏,都属于异常情况,二这些异常情况都会导致Activity有不同的生命周期。
-
Activity异常终止的情况下
以竖屏切换成横屏为例:
这些异常情况是由于系统配置发生改变导致的Activity销毁,所以onPause->onStop->onDestroy还是会走一遍,但是在onStop之前会调用
onSaveIntenceState
保存当前的Activity状态,当切到横屏后Activity会在onStart之后调用onRestoreInstanceState
重建,并把onSaveIntanceState中保存的Bundle对象同时传递给onRestoreInstanceState和onCreate。当然,这种情况的发生也可以不用重新创建Activity,在Activity的配置中,配置属性
android:configChanges="orientation|screenSize"
,可以在屏幕旋转的时候,不重新创建Activity,取而代之的是调用Activity的onConfigurationChanged
方法。其他可配置项目如下:
项目 含义 locale 一般指切换了系统语言 keboardHidden 键盘的可访问性发生了变化 orientation 屏幕方向发生了变化 screenSize 屏幕尺寸信息发生了变化,旋转屏幕尺寸信息就会发生变化。若编译版本小于13,不会导致Activity的重启;若大于13,则会导致Activity的重启 -
内存资源不足,导致低优先级Activity被杀死
Activity的优先级:
前台Activity > 可见非前台Activity > 后台Activity
这种Activity的销毁也是通过onSaveIntanceState和onRestoreInstanceState来存储和恢复数据。
Service
使用场景
Android的四大组件之一:
- 长期运行在后台没有单独的进程,运行在主线程中,没有用户界面。
- 适合执行一些不需要显示界面的后台耗时操作,但耗时操作也需要放在子线程。
- Service依赖于创建服务时所在的应用进程,当某个进程被杀掉以后,依赖于该进程的服务都会停止运行
- 适用于下载网络数据、播放音乐、访问文件、数据库等业务逻辑功能
生命周期
根据它不同的启动方式有不同的生命周期
-
启动服务:
startService()
一旦启动,Service将一直运行在后台,即便启动Service的组件已经被destory。但是,Service会在后台执行单独的操作,也并不会给启动它的组件返回结果,它的生命周期和启动它的组件的生命周期无关方法名 作用 描述 onCreate() 创建服务 由于是单例模式,所以无论启动多少次,服务都只会创建唯一实例 onStartCommand() 启动服务 在重复启动服务时,虽然不会重复创建实例,但是此方法会被重复调用 onDestroy() 销毁服务 服务是通过startService()启动的,**stopService()**关闭的,所以在调用 stopService()
时,会回调onDestroy**onStartCommand(Intent intent,int flag,int startId) **:这个方法的返回值有三种
START_STICKY
:当Service因为内存不足而被系统杀掉时,一段时间后,内存如果再次空闲,系统将会重新创建此Service,创建成功后回调此方法,但其中参数Intent为null。这个状态下适合用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。START_NOT_STICKY
:和上面情况相同,不过就算内存空闲后,系统也不会重新创建这个Service,除非程序中再次调用startService,这样可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。START_REDELIVER_INTENT
:和第一种情况相同,不过在重新创建的时候,会通过传递给服务的最后一个Intent调用onStartCommand(),任何挂起的Intent均以此传递。适合于主动执行应该立即恢复的作业(例如下载文件)的服务参数flags有三个选值,表示启动请求时是否有额外数据:
0
:0代表没有START_FLAG_REDELIVERY
:这个值代表返回值为START_REDELIVER_INTENT,而且在上一次服务被杀死前会去调用stopSelf()
方法停止服务START_FLAG_RETRY
:该flag代表当onStartCommand调用后一直没有返回值时,会尝试重新去调用onStartCommand()。 -
绑定服务:其他组件
bindService()
绑定一个Service,通过绑定方式启动的Service是一个client-server结构,该Service可以与绑定它的组件进行交互。一个绑定的Service仅在仅有组件与其绑定时才会运行,多个组件可与一个Service绑定,所以当绑定的组件如Activity出栈时,就会自动解绑,同时绑定的Service也会停止并销毁。方法名称 作用 描述 onCreate() 创建服务 作用同上面的onCreate onStartCommand() 启动服务 在绑定方式启动服务时,这个方法不被调用 onBind() 绑定服务 返回一个Binder对象,这个对象是自己写的一个继承Binder的类 onUnbind() 解绑服务 在Activity中调用unBindService会回调此方法,服务解绑后就会被Destroy
调用bindService或者是unBindService都会传入参数ServiceConnection,所以要一个实现接口ServiceConnection的类:
/**
* 创建类继承ServiceConnection,用于解绑服务方法调用
*/
public class MyServiceConnection implements ServiceConnection{
//当客户端正常连接这个服务时,成功绑定到服务时调用该方法。注意IBinder参数对象
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//如果成功绑定,iBinder为MyService里面的IBinder对象
Log.i("MainActivity","服务绑定成功,内存地址为:"+iBinder.toString());
}
//当客户端与服务失去连接时调用该方法
@Override
public void onServiceDisconnected(ComponentName componentName) {
//解绑
Log.i("MainActivity","服务解绑成功");
}
}
通信方式
在Android中,服务的通信方式有两种:本地服务通信和远程服务通信。使用这两种方式进行通信时,必须保证服务是以绑定的形式开启的,否则无法进行通信和数据交换。
- 本地服务通信
指的是应用程序内部的通信。首先需要创建一个Service类,该类会提供一个onBind()方法,onBind()方法的返回值是一个IBinder对象,IBinder对象会作为参数传递给ServiceConnection类中的onServiceConnected(ComponentName name,IBinder service)方法,这样访问者就可用通过IBinder对象与Service进行通信。如下图所示:
从上图可以看出,服务在进行通信时实际使用的是IBinder对象,在ServiceConnection类中得到的IBinder对象,通过这个对象可以获取到服务中自定义的方法,执行具体的操作。
- 远程服务通信
在Android系统中,各个应用程序都运行在自己的进程中,如果想要完成不同进程之间的通信,就需要用到远程服务通信。远程服务通信时通过AIDL(Android Interface Definition Language)实现的,接口定义语言,语法格式简单,与Java中定义接口类似,但存在差异如下:
AUDL定义接口的源代码必须以.aidl结尾。
AIDL接口中用到的数据类型,除了基本数据类型及String、List、Map、CharSequence之外,其他类型全部都需要导入到包,即使它们在同一个包中。
前台服务
-
服务在后台运行,优先级比较低,当内存不足时可以会被系统回收,可以使用前台服务。
-
前台服务会在状态栏中显示图标,并且下拉状态栏,可以在通知栏中看到详细信息,类似于通知
(API26开始使用新的方法startForegroundService()来启动前台服务,和startService一样的效果
要求Service里有startForeground()方法,否则startForegroundService()启动前台服务会报ANR错误)
Service和Thread的区别
首先Service与Thread无关,只不过都有一个后台的概念。
类别 | 相同 | 不同 |
---|---|---|
Service | 执行异步操作 | 运行在主线程,不依赖UI,进程在,Service就在,所有Activity都能与Service关联获得Binder实例并操作 |
Thread | 执行异步操作 | 运行在工作线程,在一个Activity创建的子线程,另一个Activity无法操作,当Activity销毁之后就无法在获得之前的子线程实例 |
IntentService
继承于Service的一个封装类,一般用于离线下载。将线程任务按顺序在后台执行
进程的优先级
-
前台进程:某个Activity可见,获得焦点
-
可见进程:某个Activity可见,但是没有焦点
-
服务进程:有一个服务在后台运行
-
后台进程:没有任何服务,打开一个Activity然后最小化(容易被回收)
-
空进程:没有任何活动组件存在的进程(容易被回收)
BroadcastReceiver
作用和地位
Android四大组件之一,广播接受者。应用于系统接收到短信、系统电量不足、APP开机自启等。广播的发送者是Broadcast,和接收者之间的传递数据是Intent。
使用方式
继承BroadcastReceiver { }
实现抽象方法onReceive()
public class MyBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();//获取到收到的广播的名称
Log.e("123", "收到的广播的Action是:"+action);
}
}
注册BroadcastReceiver。
- BroadcastReceiver运行在UI线程所以不能执行耗时操作
- 执行耗时操作可以考虑通过Intent启动一个Service
- 监听到就会创建实例,触发onReceiver方法,执行完改方法,销毁实例
- 通过Intent激发BroadcastReceiver时,找不到也不会报错
静态注册和动态注册
静态注册
静态注册就是将Broadcast注册在AndroidManifest.xml清单文件中
例如:监听手机打电话,如果有打电话的这个行为就发送广播
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<receiver android:name=".MyBroadcastReceiver">
<intent-filter>
<action android:name="android.intent.action.NEW_OUTGOING_CALL" />
</intent-filter>
</receiver>
动态注册
在代码中注册广播,比如下例:
监听屏幕的点亮与关闭
public class MainActivity extends Activity {
private MyBroadcastReceiver receiver;//MyBroadcastReceiver为继承于BroadcastReceiver的类
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
registerMyReceiver();//在activity创建的时候进行注册监听
}
private void registerMyReceiver() {
receiver = new MyBroadcastReceiver();
IntentFilter filter = new IntentFilter();//创建IntentFilter对象
filter.addAction(Intent.ACTION_SCREEN_OFF);//IntentFilter对象中添加要接收的关屏广播
filter.addAction(Intent.ACTION_SCREEN_ON);//添加点亮屏幕广播
registerReceiver(receiver, filter);
}
private void unRegisterMyReceiver(){
if(receiver != null){
unregisterReceiver(receiver);//反注册广播,也就是注销广播接收者,使其不起作用
}
}
}
两者区别与细节
- 静态注册依附于清单文件,只要APP启动过一次,就算关闭了APP(除非卸载)只要相应的广播事件发生,系统就会遍历所有的清单文件,通知相应的广播接受者接收广播,缺点就是耗电、占内存。
- 这个清单文件的receiver节点可以添加android:exported=”false”属性 ,这样系统遍历全部APP清单文件的广播接收者时不会对本receiver进行判断及处理。
这个值为FALSE表示不予其他APP相交互。
- 这个清单文件的receiver节点可以添加android:exported=”false”属性 ,这样系统遍历全部APP清单文件的广播接收者时不会对本receiver进行判断及处理。
- 动态注册依赖于注册他的组件,当APP关闭后,广播就失效了,
- 所以静态注册的广播传播速度要小于动态广播
- 有个原则(谷歌的源代码规定):动态注册的广播优先级大于静态广播
注意
动态广播最好在Activity的onResume()注册、onPause()注销,不销毁会导致内存泄漏。(重复注册、重复注销也不允许)。不在onCreate() & onDestory() 或 onStart() & onStop()注册、注销是因为:
当系统因为内存不足(优先级更高的应用需要内存)要回收Activity占用的资源时,Activity在执行完onPause()方法后就会被销毁,有些生命周期方法onStop(),onDestory()就不会执行。当再回到此Activity时,是从onCreate方法开始执行。
假设我们将广播的注销放在onStop(),onDestory()方法里的话,有可能在Activity被销毁后还未执行onStop(),onDestory()方法,即广播仍还未注销,从而导致内存泄露。但是,onPause()一定会被执行,从而保证了广播在App死亡前一定会被注销,从而防止内存泄露。(详见Activity生命周期图)
BroadcastReceiver的分类
无序广播
当广播发送以后与之相关的所有广播接受者都会收到广播,没有先后顺序,就比如一个通知文件要下达到所有人,这个时候管理员在群里直接发了个通知,然后所有人就都可以收到这个通知了,也就是**sendBroadcast();**这个方法
public void sendCustomBroadcast(View view){
Intent intent = new Intent("my.broadcast.message");//action是my.broadcast.message,清单文件中的action与之一致方可收到广播
intent.putExtra("qian", "100");//广播中携带的数据
sendBroadcast(intent);//发送无序广播
}
有序广播
一种可以分先后广播接收者顺序的发送方式,广播接收者的优先级(范围在-1000~1000之间)越高,越先接收广播。优先级高的广播先收到广播,收到广播后可以修改广播的内容,也可以拦截广播不让广播向下传递。比如,学校发了一个通知给老师,然后老师下发给学生,最后学生才知道这个通知,老师可以将这个通知拦截下来不给学生,也可以修改这个通知再发给学生。
所以它有两种发送方式:
-
这里使用静态注册的方式,在清单中设置它的优先级
<receiver android:name=".MainReceiver"> <intent-filter android:priority="100">//设置优先级,为整数,越大优先级越高 <action android:name="my.broadcast.message"/> </intent-filter> </receiver> <receiver android:name=".MainReceiver2"> <intent-filter android:priority="200" > <action android:name="my.broadcast.message"/> </intent-filter> </receiver>
public void sendCustomBroadcast(View view){ Intent intent = new Intent("my.broadcast.message");//action是my.broadcast.faqian,清单文件中的action与之一致方可收到广播 intent.putExtra("qian", "100");//广播中携带的数据 /** * sendOrderedBroadcast(Intent intent, String receiverPermission); */ sendOrderedBroadcast(intent, null);//发送有序广播 }
结果就是MainReceiver比MainReceiver2先接到通知
-
使用sendOrderedBroadcast的另一个重载方法
sendOrderedBroadcast( Intent intent,//封装了action及其他数据 String receiverPermission, //广播接收者需要的权限 BroadcastReceiver resultReceiver,//表示无论当广播传播结束还是拦截以后我任然会收到广播 Handler scheduler, int initialCode, String initialData, Bundle initialExtras);
public void sendCustomBroadcast(View view){ Intent intent = new Intent("my.broadcast.message");//action是my.broadcast.message,清单文件中的action与之一致方可收到广播 Bundle bundle = new Bundle(); bundle.putString("qian","100");//广播中携带的bundle数据 sendOrderedBroadcast( intent, null, //permission为null new MainReceiver2(), //这里的new MainReceiver2()为最终的广播接收者,也就是说无论他曾经有没有收到广播都会再次收到广播。 null, 666,//initCode "我是initialData",//initData bundle);//bundle //以上所有入参都会携带在广播中,如何取出呢? }
public class MainReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { /***********获取数据*************/ int initCode = getResultCode();//获取传递过来的initCode String initData = getResultData();//获取传递过来的initData Bundle initBundle = getResultExtras(true);//获取传递过来的Bundle Log.d("hui", "ZhiXianReceiver = " +"initCode = "+initCode +" ,initdata = " +initData +" ,bundle = " +initBundle.getString("qian")); } } public class MainReceiver2 extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { /***********获取数据*************/ int initCode = getResultCode();//获取传递过来的initCode String initData = getResultData();//获取传递过来的initData Bundle initBundle = getResultExtras(true);//获取传递过来的Bundle Log.d("hui", "ZhiFuReceiver = " +"initCode = "+initCode +" ,initdata = " +initData +" ,bundle = " +initBundle.getString("qian")); } }
结果就是在方式一的基础上MainReceiver2会收到两次广播
可以在MainReceiver这种接收者中写入
abortBroadcast();*//拦截广播,广播被终止,以后不会有其他广播接收者再收到广播了。*
这个方法,拦截广播也可以在收到广播后去修改广播使用下面这几个方法
setResultCode(3306);//修改initCode setResultData("MainReceiver修改了数据"); //修改initData //修改bundle数据 Bundle bundle = new Bundle(); bundle.putString("qian", "10"); setResultExtras(bundle);
本地广播
上面两种发出的广播手机里面所有的APP都能收到,本地广播是局部的广播基于本程序的广播,其他的程序无法收到这个广播,这种广播是安全的,外界不会干扰他,广播也不会被其他进程所收到。就好像老师收到通知后单独的发给了某一个学生,所以这个通知只被老师和学生知道.
/**
* 本地广播接收者进行注册,必须在代码中注册,清单文件注册是无效的
*/
public void registerMyAPPReceiver(View view) {
//创建广播接收者
MyBroadCastReceiver myBroadCastReceiver = new MyBroadCastReceiver();
MyBroadcastReceiver2 myBroadCastReceiver2 = new MyBroadcastReceiver2();
//封装要接收的广播类型
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("my.broadcast.Message2");
//拿到LocalBroadcastManager对象,对固定的Receiver进行注册,成为本地广播接收者
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(MainActivity.this);
localBroadcastManager.registerReceiver( myBroadCastReceiver, intentFilter);
localBroadcastManager.registerReceiver(myBroadCastReceiver2, intentFilter);
}
- registerReceiver注册一个广播接收者可以多次执行,比如:我把LocalBroadcastManager.registerReceiver( myBroadCastReceiver, intentFilter);写两遍,那么myBroadCastReceiver的onReceiver会被调用两次,不建议这样写。
- 本地广播不能拦截
- registerReceiver对应的还有unregisterReceiver(receiver
sticky广播
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A5rtei6f-1612606912809)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210203210241851.png)]
Content Provider
地位和作用
Android四大组件之一,但使用的频率没有其他的三种组件高,他主要是实现两个进程(两个应用)间的数据共享和交互,比如手机QQ要获取手机的通讯录的数据…
原理属于是Android的Builder机制…
内容
说到ContentProvider那么和他相关的几个东西就得先摆出来了…
URI
这个全称叫做Uniform Resource Identifier
统一资源标识符。如名称所示,就是标识符,标识的内容就是ContentProvider和其中的数据。这个也用在过Activity的隐式启动上,格式如下:
[scheme:][//host:port][path][?query]
URI有分为预置的和自定义的,预置的一般有通讯录、日程表等数据。自定义的话例如下面这个:
Uri uri = Uri.parse("content://com.carson.provider/User/1")
标识的资源就是名为 com.carson.provider
的ContentProvider
中表名 为User
中的 id
为1的数据。
并且URI模式存在匹配通配符。
MIME数据类型
这个主要用于指定某个扩展名的文件用某种应用程序来打开,比如指定.html文件由text程序打开。
这个类型一般是在ContentProvider里面的getType(Uri uri)
里面返回得到。
MIME类型数据由两部分组成:类型 + 子类型
// 形式1:单条记录
vnd.android.cursor.item/自定义
// 形式2:多条记录(集合)
vnd.android.cursor.dir/自定义
// 注:
// 1. vnd:表示父类型和子类型具有非标准的、特定的形式。
// 2. 父类型已固定好(即不能更改),只能区别是单条还是多条记录
// 3. 子类型可自定义
ContentProvider类
主要以表格的形式组织数据,它的核心方法有四个:增、删、改、查
<-- 4个核心方法 -->
public Uri insert(Uri uri, ContentValues values)
// 外部进程向 ContentProvider 中添加数据
public int delete(Uri uri, String selection, String[] selectionArgs)
// 外部进程 删除 ContentProvider 中的数据
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
// 外部进程更新 ContentProvider 中的数据
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
// 外部应用 获取 ContentProvider 中的数据
// 注:
// 1. 上述4个方法由外部进程回调,并运行在ContentProvider进程的Binder线程池中(不是主线程)
// 2. 存在多线程并发访问,需要实现线程同步
// a. 若ContentProvider的数据存储方式是使用SQLite & 一个,则不需要,因为SQLite内部实现好了线程同步,若是多个SQLite则需要,因为SQL对象之间无法进行线程同步
// b. 若ContentProvider的数据存储方式是内存,则需要自己实现线程同步
<-- 2个其他方法 -->
public boolean onCreate()
// ContentProvider创建后 或 打开系统后其它进程第一次访问该ContentProvider时 由系统进行调用
// 注:运行在ContentProvider进程的主线程,故不能做耗时操作
public String getType(Uri uri)
// 得到数据类型,即返回当前 Url 所代表数据的MIME类型
ContentResolver类
ContentProvider不直接与外部进程交互,而是通过ContentResolver。一款应用要和多个ContentProvider交互,如果不用ContentResolver进行抽象,那么可能在交互时要考虑是SQLite的数据库还是MongoDB数据库,这样的话操作成本高、难度大。
ContentResolver也提供了名称相同的增、删、改、查四个方法。使用方法如下:
// 使用ContentResolver前,需要先获取ContentResolver
// 可通过在所有继承Context的类中 通过调用getContentResolver()来获得ContentResolver
ContentResolver resolver = getContentResolver();
// 设置ContentProvider的URI
Uri uri = Uri.parse("content://cn.scu.myprovider/user");
// 根据URI 操作 ContentProvider中的数据
// 此处是获取ContentProvider中 user表的所有记录
Cursor cursor = resolver.query(uri, null, null, null, "userid desc");
ContentUris类
这个类是用来操作URI的,核心方法有两个:
// withAppendedId()作用:向URI追加一个id
Uri uri = Uri.parse("content://cn.scu.myprovider/user")
Uri resultUri = ContentUris.withAppendedId(uri, 7);
// 最终生成后的Uri为:content://cn.scu.myprovider/user/7
// parseId()作用:从URL中获取ID
Uri uri = Uri.parse("content://cn.scu.myprovider/user/7")
long personid = ContentUris.parseId(uri);
//获取的结果为:7
UriMatcher类
在ContentProvider里面注册URI,根据URI匹配ContentProvider中对应的数据,具体使用:
// 步骤1:初始化UriMatcher对象
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
//常量UriMatcher.NO_MATCH = 不匹配任何路径的返回码
// 即初始化时不匹配任何东西
// 步骤2:在ContentProvider 中注册URI(addURI())
int URI_CODE_a = 1;
int URI_CODE_b = 2;
matcher.addURI("cn.scu.myprovider", "user1", URI_CODE_a);
matcher.addURI("cn.scu.myprovider", "user2", URI_CODE_b);
// 若URI资源路径 = content://cn.scu.myprovider/user1 ,则返回注册码URI_CODE_a
// 若URI资源路径 = content://cn.scu.myprovider/user2 ,则返回注册码URI_CODE_b
// 步骤3:根据URI 匹配 URI_CODE,从而匹配ContentProvider中相应的资源(match())
@Override
public String getType(Uri uri) {
Uri uri = Uri.parse(" content://cn.scu.myprovider/user1");
switch(matcher.match(uri)){
// 根据URI匹配的返回码是URI_CODE_a
// 即matcher.match(uri) == URI_CODE_a
case URI_CODE_a:
return tableNameUser1;
// 如果根据URI匹配的返回码是URI_CODE_a,则返回ContentProvider中的名为tableNameUser1的表
case URI_CODE_b:
return tableNameUser2;
// 如果根据URI匹配的返回码是URI_CODE_b,则返回ContentProvider中的名为tableNameUser2的表
}
}
ContentObserver类
观察URI引起ContentProvider中的数据变化并且可以通知外界去更新数据,主要是由notifyChange(Uri uri)方法来实现通知
使用如下:
// 步骤1:注册内容观察者ContentObserver
getContentResolver().registerContentObserver(uri);
// 通过ContentResolver类进行注册,并指定需要观察的URI
// 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
public class UserContentProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
db.insert("user", "userid", values);
getContext().getContentResolver().notifyChange(uri, null);
// 通知访问者
}
}
// 步骤3:解除观察者
getContentResolver().unregisterContentObserver(uri);
// 同样需要通过ContentResolver类进行解除
实例
ContentProvider主要作用是进程通信,所以实例有两个分别是进程内和进程间。
两者相同的是创建数据库类、自定义ContentProvider类
DBHelper.java用于创建数据库
public class DBHelper extends SQLiteOpenHelper {
// 数据库名
private static final String DATABASE_NAME = "finch.db";
// 表名
public static final String USER_TABLE_NAME = "user";
public static final String JOB_TABLE_NAME = "job";
private static final int DATABASE_VERSION = 1;
//数据库版本号
public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// 创建两个表格:用户表 和职业表
db.execSQL("CREATE TABLE IF NOT EXISTS " + USER_TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " name TEXT)");
db.execSQL("CREATE TABLE IF NOT EXISTS " + JOB_TABLE_NAME + "(_id INTEGER PRIMARY KEY AUTOINCREMENT," + " job TEXT)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
自定义ContentProvider类
public class MyProvider extends ContentProvider {
private Context mContext;
DBHelper mDbHelper = null;
SQLiteDatabase db = null;
public static final String AUTOHORITY = "cn.scu.myprovider";
// 设置ContentProvider的唯一标识
public static final int User_Code = 1;
public static final int Job_Code = 2;
// UriMatcher类使用:在ContentProvider 中注册URI
private static final UriMatcher mMatcher;
static{
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 初始化
mMatcher.addURI(AUTOHORITY,"user", User_Code);
mMatcher.addURI(AUTOHORITY, "job", Job_Code);
// 若URI资源路径 = content://cn.scu.myprovider/user ,则返回注册码User_Code
// 若URI资源路径 = content://cn.scu.myprovider/job ,则返回注册码Job_Code
}
// 以下是ContentProvider的6个方法
/**
* 初始化ContentProvider
*/
@Override
public boolean onCreate() {
mContext = getContext();
// 在ContentProvider创建时对数据库进行初始化
// 运行在主线程,故不能做耗时操作,此处仅作展示
mDbHelper = new DBHelper(getContext());
db = mDbHelper.getWritableDatabase();
// 初始化两个表的数据(先清空两个表,再各加入一个记录)
db.execSQL("delete from user");
db.execSQL("insert into user values(1,'Carson');");
db.execSQL("insert into user values(2,'Kobe');");
db.execSQL("delete from job");
db.execSQL("insert into job values(1,'Android');");
db.execSQL("insert into job values(2,'iOS');");
return true;
}
/**
* 添加数据
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
// 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
// 该方法在最下面
String table = getTableName(uri);
// 向该表添加数据
db.insert(table, null, values);
// 当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
mContext.getContentResolver().notifyChange(uri, null);
// // 通过ContentUris类从URL中获取ID
// long personid = ContentUris.parseId(uri);
// System.out.println(personid);
return uri;
}
/**
* 查询数据
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
// 该方法在最下面
String table = getTableName(uri);
// // 通过ContentUris类从URL中获取ID
// long personid = ContentUris.parseId(uri);
// System.out.println(personid);
// 查询数据
return db.query(table,projection,selection,selectionArgs,null,null,sortOrder,null);
}
/**
* 更新数据
*/
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// 由于不展示,此处不作展开
return 0;
}
/**
* 删除数据
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// 由于不展示,此处不作展开
return 0;
}
@Override
public String getType(Uri uri) {
// 由于不展示,此处不作展开
return null;
}
/**
* 根据URI匹配 URI_CODE,从而匹配ContentProvider中相应的表名
*/
private String getTableName(Uri uri){
String tableName = null;
switch (mMatcher.match(uri)) {
case User_Code:
tableName = DBHelper.USER_TABLE_NAME;
break;
case Job_Code:
tableName = DBHelper.JOB_TABLE_NAME;
break;
}
return tableName;
}
}
进程内的通信注册
AndroidManifest.xml
<provider android:name="MyProvider"
android:authorities="cn.scu.myprovider"/>
进程内访问ContentProvider的数据
MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/**
* 对user表进行操作
*/
// 设置URI
Uri uri_user = Uri.parse("content://cn.scu.myprovider/user");
// 插入表中数据
ContentValues values = new ContentValues();
values.put("_id", 3);
values.put("name", "Iverson");
// 获取ContentResolver
ContentResolver resolver = getContentResolver();
// 通过ContentResolver 根据URI 向ContentProvider中插入数据
resolver.insert(uri_user,values);
// 通过ContentResolver 向ContentProvider中查询数据
Cursor cursor = resolver.query(uri_user, new String[]{"_id","name"}, null, null, null);
while (cursor.moveToNext()){
System.out.println("query book:" + cursor.getInt(0) +" "+ cursor.getString(1));
// 将表中数据全部输出
}
cursor.close();
// 关闭游标
/**
* 对job表进行操作
*/
// 和上述类似,只是URI需要更改,从而匹配不同的URI CODE,从而找到不同的数据资源
Uri uri_job = Uri.parse("content://cn.scu.myprovider/job");
// 插入表中数据
ContentValues values2 = new ContentValues();
values2.put("_id", 3);
values2.put("job", "NBA Player");
// 获取ContentResolver
ContentResolver resolver2 = getContentResolver();
// 通过ContentResolver 根据URI 向ContentProvider中插入数据
resolver2.insert(uri_job,values2);
// 通过ContentResolver 向ContentProvider中查询数据
Cursor cursor2 = resolver2.query(uri_job, new String[]{"_id","job"}, null, null, null);
while (cursor2.moveToNext()){
System.out.println("query job:" + cursor2.getInt(0) +" "+ cursor2.getString(1));
// 将表中数据全部输出
}
cursor2.close();
// 关闭游标
}
}
进程间的通信注册
进程一的AndroidManifest.xml
<provider
android:name="MyProvider"
android:authorities="scut.carson_ho.myprovider"
// 声明外界进程可访问该Provider的权限(读 & 写)
android:permission="scut.carson_ho.PROVIDER"
// 权限可细分为读 & 写的权限
// 外界需要声明同样的读 & 写的权限才可进行相应操作,否则会报错
// android:readPermisson = "scut.carson_ho.Read"
// android:writePermisson = "scut.carson_ho.Write"
// 设置此provider是否可以被其他进程使用
android:exported="true"/>
// 声明本应用 可允许通信的权限
<permission android:name="scut.carson_ho.Read" android:protectionLevel="normal"/>
// 细分读 & 写权限如下,但本Demo直接采用全权限
// <permission android:name="scut.carson_ho.Write" android:protectionLevel="normal"/>
// <permission android:name="scut.carson_ho.PROVIDER" android:protectionLevel="normal"/>
进程二的AndroidManifest.xml需要添加相关的权限
// 声明本应用可允许通信的权限(全权限)
<uses-permission android:name="scut.carson_ho.PROVIDER"/>
// 细分读 & 写权限如下,但本Demo直接采用全权限
// <uses-permission android:name="scut.carson_ho.Read"/>
// <uses-permission android:name="scut.carson_ho.Write"/>
// 注:声明的权限必须与进程1中设置的权限对应
进程二的ContentProvider
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/**
* 对user表进行操作
*/
// 设置URI
Uri uri_user = Uri.parse("content://scut.carson_ho.myprovider/user");
// 插入表中数据
ContentValues values = new ContentValues();
values.put("_id", 4);
values.put("name", "Jordan");
// 获取ContentResolver
ContentResolver resolver = getContentResolver();
// 通过ContentResolver 根据URI 向ContentProvider中插入数据
resolver.insert(uri_user,values);
// 通过ContentResolver 向ContentProvider中查询数据
Cursor cursor = resolver.query(uri_user, new String[]{"_id","name"}, null, null, null);
while (cursor.moveToNext()){
System.out.println("query book:" + cursor.getInt(0) +" "+ cursor.getString(1));
// 将表中数据全部输出
}
cursor.close();
// 关闭游标
/**
* 对job表进行操作
*/
// 和上述类似,只是URI需要更改,从而匹配不同的URI CODE,从而找到不同的数据资源
Uri uri_job = Uri.parse("content://scut.carson_ho.myprovider/job");
// 插入表中数据
ContentValues values2 = new ContentValues();
values2.put("_id", 4);
values2.put("job", "NBA Player");
// 获取ContentResolver
ContentResolver resolver2 = getContentResolver();
// 通过ContentResolver 根据URI 向ContentProvider中插入数据
resolver2.insert(uri_job,values2);
// 通过ContentResolver 向ContentProvider中查询数据
Cursor cursor2 = resolver2.query(uri_job, new String[]{"_id","job"}, null, null, null);
while (cursor2.moveToNext()){
System.out.println("query job:" + cursor2.getInt(0) +" "+ cursor2.getString(1));
// 将表中数据全部输出
}
cursor2.close();
// 关闭游标
}
}
优点
-
ContentProvider
为应用间的数据交互提供了一个安全的环境:允许把自己的应用数据根据需求开放给 其他应用 进行 增、删、改、查,而不用担心因为直接开放数据库权限而带来的安全问题 -
采用
ContentProvider
方式,其 解耦了 底层数据的存储方式,使得无论底层数据存储采用何种方式,外界对数据的访问方式都是统一的,这使得访问简单 & 高效( 如一开始数据存储方式 采用SQLite
数据库,后来把数据库换成MongoDB
,也不会对上层数据ContentProvider
使用代码产生影响 )
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JaFgUtQ2-1612606912813)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210204162349042.png)]
工作过程
Activity的工作过程
(1) Activity的所有 startActivity 重载方法最终都会调用 startActivityForResult。
(2) 调用 mInstrumentation.execStartActivity.execStartActivity()方法。
(3) 代码中启动Activity的真正实现是由ActivityManagerNative.getDefault().startActivity()方法完成的. ActivityManagerService简称AMS. AMS继承自ActivityManagerNative(), 而ActivityManagerNative()继承自Binder并实现了IActivityManager这个Binder接口, 因此AMS也是一个Binder, 它是IActivityManager的具体实现.ActivityManagerNative.getDefault()本质是一个IActivityManager类型的Binder对象, 因此具体实现是AMS.
(4) 在ActivityManagerNative中, AMS这个Binder对象采用单例模式对外提供, Singleton是一个单例封装类. 第一次调用它的get()方法时会通过create方法来初始化AMS这个Binder对象, 在后续调用中会返回这个对象.
(5) AMS的startActivity()过程
-
checkStartActivityResult () 方法检查启动Activity的结果( 包括检查有无在
manifest注册) -
Activity启动过程经过两次转移, 最后又转移到了mStackSupervisor.startActivityMayWait()这个方法, 所属类为ActivityStackSupervisor. 在startActivityMayWait()内部又调用了startActivityLocked()这里会返回结果码就是之前checkStartActivityResult()用到的。
-
方法最后会调用startActivityUncheckedLocked(), 然后又调用了ActivityStack#resumeTopActivityLocked(). 这个时候启动过程已经从ActivityStackSupervisor转移到了ActivityStack类中.
(6) 在最后的 ActivityStackSupervisor. realStartActivityLocked() 中,调用了 app.thread.scheduleLaunchActivity() 方法。 这个app.thread是ApplicationThread 类型,继承于 IApplicationThread 是一个Binder类,内部是各种启动/停止 Service/Activity的接口。
(7) 在ApplicationThread中, scheduleLaunchActivity() 用来启动Activity,里面的实现就是发送一个Activity的消息( 封装成 从ActivityClientRecord 对象) 交给Handler处理。这个Handler有一个简洁的名字 H 。
(8) 在H的 handleMessage() 方法里,通过 handleLaunchActivity() 方法完成Activity对象的创建和启动,并且ActivityThread通过handleResumeActivity()方法来调用被启动的onResume()这一生命周期方法。PerformLaunchActivity()主要完成了如下几件事:
-
从ActivityClientRecord对象中获取待启动的Activity组件信息
-
通过 Instrumentation 的 newActivity 方法使用类加载器创建Activity对象
-
通过 LoadedApk 的makeApplication方法尝试创建Application对象,通过类加载器实现( 如果Application已经创建过了就不会再创建)
-
创建 ContextImpl 对象并通过Activity的 attach 方法完成一些重要数据的初始化(ContextImpl是一个很重要的数据结构, 它是Context的具体实现, Context中的大部分逻辑都是由ContentImpl来完成的. ContextImpl是通过Activity的attach()方法来和Activity建立关联的,除此之外, 在attach()中Activity还会完成Window的创建并建立自己和Window的关联, 这样当Window接收到外部输入事件收就可以将事件传递给Activity.)
-
通过 mInstrumentation.callActivityOnCreate(activity, r.state) 方法调用Activity的 onCreate 方法
Service的工作过程
- 启动状态:执行后台计算
- 绑定状态:用于其他组件与Service交互
两种状态是可以共存的
1. Service的启动过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GrvZb2Br-1612606912820)(http://hujiaweibujidao.github.io/images/androidart_service1.png)]
image
(1) Service的启动从 ContextWrapper 的 startService 开始
(2) 在ContextWrapper中,大部分操作通过一个 ContextImpl 对象mBase实现
(3) 在ContextImpl中, mBase.startService() 会调用 startServiceCommon 方法,而
startServiceCommon方法又会通过 ActivityManagerNative.getDefault() ( 实际上就是AMS) 这个对象来启动一个服务。
(4) AMS会通过一个 ActiveService 对象( 辅助AMS进行Service管理的类,包括Service的启动,绑定和停止等) mServices来完成启动Service: mServices.startServiceLocked() 。
(5) 在mServices.startServiceLocked()最后会调用 startServiceInnerLocked() 方法:将Service的信息包装成一个 ServiceRecord 对象,ServiceRecord一直贯穿着整个Service的启动过程。通过 bringUpServiceLocked()方法来处理,bringUpServiceLocked()又调用了 realStartServiceLocked() 方法,这才真正地去启动一个Service了。
(6) realStartServiceLocked()方法的工作如下:
- app.thread.scheduleCreateService() 来创建Service并调用其onCreate()生命周期方法
- sendServiceArgsLocked() 调用其他生命周期方法,如onStartCommand()
- app.thread对象是 IApplicationThread 类型,实际上就是一个Binder,具体实现是ApplicationThread继承ApplictionThreadNative
(7) 具体看Application对Service的启动过程app.thread.scheduleCreateService():通过 sendMessage(H.CREATE_SERVICE , s) ,这个过程和Activity启动过程类似,同时通过发送消息给Handler H来完成的。
(8) H会接受这个CREATE_SERVICE消息并通过ActivityThread的 handleCreateService() 来完成Service的最终启动。
(9) handleCreateService()完成了以下工作:
- 通过ClassLoader创建Service对象
- 创建Service内部的Context对象
- 创建Application,并调用其onCreate()( 只会有一次)
- 通过 service.attach() 方法建立Service与context的联系( 与Activity类似)
- 调用service的 onCreate() 生命周期方法,至此,Service已经启动了
- 将Service对象存储到ActivityThread的一个ArrayMap中
2. Service的绑定过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-POL4RFHz-1612606912823)(http://hujiaweibujidao.github.io/images/androidart_service2.png)]
image
和service的启动过程类似的:
(1) Service的绑定是从 ContextWrapper 的 bindService 开始
(2) 在ContextWrapper中,交给 ContextImpl 对象 mBase.bindService()
(3) 最终会调用ContextImpl的 bindServiceCommon 方法,这个方法完成两件事:
- 将客户端的ServiceConnection转化成 ServiceDispatcher.InnerConnection 对象。ServiceDispatcher连接ServiceConnection和InnerConnection。这个过程通过 LoadedApk 的 getServiceDispatcher 方法来实现,将客户端的ServiceConnection和ServiceDispatcher的映射关系存在一个ArrayMap中。
- 通过AMS来完成Service的具体绑定过程 ActivityManagerNative.getDefault().bindService()
(4) AMS中,bindService()方法再调用 bindServiceLocked() ,bindServiceLocked()再调用 bringUpServiceLocked() ,bringUpServiceLocked()又会调用 realStartServiceLocked() 。
(5) AMS的realStartServiceLocked()会调用 ActiveServices 的requrestServiceBindingLocked() 方法,最终是调用了ServiceRecord对象r的 app.thread.scheduleBindService() 方法。
(6) ApplicationThread的一系列以schedule开头的方法,内部都通过Handler H来中转:scheduleBindService()内部也是通过 sendMessage(H.BIND_SERVICE , s)
(7) 在H内部接收到BIND_SERVICE这类消息时就交给 ActivityThread 的handleBindService() 方法处理:
- 根据Servcie的token取出Service对象
- 调用Service的 onBind() 方法,至此,Service就处于绑定状态了。
- 这时客户端还不知道已经成功连接Service,需要调用客户端的binder对象来调用客户端的ServiceConnection中的 onServiceConnected() 方法,这个通过 ActivityManagerNative.getDefault().publishService() 进行。ActivityManagerNative.getDefault()就是AMS。
(8) AMS的publishService()交给ActivityService对象 mServices 的 publishServiceLocked() 来处理,核心代码就一句话 c.conn.connected(r.name,service) 。对象c的类型是 ConnectionRecord ,c.conn就是ServiceDispatcher.InnerConnection对象,service就是Service的onBind方法返回的Binder对象。
(9) c.conn.connected(r.name,service)内部实现是交给了mActivityThread.post(new RunnConnection(name ,service,0)); 实现。ServiceDispatcher的mActivityThread是一个Handler,其实就是ActivityThread中的H。这样一来RunConnection就经由H的post方法从而运行在主线程中,因此客户端ServiceConnection中的方法是在主线程中被回调的。
(10) RunConnection的定义如下:
- 继承Runnable接口, run() 方法的实现也是简单调用了ServiceDispatcher的 doConnected 方法。
- 由于ServiceDispatcher内部保存了客户端的ServiceConntion对象,可以很方便地调用ServiceConntion对象的 onServiceConnected 方法。
- 客户端的onServiceConnected方法执行后,Service的绑定过程也就完成了。
- 根据步骤8、9、10service绑定后通过ServiceDispatcher通知客户端的过程可以说明ServiceDispatcher起着连接ServiceConnection和InnerConnection的作用。 至于Service的停止和解除绑定的过程,系统流程都是类似的。
BroadcastReceiver的工作过程
简单回顾一下广播的使用方法, 首先定义广播接收者, 只需要继承BroadcastReceiver并重写onReceive()方法即可. 定义好了广播接收者, 还需要注册广播接收者, 分为两种静态注册或者动态注册. 注册完成之后就可以发送广播了.
1. 广播的注册过程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-56GTjrdX-1612606912824)(http://hujiaweibujidao.github.io/images/androidart_broadcastreceiver1.png)]
image
(1) 动态注册的过程是从ContextWrapper#registerReceiver()开始的. 和Activity或者Service一样. ContextWrapper并没有做实际的工作, 而是将注册的过程直接交给了ContextImpl来完成.
(2) ContextImpl#registerReceiver()方法调用了本类的registerReceiverInternal()方法.
(3) 系统首先从mPackageInfo获取到IIntentReceiver对象, 然后再采用跨进程的方式向AMS发送广播注册的请求. 之所以采用IIntentReceiver而不是直接采用BroadcastReceiver, 这是因为上述注册过程中是一个进程间通信的过程. 而BroadcastReceiver作为Android中的一个组件是不能直接跨进程传递的. 所有需要通过IIntentReceiver来中转一下.
(4) IIntentReceiver作为一个Binder接口, 它的具体实现是LoadedApk.ReceiverDispatcher.InnerReceiver, ReceiverDispatcher的内部同时保存了BroadcastReceiver和InnerReceiver, 这样当接收到广播的时候, ReceiverDispatcher可以很方便的调用BroadcastReceiver#onReceive()方法. 这里和Service很像有同样的类, 并且内部类中同样也是一个Binder接口.
(5) 由于注册广播真正实现过程是在AMS中, 因此跟进AMS中, 首先看registerReceiver()方法, 这里只关心里面的核心部分. 这段代码最终会把远程的InnerReceiver对象以及IntentFilter对象存储起来, 这样整个广播的注册就完成了.
2. 广播的发送和接收过程
广播的发送有几种:普通广播、有序广播和粘性广播,他们的发送/接收流程是类似的,因此只分析普通广播的实现。
(1) 广播的发送和接收, 本质就是一个过程的两个阶段. 广播的发送仍然开始于ContextImpl#sendBroadcase()方法, 之所以不是Context, 那是因为Context#sendBroad()是一个抽象方法. 和广播的注册过程一样, ContextWrapper#sendBroadcast()仍然什么都不做, 只是把事情交给了ContextImpl去处理.
(2) ContextImpl里面也几乎什么都没有做, 内部直接向AMS发起了一个异步请求用于发送广播.
(3) 调用AMS#broadcastIntent()方法,继续调用broadcastIntentLocked()方法。
(4) 在broadcastIntentLocked()内部, 会根据intent-filter查找出匹配的广播接收者并经过一系列的条件过滤. 最终会将满足条件的广播接收者添加到BroadcastQueue中, 接着BroadcastQueue就会将广播发送给相应广播接收者.
(5) BroadcastQueue#scheduleBroadcastsLocked()方法内并没有立即发送广播, 而是发送了一个BROADCAST_INTENT_MSG类型的消息, BroadcastQueue收到消息后会调用processNextBroadcast()方法。
(6) 无序广播存储在mParallelBroadcasts中, 系统会遍历这个集合并将其中的广播发送给他们所有的接收者, 具体的发送过程是通过deliverToRegisteredReceiverLocked()方法实现. deliverToRegisteredReceiverLocked()负责将一个广播发送给一个特定的接收者, 它的内部调用了performReceiverLocked方法来完成具体发送过程.
(7) performReceiverLocked()方法调用的ApplicationThread#scheduleRegisteredReceiver()实现比较简单, 它通过InnerReceiver来实现广播的接收
(8) scheduleRegisteredReceiver()方法中,receiver.performReceive()中的receiver对应着IIntentReceiver类型的接口. 而具体的实现就是ReceiverDispatcher
I
n
n
e
r
R
e
c
e
i
v
e
r
.
这
两
个
嵌
套
的
内
部
类
是
所
属
在
L
o
a
d
e
d
A
p
k
中
的
。
(
9
)
又
调
用
了
L
o
a
d
e
d
A
p
k
InnerReceiver. 这两个嵌套的内部类是所属在LoadedApk中的。 (9) 又调用了LoadedApk
InnerReceiver.这两个嵌套的内部类是所属在LoadedApk中的。(9)又调用了LoadedApkReceiverDispatcher#performReceive()的方法.在performReceiver()这个方法中, 会创建一个Args对象并通过mActivityThread的post方法执行args中的逻辑. 而这些类的本质关系就是:
- Args: 实现类Runnable
- mActivityThread: 是一个Handler, 就是ActivityThread中的mH. mH就是ActivityThread$H. 这个内部类H以前说过.
(10) 实现Runnable接口的Args中BroadcastReceiver#onReceive()方法被执行了, 也就是说应用已经接收到了广播, 同时onReceive()方法是在广播接收者的主线程中被调用的.
android 3.1开始就增添了两个标记为. 分别是FLAG_INCLUDE_STOPPED_PACKAGES, FLAG_EXCLUDE_STOPPED_PACKAGES. 用来控制广播是否要对处于停止的应用起作用.
- FLAG_INCLUDE_STOPPED_PACKAGES: 包含停止应用, 广播会发送给已停止的应用.
- FLAG_EXCLUDE_STOPPED_PACKAGES: 不包含已停止应用, 广播不会发送给已停止的应用
在android 3.1开始, 系统就为所有广播默认添加了FLAG_EXCLUDE_STOPPED_PACKAGES标识。 当这两个标记共存的时候以FLAG_INCLUDE_STOPPED_PACKAGES(非默认项为主).
应用处于停止分为两种
- 应用安装后未运行
- 被手动或者其他应用强停
开机广播同样受到了这个标志位的影响. 从Android 3.1开始处于停止状态的应用同样无法接受到开机广播, 而在android 3.1之前处于停止的状态也是可以接收到开机广播的.
ContentProvider的工作机制
ContentProvider是一种内容共享型组件, 它通过Binder向其他组件乃至其他应用提供数据. 当ContentProvider所在的进程启动时, ContentProvider会同时启动并发布到AMS中. 要注意:这个时候ContentProvider的onCreate()方法是先于Application的onCreate()执行的,这一点在四大组件是少有的现象.
image
(1) 当一个应用启动时,入口方法是ActivityThread的main方法,其中创建ActivityThread的实例并创建主线程的消息队列;
(2) ActivityThread的attach方法中会远程调用ActivityManagerService的attachApplication,并将ApplicationThread提供给AMS,ApplicationThread主要用于ActivityThread和AMS之间的通信;
(3) ActivityManagerService的attachApplication会调用ApplicationThread的bindApplication方法,这个方法会通过H切换到ActivityThread中去执行,即调用handleBindApplication方法;
(4) handleBindApplication方法会创建Application对象并加载ContentProvider,注意是先加载ContentProvider,然后调用Application的onCreate方法。
(5) ContentProvider启动后, 外界就可以通过它所提供的增删改查这四个接口来操作ContentProvider中的数据源, 这四个方法都是通过Binder来调用的, 外界无法直接访问ContentProvider, 它只能通过AMS根据URI来获取到对应的ContentProvider的Binder接口IContentProvider, 然后再通过IContentProvider来访问ContentProvider中的数据源.
ContentProvider的android:multiprocess属性决定它是否是单实例,默认值是false,也就是默认是单实例。当设置为true时,每个调用者的进程中都存在一个ContentProvider对象。
当调用ContentProvider的insert、delete、update、query方法中的任何一个时,如果ContentProvider所在的进程没有启动的话,那么就会触发ContentProvider的创建,并伴随着ContentProvider所在进程的启动。
以query调用为例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QCLz1Wpd-1612606912828)(http://hujiaweibujidao.github.io/images/androidart_contentprovider.png)]
(1) 首先会获取IContentProvider对象, 不管是通过acquireUnstableProvider()方法还是直接通过acquireProvider()方法, 他们的本质都是一样的, 最终都是通过acquireProvider方法来获取ContentProvider.
(2) ApplicationContentResolver#acquireProvider()方法并没有处理任何逻辑, 它直接调用了ActivityThread#acquireProvider()
(3) 从ActivityThread中查找是否已经存在了ContentProvider了, 如果存在那么就直接返回. ActivityThread中通过mProviderMap来存储已经启动的ContentProvider对象, 这个集合的存储类型ArrayMap mProviderMap. 如果目前ContentProvider没有启动, 那么就发送一个进程间请求给AMS让其启动项目目标ContentProvider, 最后再通过installProvider()方法来修改引用计数.
(4) AMS是如何启动ContentProvider的呢?首先会启动ContentProvider所在的进程, 然后再启动ContentProvider. 启动进程是由AMS#startProcessLocked()方法来完成, 其内部主要是通过Process#start()方法来完成一个新进程的启动, 新进程启动后其入口方法为ActivityThread#main()方法。
(5) ActivityThread#main()是一个静态方法, 在它的内部首先会创建ActivityThread实例并调用attach()方法来进行一系列初始化, 接着就开始进行消息循环. ActivityThread#attach()方法会将Application对象通过AMS#attachApplication方法跨进程传递给AMS, 最终AMS会完成ContentProvider的创建过程.
(6) AMS#attachApplication()方法调用了attachApplication(), 然后又调用了ApplicationThread#bindApplication(), 这个过程也属于进程通信.bindApplication()方法会发送一个BIND_APPLICATION类型的消息给mH, 这是一个Handler, 它收到消息后会调用ActivityThread#handleBindApplication()方法.
(7) ActivityThread#handlerBindApplication()则完成了Application的创建以及ContentProvider 可以分为如下四个步骤:
- 创建ContentProvider和Instrumentation
- 创建Application对象
- 启动当前进程的ContentProvider并调用onCreate()方法. 主要内部实现是installContentProvider()完成了ContentProvider的启动工作, 首先会遍历当前进程的ProviderInfo的列表并一一调用installProvider()方法来启动他们, 接着将已经启动的ContentProvider发布到AMS中, AMS会把他们存储在ProviderMap中, 这样一来外部调用者就可以直接从AMS中获取到ContentProvider. installProvider()内部通过类加载器创建的ContentProvider实例并在方法中调用了attachInfo(), 在这内部调用了ContentProvider#onCreate()
- 调用Application#onCreate()
经过了上述的四个步骤, ContentProvider已经启动成功, 并且其所在的进程的Application也已经成功, 这意味着ContentProvider所在的进程已经完成了整个的启动过程, 然后其他应用就可以通过AMS来访问这个ContentProvider了.
当拿到了ContentProvider以后, 就可以通过它所提供的接口方法来访问它. 这里要注意: 这里的ContentProvider并不是原始的ContentProvider. 而是ContentProvider的Binder类型对象IContentProvider, 而IContentProvider的具体实现是ContentProviderNative和ContentProvider.Transport. 后者继承了前者.
如果还用query方法来解释流程: 那么最开始其他应用通过AMS获取到ContentProvider的Binder对象就是IContentProvider. 而IContentProvider的实际实现者是ContentProvider.Transport. 因此实际上外部应用调用的时候本质上会以进程间通信的方式调用ContentProvider.Transport的query()方法。
标签:调用,Service,--,广播,Activity,组件,Android,ContentProvider,方法 来源: https://blog.csdn.net/qq_43337254/article/details/113729021