Android学习笔记-四大组件-基础知识2

BroadcastReceiver

Android应用可以从Android系统和其他Android应用发送或接收广播消息,类似于 发布 - 订阅 设计模式。当感兴趣的事件发生时,发送这些广播。例如,Android系统在发生各种系统事件时发送广播,例如系统启动或设备开始充电时。例如,应用程序还可以发送自定义广播,以通知其他应用程序他们可能感兴趣的内容(例如,已下载了一些新数据)。

什么是广播

由上述摘自官方文档的描述我们可以知道,广播可以用来在:APP内部、不同APP之间、APP与Android系统之间传递消息。

广播应用场景

通过上述理解我们可以总结一下广播的常用的应用场景

  • 应用内外不同组件的通信
  • 多线程通信
  • 与Android系统在特定情况下的通信

广播使用

广播角色

使用广播来进行消息传递,如果需要传递一个消息,那么最少需要一个消息的发送者,一个消息的接收者。广播进行消息的传递也是使用了广播发送广播接收两个角色

广播接收

为了方便理解先说广播接收,即如何接收其他广播发送来的消息。广播的接收使用BroadcastReceiver类,要实现一个广播接收者需要两个步骤
1、继承BroadcastReceiver并重写onReceive()方法

public class MyBroadcastReceiver extends BroadcastReceiver {
   /**
    * 重写该方法,当接收到注册的相应广播后会执行该方法
    * @param context
    * @param intent
    */
   @Override
   public void onReceive(Context context, Intent intent) {
       Log.e("接收到一个广播");
   }
}

广播接收者示例代码
2、注册广播接收者
广播接收的注册分为两种方式。

  • 静态注册
    静态注册的广播为常驻广播,即不会受到任何组件生命周期的影响。如果需要时刻监听某广播则需要静态注册,如监听手机开机、锁屏等操作。在App首次启动时,系统会自动实例化继承自BroadcastReceiver的类并注册到系统中。
    优点:应用程序关闭后,程序依旧会被系统调用。
    缺点:耗电占用内存。
    使用:在AndroidManifest.xml中通过<receive>标签声明。
<receiver android:name="com.example.MyBroadcastReceiver">
    <intent-filter>
        <!-- 接收的广播类型 -->
        <action android:name="MY_STATIC_BROADCAST" />
        <action android:name="Intent.ACTION_SCREEN_OFF" />
    </intent-filter>
</receiver>1

注意:Android8.0禁止了大多数静态注册
动态注册
不常驻广播,跟随组件生命周期变化,在特定时刻监听广播可以使用动态注册。
使用:在代码中调用Context.registerReceiver()方法进行注册

//动态注册广播
myBroadcastReceiver=new MyBroadcastReceiver(); //实例化广播接收者和IntentFilter
IntentFilter intentFilter=new IntentFilter();
intentFilter.addAction(RECEIVER_ACTION);    //设置接收广播类型
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);   //可以接收多个广播类型
registerReceiver(mDynamicRegisterReceiver,intentFilter);    //注册广播

注意:动态广播最好在onResume()中注册onPause()中注销,因为onPause()方法在app死亡之前一定会被执行。其他则不一定。动态广播注册后必须注销,否则将导致内存泄漏。不允许重复注册或者重复注销。
动态注册代码示例

广播发送

广播发送根据所发送的消息类型以及发送方式分为了以下几种广播类型

  • 普通广播
    开发者自定义的intent广播,广播发送后如果广播接收中注册的intentFilter的action与发送的intent相匹配,则会接收此广播并回调onReceive()。
    使用:
//发送动态广播
Intent intent=new Intent();
intent.setAction(RECEIVER_ACTION);
sendBroadcast(intent);

注意:如果发送广播需要某一权限接收广播则也需要相应权限。

  • 系统广播
    Android内值得多个系统广播,手机上的基本操作都会发出相应的广播。
    如:开关机,充电,电池状态,解锁关闭屏幕等。
    注意:使用系统广播只需要注册广播接收即可,广播由系统在相应的时刻自动发送。
  • 有序广播
    发送出去的广播被广播接受者按照先后顺序接收。
    1、广播接收顺序规则:
    按照Priority属性值从大到小排序。
    Priority属性相同者,动态注册广播优先。
    2、有序广播特点:
    接收广播按顺序接收。
    先接受广播的广播接收者可以对广播进行截断,后续的广播接收者将不会再收到此广播。
    先接受广播的广播接收者可以对广播进行修改,后续的广播接收者收到的是被修改过的广播。
    3、如何发送有序广播:
    sendBroadcast(intent)换成sendOrderdBroadcast(intent);
  • APP应用内广播
    1、全局广播导致的问题
    因为Android中的广播可以跨App直接通信,所以可能会导致其他App不断的发出针对性的广播导致当前App不断接收广播并处理,或者有其他App注册与当前App一致的intent-filter接收广播,获取广播具体信息,导致出现安全性效率问题。使用App应用内广播可以解决上述问题。
    2、应用内广播的含义
    应用内广播的广播接收发送都属于同一个app,与全局广播相比应用内广播安全性效率更高。
    3、应用内广播的使用
    3.1、将全局广播设置成局部广播
    注册广播时将exported属性设置为false(非本APP内部发出的此广播不被接收).
    广播发送和接收时增加permission权限,进行权限验证。
    广播发送时通过intent.setPackage(packageName)指定广播接收器所在的包名,则该广播只会发送到App内与之相匹配的广播接收器中。
    3.2、使用封装好的LocalBrodcastManager
    注册/取消广播接收器和广播发送器时将参数context变为LocalBroadcastManager的单一实例。
    使用LocalBroadcastManager方式发送的应用内广播只能通过LocalBroadcastManager动态注册。
//注册本地广播
mDynamicRegisterReceiver=new DynamicRegisterReceiver();              
mLocalBroadcastManager=LocalBroadcastManager.getInstance(BroadcastReceiverActivity.this);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(RECEIVER_ACTION);              
mLocalBroadcastManager.registerReceiver(mDynamicRegisterReceiver,intentFilter);
//发送本地广播
Intent intent=new Intent();
intent.setAction(RECEIVER_ACTION);
mLocalBroadcastManager.sendBroadcast(intent);
  • 粘性广播
    在Android5.0,API21中已失效。

广播原理

广播接收者通过Binder机制在AMS注册,
广播发送者通过Binder机制向AMS发送广播,
AMS根据广播发送者要求,在已注册列表中,依据IntentFilter/Permission寻找合适的广播接收者
AMS将广播发送到合适的广播接收者相应的消息循环队列中
广播接受者通过消息队列拿到广播,并回调onReceive()方法。
广播接收和注册异步执行。

注意事项

  • 不同注册方式的广播接收器回调onReceive中的context返回值不同
    静态注册:
            返回ReceiverRestrictedContext
    全局广播的动态注册:
            返回Activity Context
    应用内广播LocalBroadcastManager的动态注册:
            返回Application Context
    应用内广播非LocalBroadcastManager的动态注册:
            返回Activity Context
  • Andorid 8.0禁止使用大部分广播的静态注册

上文代码


ContentProvider

内容提供程序管理对结构化数据集的访问。它们封装数据,并提供用于定义数据安全性的机制。 内容提供程序是连接一个进程中的数据与另一个进程中运行的代码的标准界面。

什么是ContentProvider

ContentProvider内容提供者,在Android四大组件的日常使用中出现次数相对较少,但是重要性一点也没有减少,由官方解释我们可以知道它是用来统一管理数据进行数据共享并且可以进行跨进程通信

  • 使用场景
    如:我们1使用ContentResolverapp中得到手机通讯录的信息或者通过相应的uri访问其他应用的ContentProvider提供的信息等。

ContentProvider的使用

上面说道ContentProvider提供统一的数据管理和数据共享、进行跨进程通信并举了相关例子,接下来详细说明一下。

  • 统一数据管理、数据共享
    我们平常进行对数据操作的时候由于数据的不同类型有了许多不同的组织和使用方式,如对文件的存储和数据库的使用,便截然不同,而ContentProvider相当于在这些不同的操作上又进行了一层包装并以Uri的方式提供数据的访问接口,使得我们在对数据操作时,可以忽略底层的差异。统一使用ContentResolver调用上层接口进行数据操作
  • 跨进程通信
    我们知道一个APP就是一个进程,而不同进程之间通信是比较困难的,但是ContentProvider就提供了这种在不同APP间进行通信的功能,即跨进程通信,也是ContentProvider被使用最多的方面。ContentProvider进行跨进程通信的底层原理使用了Binder

接下来说一下具体使用

URI、UriMatcher、MIME

  • URI(Uniform Resource Identifier):统一资源标识符
    可以用来唯一标识ContentProvider和其中的数据
  • 格式
    一个uri由以下部分组成:
主题名 授权信息 表名 记录
content com.example.provider User 1

如:content://com.example.provider/User/1
uri中可以使用通配符*#
*:任意长度的有效字符串
#:任意长度的数字字符串

  • UriMatcher
    UriMatcher简单说来是一个对uri进行管理的类。它主要的方法有:
    1、UriMatcher.addURI
    ContentProvider中注册uri
//初始化
mUriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
//在ContentProvider中注册uri
mUriMatcher.addURI(AUTHORITY,"user",user_code);

2、UriMatcher.match(uri)
根据uri返回匹配该uri的自定义代码

  • MIME

用于指定某个扩展名的文件用某种应用程序打开。
类型+子类型组成
1、必须以vnd开头
2、内容uri以路径结尾即表名结尾后接android.cursor.dir/,如果以id结尾即数据库表id结尾则后接android.cursor.item/
3、最后接上vnd.<authority>.<path>,例:
content://com.chenlei.content_provider.user
对应MIME类型为:vnd.android.cursor.dir/vnd.com.chenlei.content_provider.user
content://com.chenlei.content_provider.user/1
对应MIME类型为:vnd.android.cursor.item/vnd.com.chenlei.content_provider.user

ContentProvider

ContentProvider即是内容提供器,它可以用来统一管理和组织应用数据,向其他应用程序提供接口 便于其他应用程序对本应用允许的数据进行操作,以进行跨进程通讯。
简单来说就是ContentProvider向其他APP提供,自己APP内数据,的访问接口
主要方法由以下几个

  • onCreate
    初始化ContentProvider的使用使用,如果对数据库进行操作则通常完成数据库的创建和升级操作
  • insert、delete、update、query
    对数据进行操作的核心方法增删改查,方法中对应方法名来对数据进行相应的增删改查操作。
  • getType
    用来得到数据类型,即返回当前uri 所代表数据的MIME类型
    /**
     * 初始化ContentProvider的使用使用
     * 通常完成数据库的创建和升级操作
     * @return true初始化成功,false初始化失败
     */
    @Override
    public boolean onCreate() {
        mContext=getContext();
        mDBHelper=new DBHelper(getContext());
        sqLiteDatabase=mDBHelper.getWritableDatabase();
        //初始化数据库表
        sqLiteDatabase.execSQL("delete from user");
        sqLiteDatabase.execSQL("insert into user values(1,'Carson');");
        sqLiteDatabase.execSQL("insert into user values(2,'Kobe');");
        sqLiteDatabase.execSQL("delete from job");
        sqLiteDatabase.execSQL("insert into job values(1,'Android');");
        sqLiteDatabase.execSQL("insert into job values(2,'iOS');");

        return true;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
        String tableName = getTableName(uri);
        sqLiteDatabase=mDBHelper.getReadableDatabase();
        Cursor cursor=null;

        cursor=sqLiteDatabase.query(tableName,strings,s,strings1,null,null,s1,null);

        return cursor;
    }

    /**
     * 根据传入的内容来返回相应的MIME类型
     * @param uri
     * @return
     */
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        String mime = null;
        switch (mUriMatcher.match(uri)){
            case user_code:
                mime="vnd.android.cursor.dir/vnd.com.chenlei.content_provider.user";
                break;
            case job_code:
                mime="vnd.android.cursor.dir/vnd.com.chenlei.content_provider.job";
                break;
        }
        return mime;
    }

    /**
     * 插入数据
     * @param uri 数据的资源路径
     * @param contentValues 要插入的数据内容
     * @return 返回一个用于记录新纪录的uri
     */
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
        String tableName=getTableName(uri);
        //插入数据
        sqLiteDatabase.insert(tableName,null,contentValues);

        mContext.getContentResolver().notifyChange(uri,null);

        return uri;
    }

示例代码

AndoridManifest.xml中注册

当代码完成后还需要在AndoridManifest.xml中注册才能被访问使用。

<!--声明外界进程可访问该Provider的权限(读 & 写)-->
<!--android:permission="com.chenlei.PROVIDER"-->
<!--权限可细分为读 & 写的权限-->
<!--外界需要声明同样的读 & 写的权限才可进行相应操作,否则会报错-->
<!--android:readPermisson = "com.chenlei.Read"-->
<!--android:writePermisson = "com.chenlei.Write"-->
<!--设置此provider是否可以被其他进程使用-->
<!--android:exported="true"-->
<provider
    android:authorities="com.chenlei.content_provider" 
    android:name="com.example.androidprimarycodedemo.four_components.about_content_provider.CreateLocalContentProvider"
    android:permission="com.chenlei.PROVIDER"
    android:exported="true"
/>

ContentResolver

由于如果应用需要对多个ContentProvider进行操作,需要了解各个不同ContentProvider的实现等再进行操作。所以API中提供了ContentResolver类统一管理对ContentProvider的操作。
简单来说就是通过ContentResolver,来对其他APP内的数据,进行操作
ContentResolver使用示例。

    //user表的资源路径
    private Uri uriUser=Uri.parse("content://com.chenlei.content_provider/user");
    /**
     * ContentResolver统一管理ContentProvider间的操作
     * 由于如果需要使用多个ContentProvider进行操作,需要了解各个不同ContentProvider的实现等再进行操作
     * 使用ContentResolver同一管理方便操作和使用。
     */
    private ContentResolver contentResolver=null;
    /**
     * 向ContentProvider中插入数据
     */
    private void insertData(){
        contentResolver=getContentResolver();
        //插入表中的数据
        ContentValues contentValues=new ContentValues();
        contentValues.put("_id", 3);
        contentValues.put("name", "Iverson");

        contentResolver.insert(uriUser,contentValues);
        Logger.e("插入完成");
    }

    /**
     * 从ContentProvider中查询数据
     */
    private void queryData(){
        contentResolver=getContentResolver();
        Cursor cursor=contentResolver.query(uriUser,new String[]{"_id","name"}, null, null, null);
        while (cursor.moveToNext()){
            Logger.e(cursor.getInt(0) +","+ cursor.getString(1));
        }
        cursor.close();
    }

示例代码

  • AndoridManifest.xml中注册权限
    <!--应用跨进程通讯的权限-->
    <uses-permission android:name="com.chenlei.PROVIDER"/>

上文代码

ContentProvider部分知识点参考自https://blog.csdn.net/carson_ho/article/details/76101093

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