转自:http://magiclen.org/android-drawingcache/

日期: 2014 年 8 月 27 日 | 作者: Magic Len

開發Android的時候,在許多情況下會使用到View的getDrawingCache方法來取得View目前顯示出來的樣子(DrawingCache),雖然算是一個還蠻方便的方法,但是這個方法卻有著許多的缺陷,它不但效能極差,內部實作方式和回傳的結果隨著Android API版本不同還有很大的差異。最嚴重的一點是,getDrawingCache常常會請你吃null。在這篇文章中,將會探討為什麼getDrawingCache會回傳null,以及解決這個問題的方法。

setDrawingCacheEnabledbuildDrawingCachegetDrawingCache彼此間的關係

Android SDK上,所有的View都擁有setDrawingCacheEnabledbuildDrawingCachegetDrawingCache這三種方法,這三種看起來頗為相似的方法到底有什麼樣的情感糾葛呢?讓我們繼續看下去。

大部分的View,如果沒有使用setDrawingCacheEnabled方法來啟用View的DrawingCache功能的話,那預設是不啟用的。啟用DrawingCache的話,使用到getDrawingCache方法時,會先自動去呼叫buildDrawingCache方法建立DrawingCache,再將結果回傳;不啟用DrawingCache的話,使用getDrawingCache方法時,會回傳上一次使用buildDrawingCache方法所產生出來的結果,如果在此之前都沒有使用過buildDrawingCache來建立DrawingCache的話,那麼getDrawingCache就會回傳null,當然,就算沒有啟用DrawingCache,也還是可以事先使用buildDrawingCache來建立DrawingCache,避免getDrawingCache傳回null。

以下幾兩方式都可以取得View最新的DrawingCache

...
view.setDrawingCacheEnabled(true);
...
Bitmap drawingCache = view.getDrawingCache();
...
...
view.buildDrawingCache();
Bitmap drawingCache = view.getDrawingCache();
...

啟用DrawingCache之後,就不要再呼叫buildDrawingCache方法了,以下寫法應該避免,會造成兩次建立DrawingCache

...
view.setDrawingCacheEnabled(true);
...
view.buildDrawingCache();
Bitmap drawingCache = view.getDrawingCache();
...

在使用buildDrawingCache方法建立DrawingCache的同時,Android SDK預設會將上次的DrawingCache給recycle掉,因此不必自作聰明在使用buildDrawingCache方法之前,或是在DrawingCache啟用的狀態下使用getDrawingCache方法之前,把前次的DrawingCache給手動recycle,如果真的這樣做的話,將會出現重複recycle的RumtimeException。所以下面的寫法也應該要避免:

...
if(view.getDrawingCache() != null){
view.getDrawingCache().recycle();;
}
view.buildDrawingCache();
Bitmap drawingCache = view.getDrawingCache();
...

為什麼getDrawingCache效能會很差?

文章一開始便提到getDrawingCache的效能極差,這是為什麼呢?就像上面提到的,一旦啟用了DrawingCache之後,每次呼叫getDrawingCache,都會自動重新呼叫buildDrawingCache方法來建立新的DrawingCache,但是在大部分的情況下,一個View的狀態是不會任意改變的,如果此時將getDrawingCache使用在onDraw之類的事件中,將會使效能非常地低落。再來就是隨著Android API層級愈來愈高,DrawingCache的品質也跟著愈設愈高,在絕大部分的情況下都是使用最佔用記憶體且運算速度最慢的ARGB_8888,過去View所提供的setDrawingCacheQuality方法已經沒有實質作用了,不管設定哪種品質,都還是會使用ARGB_8888。

為什麼getDrawingCache常常傳回null?

如果遭遇getDrawingCache方法傳回null的狀況,請先確定View的DrawingCache有無啟用,如果沒有啟用,再確定有沒有呼叫過buildDrawingCache方法。如果以上都確認過沒問題,再看看使用getDrawingCache時機是否有誤,View一定要經過measure和layout的過程才可以被繪製出來。以下面的例子為例,即使沒有直接將View加入至Activity或是Fragment的RootView中,也可以取得DrawingCache

...
ViewGroup viewGroup = new FrameLayout(getContext());
ImageView image = new ImageView(getContext());
image.setImageResource(R.drawable.ic_launcher);
viewGroup.addView(image);
viewGroup.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
viewGroup.layout(0, 0, viewGroup.getMeasuredWidth(), viewGroup.getMeasuredHeight());
viewGroup.buildDrawingCache();
Bitmap bitmap = viewGroup.getDrawingCache();
...

如果很確定View已經有經過measure和layout且也有呼叫buildDrawingCache(無論自動或手動)方法了,但是getDrawingCache卻還是傳回null,那就是因為要繪製的DrawingCache太大張了,超過Android系統預設的drawingCacheSize,所以系統就不給畫啦!當遇到這種狀況時,就只能放棄使用DrawingCache了,而事實上,這種狀況還蠻常發生的。

Android系統預設的DrawingCache大小上限,在不同的裝置上有不同的設定,甚至有可能差了好幾倍,如果要查看數值的話可以在Android SDK中使用以下方式來取得drawingCacheSize:

ViewConfiguration.get(context).getScaledMaximumDrawingCacheSize();

不使用getDrawingCache的替代方法

文章看到這裡大家應該都可以了解到getDrawingCache實在是異常難用,既然如此,那就完全放棄Android內建的DrawingCache機制吧!實際上,要自己實作出類似的功能並不會太難,大致上的概念是自行建立出Bitmap,並且使用一個Canvas在這個Bitmap上作畫,只要調用View的draw方法,將自己的Canvas作為參數傳入,結果就會出現在Bitmap上了。可寫成如以下的程式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public Bitmap getMagicDrawingCache(View view) {
Bitmap bitmap = (Bitmap) view.getTag(cacheBitmapKey);
Boolean dirty = (Boolean) view.getTag(cacheBitmapDirtyKey);
int viewWidth = view.getWidth();
int viewHeight = view.getHeight();
if (bitmap == null || bitmap.getWidth() != viewWidth || bitmap.getHeight() != viewHeight) {
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
bitmap = Bitmap.createBitmap(viewWidth, viewHeight, bitmap_quality);
view.setTag(cacheBitmapKey, bitmap);
dirty = true;
}
if (dirty == true || !quick_cache) {
bitmap.eraseColor(color_background);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
view.setTag(cacheBitmapDirtyKey, false);
}
return bitmap;
}

其中,cacheBitmapKey和cacheBitmapDirtyKey為相異的整數數值,分別用來指定View的Tag ID。cacheBitmapKey的位置會存放使用這個方法建立出來的DrawingCache;cacheBitmapDirtyKey的位置會存放這個View的DrawingCache是否已經髒掉了(dirty)而需要呼叫View的draw方法重新繪製。DrawingCache所用的Bitmap只在沒有Bitmap物件或是Bitmap物件的大小和View的大小不合的時候才重新建立,在建立新的Bitmap前會先將先前的Bitmap進行recycle,新的Bitmap物件的參考會再被存入至View的Tag中。quick_cache若設定為false,則不論DrawingCache是否dirty,都進行重繪,只有在View常常變化的時候才需要這樣做。bitmap_quality可以設定為Bitmap.Config.RGB_565或是Bitmap.Config.ARGB_8888,Bitmap.Config.ARGB_4444已經隨著Android API層級愈來愈高而慢慢被禁用了,在實際應用上,RGB_565雖然沒有透明層,但是效能會比ARGB_8888還要好很多。

如果要加入View不在Activity或是Fragment的RootView中的判斷的話,可以寫成以下程式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public Bitmap getMagicDrawingCache(View view) {
Bitmap bitmap = (Bitmap) view.getTag(cacheBitmapKey);
Boolean dirty = (Boolean) view.getTag(cacheBitmapDirtyKey);
if (view.getWidth() + view.getHeight() == 0) {
view.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
}
int viewWidth = view.getWidth();
int viewHeight = view.getHeight();
if (bitmap == null || bitmap.getWidth() != viewWidth || bitmap.getHeight() != viewHeight) {
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
bitmap = Bitmap.createBitmap(viewWidth, viewHeight, bitmap_quality);
view.setTag(cacheBitmapKey, bitmap);
dirty = true;
}
if (dirty == true || !quick_cache) {
bitmap.eraseColor(color_background);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
view.setTag(cacheBitmapDirtyKey, false);
}
return bitmap;
}

DrawingCache實際應用

DrawingCache的用途很非常廣,可以用來製作Android SDK所沒有內建的View,如以下時間軸樣式的View。

【转】Android DrawingCache的更多相关文章

  1. Android:将View的内容映射成Bitmap转图片导出

    前段时间在网上看到这么个例子是将view映射到一个bitmap中,稍加改进可以用于一些截图工具或者截图软件(QQ截图之类),例子写的不够完善,不过很有些学习的意义内容大致如下: 在Android中自有 ...

  2. android开发设置dialog的高宽

    这里设置为跟屏幕一样的宽度,:看代码 dlg.show(); WindowManager.LayoutParams params = dlg.getWindow().getAttributes(); ...

  3. Android中View转换为Bitmap及getDrawingCache=null的解决方法

    1.前言 Android中经常会遇到把View转换为Bitmap的情形,比如,对整个屏幕视图进行截屏并生成图片:Coverflow中需要把一页一 页的view转换为Bitmap.以便实现复杂的图形效果 ...

  4. Android 播放视频并获取指定时间的帧画面

    最近做的项目要求既能播放视频(类似于视频播放器),又能每隔1s左右获取一帧视频画面,然后对图片进行处理,调查了一周,也被折磨了一周,总算找到了大致符合要求的方法.首先对调查过程中涉及到的方法进行简单介 ...

  5. Android滚动截屏,ScrollView截屏

    在做分享功能的时候,需要截取全屏内容,一屏展示不完的内容,一般我们会用到 ListView 或 ScrollView 一: 普通截屏的实现 获取当前Window 的 DrawingCache 的方式, ...

  6. 实现Android 版网页快照功能

    现在一般的购物网站,在你完成交易后都会将页面拍照以免日后发生商务纠纷,而对于我们移动开发者这个传统互联网上的优秀经验也同样给了我们一些设计上的启迪,接下来我将几种实现思路写出来供大家参考. 方案一:使 ...

  7. Android应用截图方法

    在Android应用开发过程中,可能会遇到需要对整个界面或者某一部分进行截图的需求.Android中对View的截图也有很多中方式: 使用DrawingCache 直接调用View.draw Draw ...

  8. Android学习系列(10)--App列表之拖拽ListView(上)

     研究了很久的拖拽ListView的实现,受益良多,特此与尔共飨.      鉴于这部分内容网上的资料少而简陋,而具体的实现过程或许对大家才有帮助,为了详尽而不失真,我们一步一步分析,分成两篇文章. ...

  9. Android长截屏-- ScrollView,ListView及RecyclerView截屏

    http://blog.csdn.net/wbwjx/article/details/46674157       Android长截屏-- ScrollView,ListView及RecyclerV ...

随机推荐

  1. 使用ST-Link下载程序出现Error:Flash Download Failed-“Cortex-M3“ 解决详细步骤(附图)

    我一直用stm32 f407开发. 最近要学mqtt与阿里云联网之类的课程,因为没有做过,所以网上搜了一遍,结果全是stm32 f103c8t6的例程. 后来我就搬出我的f103最小系统版 (这个就为 ...

  2. day4. 运算符

    运算符包括算数运算符.比较运算符.赋值运算符.成员运算符.身份运算符.逻辑运算符.位运算符 python运算符 注意点 算数运算符 % 取余 , //地板除 , ** 幂运算 比较运算符 == 比较两 ...

  3. 使用ajax发送的请求实现页面跳转

    ajax 本身是不适用于页面跳转的: 可以借助其他方法实现: 1,window.location.href = "/home"; 2,springMVC 返回的modelAndVi ...

  4. Android Studio简单的登陆界面

    在app->src->main->java里面找到MainActivity.java,将鼠标放到activity-main上按住Ctrl后单击跳转到activity-main.xml ...

  5. Python实现图片滑动式验证识别

    1 abstract 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例的人,却不知道如何去学习更加高深的知识.那么针对这三类 ...

  6. 5个Python特性 越早知道越好的

    Kirill Sharkovski 发布在 Unsplash 杂志上的照片 AI开发者按,Python 是近十年来兴起的编程语言,并且被证明是一种非常强大的语言.我用 Python 构建了很多应用程序 ...

  7. Linux学习笔记之配置网络

    1.打开VMware Workstation虚拟机 2.在VMware下安装虚拟ubunt系统后配置网络,如图所示配置,即可. 3.检查笔记本所使用的网段 ①按 “win + R ”键,并输入“cmd ...

  8. 异步文件通道Java NIO你需要了解多少,来看看这篇文章

    在Java 7,AsynchronousFileChannel 被添加到了Java NIO中.使用AsynchronousFileChannel可以实现异步地读取和写入文件数据. 创建一个Asynch ...

  9. C#LeetCode刷题之#598-范围求和 II​​​​​​​(Range Addition II)

    问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3881 访问. 给定一个初始元素全部为 0,大小为 m*n 的矩阵 ...

  10. Vue 使用mixin抽取共通方法

    引入原因: 当一段逻辑在不同的地方使用时 step-1: 定义mixin文件,methods里有一个handleToLink方法 /** * this mixin file will be used ...