第2章Android四大组件

2.1 Activity

2.1.1 Activity的生命周期全面分析

典型情况下的生命周期:在用户参与的情况下,Activity所经过的生命周期改变。
异常情况下的生命周期:Activity被系统回收或者设备的Configuration发生改变从而导致Activity被销毁重建。

1. 典型情况下的生命周期分析

Activity生命周期
  • ==onPause必须执行完,新的Activity的onResume才会执行==。比如A打开B,生命周期顺序是 A onPause->B onCreate ->B onStart ->B onResume ->A onStop。因此onPause不能做耗时操作,才能使新的Activity尽快显示出来
  • onStart表示Activity已经可见但还在后台我们看不见,没有出现在前台无法与用户进行交互。
  • onResume表示Activity已经出现在前台且可以与用户进行交互。
  • Activity切换到后台( 用户打开新的Activity或者切换到桌面) ,onPause->onStop(如果新Activity采用了透明主题,则当前Activity不会回调onStop)。

2. 异常情况下的生命周期分析

(1) 情况1:系统配置发生改变导致Activity被杀死并重新创建

比如横竖屏切换,默认情况下,Activity就会销毁重建。生命周期如下:


异常情况下Activity的重建过程

当系统配置发生改变导致Activity被销毁,onPause->onStop->onDestroy,还会==调用onSaveIntanceState保存当前Activity的状态,时机是在onStop之前==(仅仅出现在Activity异常终止的情况)。当Activity被重建时,==会调用onRestoreInstanceState,时机是在onStart之后==,并把onSaveIntanceState中保存的Bundle对象同时传递给onRestoreInstanceState和onCreate。注意,onRestoreInstanceState被调用,其入参Bundle一定有值,但是onCreate的Bundle入参可能会null,建议采用onRestoreInstanceState去恢复数据。

==每个View都有onSaveIntanceState和onRestoreInstanceState方法,系统会自动帮我们做一些恢复工作==,具体恢复哪些数据,要看每个View的实现。大致的工作流程如下:Activity意外终止,调用onSaveIntanceState去保存数据,然后Activity委托Window去保存数据,Window再委托上面的顶层容器(ViewGroup,一般为DecorView)去保存数据,顶层容器再去一一通知它的子元素来保存数据。(这是一种典型的委托思想,事件分发和View的绘制过程也是采用类似的思想)

系统只有在Activity异常终止的情况下,才会调用onSaveIntanceState和onRestoreInstanceState方法,其他情况不会调用。

(2) 情况2:内存资源不足,导致低优先级Activity被杀死

Activity按照优先级分类:
1)前台Activity
2)可见非前台Activity
3)后台Activity

系统内存不足时,低优先级的Activity所在进程会被杀死,并通过onSaveIntanceState和onRestoreInstanceState来存储和恢复数据。

3. 系统配置发生改变,Activity如何不重新创建?

在Activity的配置中,配置属性android:configChanges="orientation|screenSize",可以在屏幕旋转的时候,不重新创建Activity,取而代之的是==调用Activity的onConfigurationChanged方法==。

其他可以配置项目如下表所示

项目 含义
locale 一般指切换了系统语言
keboardHidden 键盘的可访问性发生了变化
orientation 屏幕方向发生了变化
screenSize 屏幕尺寸信息发生了变化,旋转屏幕尺寸信息就会发生变化。若编译版本小于13,不会导致Activity的重启;若大于13,则会导致Activity的重启

2.1.2 Activity的启动模式

1. Activity的LaunchMode

四种启动模式:standard、singleTop、singleTask和singleInstance。

(1) standard

默认模式,谁启动了Activity,那么这个Activity就运行在启动它的那个Activity所在的栈中。非Activity类型的Context(如:ApplicationContext)并没有所谓的任务栈,所以就会有问题。

(2) singleTop

栈顶复用模式。新的Activity位于任务栈的顶部,那么此Activity不会重新创建(onCreate、onStart不会调用),此时==onNewIntent(Intent intent)会被调用==,入参就是我们请求的信息。

(3) singleTask

栈内复用模式。比如启动ActivityA,系统会先找A想要的任务栈,若不存在,则创建任务栈、A实例、入栈;若存在,栈中已经存在A实例,移除A之上的元素,使A在栈顶,并调用onNewIntent,否则创建A实例,入栈。

(4) singleInstance

加强的singTask模式,加强一点:此类Activity只能单独的位于一个任务栈中,==后续的请求均不会创建新的Activity,除非这个任务栈被销毁。==

(5) Activity所需的任务栈

==TaskAffinity==:任务相关性,标识了一个Activity所需要的任务栈的名字,默认情况下是包名。==必须和singleTask启动模式或者allowTaskReparenting属性配对使用==,否则没有意义。

任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity处于暂停状态,用户可以通过切换将后台任务栈调到前台。

TaskAffinity和allowTaskReparenting结合使用:现在有2个应用A和B,A启动了B的Activity C,然后按Home键回到桌面,然后单击桌面图标启动B,此时并不是启动B的主Activity,而是重新显示了Activity C或者说,==C从A的任务栈转移到了B的任务栈==。可以这么理解,由于A启动了C,这个C只能运行在A所在的任务栈中,但是C是属于B应用的,正常情况下,它的TaskAffinity应该是B的包名。所以,B被启动之后,B会创建自己的任务栈,此时系统发现C原本想要的任务栈已经有了,就会把C从A的任务栈中转移过。

(6) 指定启动模式的两种方式

第一种:通过AndroidMenifest指定android:launchMode="singTask"
第二种:通过Intent的addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

2. Activity的Flags

Flags有很多,有些可以设定Activity的启动模式,有些可以影响Activity的运行状态。

FLAG_ACTIVITY_NEW_TASK

为Activity指定“singleTask”启动模式

FLAG_ACTIVITY_SINGLE_TOP

为Activity指定“singleTop”启动模式

FLAG_ACTIVITY_CLEAR_TOP

具有此标记的Activity在启动时,同一个任务栈中位于它上面的都要出栈。与singleTask一起使用,若实例已经存在,会调用newIntent方法;若被启动的Activity是standard,则它自己也会出栈,然后重新创建一个新的Activity实例入栈。

FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS

等价于android:excludeFromRecents="true",表明==此Activity不会出现在历史Activity列表中==。

三、IntentFilter的匹配规则

Activity的启动分为显式调用和隐式调用。隐式调用需要Intent能够匹配目标组件的IntentFilter中设置的过滤信息,不匹配将无法启动目标Activity。IntentFilter的过滤信息有action、category、data。

<intent-filter>  
    <action android:name="android.intent.action.MEDIA_MOUNTED"/>  
    <category android:name="com.hilton.controlpanel.category.SELF_MAINTAIN" /> 
    <data android:scheme="file" />  
</intent-filter>

1. action的匹配规则

是一个区分大小写的字符串,一个过滤规则中可以有多个action。

匹配要求:Intent中的action存在,且能够和过滤规则中的==任何一个action相同==即可匹配成功。

2. category的匹配规则

是一个字符串,一个过滤规则中可以有多个category。

匹配要求:Intent可以没有category,但是如果有,==每一个都要和过滤规则中任何一个的匹配==。在startActivity或者startActivityForResult的时候,会默认加上“android.inent.category,DEFULT”,所以必须在intent-filter中指定这个category。

3. data的匹配规则

由两部分组成:mimeType和URI。一个过滤规则中可以有多个。

匹配要求:Intent中必须有,且==和过滤规则中的一个相匹配==。

<data android:scheme="string"
    android:host="string"
    android:port="string"
    android:path="string"
    android:pathPattern="string"
    android:pathPrefix="string"
    android:mimeType="string"/>
(1) mimeType

指媒体类型,如 image/jpeg、vide/*等,可以表示图片、文本、视频等不同的媒体格式、

<data android:mimeType="image/*"/>

如上匹配规则==指定了媒体类型为所有类型的图片,虽然没有指定URI,但是却默认为content和file==。Intent中的URI部分的scheme必须为content或者file才能匹配。如下:

intent.setDataAndType(Uri.parse("file://abc"),"image/png")

setDataAndType(Uri data, String type)指定Data和Type属性,因为setAction(String action)addCategory(String category)2个方法会相互覆盖,所以当要指定Data和Type时,使用这个方法

(2) URI
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]

scheme:URI的模式,如http、file、content。没有指定,则URI无效。
host:URI的主机名。没有指定,则URI无效。
port:URI中的端口号,可以没有。
path:表示完整的路径信息。
pathPattern:表示完整的路径信息,建议包含通配符“*”(表示0个或多个任意字符)。
pathPrefix:表示路径的前缀信息。

<data android:scheme="file" android:host="www.baidu.com"/>
等价于
<data android:scheme="file"/>
<data android:host="www.baidu.com"/>

2.2 Service

2.2.1 基础知识

1. 定义

服务,属于Android中的计算型组件

2. 作用

提供需要在后台长期运行的服务(如复杂计算、下载等等)

3. 特点

  • 长生命周期的、没有用户界面、在后台运行、用户不手动关闭不会停止
  • 从Context派生出来的,也有getResources(),getContentResolver()等方法

2.2.2 相关方法

1. Context相关方法

  • startService(Intent intent) ComponentName 启动一个Service,访问者和Service之间没有关联,==即使访问者退出了,Service仍然运行==
  • stopService (Intent intent) boolean 之后会自动调用内部方法:onDestory()
  • bindService(Intent service, ServiceConnection conn, int flags) boolean 访问者与Service绑在了一起,==访问者一旦全部退出,Service也将终止==。conn:该参数是一个ServiceConnection(I)对象,用于==监听访问者和Service之间的连接情况==,若连接成功,将回调ServiceConnection#onServiceConnected(ComponentName name, IBinder service)方法,当异常终止连接时,回调ServiceConnection#onServiceDisconnected(ComponentName name)方法(若访问者主动调用unbindService(ServiceConnection conn) 断开连接,不会回调此方法),flags:指定绑定时若Service未创建是否自动创建,值:0或BIND_AUTO_CREATE
  • unbindService(ServiceConnection conn)

2. 生命周期方法

  • onBind(Intent intent) IBinder 应用程序可以通过IBinder与Service组件进行通信,绑定该Service时回调该方法。

在绑定本地Service的情况下,onBind(Intent service)方法返回的IBinder对象会传给ServiceConnection#onServiceConnected(ComponentName name, IBinder service)的service参数,IBinder相当于一个代理人的角色,实现互相通信,所以onBind方法应该返回一个继承Binder类的对象,可以操作Service中的内容,在onServiceConnected方法就可以使用该代理人。

  • onUnbind(Intent intent) boolean 当绑定在该Service上的所有客户端都断开连接时回调该方法
  • onStartCommand(Intent intent, int flags, int startId) int 调用startService(Intent)方法启动Service时回调该方法,==会被调用多次==
  • onCreate() 第一次被创建时回调该方法,仅被调用一次
  • onDestroy()
  • stopSelf()

3. onStartCommand(Intent intent, int flags, int startId)

(1) 返回值

有三种可选值。

  • START_STICKY
    当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand方法,但其中的Intent将是null,除非有挂起的Intent,如pendingintent。这个状态下比较适用于不执行命令、但无限期运行并等待作业的媒体播放器或类似服务。
  • START_NOT_STICKY
    当Service因内存不足而被系统kill后,即使系统内存再次空闲时,系统也不会尝试重新创建此Service。除非程序中再次调用startService启动此Service,这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。
  • START_REDELIVER_INTENT
    当Service因内存不足而被系统kill后,一段时间后内存再次空闲时,系统将会尝试重新创建此Service,并通过传递给服务的最后一个 Intent 调用 onStartCommand(),任何挂起 Intent均依次传递。与START_STICKY不同的是,其中的传递的Intent将是非空,是最后一次调用startService中的intent。这个值适用于主动执行应该立即恢复的作业(例如下载文件)的服务。
(2) 方法入参
  • intent :启动Service组件传递过来的Intent。
  • flags:表示启动请求时是否有额外数据。可选值有0,START_FLAG_REDELIVERY,START_FLAG_RETRY。0代表没有,它们具体含义如下:
    a)START_FLAG_REDELIVERY
    这个值代表返回值为START_REDELIVER_INTENT,而且在上一次服务被杀死前会去调用stopSelf()方法停止服务。
    b)START_FLAG_RETRY
    该flag代表当onStartCommand调用后一直没有返回值时,会尝试重新去调用onStartCommand()。
  • startId : 指明当前服务的唯一ID,与stopSelfResult(int startId)配合使用,stopSelfResult 可以更安全地根据ID停止服务。

4. Service生命周期

(1) 单独调用
  • startService()->onCreate()->onStartCommand()->onStop()->onDestory()
  • bindService()->onCreate()->onBind()->onUnbind()->onDestory()
(2) 混合调用
  • onCreate()->onStartCommand()->onBind()->onUnbind()->onRebind()

说明:混合调用时,要两两对应,不要相互嵌套(类似于html标签)
服务只能解绑一次,多次会报错
建议在Activity的onDestroy方法中解绑掉服务
startService用于保证服务的后台运行,bindService用于调用服务的方法

2.2.3 Service分类

1. 本地Service

这是最普通、最常用的后台服务Service。

(1) 使用步骤

步骤1:新建子类继承Service类
需重写父类的onCreate()、onStartCommand()、onDestroy()和onBind()方法
步骤2:构建用于启动Service的Intent对象
步骤3:调用startService()启动Service、调用stopService()停止服务
步骤4:在AndroidManifest.xml里注册Service

属性说明

  • android:name Service的类名
  • android:label Service的名字,若不设置,默认为Service类名
  • android:icon Service的图标
  • android:permission 声明此Service的权限,提供了该权限的应用才能控制或连接此服务
  • android:process 表示该服务是否在另一个进程中运行(远程服务) 不设置默认为本地服务;remote则设置成远程服务
  • android:enabled 是否可用即是否可以被系统实例化
  • android:exported 是否能被其他应用隐式调用。 默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。为false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用

2. 可通信Service

实例Demo

步骤1:在新建子类继承Service类,并新建一个子类继承自Binder类、写入与Activity关联需要的方法、创建实例

public class MyService extends Service {

    private MyBinder mBinder = new MyBinder();

    @Override
    public void onCreate() {
        super.onCreate();
        System.out.println("执行了onCreat()");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        System.out.println("执行了onStartCommand()");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        System.out.println("执行了onDestory()");
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        System.out.println("执行了onBind()");
        //返回实例
        return mBinder;
    }


    @Override
    public boolean onUnbind(Intent intent) {
        System.out.println("执行了onUnbind()");
        return super.onUnbind(intent);
    }

    //新建一个子类继承自Binder类
    class MyBinder extends Binder {
        public void service_connect_Activity() {
            System.out.println("Service关联了Activity,并在Activity执行了Service的方法");
        }
    }
}

步骤2:在Activity通过调用MyBinder类中的public方法来实现Activity与Service的联系,即实现了Activity指挥Service干什么Service就去干什么的功能

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button startService;
    private Button stopService;
    private Button bindService;
    private Button unbindService;

    private MyService.MyBinder myBinder;


    //创建ServiceConnection的匿名类
    private ServiceConnection connection = new ServiceConnection() {

        //重写onServiceConnected()方法和onServiceDisconnected()方法
        //在Activity与Service建立关联和解除关联的时候调用
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }

        //在Activity与Service解除关联的时候调用
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //实例化Service的内部类myBinder
            //通过向下转型得到了MyBinder的实例
            myBinder = (MyService.MyBinder) service;
            //在Activity调用Service类的方法
            myBinder.service_connect_Activity();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startService = (Button) findViewById(R.id.startService);
        stopService = (Button) findViewById(R.id.stopService);
        startService.setOnClickListener(this);
        stopService.setOnClickListener(this);
        bindService = (Button) findViewById(R.id.bindService);
        unbindService = (Button) findViewById(R.id.unbindService);
        bindService.setOnClickListener(this);
        unbindService.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            //点击启动Service
            case R.id.startService:
                //构建启动服务的Intent对象
                Intent startIntent = new Intent(this, MyService.class);
                //调用startService()方法-传入Intent对象,以此启动服务
                startService(startIntent);
                break;
            //点击停止Service
            case R.id.stopService:
                //构建停止服务的Intent对象
                Intent stopIntent = new Intent(this, MyService.class);
                //调用stopService()方法-传入Intent对象,以此停止服务
                stopService(stopIntent);
                break;
            //点击绑定Service
            case R.id.bindService:
                //构建绑定服务的Intent对象
                Intent bindIntent = new Intent(this, MyService.class);
                //调用bindService()方法,以此停止服务
                bindService(bindIntent,connection,BIND_AUTO_CREATE);
                //参数说明
                //第一个参数:Intent对象
                //第二个参数:上面创建的Serviceconnection实例
                //第三个参数:标志位
                //这里传入BIND_AUTO_CREATE表示在Activity和Service建立关联后自动创建Service
                //这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行
                break;
            //点击解绑Service
            case R.id.unbindService:
                //调用unbindService()解绑服务
                //参数是上面创建的Serviceconnection实例
                unbindService(connection);
                break;
                default:
                    break;
        }
    }
}

3. 前台Service

前台Service和后台Service(普通)最大的区别就在于:

  • 前台Service在下拉通知栏有显示通知(如下图),但后台Service没有;

  • 前台Service优先级较高,不会由于系统内存不足而被回收;后台Service优先级较低,当系统出现内存不足情况时,很有可能会被回收

//用法很简单,只需要在原有的Service类对onCreate()方法进行稍微修改即可
@Override
public void onCreate() {
    super.onCreate();
    System.out.println("执行了onCreat()");

    //添加下列代码将后台Service变成前台Service
    //构建"点击通知后打开MainActivity"的Intent对象
    Intent notificationIntent = new Intent(this, MainActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

    //新建Builer对象
    Notification.Builder builer = new Notification.Builder(this);
    builer.setContentTitle("前台服务通知的标题");//设置通知的标题
    builer.setContentText("前台服务通知的内容");//设置通知的内容
    builer.setSmallIcon(R.mipmap.ic_launcher);//设置通知的图标
    builer.setContentIntent(pendingIntent);//设置点击通知后的操作

    Notification notification = builer.getNotification();//将Builder对象转变成普通的notification
    startForeground(1, notification);//让Service变成前台Service,并在系统的状态栏显示出来
}

4. 使用场景

2.2.4 其他

1. Service和Thread的区别

Service和Thread之间没有任何关系,之所以有不少人会把它们联系起来,主要因为Service的后台概念

后台的定义:后台任务运行完全不依赖UI,即使Activity被销毁,或者程序被关闭,只要进程还在,后台任务就可以继续运行


一般来说,会将Service和Thread联合着用,即在Service中再创建一个子线程(工作线程)去处理耗时操作逻辑,如下:

@Override  
public int onStartCommand(Intent intent, int flags, int startId) {  
    //新建工作线程
    new Thread(new Runnable() {  
        @Override  
        public void run() {  
            // 开始执行后台任务  
        }  
    }).start();  
    return super.onStartCommand(intent, flags, startId);  
}  

class MyBinder extends Binder {  
    public void service_connect_Activity() {  
        //新建工作线程
        new Thread(new Runnable() {  
            @Override  
            public void run() {  
                // 执行具体的下载任务  
            }  
        }).start();  
    }  
}  

2. IntentService

(1) 定义

Android里的一个封装类,继承四大组件之一的Service

(2) 作用

处理异步请求 & 实现多线程

(3) 使用场景

线程任务需按顺序在后台执行

  • 最常见的场景:离线下载
  • 不符合多个数据同时请求的场景:所有的任务都在同一个Thread looper里执行
(4) 使用步骤

步骤1:定义 IntentService的子类

需传入线程名称、复写onHandleIntent()方法

public class myIntentService extends IntentService {

    /** 
    * 在构造函数中传入线程名字
    **/  
    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();
    }
}

步骤2:在Manifest.xml中注册服务

<service android:name=".myIntentService">
    <intent-filter >
        <action android:name="cn.scu.finch"/>
    </intent-filter>
</service>

步骤3:在Activity中开启Service服务

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("cn.scu.finch");
            Bundle bundle = new Bundle();
            bundle.putString("taskName", "task1");
            i.putExtras(bundle);
            startService(i);

            // 请求2
            Intent i2 = new Intent("cn.scu.finch");
            Bundle bundle2 = new Bundle();
            bundle2.putString("taskName", "task2");
            i2.putExtras(bundle2);
            startService(i2);
            startService(i);  //多次启动
        }
    }

测试结果

3. 进程优先级

  • 前台进程:某个Activity可见,获得焦点
  • 可见进程:某个Activity可见,但是没有焦点
  • 服务进程:有一个服务在后台运行
  • 后台进程:没有任何服务,打开一个Activity然后最小化(容易被回收)
  • 空进程:没有任何活动组件存在的进程(容易被回收)

4. AIDL

(1) 简介

为了实现跨进程通信(IPC),实现进程之间的数据交换

(2) 步骤

①:服务端创建.aidl文件
②:服务端创建Service,并在onBind中返回一个IBinder(接口对象(代理人))

IBinder binder = new IMyAidlInterface.Stub() {
    @Override
    public int add(int num1, int num2) throws RemoteException {
         return num1 + num2;
    }
};

③:客户端创建(复制)和服务端一样的.aidl文件(包名也必须一致)
④:客户端创建ServiceConnection的子类,并实现其onServiceConnected(ComponentName name, IBinder service),在方法中(service就是中间人)iMyAdil = IMyAidlInterface.Stub.asInterface(service),客户端可以使用服务端的方法了
⑤:客户端bindService

(3) AIDL支持的数据类型(支持其实就是定义aidl的时候,参数可以使用的类型)

基本数据类型(除short),String,CharSequence,List(仅支持ArrayList),Map(仅支持HashMap),Parcelable

注意:
①:非基本数据类型,需要用in,out,inout指定数据的走向
②:复杂类型(如Book)必须实现Parcelable,且需要Book.aidl(内容parcelable Book;)—— 包名必须和Book.java相同,无论是否相同的包,都需要导入包
③:AIDL接口中只支持方法,不支持声明静态变量

2.3 IPC与多进程

2.3.1 IPC简介

线程:① CPU最小的调度单元 ② 一种有限的系统资源
进程:一个执行单元。一般指一个程序或一个应用。一个进程可以包含多个线程。
IPC:进程间通信。

多线程的情况
1)因为某些原因自身需要采用多进程模式来实现。比如:某些模块需要运行在单独进程;为了加大一个应用可以使用的内存。
2)需要从其他应用获取数据。

2.3.2 Android中的多进程模式

1. 开启多进程模式

在Android中一个应用开启多进程唯一办法:给四大组件在AndroidMenifest.xml中指定android:process属性。

<service
    android:name=".MyService"
    android:process=":remote" />
<service
    android:name=".SecondService"
    android:process="com.example.xiang.myapplication.remote" />

默认进程名是包名。“:remote”是一种省略写法,完整名为“com.example.xiang.myapplication:remote”进程名,以“:”开头,属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中。

2. 多进程模式的运行机制

Android系统为==每一个进程分配了一个独立的虚拟机==,不同的虚拟机在内存分配上有不同的地址空间。将导致存在如下问题:
1)静态成员和单例完全失效
2)线程同步机制完全失效
3)SharedPreferences的可靠性下降
4)Application会多次创建
不同的进程拥有独立的虚拟机、Application和内存空间,导致通过内存来共享数据,都会共享失败。

Android的IPC方式
1)Intent
2)文件共享方式
3)Binder(AIDL和Messenger)
4)ContentProvider
5)Socket

2.3.3 IPC的基础概念

为什么要序列化?
1)永久性保存对象的字节序列到本地文件中
2)通过序列化在网络中传递对象
3)通过序列化在进程间传递对象

1. Serializable接口

Serializable是Java提供的一个序列化接口,是一个空接口,为对象提供标准的序列化和反序列化操作。

private static final long serialVersionUID = 8154678445665565611L;

serialVersionUID是用来辅助序列化和序列化的过程,原则上==序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能够正常地被反序列化==。一般我们应该手动指定serialVersionUID的值,比如1L(或者根据类结构生成hash值)。若不指定,反序列化时当前类有所改变(比如增加或者删除了成员变量),那么系统会==根据类结构重新计算当前类的hash值==并赋给serialVersionUID,导致serialVersionUID不一致,于是反序列化失败,程序就会crash。若手动指定了,可以在很大程度上避免序列化失败。比如当版本升级后,可能删除/增加了某个成员变量,程序仍然能够最大限度的恢复数据。(若类结构发生了非常规改变,比如改了类名,修改了成员变量的类型,还是会反序列化失败)

静态成员变量属于类不属于对象,不会参加序列化
用transient标记的成员变量不会参与序列化

2. Parcelable接口

主要基于Parcel实现。

Serializable接口是JAVA中的序列化接口,==使用简单开销大==(序列化和反序列化过程中包含大量的IO操作)。Parcelable接口是Android的序列化方式,==使用麻烦效率高==。
1)在使用内存的时候,Parcelable比Serializable性能高(Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC),所以推荐使用Parcelable
2)Parcelable不能使用在要将数据存储在磁盘上的情况,因为在外界有变化的情况下,Parcelable不能很好的保证数据的持续性。==将对象序列化到存储设备==或==将对象序列化后通过网络传输==,建议使用Serializable接口。

3. Binder

Binder是什么?
a) 实现了IBinder接口的一个类。
b) 跨进程通信的==媒介==。
c) ServiceManager连接==各种Manager和相应的ManagerService的桥梁==。

(1) AIDL接口的创建

在Android开发中,Binder主要用在Service中,包括Messenger(底层其实是AIDL)和AIDL。

//Book.java
package com.example.xiang.myapplication;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {
    private int bookId;
    private String bookName;

    public Book(int bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    /**
     * 几乎所有情况都返回0
     * @return
     */
    @Override
    public int describeContents() {
        return 0;
    }

    /**
     * 实现序列化
     * @param parcel
     * @param i
     */
    @Override
    public void writeToParcel(Parcel parcel, int i) {
        parcel.writeInt(bookId);
        parcel.writeString(bookName);
    }

    /**
     * 实现反序列化
     */
    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };

    public Book(Parcel in) {
        bookId = in.readInt();
        bookName = in.readString();
    }
}

//Book.aidl
package com.example.xiang.myapplication;

parcelable Book;

// IBookManager.aidl
package com.example.xiang.myapplication;

import com.example.xiang.myapplication.Book;//必须导入,否则报错

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
}

Book.aidl是Book类在AIDL中的声明。IBookManager.aidl是定义的一个接口,虽然Book类和IBookManager在同一个包中,但是还是要显示导入Book类。目录结构如下:

AIDL目录结构

(新建Book.aidl时候,直接填Book为名字时候会报错,只有先创建完之后再RENAME才不会报错)

(2) Binder类分析

系统为IBookManager.aidl自动生成的Binder类

package com.example.xiang.myapplication;

public interface IBookManager extends android.os.IInterface {
   
    public static abstract class Stub extends Binder implements IBookManager {
        //Binder的唯一标识
        private static final String DESCRIPTOR = "com.example.xiang.myapplication.IBookManager";
       
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        public static IBookManager asInterface(IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof IBookManager))) {
                return ((IBookManager) iin);
            }
            return new IBookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, Parcel data,
        Parcel reply, int flags) throws RemoteException {
             switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    List<Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements IBookManager {
            private IBinder mRemote;

            Proxy(IBinder remote) {
                mRemote = remote;
            }

            @Override
            public IBinder asBinder() {
                return mRemote;
            }

            public String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public List<Book> getBookList() throws RemoteException {
                Parcel _data = Parcel.obtain();
                Parcel _reply = Parcel.obtain();
                List<Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    //_data写入参数
                    //发起远程调用,当前线程挂起
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(Book book) throws RemoteException {
                Parcel _data = Parcel.obtain();
                Parcel _reply = Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_getBookList = (IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (IBinder.FIRST_CALL_TRANSACTION + 1);
    }

    public List<Book> getBookList() throws RemoteException;

    public void addBook(Book book) throws RemoteException;
}

Binder类中比较重要的方法有2个:==transact方法和onTransact方法==

  • asInterface //将服务端的Binder对象转换为客户端所需的AIDL接口类型的对象,如果C/S位于同一进程,此方法返回就是服务端的==Stub对象==本身,否则返回的就是系统封装后的==Stub.Proxy对象==
  • onTransact//运行在服务端
  • Proxy#getBookList//运行在客户端,内部实现过程如下:首先创建该方法所需要的输入型对象Parcel对象_data,输出型Parcel对象_reply和返回值对象List。然后把该方法的参数信息写入_data(如果有参数);接着调用transact方法发起RPC( 远程过程调用),同时当前线程挂起(因此不能在UI线程中发起远程请求);然后服务端的onTransact方法会被调用(服务端的Binder方法==运行在线程池==,所以需要采用同步方式实现),直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果,最后返回_reply中的数据。
Binder工作机制

AIDL的本质:==系统提供的一个快速实现Binder的工具而已==。

(3) 远程服务端Service的实现
public class BookManagerService extends Service {
    private static final String TAG = "BookManagerService";
    //CopyOnWriteArrayList支持并发读/写
    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mBookList.add(new Book(1, "Android开发艺术探索"));
        mBookList.add(new Book(2, "Android进阶之光"));
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

//注册Service
<service
    android:name="com.example.service.MessengerService"
    android:process=":remote" />

AIDL方法(getBookList和addBook)是运行在Binder线程池中的,所以需要处理线程同步,这里采用CopyOnWriteArrayList来进行自动的线程同步。

(4) 客户端的实现
public class BookManagerActivity extends Activity {
    private static final String TAG = "BookManagerActivity";
    
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try {
                List<Book> list = bookManager.getBookList();
                Log.d(TAG, "查询图书列表:" + list.toString());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Intent intent = new Intent(this, BookManagerService.class);
        bindService(intent, connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        unbindService(connection);
        super.onDestroy();
    }
}

服务端的方法可能需要很久才能执行完毕,上面这样写的目的是为了更好的了解AIDL的实现步骤。
对象是不能跨进程直接传输的,对象跨进程传输的本质是反序列化过程,这也是为什么AIDL中定义的对象必须实现Parcelable接口。

4. Binder的两个重要方法

linkToDeath(DeathRecipient recipient, int flags) //设置Binder的死亡代理。当Binder死亡时,系统会回调DeathRecipient的binderDied()方法,所以需要在此方法中移除之前绑定的binder代理(调用unlinkToDeath)并重新绑定远程服务
unlinkToDeath(DeathRecipient recipient, int flags) //移除死亡代理
isBinderAlive() //判断Binder是否死亡

DeathRecipient是IBinder的一个内部接口,里面只有一个方法binderDied()

5. 补充说明

  • 自定义的Parcelable对象(如上例中的Book类),必须新建一个和它同名的AIDL文件(Book.aidl),并添加相应的内容。
  • 自定义Parcelable对象和AIDL对象必须要显式import进来。
  • 除了基本数据类型的其他类型参数,都需要标上方向:in、out或者inout。
  • AIDL接口中只支持方法,不支持声明静态常量。
  • 建议把所有和AIDL相关的类和文件全部放在同一个包中,好处是,若客户端在另一应用(模块),复制整个包即可。
  • AIDL的包结构在服务端和客户端必须保持一致,否则运行出错。

AIDL文件支持的数据类型
1)基本数据类型(int、long、char、boolean等)
2)String和CharSequence
3)List:只支持ArrayList,里面的每个元素必须被AIDL所支持
4)Map:只支持HashMap,里面的每个元素必须被AIDL所支持,包括key和value
5)Parcelable:所有实现了Parcelable接口的对象
6)AIDL:所有AIDL接口本身也可以在AIDL文件中使用

2.4 BroadcastReceiver

2.4.1 简介

1. 定义

系统级的监听器,默认运行在本进程的主线程

2. 作用

用于监听应用发出的广播消息,并做出响应

最常见的应用场景
a. 不同组件之间通信(包括应用内 / 不同应用之间)
b. Android系统在特定情况下与App之间的消息通信(如当电话呼入时、网络可用时)
c. 多线程通信

3. 实现原理

1)自定义广播接收者BroadcastReceiver,并复写onRecvice()方法;
2)通过Binder机制向AMS(Activity Manager Service)进行注册;
3)广播发送者通过Binder机制向AMS发送广播;
4)AMS查找符合相应条件(IntentFilter/Permission等)的BroadcastReceiver,将广播发送到BroadcastReceiver(一般情况下是Activity)相应的消息循环队列中;
5)消息循环执行拿到此广播,回调BroadcastReceiver中的onReceive()方法。

2.4.2 使用流程

1. 自定义BroadcastReceiver

新建一个BroadcastReceiver子类,并重写onReceive(Context context, Intent intent)方法

注意
a)广播接收器运行在UI线程,因此,onReceive方法不能执行耗时操作,否则将导致ANR
b)执行耗时操作,考虑通过Intent启动一个Service(不应考虑启动新的进程,原因:BroadcastReceiver生命周期短,子线程没有结束,BroadcastReceiver进程结束了,由于新进程没有任何活动的组件,当内存紧张系统优先结束该进程,导致不能执行完成)

c)监听到就会创建实例,触发onReceive()方法,执行完该方法,销毁实例
d)系统通过Intent激发BrocastReceiver组件时,找不到也不会报错(区别Activity)

2. 配置

注册的方式分为两种:静态注册、动态注册

(1) 静态注册

在AndroidManifest.xml里通过标签声明,当此App首次启动时,系统会自动实例化mBroadcastReceiver类,并注册到系统中

<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) 动态注册

调用Context#registerReceiver(BroadcastReceiver receiver, IntentFilter filter) Intent注册到系统中。所有匹配该Intent的BrocastReceiver都有可能被启动。

注意
动态广播最好在Activity的onResume()注册、onPause()注销,不销毁会导致内存泄漏。(重复注册、重复注销也不允许)。不在onCreate() & onDestory() 或 onStart() & onStop()注册、注销是因为:
当系统因为内存不足(优先级更高的应用需要内存)要回收Activity占用的资源时,Activity在执行完onPause()方法后就会被销毁,有些生命周期方法onStop(),onDestory()就不会执行。当再回到此Activity时,是从onCreate方法开始执行。
假设我们将广播的注销放在onStop(),onDestory()方法里的话,有可能在Activity被销毁后还未执行onStop(),onDestory()方法,即广播仍还未注销,从而导致内存泄露。但是,onPause()一定会被执行,从而保证了广播在App死亡前一定会被注销,从而防止内存泄露。(详见Activity生命周期图)

(3) 两种注册方式的区别

3. 广播发送者向AMS发送广播

调用Context#sendBroadcast(Intent intent)sendOrderedBroadcast(Intent intent, String receiverPermission)方法来发送广播

2.4.3 广播的分类

广播的类型主要分为5类:

  • 普通广播(Normal Broadcast)
  • 有序广播(Ordered Broadcast)
  • App应用内广播(Local Broadcast)
  • 系统广播(System Broadcast)
  • 粘性广播(Sticky Broadcast) 已废弃

1. 普通广播

sendBroadcast(Intent intent)发送的广播。
同一时刻被所有的接收者接收到,消息传递的效率高。

2. 有序广播

sendOrderedBroadcast(Intent intent, String receiverPermission) 发送的广播。
按预先声明的优先级依次接收广播,接收者可以将数据传递给下一个接收者,也可以终止广播的传播(abortBroadcast()方法)

设置优先级:
①:<inter-filter.../>元素的priority属性,范围在-1000~1000之间
②:调用InterFilter的setPriority(int priority)

3. 应用内广播

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

可能出现的问题

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

即会出现安全性 & 效率性的问题。

解决方案

使用App应用内广播(Local Broadcast)

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

具体使用1 - 将全局广播设置成局部广播

  • 注册广播时将exported属性设置为false,使得非本App内部发出的此广播不被接收;
  • 在广播发送和接收时,增设相应权限permission,用于权限验证;
  • 发送广播时指定该广播接收器所在的包名,此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。
    通过intent#setPackage(packageName)指定包名

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

Android Support包提供了一个工具,是用来在同一个应用内的不同组件间发送Broadcast的。
使用方式上与全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将参数的context变成了LocalBroadcastManager的单一实例

注:对于LocalBroadcastManager方式发送的应用内广播,只能通过LocalBroadcastManager动态注册,不能静态注册

//步骤1:实例化BroadcastReceiver子类 & IntentFilter mBroadcastReceiver 
mBroadcastReceiver = new mBroadcastReceiver(); 
IntentFilter intentFilter = new IntentFilter(); 
//步骤2:实例化LocalBroadcastManager的实例
localBroadcastManager = LocalBroadcastManager.getInstance(this);
//步骤3:设置接收广播的类型 
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);
//步骤4:调用LocalBroadcastManager单一实例的registerReceiver()方法进行动态注册 
localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);
//取消注册应用内广播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver);
//发送应用内广播
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
localBroadcastManager.sendBroadcast(intent);

2.4.4 补充说明

1. context返回值

对于不同注册方式的广播接收器回调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;

2. 方法

  • onReceive(Context context, Intent intent) 收到广播时回调的方法
  • abortBroadcast()
  • setResultExtras(Bundle extras) 将处理结果放入广播中
  • getResultExtras(boolean makeMap) Bundle 获取上一接收者存入的数据,boolean:true,Bundle为null,创建一个空的
  • getResultData() String
  • setResultData(String data)

3. 常见系统广播

常用的系统广播的Action常量(对应类似android.intent.action.BOOT_COMPLETED字符串)

  • ACTION_BOOT_COMPLETED :系统启动完成
  • ACTION_SHUTDOWN :系统关闭
  • ACTION_POWER_CONNECTED :连接电源
  • ACTION_POWER_DISCONNECTED :与电源断开
  • ACTION_BATTERY_CHANGED :电量低
  • android.provider.Telephony.SMS_REVEIVED :收到短信

2.5 ContentProvider

2.5.1 ContentProvider

ContentProvider为存储和获取数据提供统一的接口,可以在不同的应用程序之间共享数据。如果你不需要在多个应用之间共享数据,你可以直接通过SQLite来操作数据库。

使用ContentProvider,主要有以下几个理由:

  • ContentProvider提供了对底层数据存储方式的抽象。比如下图中,底层使用了SQLite数据库,在用了ContentProvider封装后,即使你把数据库换成MongoDB,也不会对上层数据使用层代码产生影响。


  • Android框架中的一些类需要ContentProvider类型数据。如果你想让你的数据可以使用在如SyncAdapter, Loader, CursorAdapter等类上,那么你就需要为你的数据做一层ContentProvider封装
  • 最主要的原因,是ContentProvider为应用间的数据交互提供了一个安全的环境。它准许你把自己的应用数据根据需求开放给其他应用进行增、删、改、查,而不用担心直接开放数据库权限而带来的安全问题。
onCreate() boolean //当第一次访问ContentProvider,创建完对象之后调用,唯一的生命周期方法
insert(Uri uri, ContentValues values) Uri  //根据Uri插入values对应的数据
delete(Uri uri, String selection, String[] selectionArgs) int    //根据Uri删除select条件所匹配的全部记录
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)    int //根据Uri修改select条件所匹配的全部记录
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) Cursor    //查询,projection:选择的指定的列
getType(Uri uri) String    //返回当前Uri所代表的MIME类型。

2.5.2 ContentResolver

Android为我们提供了ContentResolver来统一管理与不同ContentProvider间的操作,通过URI来区别不同的ContentProvider
ContentResolver 类也提供了与ContentProvider类相对应的四个方法:

insert(Uri uri, ContentValues values) Uri    
delete(Uri uri, String selection, String[] selectionArgs) int   
update(Uri uri, ContentValues values, String selection, String[] selectionArgs)  int   
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) Cursor 
ContentResolver角色

ContentProvider中的URI有固定格式,如下图:

Scheme:URI的命名空间标识
Authority:授权信息,用以区别不同的ContentProvider
Path:表名,用以区分ContentProvider中不同的数据表
Id:Id号,用以区别表中的不同数据

2.5.3 URI

1. URI

URI:通用资源标志符 —— Uniform Resource Identifier
URI类:java.net.URI,是Java提供的一个类,代表了URI的一个实例
Uri类:android.net.Uri,扩展了JAVA中URI的一些功能来适用于Android开发

2. URI的结构

(1) 基本结构
[scheme:]scheme-specific-part[#fragment]  
[scheme:][//authority][path][?query][#fragment]  
[scheme:][//host:port][path][?query][#fragment]
http://www.java2s.com:8080/yourpath/fileName.htm?stove=10&path=32&id=4#harvic

在android中,除了scheme、authority是必须要有的,其他都可以不要,Android中可用的每种资源( 图像、视频片段等)都可以用URI来表示

(2) 代码中提取
Uri.parse(String uriString)    //返回一个Uri对象
getScheme()     //获取Uri中的scheme字符串部分,在这里即,http
getSchemeSpecificPart()   //获取Uri中的scheme-specific-part:部分,这里是://www.java2s.com:8080/yourpath/fileName.htm?
getFragment()   //获取Uri中的Fragment部分,即harvic
getAuthority()   //获取Uri中Authority部分,即www.java2s.com:8080
getPath()   //获取Uri中path部分,即/yourpath/fileName.htm
getQuery()   //获取Uri中的query部分,即stove=10&path=32&id=4
getHost()   //获取Authority中的Host字符串,即www.java2s.com
getPost()   //获取Authority中的Port字符串,即8080
getPathSegments() List<String>    //依次提取出Path的各个部分的字符串,以字符串数组的形式输出
getQueryParameter(String key)   //根据传进去path中某个Key的字符串,返回对应的值
(3) 绝对URI和相对URI

绝对URI:以scheme组件起始的完整格式,如http://fsjohnhuang.cnblogs.com。表示以对标识出现的环境无依赖的方式引用资源
相对URI:不以scheme组件起始的非完整格式,如fsjohnhuang.cnblogs.com。表示以对标识出现的环境有依赖的方式引用资源

(4) 不透明URI和分层URI

不透明URI:scheme-specific-part组件不是以正斜杠(/)起始的,如mailto:fsjohnhuang@xxx.com
分层URI:scheme-specific-part组件是以正斜杠(/)起始的,如http://fsjohnhuang.com

(5) UriMatcher工具类

因为Uri代表了要操作的数据,所以我们很经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher和ContentUris。掌握它们的使用,会便于我们的开发工作。

UriMatcher

UriMatcher本质上是一个文本过滤器,用在contentProvider中帮助我们过滤,分辨出查询者想要查询哪个数据表

UriMatcher(int code)   //code:匹配未成功时返回的标志码,一般置为:UriMatcher.NO_MATCH(值为-1)
//向UriMatcher中注册Uri,其中authority和path组合成一个Uri,code标识该Uri对应的标识码,path中*表示可匹配任意文本,#表示只能匹配数字
addURI(String authority, String path, int code) void    
match(Uri uri) int   //该Uri是否匹配,若匹配返回注册时候的标志码,否则返回-1

matcher.addURI("com.xx","person/id/#", 2) 匹配content://com.xx/person/id/1any://com.xx/person/id/12

ContentUris工具类

其实就是在末尾加上一个id

ContentUris.withAppendedId(Uri contentUri, long id) //为路径加上ID部分
ContentUris.parseId(Uri contentUri) //解析Uri中的ID值

3. URL

URL = URI(scheme组件为部分已知的网络协议) + 与scheme组件标识的网络协议匹配的协议处理器(URL Protocol Handler),是URI子集

  • URI的scheme组件在URL中称为protocol组件,一般http、https、ftp、file、data、jar等。
  • URL Protocol Handler则是一种资源定位器和根据协议建立的约束规则与资源通信的读写机制,用于定位、读写资源。

2.5.4 ContentObserver

1. 基本认知

内容观察者,观察指定的Uri引起数据库变化后通知主线程,然后根据需求做处理。首先在需要监测ContentProvider的应用中进行注册(ContentResolver调用方法的地方),在ContentProvider中要做的就是当数据变化时进行通知。

ContentResolver相关方法:

//传递一个ContentObserver的子类对象进去,会回调其onChange(boolean selfChange)
registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
//取消对注册的那个Uri的观察,这里传进去的就是在registerContentObserver中传递进去的ContentObserver对象。
unregisterContentObserver(ContentObserver observer) 
notifyChange(Uri uri, ContentObserver observer)//在Provider中调用,通知观察uri的观察者,observer可以传null

registerContentObserver方法是注册一个观察者实例,当指定的Uri发生改变时,这个实例会回调实例对象做相应处理。uri:需要观察的Uri,notifyForDescendents:如果为true表示以这个Uri为开头的所有Uri都会被匹配到,如果为false表示精确匹配,即只会匹配这个给定的Uri。
举个例子,假如有这么几个Uri:
content://com.example.studentProvider/student
content://com.example.studentProvider/student/#
content://com.example.studentProvider/student/10
content://com.example.studentProvider/student/teacher
假如观察的Uri为content://com.example.studentProvider/student,当notifyForDescendents为true时则以这个Uri开头的Uri的数据变化时都会被捕捉到,在这里也就是①②③④的Uri的数据的变化都能被捕捉到,当notifyForDescendents为false时则只有①中Uri变化时才能被捕捉到。

2. 实现一个ContentObserver

直接创建一个类继承ContentObserver,实现其onChange(boolean selfChange)方法,当指定的Uri的数据发生变化时会回调这个方法。在此方法中,调用构造函数ContentObserver(Handlerhandler)中传入的 Handler对象发送消息到Handler中,做相应的处理。

2.5.5 数据共享

如何让其他应用也可以访问此应用中的数据?

1. android:sharedUserId

向此应用设置一个android:sharedUserId,然后需要访问此数据的应用也设置同一个sharedUserId,具有同样的sharedUserId的应用间可以共享数据。

不足:
1)不够安全
2)无法做到对不同数据设置不同读写权限的管理

2. android:exported

  • android:exported 设置此provider是否可以被其他应用使用。
  • android:readPermission 该provider的读权限的标识
  • android:writePermission 该provider的写权限标识
  • android:permission 该provider的读写权限标识
  • android:grantUriPermissions 临时权限标识,true时,意味着该provider下所有数据均可被临时使用;false时,则反之。但可以通过设置<grantUriPermission>标签来指定哪些路径可以被临时使用。举个例子,比如你开发了一个邮箱应用,其中含有附件需要第三方应用打开,但第三方应用又没有向你申请该附件的读权限,但如果你设置了此标签,则可以在start第三方应用时,传入FLAG_GRANT_READ_URI_PERMISSION或FLAG_GRANT_WRITE_URI_PERMISSION来让第三方应用临时具有读写该数据的权限。

实例:
1)声明一个权限
<permission android:name="me.pengtao.READ" android:protectionLevel="normal"/>
2)配置

<provider
android:authorities="me.pengtao.contentprovidertest"
android:name=".provider.TestProvider"
android:readPermission="me.pengtao.READ"
android:exported="true">
</provider>

3)其他应用中可以使用以下权限来对TestProvider进行访问<uses-permission android:name="me.pengtao.READ"/>

对不同的数据表有不同的权限操作,要如何做呢?Android为这种场景提供了provider的子标签<path-permission>,path-permission包括了以下几个标签。

<path-permission android:path="string"
android:pathPrefix="string"
android:pathPattern="string"
android:permission="string"
android:readPermission="string"
android:writePermission="string" />

2.5.6 开发ContentProvider步骤

1. 步骤一:暴露数据

  1. 开发一个ContentProvider的子类,默认需要实现上面的6个方法。
    数据访问的方法(如:insert和update)可能被多个线程同时调用,此时必须是线程安全的。其他方法(如: onCreate())只能被应用的主线程调用,它应当避免冗长的操作。ContentResolver(内容解析者)请求被自动转发到合适的内容提供者实例,所以子类不需要担心跨进程调用的细节。

实例代码
实现的ContentProvider子类中:

private final static int TEST = 100;

static UriMatcher buildUriMatcher() {
    final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
    final String authority = TestContract.CONTENT_AUTHORITY;//必须和清单文件中配置的一致
    matcher.addURI(authority, TestContract.PATH_TEST, TEST);
    return matcher;
}

@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    Cursor cursor = null;
    switch (buildUriMatcher().match(uri)) {
        case TEST://匹配不同的表
            cursor = db.query(TestContract.TestEntry.TABLE_NAME, projection, selection, selectionArgs, sortOrder, null, null);
            break;
    }
    return cursor;
}

@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
    final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    Uri returnUri;
    long _id;
    switch ( buildUriMatcher().match(uri)) {
        case TEST:
            _id = db.insert(TestContract.TestEntry.TABLE_NAME, null, values);
            if ( _id > 0 )
                returnUri = TestContract.TestEntry.buildUri(_id);
            else
                throw new android.database.SQLException("Failed to insert row into " + uri);
            break;
        default:
            throw new android.database.SQLException("Unknown uri: " + uri);
    }
    return returnUri;
}

  1. 在清单文件中配置
<provider    
    android:authorities="me.pengtao.contentprovidertest" //一般为包名.含义 
    android:name=".provider.TestProvider"
    android:exprorted="true"/>//是否允许其他应用调用

2. 步骤二:获取ContentResolver对象,并使用

  1. 获取ContentResolver对象。Context的方法:getContentResolver(),因此Activity,Service都能获得该对象
  2. 调用ContentResolver的insert,delete,update,query方法
ContentValues contentValues = new ContentValues();
contentValues.put(TestContract.TestEntry.COLUMN_NAME, "peng");
contentValues.put(TestContract.TestEntry._ID, System.currentTimeMillis());
getContentResolver().insert(TestContract.TestEntry.CONTENT_URI, contentValues);
Cursor cursor = getContentResolver().query(TestContract.TestEntry.CONTENT_URI, null, null, null, null);
try {
    Log.e("ContentProviderTest", "total data number = " + cursor.getCount());
    cursor.moveToFirst();
    Log.e("ContentProviderTest", "total data number = " + cursor.getString(1));
} finally {
    cursor.close();
}

2.6 四大组件的工作过程

2.6.1 四大组件的运行状态

Android的四大组件除了BroadcastReceiver以外,都需要在AndroidManifest文件注册,BroadcastReceiver可以通过代码注册。调用方式上,除了ContentProvider以外的三种组件都需要借助intent。

Activity

是一种展示型组件,用于向用户直接地展示一个界面,并且可以接收用户的输入信息从而进行交互,扮演的是一个前台界面的角色。Activity的启动由intent触发,有隐式和显式两种方式。一个Activity可以有特定的启动模式,finish方法结束Activity运行。

Service

是一种计算型组件,在后台执行一系列计算任务。它==本身还是运行在主线程中的==,所以耗时的逻辑仍需要单独的线程去完成。Activity只有一种状态:启动状态。而service有两种:启动状态和绑定状态。当service处于绑定状态时,外界可以很方便的和service进行通信,而在启动状态中是不可与外界通信的。Service可以停止,需要灵活采用stopServiceunBindService

BroadcastReceiver

是一种消息型组件,用于在不同的组件乃至不同的应用之间传递消
息。

  • 静态注册
    在清单文件中进行注册广播, 这种广播在应用安装时会被系统解析, 此种形式的广播不需要应用启动就可以接收到相应的广播.
  • 动态注册
    需要通过Context.registerReceiver()来实现, 并在不需要的时候通过Context#unRegisterReceiver()来解除广播. 此种形态的广播要应用启动才能注册和接收广播. 在实际开发中通过Context的一系列的send方法来发送广播, 被发送的广播会被系统发送给感兴趣的广播接收者,发送和接收的过程的匹配是通过广播接收者的<intent-filter>来描述的.可以实现低耦合的观察者模式, 观察者和被观察者之间可以没有任何耦合. 但广播不适合来做耗时操作.

ContentProvider

是一种数据共享型组件,用于向其他组件乃至其他应用共享数据。在它内部维持着一份数据集合, 这个数据集合既可以通过数据库来实现, 也可以采用其他任何类型来实现, 例如list或者map. ContentProvider对数据集合的具体实现并没有任何要求.要注意处理好内部的insert, delete, update, query方法的线程同步, 因为这几个方法是==在Binder线程池被调用==.

2.6.2 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 方法

2.6.3 Service的工作过程

  • 启动状态:执行后台计算
  • 绑定状态:用于其他组件与Service交互

两种状态是可以共存的

1. Service的启动过程

image
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的绑定过程

image
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的停止和解除绑定的过程,系统流程都是类似的。

2.6.4 BroadcastReceiver的工作过程

简单回顾一下广播的使用方法, 首先定义广播接收者, 只需要继承BroadcastReceiver并重写onReceive()方法即可. 定义好了广播接收者, 还需要注册广播接收者, 分为两种静态注册或者动态注册. 注册完成之后就可以发送广播了.

1. 广播的注册过程

image
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$InnerReceiver. 这两个嵌套的内部类是所属在LoadedApk中的。
(9) 又调用了LoadedApk$ReceiverDispatcher#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之前处于停止的状态也是可以接收到开机广播的.

更多参考1参考2参考3

2.6.5 ContentProvider的工作机制

ContentProvider是一种内容共享型组件, 它通过Binder向其他组件乃至其他应用提供数据. 当ContentProvider所在的进程启动时, ContentProvider会同时启动并发布到AMS中. 要注意:这个时候ContentProvider的onCreate()方法是先于Application的onCreate()执行的,这一点在四大组件是少有的现象.


image
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调用为例

(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()方法。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,512评论 25 707
  • 1.什么是Activity?问的不太多,说点有深度的 四大组件之一,一般的,一个用户交互界面对应一个activit...
    JoonyLee阅读 5,728评论 2 51
  • 姓名:刘芸 时间真的好快,转眼又第7周了,这周基本在外地。跟舅姆、小姨她们去浙江嵊州西青村看从车上摔下来的姨夫。还...
    云变阅读 200评论 0 0
  • 第一次来郑州找瑞娴,第二次来的时候她已经离开拉这座城,我也以另外的一种身份出现在这里,如今我们都是实习生,面临这...
    糖豆Y阅读 319评论 0 0
  • 最好的时光、最好的我们 ——《最好的我们》感 ✨洛枳爱盛淮南全世界都不知道...
    珺瑜阅读 473评论 2 0