Android进程间通信之应用层(一)

150 阅读5分钟

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"))
    }
}