Android

关注公众号 jb51net

关闭
首页 > 软件编程 > Android > Android跨应用数据共享

深入探讨Android中跨应用数据共享的权限管理

作者:时小雨

本文将和大家深入探讨Android跨应用数据共享的安全机制,结合完整Kotlin代码实现,覆盖ContentProvider、FileProvider等核心技术的权限控制策略,并附赠最佳实践和性能优化技巧

一、ContentProvider深度解析与实战

1.1 权限声明与配置

<!-- AndroidManifest.xml -->
<provider
    android:name=".data.UserDataProvider"
    android:authorities="com.example.app.provider.userdata"
    android:exported="true"
    android:readPermission="com.example.app.permission.READ_USER_DATA"
    android:writePermission="com.example.app.permission.WRITE_USER_DATA">
    
    <!-- 细粒度路径权限控制 -->
    <path-permission
        android:pathPattern="/sensitive/.*"
        android:permission="com.example.app.permission.ACCESS_SENSITIVE_DATA"
        android:readPermission=""/>
        
    <!-- 允许动态授权的URI -->
    <grant-uri-permission android:path="/public/*"/>
</provider>

1.2 ContentProvider完整实现

class UserDataProvider : ContentProvider() {

    private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
        addURI(AUTHORITY, "users", USERS)
        addURI(AUTHORITY, "users/#", USER_ID)
        addURI(AUTHORITY, "sensitive/*", SENSITIVE)
    }

    override fun query(
        uri: Uri,
        projection: Array<String>?,
        selection: String?,
        selectionArgs: Array<String>?,
        sortOrder: String?
    ): Cursor? {
        // 权限检查
        when (uriMatcher.match(uri)) {
            USERS, USER_ID -> {
                checkPermission(READ_PERMISSION)
            }
            SENSITIVE -> {
                // 特殊路径需要额外权限
                context?.checkCallingPermission(SENSITIVE_PERMISSION)?.let {
                    if (it != PERMISSION_GRANTED) throw SecurityException("Requires $SENSITIVE_PERMISSION")
                }
            }
            else -> throw IllegalArgumentException("Unknown URI: $uri")
        }
        
        // 实际数据库查询逻辑
        return db.query(
            "users", 
            projection, 
            selection, 
            selectionArgs, 
            null, 
            null, 
            sortOrder
        )
    }

    private fun checkPermission(permission: String) {
        context?.checkCallingOrSelfPermission(permission)?.let {
            if (it != PERMISSION_GRANTED) {
                throw SecurityException("Requires $permission")
            }
        }
    }

    companion object {
        const val AUTHORITY = "com.example.app.provider.userdata"
        const val READ_PERMISSION = "com.example.app.permission.READ_USER_DATA"
        const val SENSITIVE_PERMISSION = "com.example.app.permission.ACCESS_SENSITIVE_DATA"
        
        // URI匹配码
        const val USERS = 1
        const val USER_ID = 2
        const val SENSITIVE = 3
    }
}

1.3 动态URI权限授予

// 数据提供方
fun shareDataWithApp(targetPackage: String) {
    val contentUri = Uri.parse("content://$AUTHORITY/public/shared_data")
    
    // 创建临时授权Intent
    val intent = Intent(Intent.ACTION_VIEW).apply {
        data = contentUri
        `package` = targetPackage
        flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
    }
    
    // 可选:持久化授权(重启后仍有效)
    context.grantUriPermission(
        targetPackage, 
        contentUri, 
        Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
    )
    
    startActivity(intent)
}

// 数据接收方
fun accessSharedData(uri: Uri) {
    try {
        contentResolver.query(uri, null, null, null, null)?.use { cursor ->
            // 处理数据
        }
    } catch (se: SecurityException) {
        // 处理权限异常
    }
}

二、FileProvider安全文件共享

2.1 配置与声明

<!-- res/xml/file_paths.xml -->
<paths>
    <files-path name="internal_files" path="." />
    <cache-path name="internal_cache" path="." />
    <external-files-path name="external_files" path="documents/" />
    <external-cache-path name="external_cache" path="." />
    <external-media-path name="external_media" path="." />
</paths>

<!-- AndroidManifest.xml -->
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.example.app.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths"/>
</provider>

2.2 安全共享文件

fun shareImage(imageFile: File) {
    val contentUri = FileProvider.getUriForFile(
        context, 
        "com.example.app.fileprovider", 
        imageFile
    )

    val shareIntent = Intent(Intent.ACTION_SEND).apply {
        type = "image/*"
        putExtra(Intent.EXTRA_STREAM, contentUri)
        
        // 关键:授予临时访问权限
        addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    }

    startActivity(Intent.createChooser(shareIntent, "分享图片"))
}

三、广播通信权限控制

3.1 带权限的广播发送

// 发送带权限的广播
fun sendSecureBroadcast() {
    val intent = Intent("com.example.app.ACTION_SECURE_EVENT").apply {
        putExtra("data", "敏感信息")
    }
    
    // 只有持有指定权限的接收器才能接收
    sendBroadcast(intent, "com.example.app.permission.RECEIVE_SECURE_BROADCAST")
}

3.2 受保护的广播接收器

<!-- 接收方声明 -->
<receiver 
    android:name=".SecureBroadcastReceiver"
    android:permission="com.example.app.permission.SEND_SECURE_BROADCAST"
    android:exported="true">
    <intent-filter>
        <action android:name="com.example.app.ACTION_SECURE_EVENT"/>
    </intent-filter>
</receiver>
class SecureBroadcastReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        // 验证发送方身份
        if (!isValidSender(context)) {
            abortBroadcast()
            return
        }
        
        // 处理广播数据
        val data = intent.getStringExtra("data")
    }
    
    private fun isValidSender(context: Context): Boolean {
        // 检查发送方证书签名
        val packageManager = context.packageManager
        val callingUid = Binder.getCallingUid()
        val packageName = packageManager.getNameForUid(callingUid) ?: return false
        
        return packageManager.checkSignatures(
            context.packageName,
            packageName
        ) == PackageManager.SIGNATURE_MATCH
    }
}

四、跨技术方案对比

特性ContentProviderFileProviderBroadcastSharedPreferences
数据粒度行级控制文件级消息级键值对
权限模型声明式+运行时URI授权发送/接收控制无原生控制
适用场景结构化数据文件共享事件通知简单配置
安全性★★★★★★★★★☆★★★☆☆★☆☆☆☆
实现复杂度

五、自定义权限深度应用

5.1 定义签名级权限

<permission
    android:name="com.example.app.permission.INTERNAL_API"
    android:protectionLevel="signature"
    android:label="内部API访问权限"
    android:description="允许访问内部API,仅限相同签名应用"/>

5.2 权限使用与验证

// 服务端验证
fun verifyCallerSignature(context: Context): Boolean {
    val callingUid = Binder.getCallingUid()
    val packageManager = context.packageManager
    val callerPackage = packageManager.getPackagesForUid(callingUid)?.firstOrNull()
        ?: return false
    
    return packageManager.checkSignatures(
        context.packageName, 
        callerPackage
    ) == PackageManager.SIGNATURE_MATCH
}

六、Scoped Storage最佳实践

// 使用MediaStore保存图片
fun saveImageToGallery(bitmap: Bitmap, context: Context) {
    val contentValues = ContentValues().apply {
        put(MediaStore.Images.Media.DISPLAY_NAME, "my_image.jpg")
        put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
        }
    }

    val resolver = context.contentResolver
    val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
    
    uri?.let {
        resolver.openOutputStream(it)?.use { os ->
            bitmap.compress(Bitmap.CompressFormat.JPEG, 90, os)
        }
    }
}

// 通过SAF访问文件
fun openDocument() {
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "*/*"
    }
    startActivityForResult(intent, REQUEST_CODE_OPEN_DOC)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == REQUEST_CODE_OPEN_DOC && resultCode == RESULT_OK) {
        data?.data?.let { uri ->
            // 获取持久化访问权限
            contentResolver.takePersistableUriPermission(
                uri,
                Intent.FLAG_GRANT_READ_URI_PERMISSION
            )
            
            // 使用URI访问文件
            contentResolver.openInputStream(uri)?.use { input ->
                // 处理文件内容
            }
        }
    }
}

七、权限管理流程图解

7.1 ContentProvider访问控制流程

7.2 URI动态授权流程

八、安全最佳实践与性能优化

权限最小化原则

<!-- 显式设置exported属性 -->
<activity android:exported="false"/>
<service android:exported="false"/>

深度防御策略

// 在ContentProvider中二次验证
override fun insert(uri: Uri, values: ContentValues?): Uri {
    // Manifest声明的权限检查
    checkWritePermission()
    
    // 运行时二次验证
    if (isSensitiveUri(uri)) {
        val caller = callingPackage
        if (!isTrustedPackage(caller)) {
            throw SecurityException("Untrusted package: $caller")
        }
    }
    // ...
}

URI权限回收

// 在适当时机回收权限
fun revokeUriPermissions() {
    val uri = Uri.parse("content://$AUTHORITY/public/shared_data")
    context.revokeUriPermission(uri, 
        Intent.FLAG_GRANT_READ_URI_PERMISSION or 
        Intent.FLAG_GRANT_WRITE_URI_PERMISSION
    )
}

Binder调用优化

// 使用ParcelFileDescriptor传输大文件
fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
    val file = File(getContext().filesDir, uri.lastPathSegment)
    return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
}

九、前沿技术与扩展

9.1 Android 12更细粒度媒体权限

// 请求特定媒体类型权限
val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    arrayOf(
        Manifest.permission.READ_MEDIA_IMAGES,
        Manifest.permission.READ_MEDIA_VIDEO
    )
} else {
    arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
}

requestPermissions(permissions, MEDIA_PERMISSION_REQUEST)

9.2 使用AppSearch实现安全数据共享

// 配置共享数据模式
val schema = AppSearchSchema.Builder("UserSchema")
    .addProperty(....)
    .build()

val setSchemaRequest = SetSchemaRequest.Builder()
    .addSchemas(schema)
    .setSchemaTypeVisibilityForPackage(
        "UserSchema", 
        /* visible= */ true,
        /* packageName= */ "com.trusted.app"
    ).build()

val future = session.setSchema(setSchemaRequest)

十、关键点总结

1.权限控制三原则:最小权限、显式声明、运行时验证

2.ContentProvider最佳实践

3.文件共享安全

4.防御性编程

// 典型的安全检查模板
fun sensitiveOperation() {
    // 1. 检查声明权限
    checkPermission(MANIFEST_PERMISSION)
    
    // 2. 验证调用方身份
    validateCallerIdentity()
    
    // 3. 校验输入参数
    validateInputParameters()
    
    // 4. 执行核心逻辑
    executeCoreLogic()
}

2.性能优化要点

6.前沿适配

最佳实践建议:对于新项目,优先采用ContentProvider + URI动态授权方案;对于文件共享,必须使用FileProvider;跨应用通信考虑自定义签名级权限。始终在AndroidManifest.xml中显式设置android:exported属性,这是Android 12+的强制要求。

以上就是深入探讨Android中跨应用数据共享的权限管理的详细内容,更多关于Android跨应用数据共享的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文