四大组件问题记录
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属性
- 应用启动Activity设置为
8. AndroidManifest.xml中移除3方jar中的权限声明
- AndroidManifest.xml中配置
// 使用如下配置即可移除jar中声明的文件写权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:node="remove"/>
7. 一个应用实现多个页面同时出现在多任务窗口
-
- AndroidManifest.xml中配置
<activity android:name=".XxxActivity"
android:theme="@style/XxxTheme"
android:launchMode="singleTask"
android:taskAffinity="xxx.newtask"/>
-
- 启动方式使用startActivity方式(不要使用
ActivityResultLauncher方式启动,该方式无效)
- 启动方式使用startActivity方式(不要使用
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传输大量数据方法
- 例;
PMS的getInstalledApplications(),使用
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
}
}