BroadcastReceiver
、ContentProvider
分两篇来总结,但的确,这两大组件的使用不像 Activity
、Service
那么频繁,因此仍是决定一次性搞定。1.5 W
字的文章就诞生了。能够说这周几乎全部时间都花在这上面,本身看了几遍感受已经极为全面了。最后,但愿你们都能有所收获,欢迎食用!java
仓库内容与博客同步更新。因为我在 稀土掘金
简书
CSDN
博客园
等站点,都有新内容发布。因此你们能够直接关注该仓库,以避免错过精彩内容!android
BroadcastReceiver
,顾名思义就是“广播接收者”的意思,它是Android四大基本组件之一。app
发送的广播android
系统的 Binder
机制.SendOrderedBroadcast()
方法来发送广播,同时也可调用 abortBroadcast()
方法拦截该广播。可经过 <intent-filter>
标签中设置 android:property
属性来设置优先级,未设置时按照注册的顺序接收广播。setResultData
方法将数据传给下一个接收器。getStringExtra
函数获取广播的原始数据,经过 getResultData
方法取得上个广播接收器本身添加的数据,并可用 abortBroadcast
方法丢弃该广播,使该广播再也不被别的接收器接收到。A > B > C
,Context.sendOrderedBroadcast ( intent , receiverPermission , resultReceiver , scheduler , initialCode , initialData , initialExtras )
时咱们能够指定 resultReceiver
为最终广播接收者.onReceive
会执行两次sd
卡挂载, 低电量, 外拨电话, 锁屏等对前一部分 “ 请描述一下
BroadcastReceiver
” 进行展开补充github
APP
内部的消息通讯。不一样 APP
之间的消息通讯。算法
Android
系统在特定状况下与 APP 之间的消息通讯。sql
广播使用了观察者模式,基于消息的发布 / 订阅事件模型。广播将广播的发送者和接受者极大程度上解耦,使得系统可以方便集成,更易扩展。数据库
BroadcastReceiver 本质是一个全局监听器,用于监听系统全局的广播消息,方便实现系统中不一样组件间的通讯。设计模式
自定义广播接收器须要继承基类 BroadcastReceiver
,并实现抽象方法 onReceive ( context, intent )
。默认状况下,广播接收器也是运行在主线程,所以 onReceiver()
中不能执行太耗时的操做( 不超过 10s
),不然将会产生 ANR
问题。onReceiver()
方法中涉及与其余组件之间的交互时,可使用发送 Notification
、启动 Service
等方式,最好不要启动 Activity
。数组
Android
系统内置了多个系统广播,只要涉及手机的基本操做,基本上都会发出相应的系统广播,如开机启动、网络状态改变、拍照、屏幕关闭与开启、电量不足等。在系统内部当特定时间发生时,系统广播由系统自动发出。缓存
常见系统广播 Intent
中的 Action
为以下值:
android.provider.Telephony.SMS_RECEIVED
ACTION_BATIERY_LOW
ACTION_BATTERY_CHANGED
ACTION_POWER_CO
Android 7.0
开始,系统不会再发送广播 ACTION_NEW_PICTURE
和 ACTION_NEW_VIDEO
,对于广播 CONNECTIVITY_ACTION
必须在代码中使用 registerReceiver
方法注册接收器,在 AndroidManifest
文件中声明接收器不起做用。Android 8.0
开始,对于大多数隐式广播,不能在 AndroidManifest
文件中声明接收器。APP
APP
不会受到局部广播,不用担忧数据泄露的问题。APP
不可能向当前的 APP
发送局部广播,不用担忧有安全漏洞被其余 APP
利用。Android v4
包中提供了 LocalBroadcastManager
类,用于统一处理 APP 局部广播,使用方式与全局广播几乎相同,只是调用注册 / 取消注册广播接收器和发送广播偶读方法时,须要经过 LocalBroadcastManager
类的 getInstance()
方法获取的实例调用。在 AndroidManifest.xml
文件中配置。
<receiver android:name=".MyReceiver" android:exported="true"> <intent-filter> <!-- 指定该 BroadcastReceiver 所响应的 Intent 的 Action --> <action android:name="android.intent.action.INPUT_METHOD_CHANGED" <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver>
android: exported
BroadcastReceiver
可否接受其余 APP
发出的广播 ,当设为 false
时,只能接受同一应用的的组件或具备相同 user ID
的应用发送的消息。这个属性的默认值是由 BroadcastReceiver
中有无 Intent-filter
决定的,若是有 Intent-filter
,默认值为 true
,不然为 false
。android: permission
BroadcastReceiver
所接受;若是没有设置,这个值赋予整个应用所申请的权限。Context
的 registerReceiver ( BroadcastReceiver receiver , IntentFilter filter )
方法指定。app
还在运行,那么会一直收到广播消息app
里: 自定义一个类继承 BroadcastReceiver
而后要求重写 onReveiver
方法public class MyBroadCastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.d("MyBroadCastReceiver", "收到信息,内容是 : " + intent.getStringExtra("info") + ""); } }
Action
, 就那么简单完成接收准备工做<receiver android:name=".MyBroadCastReceiver"> <intent-filter> <action android:name="myBroadcast.action.call"/> </intent-filter> </receiver>
Activity
或者 Service
销毁了那么就会接收不到广播.app
里的 MainActivity
添加一个注册按钮 , 用来注册广播接收者Action
//onCreate建立广播接收者对象 mReceiver = new MyBroadCastReceiver(); //注册按钮 public void click(View view) { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("myBroadcast.action.call"); registerReceiver(mReceiver, intentFilter); }
@Override protected void onDestroy() { unregisterReceiver(mReceiver); super.onDestroy(); }
public void click(View view) { Intent intent = new Intent(); intent.putExtra("info", "消息内容"); intent.setAction("myBroadcast.action.call"); sendBroadcast(intent); }
app
以后:app
直接发广播就收到了app
先代码注册,而后另外一个 app
直接发广播便可.-Android
中的广播使用了设计模式中的观察者模式:基于消息的发布 / 订阅事件模型。3
个角色:AMS
,即 Activity Manager Service
)广播接收者经过 Binder
机制在 AMS
( Activity Manager Service
) 注册;
广播发送者经过 Binder
机制向 AMS
发送广播;
AMS
根据广播发送者要求,在已注册列表中,寻找合适的 BroadcastReceiver
( 寻找依据:IntentFilter / Permission
);
AMS
将广播发送到 BroadcastReceiver
相应的消息循环队列中;
广播接收者经过消息循环拿到此广播,并回调 onReceive()
方法。
须要注意的是:广播的发送和接受是异步的,发送者不会关心有无接收者或者什么时候收到。
LocalBroadcastManager
来对广播进行管理,并提供了发送广播和注册广播接收器的方法。BroadcastReceiver
子类,并重写 onReceive ( Context context, Intetn intent )
方法便可。当其余组件经过 sendBroadcast()
、sendStickyBroadcast()
、sendOrderBroadcast()
方法发送广播消息时,若是该 BroadcastReceiver
也对该消息“感兴趣”,BroadcastReceiver
的 onReceive ( Context context, Intetn intent )
方法将会被触发。
使用步骤:
Activity
时,须要为 Intent
加入 FLAG_ACTIVITY_NEW_TASK
标记,不然会报错,由于须要一个栈来存放新打开的 Activity
。Alertdialog
时,须要设置对话框的类型为 TYPE_SYSTEM_ALERT
,不然没法弹出。onReceiver()
方法中添加过多的逻辑或者进行任何的耗时操做,由于在广播接收器中是不容许开启线程的,当 onReceiver()
方法运行了较长时间而没有结束时,程序就会报错。Android
引入了 StickyBroadcast
,在广播发送结束后会保存刚刚发送的广播( Intent
),这样当接收者注册完 Receiver
后就能够继续使用刚才的广播。若是在接收者注册完成前发送了多条相同 Action
的粘性广播,注册完成后只会收到一条该 Action
的广播,而且消息内容是最后一次广播内容。
Context
的 sendStickyBroadcast ( Intent )
接口发送,须要添加权限uses-permission android:name=”android.permission.BROADCAST_STICKY”
Context
的 removeStickyBroadcast ( Intent intent )
接口移除缓存的粘性广播使用它发送的广播将只在自身APP内传播,所以你没必要担忧泄漏隐私数据;
其余 APP
没法对你的 APP
发送该广播,由于你的APP根本就不可能接收到非自身应用发送的该广播,所以你没必要担忧有安全漏洞能够利用;
比系统的全局广播更加高效。
LocalBroadcastManager
内部协做主要是靠这两个 Map
集合:MReceivers
和 MActions
,固然还有一个 List 集合 MPendingBroadcasts
,这个主要就是存储待接收的广播对象。
LocalBroadcastManager
高效的缘由主要是由于它内部是经过 Handler
实现的,它的 sendBroadcast()
方法含义并不是和咱们平时所用的同样,它的 sendBroadcast()
方法实际上是经过 handler
发送一个 Message
实现的;
既然它内部是经过 Handler
来实现广播的发送的,那么相比于系统广播经过 Binder
实现那确定是更高效了,同时使用 Handler
来实现,别的应用没法向咱们的应用发送该广播,而咱们应用内发送的广播也不会离开咱们的应用;
BroadcastReceiver
设计的初衷是从全局考虑能够方便应用程序和系统、应用程序之间、应用程序内的通讯,因此对单个应用程序而言BroadcastReceiver
是存在安全性问题的 ( 恶意程序脚本不断的去发送你所接收的广播 ) 。为了解决这个问题 LocalBroadcastManager
就应运而生了。
LocalBroadcastManager
是 Android Support
包提供了一个工具,用于在同一个应用内的不一样组件间发送 Broadcast
。LocalBroadcastManager
也称为局部通知管理器,这种通知的好处是安全性高,效率也高,适合局部通讯,能够用来代替 Handler
更新 UI
Android
系统中的广播能够跨进程直接通讯,会产生如下两个问题:APP
能够接收到当前 APP
发送的广播,致使数据外泄。APP
能够向当前 APP
放广播消息,致使 APP
被非法控制。permission
,用于权限验证。Android 4.0
及以上系统中发送广播时,可使用 setPackage()
方法设置接受广播的包名。permission
,用于权限验证。android:exported
的值为false。permission
APP
必须申请相应权限,这样才能收到对应的广播,反之亦然。因广播数据在本应用范围内传播,你不用担忧隐私数据泄露的问题。
不用担忧别的应用伪造广播,形成安全隐患。
相比在系统内发送全局广播,它更高效。
app
端,自定义定义权限, 那么想要接收的另外 app
端必须声明权限才能收到.<permission android:name="broad.ok.receiver" android:protectionLevel="normal"/> <uses-permission android:name="broad.ok.receiver" />
public void click(View view) { Intent intent = new Intent(); intent.putExtra("info", "消息内容"); intent.setAction("myBroadcast.action.call"); sendBroadcast(intent, "broad.ok.receiver"); //sendOrderedBroadcast(intent,"broad.ok.receiver"); }
<uses-permission android:name="broad.ok.receiver"/>
onReceiver
方法里,直接调用判断方法得返回值public void onReceive(Context context, Intent intent) { Log.d("MyBroadCastReceiver", "收到信息,内容是 : " + intent.getStringExtra("info") + ""); boolean isOrderBroadcast = isOrderedBroadcast(); }
BroadcastReceiver
通常处于主线程。ANR
BroadcastReceiver
启动时间较短。BroadcastReceiver
组件。而且在其中开启子线程执行耗时任务。ContentProvider
应用程序间很是通用的共享数据的一种方式,也是 Android
官方推荐的方式。Android
中许多系统应用都使用该方式实现数据共享,好比通信录、短信等。Android
开发的人都不怎么使用它,以为直接读取数据库会更简单方便。Android
搞一个内容提供者在数据和应用之间,只是为了装高大上,故弄玄虚?我认为其设计用意在于:DB
,XML
、Preferences
或者网络请求来的。当项目需求要改变数据来源时,使用咱们的地方彻底不须要修改。ContentResolver
接口的 notifyChange
函数来通知那些注册了监控特定 URI的ContentObserver 对象,使得它们能够相应地执行一些处理。ContentProvider
的 Uri
访问开放的数据。ContenResolver
对象经过 Context
提供的方法 getContenResolver()
来得到。ContenResolver
提供了如下方法来操做:insert
delete
update
query
这些方法分别会调用 ContenProvider
中与之对应的方法并获得返回的结果。ContentResolver
类对象:ContentResolver cr = getContentResolver ( )
。String
数组。cr.query()
; 返回一个 Cursor
对象。while
循环获得 Cursor
里面的内容。Android
中若是想将本身应用的数据 ( 通常多为数据库中的数据 ) 提供给第三发应用, 那么咱们只能经过 ContentProvider
来实现了。 ContentProvider
是应用程序之间共享数据的接口。ContentProvider
, 而后覆写 query
、insert
、update
、delete
等 方法。AndroidManifest
文件中进行注册。uri
的形式共享出去 android
系统下 不一样程序 数据默认是不能共享访问 须要去实现一个类去继承 ContentProvider
。public class PersonContentProvider extends ContentProvider{ public boolean onCreate(){ } query(Url, String[], String, String[], String); insert(Uri,ContentValues); update(Uri,ContentValues,String[]); delete(Uri,String,String[]); }
ContentProvider
屏蔽了数据存储的细节 , 内部实现对用户彻底透明 , 用户只须要关心操做数据的 uri
就能够了, ContentProvider
能够实现不一样 app
之间 共享。Sql
也有增删改查的方法, 可是 sql
只能查询本应用下的数据库。ContentProvider
还能够去增删改查本地文件. xml
文件的读取等。ContentProvider
都拥有一个公共的 URI
,这个 URI
用于表示这个 ContentProvider
所提供的数据。Android
所提供的 ContentProvider
都存放在 android.provider
包中。A,B,C,D
4个部分:A
:标准前缀,用来讲明一个 Content Provider
控制这些数据,没法改变的;"content://"
;B
:URI
的标识,用于惟一标识这个 ContentProvider
,外部调用者能够根据这个标识来找到它。它定义了是哪一个 ContentProvider
提供这些数据。对于第三方应用程序,为了保证 URI
标识的惟一性,它必须是一个完整的、小写的类名。这个标识在元素的 authorities
属性中说明:通常是定义该 ContentProvider
的包类的名称;C
:路径( path
),通俗的讲就是你要操做的数据库中表的名字,或者你也能够本身定义,记得在使用的时候保持一致就能够了;"content://com.bing.provider.myprovider/tablename"
。D
:若是URI中包含表示须要获取的记录的 ID
;则就返回该id对应的数据,若是没有 ID
,就表示返回所有; "content://com.bing.provider.myprovider/tablename/#"
#
表示数据 id
。db
复制到 /data/data/packagename/databases/
目录下, 而后直接就能访问了。ContentProvider
能够接受来自另一个进程的数据请求。ContentResolver
与 ContentProvider
类隐藏了实现细节,可是 ContentProvider
所提供的 query()
,insert()
,delete()
,update()
都是在 ContentProvider
进程的线程池中被调用执行的,而不是进程的主线程中。Binder
建立和维护的,其实使用的就是每一个应用进程中的 Binder
线程池。ContentProvider
能够对开发的数据进行权限设置,不一样的 URI
能够对应不一样的权限,只有符合权限要求的组件才能访问到 ContentProvider
的具体操做。ContentProvider
封装了跨进程共享的逻辑,咱们只须要 Uri
便可访问数据。由系统来管理 ContentProvider
的建立、生命周期及访问的线程分配,简化咱们在应用间共享数据( 进程间通讯 )的方式。咱们只管经过 ContentResolver
访问 ContentProvider
所提示的数据接口,而不须要担忧它所在进程是启动仍是未启动。ContentProvider
的 onCreate()
是运行在 UI
线程的,而 query()
,insert()
,delete()
,update()
是运行在线程池中的工做线程的ContentProvider
所在进程的主线程,但可能会阻塞调用者所在的进程的 UI
线程!ContentProvider
的操做仍然要放在子线程中去作。CRUD
的操做是在工做线程的,但系统会让你的调用线程等待这个异步的操做完成,你才能够继续线程以前的工做。android:exported
属性很是重要。这个属性用于指示该服务是否可以被其余应用程序组件调用或跟它交互。true
,则可以被调用或交互,不然不能。设置为 false
时,只有同一个应用程序的组件或带有相同用户 ID
的应用程序才能启动或绑定该服务。
ContentProvider
,则能够设置 signature
级别的权限。你们能够参考一下系统自带应用的代码,自定义了 signature
级别的 permission
:
<permission android:name="com.android.gallery3d.filtershow.permission.READ" android:protectionLevel="signature" /> <permission android:name="com.android.gallery3d.filtershow.permission.WRITE" android:protectionLevel="signature" /> <provider android:name="com.android.gallery3d.filtershow.provider.SharedImageProvider" android:authorities="com.android.gallery3d.filtershow.provider.SharedImageProvider" android:grantUriPermissions="true" android:readPermission="com.android.gallery3d.filtershow.permission.READ" android:writePermission="com.android.gallery3d.filtershow.permission.WRITE" />
URI
给其余的应用访问呢?Provider
的 URI
权限设置,只容许访问部份 URI
,能够参考原生 ContactsProvider2
的相关代码( 注意 path-permission
这个选项 ):<provider android:name="ContactsProvider2" android:authorities="contacts;com.android.contacts" android:label="@string/provider_label" android:multiprocess="false" android:exported="true" android:grantUriPermissions="true" android:readPermission="android.permission.READ_CONTACTS" android:writePermission="android.permission.WRITE_CONTACTS"> <path-permission android:pathPrefix="/search_suggest_query" android:readPermission="android.permission.GLOBAL_SEARCH" /> <path-permission android:pathPrefix="/search_suggest_shortcut" android:readPermission="android.permission.GLOBAL_SEARCH" /> <path-permission android:pathPattern="/contacts/.*/photo" android:readPermission="android.permission.GLOBAL_SEARCH" /> <grant-uri-permission android:pathPattern=".*" /> </provider>
ContentProvider
能够在 AndroidManifest.xml
中配置一个叫作 android:multiprocess
的属性,默认值是 false ,表示 ContentProvider 是单例的ContentProvider
对象,若是设为 true
,系统会为每个访问该 ContentProvider
的进程建立一个实例。好比咱们在UI线程调用getContentResolver().query查询数据,而当数据量很大时(或者须要进行较长时间的计算)会不会阻塞UI线程呢?
要分两种状况回答这个问题:
ContentProvider
和调用者在同一个进程,ContentProvider
的方法( query/insert/update/delete
等 )和调用者在同一线程中;ContentProvider
和调用者在不一样的进程,ContentProvider
的方法会运行在它自身所在进程的一个 Binder 线程中。ContentProvider
的方法没有执行完成前都会 blocked
调用者。因此你应该知道这个上面这个问题的答案了吧。CursorLoader
这个类的源码,看 Google
本身是怎么使用 getContentResolver().query
的。16
个 Binder
线程去和远程服务进行交互,而每一个线程可占用的缓存空间是 128KB
这样,超过会报异常。ContentResolver
虽然是经过 Binder
进程间通讯机制打通了应用程序之间共享数据的通道,但 ContentProvider
组件在不一样应用程序之间传输数据是基于匿名共享内存机制来实现的。BroadcastReceiver
、ContentProvider
知识总结了,前先后后投入了大量时间来完成。但愿你们经过本次阅读都能有所收获。重点
:关于 Android
的四大组件,到如今为止我才总结完 Activity
、Service
、BroadcastRecevier
、ContentProvider
等,以及事件分发、滑动冲突、新能优化等重要模块,进行全面总结,欢迎你们关注 _yuanhao 的 博客园 ,方便及时接收更新因为我在「稀土掘金」「简书」「CSDN
」「博客园」等站点,都有新内容发布。因此你们能够直接关注个人 GitHub
仓库,以避免错过精彩内容!
一万多字长文,加上精美思惟导图,记得点赞哦,欢迎关注 _yuanhao 的 博客园 ,咱们下篇文章见!