Android 开发中遇到的 bug(9)

目录

  • 前言
  • 正文
    • 1. Error: Static interface methods are only supported starting with Android N (--min-api 24)
    • 2. An enum switch case label must be the unqualified name of an enumeration constant.
    • 3. AndroidStudio3.5 选择了 No Proxy 后,还去走代理的问题
    • 4. AndroidStudio 编译报错:Program type already present:com.xx.xx
    • 5. AndroidStudio 打包如何动态修改 aar 的名称?
    • 6. RecyclerView 在切换网格,列表布局时,ItemDecoration 出现混用
    • 7. java.lang.IllegalStateException: Software rendering doesn't support hardware bitmaps
    • 8. 错误: 不兼容的类型: RequestOptions无法转换为GlideOptions
    • 9. 应用图标不正常, 变为了默认的机器人
    • 10. 使用 ProgressBar 来显示加载进度,但是加载时间太短,ProgressBar 会在屏幕上一闪而过
  • 最后

前言

记录开发中遇到的 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文件下,竟然还写着昨天的代理。
Android 开发中遇到的 bug(9)_第1张图片
直接把方框里的内容注释掉。解决了这个问题。

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 。

8. 错误: 不兼容的类型: RequestOptions无法转换为GlideOptions

时间:2019年10月31日16:18:39
问题描述:在编译项目时报出这个异常。
解决办法:发现自己的依赖版本不一致:

implementation 'com.github.bumptech.glide:glide:4.9.0'
annotationProcessor "com.github.bumptech.glide:compiler:4.8.0"

把 4.8.0 改成 4.9.0 后运行正常。

9. 应用图标不正常, 变为了默认的机器人

时间:2019年11月30日13:44:21
问题描述:在集成了一个功能模块的代码后,发现在 Honor Play 手机上运行后,图标变为了默认的机器人。
解决办法:因为在没有集成这个功能模块之前,桌面的图标是正常的,所以就去查看一下这个功能模块。查看后,发现在这个功能模块里存在默认的机器人图标,名字是 ic_launcher。而这个模块并不需要 ic_launcer 的图片资源。果断删除它们,重新运行后桌面图标显示正常。

10. 使用 ProgressBar 来显示加载进度,但是加载时间太短,ProgressBar 会在屏幕上一闪而过

时间:2019年11月30日13:50:48
问题描述:
我们会遇到这样的情况,开启异步线程加载数据,同时显示 ProgressBar;在获取到数据后,就把之前显示的 ProgressBar 隐藏掉。但问题是,如果间隔时间较短,就会看到 ProgressBar 在屏幕上一闪而过。这样的用户体验是较差的。
解决办法:
之前自己的解决办法是在异步线程里故意去增加一点延时,比如 300 ms。但是这多么暴力啊,有略显无脑。那么有没有更好的办法呢?有的有的。那就是 ContentLoadingProgressBar 类,是官方库里面的。这个类就是为了解决这样的问题而生的。这里把源码放出来,方便及时查看:

package androidx.core.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ProgressBar;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
 * ContentLoadingProgressBar implements a ProgressBar that waits a minimum time to be
 * dismissed before showing. Once visible, the progress bar will be visible for
 * a minimum amount of time to avoid "flashes" in the UI when an event could take
 * a largely variable time to complete (from none, to a user perceivable amount)
 */
public class ContentLoadingProgressBar extends ProgressBar {
    private static final int MIN_SHOW_TIME = 500; // ms
    private static final int MIN_DELAY = 500; // ms

    long mStartTime = -1;
	// 发送隐藏任务的标记
    boolean mPostedHide = false;
	// 发送显示任务的标记
    boolean mPostedShow = false;
	// 被清除的标记
    boolean mDismissed = false;

    private final Runnable mDelayedHide = new Runnable() {

        @Override
        public void run() {
            mPostedHide = false;
            mStartTime = -1;
            setVisibility(View.GONE);
        }
    };

    private final Runnable mDelayedShow = new Runnable() {

        @Override
        public void run() {
            mPostedShow = false;
            if (!mDismissed) {
                mStartTime = System.currentTimeMillis();
                setVisibility(View.VISIBLE);
            }
        }
    };

    public ContentLoadingProgressBar(@NonNull Context context) {
        this(context, null);
    }

    public ContentLoadingProgressBar(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs, 0);
    }

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        removeCallbacks();
    }

    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        removeCallbacks();
    }

    private void removeCallbacks() {
        removeCallbacks(mDelayedHide);
        removeCallbacks(mDelayedShow);
    }

    /**
     * Hide the progress view if it is visible. The progress view will not be
     * hidden until it has been shown for at least a minimum show time. If the
     * progress view was not yet visible, cancels showing the progress view.
     * 如果进度控件是可见的,就隐藏它。进度控件不会被隐藏知道它已经至少显示了一段最小
     * 的显示时间。如果进度控件不可见,就删除显示进度控件。
     */
    public synchronized void hide() {
        mDismissed = true;
        removeCallbacks(mDelayedShow);
        mPostedShow = false;
        long diff = System.currentTimeMillis() - mStartTime;
        if (diff >= MIN_SHOW_TIME || mStartTime == -1) {
            // The progress spinner has been shown long enough
            // OR was not shown yet. If it wasn't shown yet,
            // it will just never be shown.
            setVisibility(View.GONE);
        } else {
            // The progress spinner is shown, but not long enough,
            // so put a delayed message in to hide it when its been
            // shown long enough.
            if (!mPostedHide) {
                postDelayed(mDelayedHide, MIN_SHOW_TIME - diff);
                mPostedHide = true;
            }
        }
    }

    /**
     * Show the progress view after waiting for a minimum delay. If
     * during that time, hide() is called, the view is never made visible.
     * 在等待一个最小的延时时间后才显示进度控件。如果在那段最小的延时时间内,
     * hide() 方法被调用了,那么这个进度控件就不会变为可见了。
     */
    public synchronized void show() {
        // Reset the start time. 重置开始时间
        mStartTime = -1;
        mDismissed = false;
        removeCallbacks(mDelayedHide);
        mPostedHide = false;
        if (!mPostedShow) {
            postDelayed(mDelayedShow, MIN_DELAY);
            mPostedShow = true;
        }
    }
}

最后

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

你可能感兴趣的:(Android,开发中遇到的,bug,集锦)