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文件下,竟然还写着昨天的代理。
直接把方框里的内容注释掉。解决了这个问题。
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》 Git时间
- Android中获得当前日期时间
- android bitmap图片压缩,打时间水印。
- android时间对话框的核心代码
- android实现快递跟踪进度条
- android关于时间的demo