目录

  • 前言
  • 正文
    • 1. 从 Java 转向 Kotlin,使用 Glide,但是 GlideApp 却无法生成
    • 2. 误用 Kotlin 中的 sortedWith() 方法排序,集合没有变化
    • 3. java.lang.RuntimeException: Using WebView from more than one process at once with the same data directory is not supported. https://crbug.com/558377
    • 4. net::ERR_CLEARTEXT_NOT_PERMITTED
    • 5. 使用 Room 报错:错误: There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such column: slice)
    • 6. Caused by: android.os.TransactionTooLargeException: data parcel size 1098756 bytes
    • 7. \app\src\main\res\layout\dk_zxing_activity_scanner.xml:18: AAPT: error: '16sp' is incompatible with attribute dkLabelTextSize (attr) float [weak].
    • 8. 使用 ktlint 来检查代码,检查出 Wildcard import
    • 9. 使用 GreenDao,自增长主键(@Id(autoincrement = true))却报错
    • 10. Kotlin 中出现 java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter savedInstanceState
  • 最后

前言

记录开发中遇到的 bug,不再让自己重复地被同样的 bug 折磨。

正文

1. 从 Java 转向 Kotlin,使用 Glide,但是 GlideApp 却无法生成

时间:2019年7月29日21:53:28
解决办法:
把 dependencies 中的

dependencies {    implementation 'com.github.bumptech.glide:glide:4.9.0'    annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0'}

替换为

apply plugin: 'kotlin-kapt'dependencies {    implementation 'com.github.bumptech.glide:glide:4.9.0'    kapt 'com.github.bumptech.glide:compiler:4.9.0'}

可以参考这个文档:https://kotlinlang.org/docs/reference/kapt.html。

2. 误用 Kotlin 中的 sortedWith() 方法排序,集合没有变化

时间:2019年8月4日14:17:06
问题描述:
看下边的小例子:

data class Man(val name: String, val age: Int, val type: Int)fun main(args: Array<String>) {    val list = mutableListOf<Man>()    list.add(Man("wzc", 31,2))    list.add(Man("wzj", 32,1))    list.add(Man("wcx", 3,1))    list.add(Man("wcg", 7,1))    println("before sort")    for (man in list) {        println(man)    }    list.sortedWith(Comparator {lh, rh ->        if (lh.type.compareTo(rh.type) == 0) {            lh.age.compareTo(rh.age)        } else {            lh.type.compareTo(rh.type)        }    })    println("after sort")    for (man in list) {        println(man)    }}/*打印结果:before sortMan(name=wzc, age=31, type=2)Man(name=wzj, age=32, type=1)Man(name=wcx, age=3, type=1)Man(name=wcg, age=7, type=1)after sortMan(name=wzc, age=31, type=2)Man(name=wzj, age=32, type=1)Man(name=wcx, age=3, type=1)Man(name=wcg, age=7, type=1) */

可以看到排序前后,打出的内容没有丝毫变化。
解决方法:
看一下 sortedWith 的代码:

/** * Returns a list of all elements sorted according to the specified [comparator]. *  * The sort is _stable_. It means that equal elements preserve their order relative to each other after sorting. */public fun <T> Iterable<T>.sortedWith(comparator: Comparator<in T>): List<T> {    if (this is Collection) {       if (size <= 1) return this.toList()       @Suppress("UNCHECKED_CAST")       return (toTypedArray<Any?>() as Array<T>).apply { sortWith(comparator) }.asList()    }    return toMutableList().apply { sortWith(comparator) }}

可以排序后的结果是在返回值里面。
修改代码:

data class Man(val name: String, val age: Int, val type: Int)fun main(args: Array<String>) {    val list = mutableListOf<Man>()    list.add(Man("wzc", 31,2))    list.add(Man("wzj", 32,1))    list.add(Man("wcx", 3,1))    list.add(Man("wcg", 7,1))    println("before sort")    for (man in list) {        println(man)    }//    list.sortedWith(Comparator {lh, rh ->//        if (lh.type.compareTo(rh.type) == 0) {//            lh.age.compareTo(rh.age)//        } else {//            lh.type.compareTo(rh.type)//        }//    })//    println("after sort")//    for (man in list) {//        println(man)//    }    val sortedWith = list.sortedWith(Comparator { lh, rh ->        if (lh.type.compareTo(rh.type) == 0) {            lh.age.compareTo(rh.age)        } else {            lh.type.compareTo(rh.type)        }    })    list.clear()    list.addAll(sortedWith)    println("after sort")    for (man in list) {        println(man)    }}/*打印结果:before sortMan(name=wzc, age=31, type=2)Man(name=wzj, age=32, type=1)Man(name=wcx, age=3, type=1)Man(name=wcg, age=7, type=1)after sortMan(name=wcx, age=3, type=1)Man(name=wcg, age=7, type=1)Man(name=wzj, age=32, type=1)Man(name=wzc, age=31, type=2) */

可以看到,正常排序了。可以看到还有个 sortWith 方法:

expect fun <T> MutableList<T>.sortWith(comparator: Comparator<in T>): Unit

二者的区别是:sortedWith() 方法可以通过 Iterable 对象调用,排序结果在返回值里;而 sortWith() 方法只能通过 MutableList 来调用,排序结果不在返回值里,而是直接在调用对象里了。sortedWith() 方法内部最终还是调用 sortWith() 方法来排序的。

3. java.lang.RuntimeException: Using WebView from more than one process at once with the same data directory is not supported. https://crbug.com/558377

时间:2019年8月9日15:35:31
问题描述:这个错误都是在 9.0 机子上出现的。
问题分析:查看 android 9.0 的行为变更文档:https://developer.android.google.cn/about/versions/pie/android-9.0-changes-28 。 在框架安全性变更部分可以看到如下内容:

如果您的应用必须在多个进程中使用 WebView 的实例,则必须先利用 WebView.setDataDirectorySuffix() 函数为每个进程指定唯一的数据目录后缀,然后再在该进程中使用 WebView 的给定实例。 该函数会将每个进程的网络数据放入其在应用数据目录内自己的目录中。
注:即使您使用 setDataDirectorySuffix(),系统也不会跨应用的进程界限共享 Cookie 以及其他网络数据。 如果应用中的多个进程需要访问同一网络数据,您需要自行在这些进程之间复制数据。 例如,您可以调用 getCookie() 和 setCookie(),在不同进程之间手动传输 Cookie 数据。

查看自己的应用,确实在多进程中使用了 WebView。
解决办法:在自定义的 Application 中为新的进程指定唯一的数据目录后缀

class App : Application() {    override fun onCreate() {        super.onCreate()        val processName = getCurProcessName()        if (TextUtils.equals(processName, packageName)) {             webviewSetPath(processName);        }     }    /**     * 获取当前进程名称     */    private fun getCurProcessName(): String? {        val pid = android.os.Process.myPid()        val mActivityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager        for (appProcess in mActivityManager                .runningAppProcesses) {            if (appProcess.pid == pid) {                return appProcess.processName            }        }        return null    }    fun webviewSetPath(processName: String?) {        if (TextUtils.isEmpty(processName)) {            return        }        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {            WebView.setDataDirectorySuffix(processName);        }    }}

4. net::ERR_CLEARTEXT_NOT_PERMITTED

时间:2019年8月10日19:08:40
问题描述:在 Android 9.0 手机上使用 WebView 访问页面出现
Android 开发中遇到的 bug(8)_第1张图片
问题分析:
查看文档:

默认情况下启用网络传输层安全协议 (TLS)
如果您的应用以 Android 9 或更高版本为目标平台,则默认情况下 isCleartextTrafficPermitted() 函数返回 false。 如果您的应用需要为特定域名启用明文,您必须在应用的网络安全性配置中针对这些域名将 cleartextTrafficPermitted 显式设置为 true。

需要进行网络安全性配置。打开对应的文档:https://developer.android.google.cn/training/articles/security-config.html 。
配置如下:
在 res/xml 下,新建 network_security_config.xml:

<?xml version="1.0" encoding="utf-8"?><network-security-config>    <base-config cleartextTrafficPermitted="true">        <trust-anchors>            <certificates src="system"/>            <certificates src="user"/>        trust-anchors>    base-config>network-security-config>

在清单文件中,application 节点下增加:

 <application...  android:networkSecurityConfig="@xml/network_security_config"... >

5. 使用 Room 报错:错误: There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (no such column: slice)

时间:2019年8月10日19:38:12
问题描述:

@Daointerface VideoDetailInfoDao {    @Query("select status from videodetailinfo where video_task_id =:taskId and video_type = ${VideoInfo.TYPE_SLICE}")    fun getSliceStatusList(taskId: Int): List<Byte>?}

定位到这个查询语句出错,对应的 .java 是这样的:

@org.jetbrains.annotations.Nullable()    @androidx.room.Query(value = "select status from videodetailinfo where video_task_id =:taskId and video_type = slice")    public abstract java.util.List<java.lang.Byte> getSliceStatusList(int taskId);

解决办法:
查询 sql 资料:SQL 使用单引号来环绕文本值(大部分数据库系统也接受双引号)。如果是数值,请不要使用引号。
我应该是没有加单引号导致的。加上单引号重新运行正常:

@Daointerface VideoDetailInfoDao {    @Query("select status from videodetailinfo where video_task_id =:taskId and video_type = '${VideoInfo.TYPE_SLICE}'")    fun getSliceStatusList(taskId: Int): List<Byte>?}

6. Caused by: android.os.TransactionTooLargeException: data parcel size 1098756 bytes

时间:2019年8月12日18:57:30
问题描述:

08-12 18:57:30.513 9595-9595/? E/AndroidRuntime: FATAL EXCEPTION: main    Process: com.omnipotent.free.videodownloader.pro, PID: 9595    java.lang.RuntimeException: Failure from system        at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1413)        at android.app.ContextImpl.startService(ContextImpl.java:1379)        at android.content.ContextWrapper.startService(ContextWrapper.java:581)        at com.omnipotent.free.videodownloader.pro.ui.download.DownloadIntentService$Companion.start(DownloadIntentService.kt:36)        at com.omnipotent.free.videodownloader.pro.ui.download.select.DownloadSelectDialog.startDownloadService(DownloadSelectDialog.kt:124)        at com.omnipotent.free.videodownloader.pro.ui.download.select.DownloadSelectPresenter.clickDownload(DownloadSelectPresenter.kt:61)        at com.omnipotent.free.videodownloader.pro.ui.download.select.DownloadSelectDialog$setupListeners$2.onClick(DownloadSelectDialog.kt:78)        at android.view.View.performClick(View.java:5264)        at android.view.View$PerformClick.run(View.java:21297)        at android.os.Handler.handleCallback(Handler.java:743)        at android.os.Handler.dispatchMessage(Handler.java:95)        at android.os.Looper.loop(Looper.java:150)        at android.app.ActivityThread.main(ActivityThread.java:5621)        at java.lang.reflect.Method.invoke(Native Method)        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794)        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:684)     Caused by: android.os.TransactionTooLargeException: data parcel size 1098756 bytes        at android.os.BinderProxy.transactNative(Native Method)        at android.os.BinderProxy.transact(Binder.java:505)        at android.app.ActivityManagerProxy.startService(ActivityManagerNative.java:3682)        at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1397)        at android.app.ContextImpl.startService(ContextImpl.java:1379)         at android.content.ContextWrapper.startService(ContextWrapper.java:581)         at com.omnipotent.free.videodownloader.pro.ui.download.DownloadIntentService$Companion.start(DownloadIntentService.kt:36)         at com.omnipotent.free.videodownloader.pro.ui.download.select.DownloadSelectDialog.startDownloadService(DownloadSelectDialog.kt:124)         at com.omnipotent.free.videodownloader.pro.ui.download.select.DownloadSelectPresenter.clickDownload(DownloadSelectPresenter.kt:61)         at com.omnipotent.free.videodownloader.pro.ui.download.select.DownloadSelectDialog$setupListeners$2.onClick(DownloadSelectDialog.kt:78)         at android.view.View.performClick(View.java:5264)         at android.view.View$PerformClick.run(View.java:21297)         at android.os.Handler.handleCallback(Handler.java:743)         at android.os.Handler.dispatchMessage(Handler.java:95)         at android.os.Looper.loop(Looper.java:150)         at android.app.ActivityThread.main(ActivityThread.java:5621)         at java.lang.reflect.Method.invoke(Native Method)         at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794)         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:684) 

问题分析:
我这边的使用场景是把一个集合 Parcelable 化,通过 Intent 传递给一个 Service。
查看 TransactionTooLargeException 类的文档:

The Binder transaction failed because it was too large.
During a remote procedure call, the arguments and the return value of the call are transferred as Parcel objects stored in the Binder transaction buffer. If the arguments or the return value are too large to fit in the transaction buffer, then the call will fail and TransactionTooLargeException will be thrown.
The Binder transaction buffer has a limited fixed size, currently 1Mb, which is shared by all transactions in progress for the process. Consequently this exception can be thrown when there are many transactions in progress even when most of the individual transactions are of moderate size.
There are two possible outcomes when a remote procedure call throws TransactionTooLargeException. Either the client was unable to send its request to the service (most likely if the arguments were too large to fit in the transaction buffer), or the service was unable to send its response back to the client (most likely if the return value was too large to fit in the transaction buffer). It is not possible to tell which of these outcomes actually occurred. The client should assume that a partial failure occurred.
The key to avoiding TransactionTooLargeException is to keep all transactions relatively small. Try to minimize the amount of memory needed to create a Parcel for the arguments and the return value of the remote procedure call. Avoid transferring huge arrays of strings or large bitmaps. If possible, try to break up big requests into smaller pieces.
If you are implementing a service, it may help to impose size or complexity contraints on the queries that clients can perform. For example, if the result set could become large, then don’t allow the client to request more than a few records at a time. Alternately, instead of returning all of the available data all at once, return the essential information first and make the client ask for additional information later as needed.

参考:https://mp.weixin.qq.com/s/v57mDRnSEZ6hl7-bc3ugwA 这篇文章讲得很好。
解决方案:我最后是先把数据存在单例里面,到 Service 里再去单例里面把数据取出来解决的。取出来之后再把数据清空,避免被单例持有。

7. \app\src\main\res\layout\dk_zxing_activity_scanner.xml:18: AAPT: error: ‘16sp’ is incompatible with attribute dkLabelTextSize (attr) float [weak].

时间:2019年8月29日19:26:28
问题描述:项目中的扫描条码功能直接集成了滴滴开源的哆啦A梦,同时也集成了哆啦A梦。测试反馈扫描页面的提示文字太小了,就去添加了对应的属性:

<com.view.ViewfinderViewapp:dkLabelTextSize="16sp"/>

再去看获取的地方,

labelTextSize = array.getFloat(R.styleable.ViewfinderView_dkLabelTextSize, 36f);

就改成了

labelTextSize = array.getDimensionPixelSize(R.styleable.ViewfinderView_dkLabelTextSize, 36);

同时更改了 attrs.xml

    <declare-styleable name="ViewfinderView">        <attr name="dkLabelTextSize" format="float" />    declare-styleable>

    <declare-styleable name="ViewfinderView">        <attr name="dkLabelTextSize" format="dimension" />    declare-styleable>

运行后,就报出了上面的错误:AAPT: error: ‘16sp’ is incompatible with attribute dkLabelTextSize (attr) float [weak]
问题分析:仔细看一下,这句报错的意思,16sp 和 dkLabelTextSize 这个属性不兼容,然后这个属性是 float 的。float 是哪里来的?明明我已经改掉了。但是哆啦A梦里面确实有一个的。

接着,就把自己项目里的 ViewfinderView 更名为 ViewfinderView1,重新编译,却报出了新的错误:

  G:\AndroidWorkspaces\VideoDownload_Pro\app\build\intermediates\incremental\mergeDebuggerDebugResources\merged.dir\values\values.xml:7367: AAPT: error: duplicate value for resource 'attr/dkLabelTextSize' with config ''.      G:\AndroidWorkspaces\VideoDownload_Pro\app\build\intermediates\incremental\mergeDebuggerDebugResources\merged.dir\values\values.xml:7367: AAPT: error: resource previously defined here.

这里面是说,重复定义之前使用过的属性。打开提到的位置,确实可以看到重复定义:

                                                                               

下面就把属性名,也一并改掉:

    <declare-styleable name="ViewfinderView1">        <attr name="omniCornerColor" format="color" />        <attr name="omniLaserColor" format="color" />        <attr name="omniFrameColor" format="color" />        <attr name="omniMaskColor" format="color" />        <attr name="omniResultPointColor" format="color" />        <attr name="omniResultColor" format="color" />        <attr name="omniLabelTextColor" format="color" />        <attr name="omniLabelText" format="string" />        <attr name="omniLabelTextSize" format="dimension" />    declare-styleable>

同步更新使用到的地方,编译通过了。
从这里得出一点:尽量不要使用相同的属性名字。

8. 使用 ktlint 来检查代码,检查出 Wildcard import

时间:2019年8月30日20:23:34
问题描述:看到 android-sunflower 里集成了 ktlint,这边项目里也集成了一下。可是检测出了 星号导入 的问题。这边把星号导入改成直接导入名字那种,编译器又自动变成了星号导入。
解决办法:查看 https://stackoverflow.com/questions/3348816/intellij-never-use-wildcard-imports 。我的 as 是 windows 版的。打开 Settings -> Editor -> Kotlin,Imports 选项,下面都勾选第一项,默认是最后一项。反正就是把里面可以星号导入的都去掉。可以看下面的截图:
Android 开发中遇到的 bug(8)_第2张图片

9. 使用 GreenDao,自增长主键(@Id(autoincrement = true))却报错

时间:2019年8月30日20:53:30
问题描述:这是同事问我的问题,就是一个对象使用自增长主键,来插入数据表。

@Id(autoincrement = true)private long   id;

但却报出:

android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: WEATHER._id (code 1555)in dao.insert(weather)

刚开始,在捋他的代码流程。花了不少时间。最后还是定位下是自增主键的问题。
解决办法:搜索 “greendao 自增主键报错”,“greendao autoincrement error”,迅速找到了与我们的错误一样的案例。并且在 greenDao 的 github 上,也找到了对应的 issue:https://github.com/greenrobot/greenDAO/issues/441。
就是把 long 改成 Long,

  @Id(autoincrement = true)  private Long id;

总结一下,是解决问题思路的问题,应该先去官网上查询 issue 的。走了不少弯路。真是的。

10. Kotlin 中出现 java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter savedInstanceState

时间:2019年9月2日20:01:46
问题描述:把一个原来是 .java 的类转成 .kt 的报出这个错误:

class EmptyDownDialog(context: Context) : Dialog(context, R.style.dialog) {    override fun onCreate(savedInstanceState: Bundle) {        super.onCreate(savedInstanceState)        setContentView(R.layout.pop_no_download)    }}

问题分析:再仔细看一下日志提供的信息:参数 savedInstanceState 被指定为 non-null,是 null 的。
看一下 onCreate 方法的参数 savedInstanceState 确实是指定为 non-null 的,它实际上是可以为 null 的。这样把一个 null 赋值给 non-null 的变量,就报出了这个错误。
解决办法:在 Bundle 后面加上 ?,表明 savedInstanceState 是可空的。解决了这个问题。

class EmptyDownDialog(context: Context) : Dialog(context, R.style.dialog) {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.pop_no_download)    }}

最后

代码出错了,关键是要仔细查看日志。能够仔细地查看日志,就离解决问题很近了。

更多相关文章

  1. Android 中的高效数据结构
  2. android 首开机会在数据链接图标的状态栏打开并自行消失主动
  3. Android中的进程生命周期
  4. Android Camera数据流分析全程记录
  5. 2010-02-27 传智播客—Android(二)数据存储和访问 之文件
  6. Android多个APK共享数据(Shared User ID)
  7. Android 中的时间日期控件

随机推荐

  1. Android中用Spannable在TextView中给文字
  2. 有关android内存泄露的问题以及解决方案
  3. android中checkbox文字和复选框的间距设
  4. 手机敲命令利器---Android(安卓)Terminal
  5. Unknown error: Unable to build: the fi
  6. andr
  7. Android之Media播放器源码分析(framework
  8. Android(安卓)SDK 2.2 离线安装
  9. Android(安卓)中使用代码动态网格布局
  10. Android(安卓)轻松实现语音识别