其他分享
首页 > 其他分享> > Android--四大组件 (万字详细解读!!!)

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()重启ActivityActivity在这时可见,当用户按Home键切换到桌面后又切回来或者从后一个Activity切回前一个Activity就会触发这个方法。这里一般不做什么操作。

方法间的区别

Activity的切换

正常次序是(A)onPause→(B)onCreate→(B)onStart→(B)onResume→(A)onStop

可以发现A在B启动完成后才启动的onStop方法,这里有两个原因:

  1. onPause方法会释放掉这个Activity的很多占用的系统资源,为了保证切换的流畅性没有必要再多等一个阶段
  2. 如果用户在切换的途中快速的切回原Activity就会直接调用onResume方法,会快很多。

Activity的四种启动模式

  1. 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的任务栈中转移过。

  1. 指定启动模式的两种方式

第一种:通过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有不同的生命周期。

Service

使用场景

Android的四大组件之一:

生命周期

根据它不同的启动方式有不同的生命周期

调用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中,服务的通信方式有两种:本地服务通信远程服务通信。使用这两种方式进行通信时,必须保证服务是以绑定的形式开启的,否则无法进行通信和数据交换

  1. 本地服务通信

​ 指的是应用程序内部的通信。首先需要创建一个Service类,该类会提供一个onBind()方法,onBind()方法的返回值是一个IBinder对象,IBinder对象会作为参数传递给ServiceConnection类中的onServiceConnected(ComponentName name,IBinder service)方法,这样访问者就可用通过IBinder对象与Service进行通信。如下图所示:

img

​ 从上图可以看出,服务在进行通信时实际使用的是IBinder对象,在ServiceConnection类中得到的IBinder对象,通过这个对象可以获取到服务中自定义的方法,执行具体的操作。

  1. 远程服务通信

​ 在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的一个封装类,一般用于离线下载。将线程任务按顺序在后台执行

进程的优先级

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。

静态注册和动态注册

静态注册

静态注册就是将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);//反注册广播,也就是注销广播接收者,使其不起作用
        }
    }
}

两者区别与细节

注意
动态广播最好在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之间)越高,越先接收广播。优先级高的广播先收到广播,收到广播后可以修改广播的内容,也可以拦截广播不让广播向下传递。比如,学校发了一个通知给老师,然后老师下发给学生,最后学生才知道这个通知,老师可以将这个通知拦截下来不给学生,也可以修改这个通知再发给学生。

所以它有两种发送方式:

本地广播

上面两种发出的广播手机里面所有的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);
    }

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.providerContentProvider 中表名 为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();
        // 关闭游标
    }
}

优点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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()过程

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()方法的工作如下:

(7) 具体看Application对Service的启动过程app.thread.scheduleCreateService():通过 sendMessage(H.CREATE_SERVICE , s) ,这个过程和Activity启动过程类似,同时通过发送消息给Handler H来完成的。
(8) H会接受这个CREATE_SERVICE消息并通过ActivityThread的 handleCreateService() 来完成Service的最终启动。
(9) handleCreateService()完成了以下工作:

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 方法,这个方法完成两件事:

(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() 方法处理:

(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的定义如下:

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中的逻辑. 而这些类的本质关系就是:

(10) 实现Runnable接口的Args中BroadcastReceiver#onReceive()方法被执行了, 也就是说应用已经接收到了广播, 同时onReceive()方法是在广播接收者的主线程中被调用的.

android 3.1开始就增添了两个标记为. 分别是FLAG_INCLUDE_STOPPED_PACKAGES, FLAG_EXCLUDE_STOPPED_PACKAGES. 用来控制广播是否要对处于停止的应用起作用.

在android 3.1开始, 系统就为所有广播默认添加了FLAG_EXCLUDE_STOPPED_PACKAGES标识。 当这两个标记共存的时候以FLAG_INCLUDE_STOPPED_PACKAGES(非默认项为主).

应用处于停止分为两种

更多参考1参考2参考3

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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已经启动成功, 并且其所在的进程的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