Android问题篇之四大组件问题(三)

343 阅读3分钟

四大组件问题记录

10. UnsupportedOperationException: Failed to resolve attribute at index 13: TypedValue

  • 自定义属性使用异常问题
// attrs.xml
<resources>
    <attr name="item_selector_bg" format="reference"/>
</resources>

// styles.xml
<resources>
    <style name="XxxTheme">
        <item name="item_selector_bg">@drawable/selector_xxx_bg</item>
    </style>
</resources>

// layout_xxx.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/xxx_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="?attr/item_selector_bg">
</androidx.constraintlayout.widget.ConstraintLayout>

// 应用
private val binding by lazy {
    LayoutXxxBinding.inflate(LayoutInflater.from(context), this, true)
}
  • 自定义主题
    • 使应用主题可以整体变化(App换肤),获取布局所使用的context,可以是Activity的Context,也可以是Application的Context
    • 若仅Activity使用了主题,此时只可用Activity或View的上下文
    • 若想使用Application的Context,需给Application设置主题

9. 单应用两个界面来回切换,可保留原数据,并且切至第二个页面按Home键回至Launcher页面,再次点击应用图标打开应用时原始数据丢失问题

  • 修复:再次点击应用图标可保留原数据实现方案
    • 应用启动Activity设置为singleTask启动模式
    • 第二个Activity设置为singleInstance启动模式并设置taskAffinity属性

8. AndroidManifest.xml中移除3方jar中的权限声明

  • AndroidManifest.xml中配置
// 使用如下配置即可移除jar中声明的文件写权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    tools:node="remove"/>

7. 一个应用实现多个页面同时出现在多任务窗口

    1. AndroidManifest.xml中配置
<activity android:name=".XxxActivity"
    android:theme="@style/XxxTheme"
    android:launchMode="singleTask"
    android:taskAffinity="xxx.newtask"/>
    1. 启动方式使用startActivity方式(不要使用ActivityResultLauncher方式启动,该方式无效)

6. You need to use a Theme.AppCompat theme (or descendant) with this activity.

  • Lib模块中Activity主题设置问题
  • 添加一个继承自Theme.AppCompat.xxx主题即可
# 主题
<style name="XxxTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
</style>

# 引用
<activity android:name=".XxxActivity"
    android:theme="@style/XxxTheme" />

5. Activity Dialog隐藏导航栏

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    val controller = window.insetsController
    // Android 11 及之上隐藏导航栏
    controller?.hide(WindowInsets.Type.navigationBars())
} else {
    window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
}

4. 本地全局广播使用

  • 若无法引用请添加如下依赖
  • androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
// 发广播
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)

// 注册广播
val receiver = ...
val filter = IntentFilter(action)
LocalBroadcastManager.getInstance(context).registerReceiver(receiver, filter)

// 解注册
LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver)

3. 分片传输 Parcelable 数据列表,解决Binder传递大数据问题

在使用AIDL的过程中,若存在传输大量数据,必然会遇到如下问题 android.os.DeadobjectException: Transaction failed on small parcel; remote process probably died

  • 解决方案可参照framework里AIDL传输大量数据方法
  • 例;PMSgetInstalledApplications(),使用

2. 启动服务报错AndroidRuntime:android.app.RemoteServiceException:Context.startForegroundService() did not then call Service.startForeground():

  • android8.0不允许创建后台服务时使用startService(),会引发IllegalStateException。
  • 解决:将startService() -> startForegroundService()启动,把后台服务变为前台服务。
// 1. 启动  
fun adapterStartService(context: Context) {  
    val service = ComponentName("com.dcxing.xxx", "com.dcxing.xxx.XxxService")  
    val intent = Intent()  
    intent.setComponent(service)
    // val intent = Intent(context, XxxService::java.class)  
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {  
        context.startForegroundService(intent)  
    } else {  
        context.startService(intent)  
    }  
}  
  
// 2. XxxService.class  
class XxxService: Service() {  
    override fun onBind(intent: Intent?): IBinder? = null  

    override fun onCreate() {  
        super.onCreate()  
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {  
            startForeground(100, createNotify())  
        }  
    }  

    private fun createNotify(): Notification {  
        val build: Notification.Builder =  
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {  
                val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?  
                val channel = NotificationChannel("channelId", "name", NotificationManager.IMPORTANCE_LOW)  
                manager?.createNotificationChannel(channel)  
                Notification.Builder(context, channelId)  
            } else {  
                Notification.Builder(context)  
            }  
        return build.setContentTitle("")  
            .setContentText("")  
            .setAutoCancel(true)  
            .build()  
    }  
}  
  
// 3. AndroidManifest.xml  
<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    package="com.dcxing.xxx">
        <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>  
  
        ...  
</manifest>  

1. Android java.lang.SecurityException:Failed to find provider

  • Android 8.0之后未提供相对于authority key的ContentProvider
  • Android 8.0 修改了ContentResolver.notifyChange()和registerContentObserver(Uri, boolean, ContentObserver)行为方式,使用时需在所有的URI中定义一个有效的ContentProvider(即使用相关权限定义ContentProvider),用来帮助应用防范来自恶意应用的内容变更,并防止将私密数据泄露至恶意应用。

解决方法

  • 新建ContentProvider子类,重写所有abstract方法,提高安全性
  • AndroidManifest中声明provider,并配置android:authorities属性
  • 在使用notifyChange()时,给相应URI添加authority key(Uri.Builder.authority(xxx)),key与AndroidManifest中定义的保持一致
class XContentProvider: ContentProvider() {
    companion object {
        private const val authority = "xxx"
        private const val scheme = "content"

        /**
         * uri增加authority
         */
        fun getUri(path: String): Uri {
            val builder = Uri.Builder()
            return builder.authority(authority).path(path).scheme(scheme).build()
        }
    }

    override fun onCreate(): Boolean = true

    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? {
        return null
    }

    override fun getType(uri: Uri): String? {
        return null
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        return null
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        return -1
    }

    override fun update(
        uri: Uri,
        values: ContentValues?,
        selection: String?,
        selectionArgs: Array<out String>?
    ): Int {
        return -1
    }
}