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使用ContentResolver
在app
中得到手机通讯录的信息或者通过相应的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