Android进程间通信
Android及其各种Linux衍生系统,各组件、模块往往运行在不同的进程和线程中,不可避免地需要在进程、线程间建立通信机制。Linux通信机制有管道、消息队列、共享内存、Socket、signal,Android额外增加了Binder机制。
Android中Zygote进程采用Socket机制,线程间采用Handler消息机制,杀进程Process.killProcess()采用的是signal。
本文主要介绍应用层的一些通信机制:Intent、BroadcastReceiver、ContentProvider。接口简单,方便使用,但使用存在一定的局限性(性能和无返回值)。
Intent
- 是一种运行时绑定机制,可在运行过程中连接两个不同的组件
- 通过intent,可向Android表达某种请求或意愿,Android会根据意愿内容选择适当的组件来完成请求
例当希望打开浏览器查看网页时,可通过intent发出WEB_SEARCH_ACTION意图,Android系统会根据intent的请求内容,查询注册时所声明的IntentFilter是否包含改action,并使用其打开网页
- Android中Activity、Service、BroadcastReceiver都是通过Intent激活,不同组件有不同的传递intent方式
Activity:startActivity、startActivityForResult
Service:startService、bindService
BroadcastReceiver:sendBroadcast、sendOrderBroadcast、sendStickBroadcast
- 一个intent可匹配一个或多个Activity、Service、BroadcastReceiver,不同类型intent不会重叠(即Activity的intent只会被Activity接收,而不会发给Service、BroadcastReceiver)
Intent构成
- Action
指定动作。例ACTION_CALL(启动一个电话)、ACTION_EDIT(显示用户编辑数据)等
- Category
一个字符串,包含处理intent组件的种类信息,一个intent可有任意个category
例CATEGORY_LAUNCHER(app首页)
- Data
具体数据,一般由Uri表示
val uri = Uri.parse("http://content/xxx")
val intent = Intent(Intent.ACTION_VIEW, uri)
startActivity(intent)
- Type
显示指定Intent的数据类型(MIME)
例显示图片数据的组件,不能播放声音,就可以指定mimeType为"image/*"
- component
用于指定目标组件类名称
指定该属性,可使用其指定对应组件,无需使用action、data/type、category查找匹配组件
val intent = Intent(this, XxxActivity::class.java)
startActivity(intent)
- extras
附加信息,用于传参
val intent = Intent(this, XxxActivity::class.java)
val bundle = Bundle()
bundle.putString("key", "value")
intent.putExtras(bundle)
startActivity(intent)
// 获取数据
val value = intent.getExtras()?.getString("key")
Intent解析
- 直接Intent(无需解析) 指定component,Intent通过setComponent(componentName)或setClass(context, class)指定具体组件类启动组件
- 间接Intent(需解析) 通过action、category等匹配启动组件
解析机制
通过查找注册在AndroidManifest中的所有及其中定义的intent
使用PackageManager查找能处理当前intent的组件,规则为通过action、type、category进行判断
若指定action,则目标组件必须包含该action,否则不匹配
若未指定type,则系统从data中得到数据类型。目标组件的数据类型必须包含该类型,否则不匹配
若intent数据不是content类型uri且未明确指定type,将根据intent中的scheme匹配。目标组件中必须包含该scheme,否则不匹配
若intent指定一个或多个category,目标组件必须包含指定的全部category,否则不匹配
BroadcastReceiver
在Android中,通过广播可以通知其他广播接收者发生了什么(例系统开机、电量低、信号弱等)。
- 不要执行耗时操作
- Broadcast基于一种注册方式实现 动态注册(热注册)
通常在onResume中通过registerReceiver中注册,onPause中通过unregisterReceiver反注册
保证运行期间关注相关事件,例词典app,运行时关注网络状态,网络好时使用网络查询,不好时使用本地词库 静态注册(冷注册) 配置文件中注册(AndroidManifest.xml),例邮件、短信、电话等
const val BROADCAST_ACTION = "com.android.dcxing.xxx"
fun sendBroad() {
val intent = Intent(BROADCAST_ACTION)
sendBroadcast(intent)
}
class XxxReceiver: BroadcastReceiver {
fun onReceiver(context: Context, intent: Intent) {
when(intent.action) {
BROADCAST_ACTION -> {
}
}
}
}
// 静态注册(冷注册)
<receiver android:name=".XxxReceiver">
<intent-filter>
<action android:name="com.android.dcxing.xxx"/>
</intent-filter>
</receiver>
// 动态注册(热注册)
val receiver = XxxReceiver()
val intentFilter = IntentFilter()
intentFilter.addAction(BROADCAST_ACTION)
registerReceiver(receiver, intentFilter)
// 取消注册(静态注册无需取消)
unregisterReceiver(receiver)
ContentProvider
app可以使用文件或SQLite数据库存储数据。ContentProvider提供了一种多app间数据共享的方式,提供了一组用于其他app存取数据的标准方式
例手机联系人信息可被多个app访问
app可以在ContentProvider中执行增删改查操作
- 查询数据
ContentProvider使用一种特殊的URI来完成,URI由3部分组成
"content://"
代表数据的路径
一个可选的标识数据的id
例:
content://media/internal/images => 查询设备上存储的所有图片
content://contacts/people/ => 查询设备上所有联系人信息
content://contacts/people/15 => 查询id为15的联系人信息
- 修改记录
使用ContentResolver.update()实现数据修改
- 添加记录
使用ContentResolver.insert()实现数据添加
- 删除记录
使用ContentResolver.delete()实现数据删除
使用
/**
* 内容提供者
*
* 1. 继承ContentProvider
* 2. 定义Uri类型变量,值唯一,最好为类全称
* 3. 创建数据存储系统。可以使用文件或SQLite数据库
* 4. 定义返回至客户端的数据列表。使用数据库必须定义主键保证每条记录的唯一性
* 5. 若存储字节型数据(位图),则其保存的该数据列值其实是一个表示实际保存文件的URI字符串
* 客户端可以使用它读取对应的文件数据。客户端可使用ContentResolver.openOutputStream()处理,无需权限
* 6. 声明静态变量用于指定要从cursor处返回的数据列
* 7. 查询结果为一个cursor对象。所有写方法将被监听,可通过contentResolver.notifyChange()通知监听数据更新
* 8. AndroidManifest声明ContentProvider
* 9. 若处理数据为新类型,需定义一个新的MIME类型,供ContentProvider#getType(url)返回
*/
class XxxProvider: ContentProvider() {
companion object {
val AUTHORITY = "com.dcxing.XxxProvider"
val CONTENT_URI = Uri.parse("content://com.dcxing.XxxProvider")
}
private var sqLite: SQLiteDatabase? = null
private var dbHelper: DbHelper? = null
override fun onCreate(): Boolean {
context?.let { dbHelper = DbHelper(it) }
return dbHelper != null
}
override fun query(
uri: Uri,
projection: Array<out String>?,
selection: String?,
selectionArgs: Array<out String>?,
sortOrder: String?): Cursor? {
val build = SQLiteQueryBuilder()
val db = dbHelper?.readableDatabase
build.tables = DbHelper.TABLE_NAME
val cursor =
build.query(db, projection, selection, selectionArgs, null, null, sortOrder)
cursor.setNotificationUri(context?.contentResolver, uri)
return cursor
}
override fun getType(uri: Uri): String? = null
override fun insert(uri: Uri, values: ContentValues?): Uri? {
sqLite = dbHelper?.writableDatabase
val rowId = sqLite?.insert(DbHelper.DATABASE_CREATE, "", values)?:0
if (rowId > 0) {
val rowUri = ContentUris.appendId(CONTENT_URI.buildUpon(), rowId).build()
context?.contentResolver?.notifyChange(rowUri, null)
return rowUri
}
throw SQLiteException("Failed to insert row into $uri")
}
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<out String>?): Int = 0
}
// 声明
<provider android:authorities="com.dcxing.XxxProvider"
android:name=".component.XxxProvider"/>
// 使用
// 插入
val values = ContentValues()
values.put("nameKey", "name")
contentResolver.insert(XxxProvider.CONTENT_URI, values)
// 查询
@SuppressLint("Range")
fun query() {
val cursor = managedQuery(XxxProvider.CONTENT_URI, arrayOf(""), null, null, null)
if (cursor.moveToFirst()) {
val id = cursor.getString(cursor.getColumnIndex("id"))
}
}