前言

前几天一直在调查一个重启问题,本以为是 fd 泄露导致的,没想到最终发现是 RemoteViews 中的坑导致的。下面我们就以 setImageViewBitmap(int viewId, Bitmap bitmap) 方法为例,讲解一下这个坑。

setImageViewBitmap 中的坑

首先看一下其具体实现:
RemoteViews.java

    /**     * Equivalent to calling ImageView.setImageBitmap     *     * @param viewId The id of the view whose bitmap should change     * @param bitmap The new Bitmap for the drawable     */    public void setImageViewBitmap(int viewId, Bitmap bitmap) {        setBitmap(viewId, "setImageBitmap", bitmap);    }

我想大家看到这个 api 的定义,首先就会认为存储 bitmap 的结构是个以 viewId 为 key,bitmap 为 value 的 map list 的形式,调用这个 api 会直接替换掉之前相同 viewId 的 bitmap(毕竟我之前就是这么认为的),但是实际呢?我们沿着其实现继续看下去:

RemoteViews.java

    public void setBitmap(int viewId, String methodName, Bitmap value) {        addAction(new BitmapReflectionAction(viewId, methodName, value));    }    private void addAction(Action a) {        if (hasLandscapeAndPortraitLayouts()) {            ...        }        if (mActions == null) {            mActions = new ArrayList();        }        mActions.add(a);        // update the memory usage stats        a.updateMemoryUsageEstimate(mMemoryUsageCounter);    }

从上面可以看到,我们想象中的替换并没有发生,而是新创建了一个 BitmapReflectionAction 对象,并将其添加到了 mActions 这个 ArrayList 中,我们再来看一下 BitmapReflectionAction 的构造函数:

RemoteViews.java

    private class BitmapReflectionAction extends Action {        int bitmapId;        Bitmap bitmap;        String methodName;        BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) {            this.bitmap = bitmap;            this.viewId = viewId;            this.methodName = methodName;            bitmapId = mBitmapCache.getBitmapId(bitmap);        }    }

再看一下 getBitmapId 的实现:

RemoteViews.java

    private static class BitmapCache {        ArrayList mBitmaps;        public BitmapCache() {            mBitmaps = new ArrayList();        }        public int getBitmapId(Bitmap b) {            if (b == null) {                return -1;            } else {                if (mBitmaps.contains(b)) {                    return mBitmaps.indexOf(b);                } else {                    mBitmaps.add(b);                    return (mBitmaps.size() - 1);                }            }        }        public void writeBitmapsToParcel(Parcel dest, int flags) {            int count = mBitmaps.size();            dest.writeInt(count);            for (int i = 0; i < count; i++) {                mBitmaps.get(i).writeToParcel(dest, flags);            }        }    }

 可以看到这个方法会返回 bitmap 在 mBitmaps 中的 index,如果 mBitmaps 中不包含此 bitmap,则将其添加进去。
 有的人可能会说没关系啊,只不过多存一点而已啊,显示的时候又不会出问题。但是,请看一下上面的 writeBitmapsToParcel 方法,在跨进程传输的时候,每个 mBitmaps 中的 bitmap 都是要 writeToParcel 的。
 这意谓着如果 bitmap 是通过 writeBlob 这种方式存入 Parcel 对象中的话,那么每个 bitmap 传输都要打开一个 fd,如果 mBitmaps 中 bitmap 数量很多的话,直接有可能导致 app 或者 system_server 挂掉。
 我的另一篇博文 由一份 log 看 Binder 中的异常就是由于这个原因导致的。

 即使没有上面提到的问题,也势必会影响到 app 和系统的性能。因此,开发者在平常开发中一定要注意不要在同一个 RemoteViews 实例中频繁多次地调用 setImageViewBitmap 这类的方法。

更多相关文章

  1. Android中简单实现夜间模式
  2. Appium(七):Appium API(一) 应用操作
  3. android设置中的Preferencescreen使用方法介绍与分析
  4. Android(安卓)H5混合开发 JS调用原生方法
  5. android的存储GreenDao数据库
  6. Android指纹识别,看这一篇就够了
  7. Android(安卓)layout属性详细说明
  8. Android(安卓)WebView使用经验总结
  9. android WebView拦截请求详解

随机推荐

  1. 《转载》Android(安卓)AlertDialog 方法s
  2. 【Android】16.5 Android内置的系统服务
  3. android 加载动态库
  4. Android 中文api (88)――SharedPreferen
  5. Android(安卓)Model正确使用姿势——Auto
  6. android中调用接口发送短信
  7. Android(安卓)sensor 实用篇
  8. Android三角函数
  9. 2011.12.05(2)——— android JNI学习之一
  10. 面试例题6:两种方法将图像显示在View上