Android四大组件 -ContentProvider 和 BroadcastReceiver

 

一、内容提供者ContentProvider 

1. ContentProvider 简介

ContentProvider (内容提供者) 可以让我们的 APP 访问别的应用,或者说一些 ContentProvider 暴露给我们的一些数据,比如手机联系人,短信等,如果我们想对这些数据进行读取或者修改,就需要用到 ContentProvider

当然了, ContentProvider 也允许我们将自己应用里的数据暴露出来,给其它的应用进行读取或操作,我们可以选择要暴露的数据,可以避免了我们隐私数据的的泄露

 

当我们想允许自己 APP 的数据开放给别的 APP 进行读取操作,我们就需要让 APP 实现 ContentProvider 类,同时注册一个 URI,然后其它的 APP 就可以通过 ContentResolver 根据 URI 就可以访问我们 APP 的数据

数据可以是任何内容,比如数据库,一个文件,一个 XML 或者SharedPreference ,其它

 

Android已经为常见的一些数据提供了默认的 ContentProvider。

数据通过唯一的 URI 标识来源。ContentProvider 将数据看作表,查询 / 操作数据的时候,通过类似数据库操作的 insert / delete / query / update 方法来实现增删查改操作。

作为应用间数据交换 / 共享接口,当然需要有一个“桥梁”来连接数据提供方和使用方。数据提供方提供数据,使用方使用 content://authorities/path 类似的 URI 来访问数据。 

运行流程:

ContentProvider 和 sql 区别

Uri通用资源标志符(Universal Resource Identifier, 简称 "URI"): 

从ContentProvider的数据操作方法可以看出都依赖于Uri,对于Uri有其固定的数据格式

字段含义对应项
前缀默认的固定开头格式content://
授权唯一标识providercom.example.app.provider
路径数据类别以及数据项/table1/1

一个标准的内容 URI 写法是这样的:

content://com.example.app.provider/table1/1

//这就表示调用方期望访问的是 com.example.app 这个应用的 table1 表中 id 为 1 的数据。

Authority:授权信息,用以区别不同的ContentProvider;
Path:表名,用以区分ContentProvider中不同的数据表;
Id:Id号,用以区别表中的不同数据;

2. contentProvider类的方法

ContentProvider作为Android四大组件之一,并没有Activity那样复杂的生命周期,只有简单地onCreate过程。ContentProvider是一个抽象类,当实现自己的ContentProvider类,只需继承于ContentProvider,并且实现以下六个abstract方法即可:

  • insert(Uri, ContentValues):插入新数据;
  • delete(Uri, String, String[]):删除已有数据;
  • update(Uri, ContentValues, String, String[]):更新数据;
  • query(Uri, String[], String, String[], String):查询数据;
  • onCreate():执行初始化工作;
  • getType(Uri):获取数据MIME类型。

 3. ContentResolver

其他app或者进程想要操作ContentProvider,则需要先获取其相应的ContentResolver,再利用ContentResolver类来完成对数据的增删改查操作,下面列举一个查询操作,查询得到的是一个Cursor结果集,再通过操作该Cursor便可获取想要查询的结果。

ContentResolver cr = getContentResolver();  //获取ContentResolver
Uri uri = Uri.parse("content://com.gityuan.articles/android/3");
Cursor cursor = cr.query(uri, null, null, null, null);  //执行查询操作
...
cursor.close(); //关闭

 4.ContentUris类

用于操作uri,核心方法有两个:withAppendedId() &parseId()

// 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

5. UriMatcher 类

根据 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的表
    }   
}

 

6. 不同应用/进程间的数据共享

首先我们先介绍下,共享数据所涉及到的几个重要标签:


android:exported 设置此provider是否可以被其他应用使用。
android:readPermission 该provider的读权限的标识
android:writePermission 该provider的写权限标识
android:permission  provider读写权限标识
android:grantUriPermissions 临时权限标识,true时,意味着该provider下所有数据均可被临时使用;false时,则反之,但可以通过设置<grant-uri-permission>标签来指定哪些路径可以被临时使用。

这么说可能还是不容易理解,我们举个例子,比如你开发了一个邮箱应用,其中含有附件需要第三方应用打开,但第三方应用又没有向你申请该附件的读权限,但如果你设置了此标签,则可以在start第三方应用时,传入FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION来让第三方应用临时具有读写该数据的权限。

知道了这些标签用法后,让我们改写下AndroidManifest.xml,让ContentProvider可以被其他应用查询。

注意读写权限的声明:

只需要显示authorities的前两项: com.mike

<provider 
               android:name="MyProvider"
               android:authorities="com.mike.myprovider"

               // 声明外界进程可访问该Provider的权限(读 & 写)
               android:permission="com.mike.PROVIDER"             
               
               // 权限可细分为读 & 写的权限
               // 外界需要声明同样的读 & 写的权限才可进行相应操作,否则会报错
               // android:readPermisson = "com.mike.Read"
               // android:writePermisson = "com.mike.Write"

               // 设置此provider是否可以被其他进程使用
               android:exported="true"
                
  />

// 声明本应用 可允许通信的权限
    <permission android:name="com.mike.Read" android:protectionLevel="normal"/>
    // 细分读 & 写权限如下,但本Demo直接采用全权限
    // <permission android:name="com.mike.Write" android:protectionLevel="normal"/>
    // <permission android:name="com.mike.PROVIDER" android:protectionLevel="normal"/>

7. 对数据进行增删改查

继承于CotentProvider,创建自己的内容提供者

public class MyProvider extends ContentProvider {

    private Context mContext;
    DBHelper mDbHelper = null;
    SQLiteDatabase db = null;

    public static final String AUTOHORITY = "com.mike.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;
        }
    }

需要赋予权限:

    // 声明本应用可允许通信的权限(全权限)
    <uses-permission android:name="com.mike.PROVIDER"/>

    // 细分读 & 写权限如下,但本Demo直接采用全权限
    // <uses-permission android:name="com.mike.Read"/>
    //  <uses-permission android:name="com.mike.Write"/>
    
// 注:声明的权限必须与进程1中设置的权限对应

接着,对数据进行操作:

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://com.mike.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();
        // 关闭游标
    }
}


 

拿最常用的联系人为例

  1. 系统源码文件下: all-src.rar -> TeleponeProvider -> AndroidManifest.xml 查找对应 API

  2. 打开模拟器的 file exploer/data/data/com.android.providers.contacts/databases/contact2.db

  3. 导出后使用 SQLite 图形工具查看,三个核心的表: raw_contact 表 , data 表 , mimetypes 表

动态获取联系人的信息:

1. 在AndroidManifest.xml 添加读取联系人的信息 权限

<uses-permission android:name="android.permission.READ_CONTACTS"/>

2.  具体的实现类 

package com.example.newdemo.contentprovider;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.util.Log;
import android.widget.TextView;

import com.example.newdemo.R;
import com.example.newdemo.util.ToastManager;

import java.util.ArrayList;
import java.util.List;

public class SystemProviderActivity extends AppCompatActivity {

    private TextView provider_text;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_system_provider);
        provider_text = findViewById(R.id.provider_text);

        // 动态获取权限

        // 如果 电话权限没有获取, 则先获取电话权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
        }else{
            readContacts();
        }


    }

    // 获取权限申请返回
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch(requestCode){
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                    readContacts();
                }else{
                    ToastManager.showMsg(this, "获取电话权限失败");
                }
                break;
            default:
                break;
        }
    }

    private void readContacts(){
        List contactList;
        Cursor cursor;

        contactList = new ArrayList();
        cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                null, null, null, null);
        if (cursor != null){
            while (cursor.moveToNext()){
                String name = cursor.getString(
                        cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                String number = cursor.getString(
                        cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                contactList.add(name + "\n" + number);
                String msg = "姓名: " + name + "\n" + "电话: "+ number;
                Log.d("SystemActivity", msg);
                provider_text.setText(msg);
            }
            cursor.close();
        }
    }

}

二、BroadcastReceiver 广播机制

1. 什么是广播

Android 系统自己在很多时候都会发送广播,比如电量低或者充足,刚启动完,插入耳机,输入法改变等, 发生这些时间,系统都会发送广播,这个叫系统广播

每个 APP 都会收到,如果想让一个应用在接收到广播的时候做一些操作,比如:系统开机后,偷偷后台跑服务~哈哈,这个时候只需要为应用注册一个用于监视开机的 BroadcastReceiver ,当接收到开机广播就做写偷偷摸摸的勾当

应用场景:

  • 同一应用具有多个进程的不同组件之间的消息通信
  • 不同应用间的组件之间的消息通信
  • 与Android系统在特定情况下的通信
    • 如:系统开机,网络变化等

实现原理:

  • Android中的广播使用了设计模式中的观察者模式:基于消息的发布/订阅事件模型。因此,Android将广播的发送者和接收者解耦,使得系统方便集成,更易扩展。

 

模型中有3个角色

  • 消息订阅者(广播接收者)
  • 消息发布者(广播发布者)
  • 消息中心(AMS,即Activity Manager Service)

     

原理描述:

  • 广播接收者通过Binder机制在AMS注册
  • 广播发送者通过Binder机制向AMS发送广播
  • AMS根据广播发送者要求,在已注册列表中,寻找合适的广播接收者
    寻找依据:IntentFilter/Permission
  • AMS将广播发送到合适的广播接收者响应的消息循环队列中;
  • 广播接收者通过消息循环拿到此广播,并回调onReceiver()
    特别注意:广播发送者和广播接收者的执行时异步的,发出去的广播不会关心有无接收者,也不确定接收者到底是何时才能接收到;

 

2. 四种广播类型

  1. 标准广播

    完全异步执行的广播,发出广播后,所有的广播接收器几乎会在同一时刻收到这条广播通知

  2. 有序广播

    同步执行的一种广播,发出广播后,同一时间只有一个广播接收者能收到,当这个广播接收者的逻辑执行完后,才会传递到下一个广播接收者

    前一个广播接收者还可以截断广播,让广播不会继续传递

  3. 系统广播

Android系统中内置了多个系统广播,只要涉及到手机的基本操作,基本上都会发出相应的系统广播。如:开启启动,网络状态改变,拍照,屏幕关闭与开启,点亮不足等等。每个系统广播都具有特定的intent-filter,其中主要包括具体的action,系统广播发出后,将被相应的BroadcastReceiver接收。系统广播在系统内部当特定事件发生时,有系统自动发出。

  1. 应用内广播

Android中的广播可以跨进程甚至跨App直接通信,且注册是exported对于有intent-filter的情况下默认值是true,由此将可能出现安全隐患如下:

  1. 其他App可能会针对性的发出与当前App intent-filter相匹配的广播,由此导致当前App不断接收到广播并处理;

  2. 其他App可以注册与当前App一致的intent-filter用于接收广播,获取广播具体信息。

无论哪种情形,这些安全隐患都确实是存在的。由此,最常见的增加安全性的方案是:

  1. 对于同一App内部发送和接收广播,将exported属性人为设置成false,使得非本App内部发出的此广播不被接收;

  2. 在广播发送和接收时,都增加上相应的permission,用于权限验证;

  3. 发送广播时,指定特定广播接收器所在的包名,具体是通过intent.setPackage(packageName)指定在,这样此广播将只会发送到此包中的App内与之相匹配的有效广播接收器中。

App应用内广播可以理解成一种局部广播的形式,广播的发送者和接收者都同属于一个App。实际的业务需求中,App应用内广播确实可能需要用到。同时,之所以使用应用内广播时,而不是使用全局广播的形式,更多的考虑到的是Android广播机制中的安全性问题。

相比于全局广播,App应用内广播优势体现在:

  1. 安全性更高;

  2. 更加高效。

为此,Android v4兼容包中给出了封装好的LocalBroadcastManager类,用于统一处理App应用内的广播问题,使用方式上与通常的全局广播几乎相同,只是注册/取消注册广播接收器和发送广播时将主调context变成了LocalBroadcastManager的单一实例。

3. 接受广播的方式

接收广播之前,先要为我们的 APP 注册广播接收器

Android 提供了两种注册广播的方式 动态 与 静态

动态注册:

就是在 Java 代码中指定 Intent-filter,然后添加不同的 Action

想监听什么广播就写什么 Action

动态注册需要应用程序启动后才能接收广播信息

注意: 动态注册的广播一定要调用 unregisterReceive() 取消广播注册 

例子:  网络状态广播 ,一开始时没有联网,打开网络时,Toast 通知

 

1. MsBroadcastReceiver.java, onReceive() 方法中完成广播要处理的事务

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class MsBroadcastReceiver extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context,"网络状态发生改变~",Toast.LENGTH_SHORT).show();
    }
}

2. MainActivity.java 动态注册广播

import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    MsBroadcastReceiver mReceiver;

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

        mReceiver = new MsBroadcastReceiver();

        IntentFilter itFilter = new IntentFilter();

        // 设置接受关闭的类型
        itFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        registerReceiver(mReceiver, itFilter); // 动态注册
    }

    //别忘了将广播取消掉

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mReceiver); // 销毁广播
    }
}

假如我们需要程序没有启动也能接收广播的话,那么就需要注册静态广播 

2. 静态注册

在 AndroidManifest.xml 设置 <receiver> 就可以让 APP 在未启动的情况下接收到广播

1.MsBootCompleteReceiver,重写 onReceive 完成事务处理:

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class MsBootCompleteReceiver extends BroadcastReceiver {

    private final String ACTION_BOOT = "android.intent.action.BOOT_COMPLETED";

    @Override
    public void onReceive(Context context, Intent intent) {
    if (ACTION_BOOT.equals(intent.getAction()))
        Toast.makeText(context, "开机完毕~", Toast.LENGTH_LONG).show();
    }
}

2. 添加开机广播的 intent-filter 和注册权限

<receiver android:name=".MsBootCompleteReceiver">
    <intent-filter>
        <action android:name = "android.intent.action.BOOT_COMPLETED" />
    </intent-filter>
</receiver>

注册权限

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
  1. 然后重启下手机会发现过了一会儿,就会弹出开机完毕这个 Toast 的了

    Android 4.3 以上的版本,是允许将程序安装到 SD 卡上的,假如你的程序是安装在 SD 上的,就会收不到开机广播

注意事项

广播更多的时候扮演的是一个打开其它组件的角色,比如启动 Service, Notification 提示 , Activity 等

不要在广播里添加过多逻辑或者进行任何耗时操作

因为在广播中是不允许开辟线程的,当 onReceiver() 方法运行较长时间 ( 超过 10 秒 ) 还没有结束的话,那么程序会报错

4. 发送广播

一个发送广播的流程:

自定义一个 BroadcastReceiver ,重写 onReceive() 方法,然后注册下广播:

  1. 发送标准广播 sendBroadcast(intent);
  2. 发送有序广播 `sendOrderedBroadcast(intent,null)

    可以在 AndroidManifest.xml 中的 <Intent-filter> 中通过 android:priority="100" 设置优先级,值越大优先级越高,越先收到广播,优先级可选范围 -1000 - 1000

  3. 可以调用 abortBroadcast() 截断广播,让其不再继续传递

一个自己向自己表白的例子:

a. 新建一个广播接收者 MsBroadcastReceiver.java

package cn.twle.android.sendbroadcast;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class MsBroadcastReceiver extends BroadcastReceiver {

    private final String ACTION_BOOT = "cn.twle.android.sendbroadcast.MS_BROADCAST";

    @Override
    public void onReceive(Context context, Intent intent) {
        if( ACTION_BOOT.equals(intent.getAction()))
            Toast.makeText(context, "收到告白啦~",Toast.LENGTH_LONG).show();
    }
}

b.修改 AndroidManifest.xml 注册 MsBroadcastReceiver ,写上 Intent-filter

<receiver android:name=".MsBroadcastReceiver">
    <intent-filter>
        <action android:name="cn.twle.android.sendbroadcast.MS_BROADCAST"/>
    </intent-filter>
</receiver>

c.添加一个按钮 

<?xml version="1.0" encoding="utf-8" ?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal" 
    android:orientation="vertical" >

    <Button 
        android:text="发送告白"
        android:id="@+id/btn_send"
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" />

</LinearLayout>

d. 使用 sendBroadcast 发送广播

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button btn_send = (Button) findViewById(R.id.btn_send);
        btn_send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                sendBroadcast(new Intent("cn.twle.android.sendbroadcast.MS_BROADCAST"));
            }
        });
    }
}

5. 本地广播

前面说的广播都是 全局广播,意味着我们 APP 发出的广播,其它 APP 都会接收到, 或者其它 APP 发送的广播,我们的 APP 也会接收到,这样容易引起一些安全性的问题

Android 提供了 本地广播 的机制,使用该机制发出的广播只会在 APP 内部传播,而且 广播接收者也只能收到本应用发出的广播

例子链接

使用流程

使用 LocalBroadcastManager 来管理广播

  1. 调用 LocalBroadcastManager.getInstance() 获得实例 mLBM
  2. 调用 mLBM.registerReceiver() 注册广播
  3. 调用 mLBM.sendBroadcase() 发送广播
  4. 调用 mLBM.unregisterReceiver() 取消注册广播

 

6. 系统广播

系统广播常量说明
Intent.ACTION_AIRPLANE_MODE_CHANGED关闭或打开飞行模式时的广播
Intent.ACTION_HEADSET_PLUG在耳机口上插入耳机时发出的广播
Intent.ACTION_POWER_CONNECTED插上外部电源时发出的广播
Intent.ACTION_POWER_DISCONNECTED已断开外部电源连接时发出的广播
Intent.ACTION_REBOOT重启设备时的广播
Intent.ACTION_SCREEN_OFF屏幕被关闭之后的广播
Intent.ACTION_SCREEN_ON屏幕被打开之后的广播
Intent.ACTION_UMS_DISCONNECTED设备已从USB大容量储存状态转为正常状态时发出的广播

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值