阿里Android開發規範:Bitmap、Drawable 與動畫

leeyh發表於2018-03-06

以下內容摘自 阿里巴巴Android開發手冊

我們的目標是:

  • 防患未然,提升質量意識,降低故障率和維護成本;
  • 標準統一,提升協作效率;
  • 追求卓越的工匠精神,打磨精品程式碼。
  • 【強制】必須遵守,違反本約定或將會引起嚴重的後果;
  • 【推薦】儘量遵守,長期遵守有助於系統穩定性和合作效率的提升;
  • 【參考】充分理解,技術意識的引導,是個人學習、團隊溝通、專案合作的方向。

阿里Android開發規範:資原始檔命名與使用規範
阿里Android開發規範:四大基本元件
阿里Android開發規範:UI 與佈局
阿里Android開發規範:程式、執行緒與訊息通訊
阿里Android開發規範:檔案與資料庫
阿里Android開發規範:Bitmap、Drawable 與動畫
阿里Android開發規範:安全與其他

1、【強制】載入大圖片或者一次性載入多張圖片,應該在非同步執行緒中進行。圖片的載入,涉及到 IO 操作,以及 CPU 密集操作,很可能引起卡頓。
正例:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
	...
	// 在後臺進行圖片解碼
	@Override
	protected Bitmap doInBackground(Integer... params) {
		final Bitmap bitmap = BitmapFactory.decodeFile("some path");
		return bitmap;
	}
	...
}
複製程式碼

反例:

Button btnLoadImage = (Button) findViewById(R.id.btn);
btnLoadImage.setOnClickListener(new OnClickListener(){
	public void onClick(View v) {
		Bitmap bitmap = BitmapFactory.decodeFile("some path");
	}
});
複製程式碼

2、【強制】在 ListView,ViewPager,RecyclerView,GirdView 等元件中使用圖片時,應做好圖片的快取,避免始終持有圖片導致記憶體洩露,也避免重複建立圖片,引起效能問題 。 建議使用 Fresco Glide 等圖片庫。
正例:
例如使用系統 LruCache 快取,參考:developer.android.com/topic/perfo…

private LruCache<String, Bitmap> mMemoryCache;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		...
		// 獲取可用記憶體的最大值,使用記憶體超出這個值將丟擲 OutOfMemory 異常。LruCache 通過建構函式傳入快取值,以 KB 為單位。
		final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
		// 把最大可用記憶體的 1/8 作為快取空間
		final int cacheSize = maxMemory / 8;
		mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
			@Override
			protected int sizeOf(String key, Bitmap bitmap) {
				return bitmap.getByteCount() / 1024;
			}
		};
		...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
	if (getBitmapFromMemCache(key) == null) {
		mMemoryCache.put(key, bitmap);
	}
}

public Bitmap getBitmapFromMemCache(String key) {
	return mMemoryCache.get(key);
}

public void loadBitmap(int resId, ImageView imageView) {
	final String imageKey = String.valueOf(resId);
	final Bitmap bitmap = getBitmapFromMemCache(imageKey);
	if (bitmap != null) {
		mImageView.setImageBitmap(bitmap);
	} else {
		mImageView.setImageResource(R.drawable.image_placeholder);
		BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
		task.execute(resId);
	}
}

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
	...
	// 在後臺進行圖片解碼
	@Override
	protected Bitmap doInBackground(Integer... params) {
		final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(),
		params[0], 100, 100));
		addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
		return bitmap;
	}
	...
}
複製程式碼

反例:
沒有儲存,每次都需要解碼,或者有快取但是沒有合適的淘汰機制,導致快取效果很差,依然經常需要重新解碼。
3、【強制】png 圖片使用 tinypng 或者類似工具壓縮處理,減少包體積。
4、【推薦】應根據實際展示需要,壓縮圖片,而不是直接顯示原圖。手機螢幕比較小,直接顯示原圖,並不會增加視覺上的收益,但是卻會耗費大量寶貴的記憶體。 正例:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,int reqWidth, int reqHeight) {
	// 首先通過 inJustDecodeBounds=true 獲得圖片的尺寸
	final BitmapFactory.Options options = new BitmapFactory.Options();
	options.inJustDecodeBounds = true;
	BitmapFactory.decodeResource(res, resId, options);
	// 然後根據圖片解析度以及我們實際需要展示的大小,計算壓縮率
	options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
	// 設定壓縮率,並解碼
	options.inJustDecodeBounds = false;
	return BitmapFactory.decodeResource(res, resId, options);
}
複製程式碼

反例:
不經壓縮顯示原圖。
5、【強制】使用完畢的圖片,應該及時回收,釋放寶貴的記憶體。
正例:

Bitmap bitmap = null;
loadBitmapAsync(new OnResult(result){
	bitmap = result;
});
...使用該 bitmap...
// 使用結束,在 2.3.3 及以下需要呼叫 recycle()函式,在 2.3.3 以上 GC 會自動管理,除非你明確不需要再用。
if (Build.VERSION.SDK_INT <= 10) {
	bitmap.recycle();
}
bitmap = null;
複製程式碼

反例: 使用完成圖片,始終不釋放資源。
6、【推薦】針對不同的螢幕密度,提供對應的圖片資源,使記憶體佔用和顯示效果達到合理的平衡。如果為了節省包體積,可以在不影響 UI 效果的前提下,省略低密度圖片。
7、【強制】在 Activity.onPause()或 Activity.onStop()回撥中,關閉當前 activity 正在執行的的動畫。
正例

public class MyActivity extends Activity {
	ImageView mImageView;
	Animation mAnimation;
	Button mBtn;
	/** 首次建立 activity 時呼叫 */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		mImageView = (ImageView)findViewById(R.id.ImageView01);
		mAnimation = AnimationUtils.loadAnimation(this, R.anim.anim);
		mBtn= (Button)findViewById(R.id.Button01);
		mBtn.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
			mImageView.startAnimation(mAnimation);
			}
		});
	}
	
	public void onPause() {
		//頁面退出,及時清理動畫資源
		mImageView.clearAnimation()
	}
}
複製程式碼

反例:
頁面退出時,不關閉該頁面相關的動畫。
8、【推薦】在動畫或者其他非同步任務結束時,應該考慮回撥時刻的環境是否還支援業務處理。例如 Activity 的 onStop()函式已經執行,且在該函式中主動釋放了資源,此時回撥中如果不做判斷就會空指標崩潰。
正例:

public class MyActivity extends Activity {
	private ImageView mImageView;
	private Animation mAnimation;
	/** 首次建立 activity 時呼叫 */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		mImageView = (ImageView)findViewById(R.id.ImageView01);
		mAnimation = AnimationUtils.loadAnimation(this, R.anim.anim);
		mAnimation.setAnimationListener(new AnimationListener() {
			@Override
			public void onAnimationEnd(Animation arg0) {
				//判斷一下資源是否被釋放了
				if (mImageView != null) {
					mImageView.clearAnimation();
				}
			}
		});
		mImageView.startAnimation(mAnimation);
	}
}
複製程式碼

反例:
動畫結束回撥中,直接使用資源不加判斷,導致異常。
9、【推薦】使用 inBitmap 重複利用記憶體空間,避免重複開闢新記憶體。 正例:

public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth, intreqHeight, ImageCache cache) {
	final BitmapFactory.Options options = new BitmapFactory.Options();
	...
	BitmapFactory.decodeFile(filename, options);
	...
	// 如果在 Honeycomb 或更新版本系統中執行,嘗試使用 inBitmap
	if (Utils.hasHoneycomb()) {
		addInBitmapOptions(options, cache);
	}
	...
	return BitmapFactory.decodeFile(filename, options);
}

private static void addInBitmapOptions(BitmapFactory.Options options,ImageCache cache) {
	// inBitmap 只處理可變的點陣圖,所以強制返回可變的點陣圖
	options.inMutable = true;
	if (cache != null) {
		Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
		if (inBitmap != null) {
			options.inBitmap = inBitmap;
		}
	}
}
複製程式碼

10、【推薦】使用 ARGB_565 代替 ARGB_888,在不怎麼降低視覺效果的前提下,減少記憶體佔用。 說明: android.graphics.Bitmap.Config 類中關於圖片顏色的儲存方式定義:

  1. ALPHA_8 代表 8 位 Alpha 點陣圖;
  2. ARGB_4444 代表 16 位 ARGB 點陣圖;
  3. ARGB_8888 代表 32 位 ARGB 點陣圖;
  4. RGB_565 代表 8 位 RGB 點陣圖。

點陣圖位數越高,儲存的顏色資訊越多,影象也就越逼真。大多數場景使用的是ARGB_8888 和 RGB_565,RGB_565 能夠在保證圖片質量的情況下大大減少記憶體的開銷,是解決 oom 的一種方法。但是一定要注意 RGB_565 是沒有透明度的,如果圖片本身需要保留透明度,那麼就不能使用 RGB_565。
正例:

Config config = drawableSave.getOpacity() != PixelFormat.OPAQUE ? Config.ARGB_8888 :
Config.RGB_565;
Bitmap bitmap = Bitmap.createBitmap(w, h, config);
複製程式碼

反例:

Bitmap newb = Bitmap.createBitmap(width, height, Config.ARGB_8888);
複製程式碼

擴充套件參考:

  1. www.jianshu.com/p/294b390eb…
  2. www.programcreek.com/java-api-ex…

11、【推薦】儘量減少 Bitmap (BitmapDrawable)的使用,儘量使用純色(ColorDrawable)、漸變色(GradientDrawable)、StateSelector(StateListDrawable)等與 Shape 結合的形式構建繪圖。
12、【推薦】謹慎使用 gif 圖片,注意限制每個頁面允許同時播放的 gif 圖片,以及單個gif 圖片的大小。
13、【參考】大圖片資源不要直接打包到 apk,可以考慮通過檔案倉庫遠端下載,減小包體積。
14、【推薦】根據裝置效能,選擇性開啟複雜動畫,以實現一個整體較優的效能和體驗;
15、【推薦】在有強依賴 onAnimationEnd 回撥的互動時,如動畫播放完畢才能操作頁面, onAnimationEnd 可能會因各種異常沒被回撥(參考:stackoverflow.com/questions/5…),建議加上超時保護或通過 postDelay 替代onAnimationEnd。
正例:

View v = findViewById(R.id.xxxViewID);
final FadeUpAnimation anim = new FadeUpAnimation(v);
anim.setInterpolator(new AccelerateInterpolator());
anim.setDuration(1000);
anim.setFillAfter(true);
new Handler().postDelayed(new Runnable() {
	public void run() {
		if (v != null) {
			v.clearAnimation();
		}
	}
}, anim.getDuration());
v.startAnimation(anim);
複製程式碼

16、【推薦】當 View Animation 執行結束時,呼叫 View.clearAnimation()釋放相關資源。
正例:

View v = findViewById(R.id.xxxViewID);
final FadeUpAnimation anim = new FadeUpAnimation(v);
anim.setInterpolator(new AccelerateInterpolator());
anim.setDuration(1000);
anim.setFillAfter(true);
anim.setAnimationListener(new AnimationListener() {
	@Override
	public void onAnimationEnd(Animation arg0) {
		//判斷一下資源是否被釋放了
		if (v != null) {
			v.clearAnimation();
		}
	}
});
v.startAnimation(anim);
複製程式碼

阿里Android開發規範:資原始檔命名與使用規範
阿里Android開發規範:四大基本元件
阿里Android開發規範:UI 與佈局
阿里Android開發規範:程式、執行緒與訊息通訊
阿里Android開發規範:檔案與資料庫
阿里Android開發規範:Bitmap、Drawable 與動畫
阿里Android開發規範:安全與其他

相關文章