Android 开发中遇到的 bug(9)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/willway_wang/article/details/100878020

前言

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

正文

1. Error: Static interface methods are only supported starting with Android N (–min-api 24)

时间:2019年9月16日20:31:41
问题描述:在升级 butterknife 后报出这个错误。
问题分析:查看 https://github.com/JakeWharton/butterknife/blob/master/CHANGELOG.md#version-900-rc2-2018-11-19
解决办法:

android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

2. An enum switch case label must be the unqualified name of an enumeration constant.

时间:2019年9月22日10:37:32
问题分析:
出错代码如下:

private float mapPx(Coordinate coordinate) {
    switch (coordinate) {
        case Coordinate.ZERO: // 这里编译报错:An enum switch case label must be the unqualified name of an enumeration constant.
            return 0;
        default:
            return 0;
    }
}
enum Coordinate {
    ZERO(0),
    HALF_WIDTH(1),
    WIDTH(2),
    HALF_HEIGHT(3),
    HEIGHT(4);
    private int coordinate;
    Coordinate(int coordinate) {
        this.coordinate = coordinate;
    }
}

解决办法:
去掉 ZERO 前面的 Coordinate,具体原因可以看 https://www.jianshu.com/p/380a503c7d37

3. AndroidStudio3.5 选择了 No Proxy 后,还去走代理的问题

时间:2019年9月25日19:09:52
问题描述:最近上网比较困难,昨天连了同事的代理,可以上网了。但今天又挂了,好在公司提供了内网专线。但是,使用浏览器都可以访问外网的,Android Studio 却不可以 Sync,已经选择Settings->HttpProxy->NoProxy了。开始以为是网络不好的原因。
问题分析:旁边的同事,发现了虽然选择了 No Proxy,但还是会走昨天设置的代理。真是奇怪!!!最后发现在 C:\Users\Administrator.gradle\gradle.properties文件下,竟然还写着昨天的代理。

直接把方框里的内容注释掉。解决了这个问题。

4. AndroidStudio 编译报错:Program type already present:com.xx.xx

时间:2019年10月23日14:42:30
解决办法:查看了自己的依赖,并不是因为重复依赖导致的。clean project 后正常编译。

5. AndroidStudio 打包如何动态修改 aar 的名称?

时间:2019年10月23日14:44:45
解决办法:
在类库工程的 build.gradle 文件中添加:

android {
    ...
    android.libraryVariants.all { variant ->
        if (variant.buildType.name.equals('release')) {
            variant.outputs.all {
                def time = new Date().format("yyyyMMddHHmmss", TimeZone.getTimeZone("GMT+08"))
                outputFileName = "yourlibname_v${defaultConfig.versionName}_${time}.aar"
            }
        }
    }

}

6. RecyclerView 在切换网格,列表布局时,ItemDecoration 出现混用

时间:2019年10月23日20:22:49

问题描述
应用增加了切换布局:网格布局和列表布局。对于这两种布局,分别设置有 ItemDecoration:其中网格布局设置的是间距,列表布局设置的是分隔线。代码如下:

    private void setupGridAdapter() {
        recyclerView.setLayoutManager(new GridLayoutManager(getActivity(), 3));
	    recyclerView.removeItemDecoration(listItemDecoration);
        recyclerView.addItemDecoration(gridItemDecoration);
        recyclerView.setAdapter(adapterList.get(currentAdapterIndex));
    }
    private void setupListAdapter() {
        clearRecylerViewItemDecorations();
        recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
		recyclerView.removeItemDecoration(gridItemDecoration);
        recyclerView.addItemDecoration(listItemDecoration);
        recyclerView.setAdapter(adapterList.get(currentAdapterIndex));
    }

实际上,在添加网格布局的 ItemDecoration 前,会移除列表布局的 ItemDecoration;同样地,在添加列表布局的 ItemDecoration 前,也会移除网格布局的 ItemDecoration。但是,实际测试发现,还是会出现分割线出现在网格布局里,列表布局的间距也出现了增大。这样的话,是非常影响 UI 效果的。

解决办法
查看了 RecyclerView 的代码:

	final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<>();
    public void removeItemDecoration(@NonNull ItemDecoration decor) {
        if (mLayout != null) {
            mLayout.assertNotInLayoutOrScroll("Cannot remove item decoration during a scroll  or"
                    + " layout");
        }
        mItemDecorations.remove(decor);
        if (mItemDecorations.isEmpty()) {
            setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);
        }
        markItemDecorInsetsDirty();
        requestLayout();
    }

注意到,ItemDecoration 对象都是保存在 mItemDecorations 这个 List 里面。而每次调用 addItemDecoration 都是添加,而不是设置:

    public void addItemDecoration(@NonNull ItemDecoration decor, int index) {
        if (mLayout != null) {
            mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll  or"
                    + " layout");
        }
        if (mItemDecorations.isEmpty()) {
            setWillNotDraw(false);
        }
        if (index < 0) {
            mItemDecorations.add(decor);
        } else {
            mItemDecorations.add(index, decor);
        }
        markItemDecorInsetsDirty();
        requestLayout();
    }

这样就可能导致多次添加,其实打印 getItemDecorationCount() 的值也可以发现它的值会出现大于 1 的情况。
要是有一个 setItemDecoration() 方法该多好啊。这边就添加了一个这样的方法:

    public void setItemDecoration(@NonNull RecyclerView.ItemDecoration decor) {
    	// 先取出所有的 ItemDecoration
        List<RecyclerView.ItemDecoration> list = new ArrayList<>();
        for (int i = 0; i < recyclerView.getItemDecorationCount(); i++) {
            RecyclerView.ItemDecoration itemDecoration = recyclerView.getItemDecorationAt(i);
            list.add(itemDecoration);
        }
        // 再移除所有的 ItemDecoration
        for (RecyclerView.ItemDecoration itemDecoration : list) {
            recyclerView.removeItemDecoration(itemDecoration);
        }
        // 最后添加新的 ItemDecoration
        recyclerView.addItemDecoration(decor);
    }

7. java.lang.IllegalStateException: Software rendering doesn’t support hardware bitmaps

时间:2019年10月23日20:54:53
问题描述:
在友盟上捕获到这个错误,都是在 android O 以后出现的。
完整日志如下:

java.lang.IllegalStateException: Software rendering doesn't support hardware bitmaps
    at android.graphics.BaseCanvas.throwIfHwBitmapInSwMode(BaseCanvas.java:532)
    at android.graphics.BaseCanvas.throwIfCannotDraw(BaseCanvas.java:62)
    at android.graphics.BaseCanvas.drawBitmap(BaseCanvas.java:120)
    at android.graphics.Canvas.drawBitmap(Canvas.java:1434)
    at android.graphics.drawable.BitmapDrawable.draw(BitmapDrawable.java:529)
    at android.widget.ImageView.onDraw(ImageView.java:1349)
    at android.view.View.draw(View.java:19196)
    at android.view.View.draw(View.java:19066)
    at android.view.ViewGroup.drawChild(ViewGroup.java:4236)
    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4022)
    at android.view.ViewOverlay$OverlayViewGroup.dispatchDraw(ViewOverlay.java:251)
    at android.view.View.draw(View.java:19199)
    at android.view.View.buildDrawingCacheImpl(View.java:18441)
    at android.view.View.buildDrawingCache(View.java:18304)
    at android.view.View.getDrawingCache(View.java:18210)
    at android.view.View.getDrawingCache(View.java:18175)
    at com.omnipotent.free.videodownloader.pro.utils.ViewUtils.captureView(ViewUtils.java:70)
    at com.omnipotent.free.videodownloader.pro.ui.main.MainActivity.getCurrentTabsData(MainActivity.java:325)
    at com.omnipotent.free.videodownloader.pro.ui.main.MainActivity.access$getCurrentTabsData(MainActivity.java:84)
    at com.omnipotent.free.videodownloader.pro.ui.main.MainActivity$initView$5.onClick(MainActivity.java:252)
    at android.view.View.performClick(View.java:6294)
    at android.view.View$PerformClick.run(View.java:24774)
    at android.os.Handler.handleCallback(Handler.java:790)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:164)
    at android.app.ActivityThread.main(ActivityThread.java:6518)
    at java.lang.reflect.Method.invoke(Method.java)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

定位到一个把 view 转成 Bitmap 的方法:

fun captureView(view: View): Bitmap {
        val tBitmap = Bitmap.createBitmap(
            view.width, view.height, Bitmap.Config.RGB_565
        )
        val canvas = Canvas(tBitmap)
        view.draw(canvas)
        canvas.setBitmap(null)
        return tBitmap
}

问题分析:
查询网上资料,Glide 文档上的硬件位图 讲的非常详细。其中提到了哪些情况不能使用硬件位图? 有一个情况是:

在代码中触发截屏操作,它会尝试使用 Canvas 来绘制视图层级。
作为一个替代方案,在 Android O 以上版本你可以使用 PixelCopy.

很明显,Glide 文档建议使用 PixelCopy 这个类,来解决在代码中触发截屏操作导致的异常。
解决办法:
所以这边采用的方案是在 android O 以后使用 PixelCopy 来获取 Bitmap,在 android O 以下还是使用原方案。代码如下:

fun captureView(view: View, window: Window, bitmapCallback: (Bitmap)->Unit) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        LogUtils.dTag(TAG, "captureView version O 以上")
        // 高于 O 的,使用 PixelCopy
        val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
        val location = IntArray(2)
        view.getLocationInWindow(location)
        PixelCopy.request(window,
            Rect(location[0], location[1], location[0] + view.width, location[1] + view.height),
            bitmap,
            {
                if (it == PixelCopy.SUCCESS) {
                    LogUtils.dTag(TAG, "captureView 获取到 bitmap")
                    bitmapCallback.invoke(bitmap)
                } else {
                    LogUtils.dTag(TAG, "captureView 未获取到 bitmap, copyResult = $it")
                }
            },
            Handler(Looper.getMainLooper()) )
    } else {
        LogUtils.dTag(TAG, "captureView version O 以下")
        val tBitmap = Bitmap.createBitmap(
            view.width, view.height, Bitmap.Config.RGB_565
        )
        val canvas = Canvas(tBitmap)
        view.draw(canvas)
        canvas.setBitmap(null)
        bitmapCallback.invoke(tBitmap)
    }
}

需要特别说明的是,这里通过回调获取 Bitmap,原因是 PixelCopy 需要通过回调返回 Bitmap。从目前的友盟错误上已经看不到这个异常了,说明改动是有效的。
同时可以关注一下,我在 Stack Overflow 上提的这个问题:java.lang.IllegalStateException: Software rendering doesn’t support hardware bitmaps

最后

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

猜你喜欢

转载自blog.csdn.net/willway_wang/article/details/100878020