Android DrawingCache超詳細解析,解決getDrawingCache方法回傳null
来源:互联网 发布:移动感知测试软件 编辑:程序博客网 时间:2024/06/02 21:33
原文地址:http://magiclen.org/android-drawingcache/
開發Android的時候,在許多情況下會使用到View的getDrawingCache方法來取得View目前顯示出來的樣子(DrawingCache),雖然算是一個還蠻方便的方法,但是這個方法卻有著許多的缺陷,它不但效能極差,內部實作方式和回傳的結果隨著Android API版本不同還有很大的差異。最嚴重的一點是,getDrawingCache常常會請你吃null。在這篇文章中,將會探討為什麼getDrawingCache會回傳null,以及解決這個問題的方法。
setDrawingCacheEnabled、buildDrawingCache和getDrawingCache彼此間的關係
在Android SDK上,所有的View都擁有setDrawingCacheEnabled、buildDrawingCache和getDrawingCache這三種方法,這三種看起來頗為相似的方法到底有什麼樣的情感糾葛呢?讓我們繼續看下去。
大部分的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上了。可寫成如以下的程式:
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中的判斷的話,可以寫成以下程式:
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。
转载到此结束。
最后补充一个从View生成Bitmap的简单封装,从开源项目ExploreField里拿出来的
public class BitmapUtil { public static Bitmap createBitmapSafely(int width, int height, Bitmap.Config config, int retryCount) { try { return Bitmap.createBitmap(width, height, config); } catch (OutOfMemoryError e) { e.printStackTrace(); if (retryCount > 0) { System.gc(); return createBitmapSafely(width, height, config, retryCount - 1); } return null; } } public static Bitmap createBitmapFromView(View view) { view.clearFocus(); Bitmap bitmap = createBitmapSafely(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888, 1); if (bitmap != null) { Canvas canvas = new Canvas(bitmap); view.draw(canvas); canvas.setBitmap(null); } return bitmap; }}
- Android DrawingCache超詳細解析,解決getDrawingCache方法回傳null
- Android DrawingCache超詳細解析,解決getDrawingCache方法回傳null
- android ImageView.getDrawingCache return NULL
- Android:getDrawingCache() = null的解决方法
- android截屏报getDrawingCache()==null
- Android:解决view.getDrawingCache()返回null的问题
- Android:解决view.getDrawingCache()返回null的问题
- getDrawingCache()=null的解决办法
- getDrawingCache()和Android中的截图方法简介
- Android的getDrawingCache()方法布局更新后问题
- Android中View转换为Bitmap及getDrawingCache=null的解决方法
- Android中View转换为Bitmap及getDrawingCache=null的解决方法
- Android中View转换为Bitmap及getDrawingCache=null的解决方法
- Android中View转换为Bitmap及getDrawingCache=null的解决方法
- Android之View转换为Bitmap及getDrawingCache=null的解决方法
- Android之View转换为Bitmap及getDrawingCache=null的解决方法
- Android中View转换为Bitmap及getDrawingCache=null的解决方法
- Android中View转换为Bitmap及getDrawingCache=null的解决方法
- Runtime系列Blog
- 【总结】搜索服务Solr
- 执行hibernate报错“node to traverse cannot be null”问题的解决
- Redis学习记录之命令Pub/Sub(十六)
- android开机动画启动流程
- Android DrawingCache超詳細解析,解決getDrawingCache方法回傳null
- 【总结】Hadoop剖析
- getWindow().setFlags()方法
- iframe自适应父容器宽高
- javascript 中 apply 方法的使用
- 在Eclipse中编写log4j的时候,无法输入中文的解决办法
- java两种方式实现“将字符串前m位移到字符串的第n位之后”
- IOS中输入框被软键盘遮挡的解决办法
- C语言学习之标准函数库第一讲