Android 开发中遇到的 bug(9)

前言

记录开发中遇到的 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

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;
        }
    }
}

最后

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

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: Android底层开发指的是在Android操作系统对底层系统组件进行开发和调试的过程。底层开发需要对操作系统的内部机制、底层接口和系统调用有深入的了解。 在Android底层开发,需要掌握Linux内核、C/C++编程语言以及JNI(Java Native Interface)等技术。开发者需要了解Linux内核的机制和原理,以便能够理解和分析Android系统在底层运行时的行为和逻辑。 在底层开发,还需要通过C/C++编程语言来编写底层库和驱动程序,与硬件进行交互和通信。这些底层库和驱动程序负责实现Android系统各个组件的底层功能,如图形显示、音频处理、网络通信等。 另外,JNI技术用于在Java层和底层库之间进行交互。通过JNI,可以调用底层库的函数和方法,实现Java层与底层的数据传递和功能调用。 底层开发经常会涉及到调试和性能优化的工作。开发者需要使用调试工具来分析和追踪底层代码的执行过程,以及解决底层开发遇到的问题和bug。性能优化方面,可以通过调整底层代码和参数来提高系统的性能和响应能力。 总之,Android底层开发是一项需要对操作系统原理和底层技术有深入了解的工作。通过学习和实践,开发者可以掌握底层开发技术,为Android系统的功能和性能做出贡献。 ### 回答2: Android底层开发是指在Android操作系统直接与硬件进行交互的开发工作。在Android底层开发开发者需要熟悉Linux内核、硬件驱动程序以及底层库等关键技术。 Android操作系统基于Linux内核,因此熟悉Linux内核是进行Android底层开发的基础。开发者需要了解Linux内核的基本原理和结构,以便理解Android系统底层的运行机制。 硬件驱动程序是Android底层开发的重要组成部分,它们负责将硬件设备与Android系统进行连接和通信。开发者需要掌握硬件驱动的编写和调试技巧,以确保硬件设备在Android系统能够正常工作。 底层库是Android底层开发的另一个关键技术。Android提供了一系列的底层库,用于实现底层功能,比如图形处理、网络通信、多媒体播放等。开发者需要熟悉这些库的使用方法和原理,以便在底层开发进行功能的实现和调试。 Android底层开发通常涉及到一些高级的编程语言,比如C/C++。开发者需要熟练掌握这些编程语言,以便能够编写出高效和稳定的底层代码。 总之,Android底层开发是一项复杂而庞大的工作,需要开发者具备扎实的技术基础和深厚的理论知识。只有掌握了底层开发所需的关键技术,开发者才能够在Android系统开发出高效、稳定和功能丰富的应用程序。 ### 回答3: Android底层开发是指在Android操作系统上进行系统级别的功能开发Android底层开发主要包括四个方面的内容:内核开发、HAL(硬件抽象层)开发、驱动程序开发和底层库开发。 首先,内核开发是指对Android系统内核进行修改和优化,以满足特定需求,并提供更好的性能和稳定性。内核开发需要对操作系统的核心组件进行深入研究和理解,包括进程管理、内存管理、文件系统等。 其次,HAL开发通过编写硬件抽象层的代码,将硬件和操作系统进行连接。这样做的目的是为了让Android系统能够支持不同品牌和型号的硬件设备,如传感器、摄像头、显示屏等。HAL开发需要理解硬件设备的工作原理和规范,并编写对应的接口和驱动程序。 驱动程序开发是为了让Android系统能够正确地识别和使用硬件设备。驱动程序是一种特殊的软件,用于与硬件设备进行通信和控制。驱动程序开发需要具备底层编程语言的知识,如C或C++,以及硬件设备的技术规范和接口协议。 最后,底层库开发是指编写和优化Android系统底层的库文件,以提供一些基础的功能和服务,如网络通信、图形显示、音频处理等。底层库开发需要对操作系统的功能和架构有深入了解,并具备编程技巧和算法优化的能力。 总之,Android底层开发需要深入理解操作系统和硬件设备的工作原理,具备底层编程语言的知识和技能,以及良好的系统分析和优化能力。通过进行Android底层开发开发者能够深入了解Android系统的运行原理,提升系统性能,并实现定制化和优化的功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

willwaywang6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值