背景
2018春節(jié)余味尚未消态辛,阿里巴巴為移動開發(fā)者們準(zhǔn)備了一份遲到的新年禮物——《阿里巴巴Android開發(fā)手冊》1.0.1版本起惕。
在此寫下我的閱讀筆記,記錄下自己平時沒有注意的一些問題廷支,規(guī)范自己秦士。
正文
1.【強(qiáng)制】Activity 間通過隱式 Intent 的跳轉(zhuǎn),在發(fā)出 Intent 之前必須通過 resolveActivity 檢查,避免找不到合適的調(diào)用組件,造成 ActivityNotFoundException 的異常缺厉。
public void viewUrl(String url, String mimeType) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(url), mimeType);
if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ ONLY) != null) {
startActivity(intent);
} else {
// 找不到指定的 Activity
}
}
2.【強(qiáng)制】避免在 BroadcastReceiver#onReceive()中執(zhí)行耗時操作,如果有耗時工作, 應(yīng)該創(chuàng)建 IntentService 完成,而不應(yīng)該在 BroadcastReceiver 內(nèi)創(chuàng)建子線程去做。
說明:
由于該方法是在主線程執(zhí)行,如果執(zhí)行耗時操作會導(dǎo)致 UI 不流暢伍宦⊙克溃可以使用IntentService 、 創(chuàng) 建 HandlerThread 或 者 調(diào) 用 Context#registerReceiver (BroadcastReceiver, IntentFilter, String, Handler)方法等方式,在其他 Wroker 線程執(zhí)行 onReceive 方法次洼。BroadcastReceiver#onReceive()方法耗時超過 10 秒鐘,可能會被系統(tǒng)殺死关贵。
3.【 推 薦 】 添 加 Fragment 時 , 確 保 FragmentTransaction#commit() 在 Activity#onPostResume()或者 FragmentActivity#onResumeFragments()內(nèi)調(diào)用。 不要隨意使用 FragmentTransaction#commitAllowingStateLoss()來代替,任何 commitAllowingStateLoss()的使用必須經(jīng)過 code review,確保無負(fù)面影響卖毁。
說明:
Activity 可能因?yàn)楦鞣N原因被銷毀,Android 支持頁面被銷毀前通過Activity#onSaveInstanceState() 保 存 自 己 的 狀 態(tài) 揖曾。 但 如 果FragmentTransaction.commit()發(fā)生在 Activity 狀態(tài)保存之后,就會導(dǎo)致 Activity 重 建落萎、恢復(fù)狀態(tài)時無法還原頁面狀態(tài),從而可能出錯。為了避免給用戶造成不好的體驗(yàn),系統(tǒng)會拋出 IllegalStateExceptionStateLoss 異常炭剪。推薦的做法是在 Activity 的onPostResume() 或 onResumeFragments() ( 對 FragmentActivity ) 里 執(zhí) 行 FragmentTransaction.commit(),如有必要也可在 onCreate()里執(zhí)行练链。不要隨意改用FragmentTransaction.commitAllowingStateLoss() 或 者 直 接 使 用 try-catch 避 免 crash,這不是問題的根本解決之道,當(dāng)且僅當(dāng)你確認(rèn) Activity 重建、恢復(fù)狀態(tài)時,本次 commit 丟失不會造成影響時才可這么做奴拦。
4.【推薦】不要在 Activity#onDestroy()內(nèi)執(zhí)行釋放資源的工作,例如一些工作線程的 銷毀和停止,因?yàn)?onDestroy()執(zhí)行的時機(jī)可能較晚媒鼓。可根據(jù)實(shí)際需要,在 Activity#onPause()/onStop()中結(jié)合 isFinishing()的判斷來執(zhí)行错妖。
5.【推薦】總是使用顯式Intent啟動或者綁定Service,且不要為服務(wù)聲明IntentFilter, 保證應(yīng)用的安全性绿鸣。如果確實(shí)需要使用隱式調(diào)用,則可為 Service 提供 Intent Filter 并從 Intent 中排除相應(yīng)的組件名稱,但必須搭配使用 Intent#setPackage()方法設(shè)置 Intent 的指定包名,這樣可以充分消除目標(biāo)服務(wù)的不確定性。
6.【推薦】對于只用于應(yīng)用內(nèi)的廣播,優(yōu)先使用 LocalBroadcastManager 來進(jìn)行注冊 和發(fā)送,LocalBroadcastManager 安全性更好,同時擁有更高的運(yùn)行效率暂氯。
說明:
對于使用 Context#sendBroadcast()等方法發(fā)送全局廣播的代碼進(jìn)行提示潮模。如果該廣播僅用于應(yīng)用內(nèi),則可以使用 LocalBroadcastManager 來避免廣播泄漏以及廣播被攔截等安全問題,同時相對全局廣播本地廣播的更高效。
7.【推薦】當(dāng)前 Activity 的 onPause 方法執(zhí)行結(jié)束后才會創(chuàng)建(onCreate)或恢復(fù) (onRestart)別的 Activity,所以在 onPause 方法中不適合做耗時較長的工作,這 會影響到頁面之間的跳轉(zhuǎn)效率痴施。
8.【推薦】文本大小使用單位 dp,View 大小使用單位 dp擎厢。對于 TextView,如果在文 字大小確定的情況下推薦使用 wrap_content 布局避免出現(xiàn)文字顯示不全的適配問 題。
說明:
之所以文本大小也推薦使用 dp 而非 sp,因?yàn)?sp 是 Android 早期推薦使用的,但其 實(shí) sp 不僅和 dp 一樣受屏幕密度的影響,還受到系統(tǒng)設(shè)置里字體大小的影響,所以使用 dp 對于應(yīng)用開發(fā)會更加保證 UI 的一致性和還原度辣吃。
9.【推薦】使用 Toast 時,建議定義一個全局的 Toast 對象,這樣可以避免連續(xù)顯示 Toast 時不能取消上一次 Toast 消息的情況动遭。即使需要連續(xù)彈出 Toast,也應(yīng)避免直 接調(diào)用 Toast#makeText。
10.【強(qiáng)制】線程池不允許使用 Executors 去創(chuàng)建,而是通過 ThreadPoolExecutor 的方 式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險齿尽。
說明:
Executors 返回的線程池對象的弊端如下:
FixedThreadPool 和 SingleThreadPool : 允 許 的 請 求 隊(duì) 列 長 度 為Integer.MAX_VALUE,可能會堆積大量的請求,從而導(dǎo)致 OOM;
CachedThreadPool 和 ScheduledThreadPool : 允 許 的 創(chuàng) 建 線 程 數(shù) 量 為Integer.MAX_VALUE,可能會創(chuàng)建大量的線程,從而導(dǎo)致 OOM沽损。
正例:
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
int KEEP_ALIVE_TIME = 1;
TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>();
ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES*2, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT,
taskQueue, new BackgroundThreadFactory(), new DefaultRejectedExecutionHandler());
反例:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
11.【推薦】ThreadPoolExecutor 設(shè)置線程存活時間(setKeepAliveTime),確钡平冢空閑時 線程能被釋放.
12. 【推薦】禁止在多進(jìn)程之間用 SharedPreferences 共享數(shù)據(jù),雖然可以 (MODE_MULTI_PROCESS),但官方已不推薦循头。
13. 【強(qiáng)制】任何時候不要硬編碼文件路徑,請使用 Android 文件系統(tǒng) API 訪問。
說明:
Android 應(yīng)用提供內(nèi)部和外部存儲,分別用于存放應(yīng)用自身數(shù)據(jù)以及應(yīng)用產(chǎn)生的用 戶數(shù)據(jù)炎疆】睿可以通過相關(guān) API 接口獲取對應(yīng)的目錄,進(jìn)行文件操作。
android.os.Environment#getExternalStorageDirectory()
android.os.Environment#getExternalStoragePublicDirectory()
android.content.Context#getFilesDir()
android.content.Context#getCacheDir
正例:
public File getDir(String alName) {
File file = new File(Environment.getExternalStoragePublicDirectory(Environment. DIRECTORY_PICTURES), alName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
反例:
public File getDir(String alName) {
// 任何時候都不要硬編碼文件路徑,這不僅存在安全隱患,也讓 app 更容易出現(xiàn)適配問題
File file = new File("/mnt/sdcard/Download/Album", alName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
14.【強(qiáng)制】當(dāng)使用外部存儲時,必須檢查外部存儲的可用性
正例:
// 讀/寫檢查
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
// 只讀檢查
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
15.【強(qiáng)制】應(yīng)用間共享文件時,不要通過放寬文件系統(tǒng)權(quán)限的方式去實(shí)現(xiàn),而應(yīng)使用 FileProvider形入。
16.【強(qiáng)制】如果 ContentProvider 管理的數(shù)據(jù)存儲在 SQL 數(shù)據(jù)庫中,應(yīng)該避免將不受 信任的外部數(shù)據(jù)直接拼接在原始 SQL 語句中全跨。
???這是個什么梗,都沒說清楚???
正例:
// 使用一個可替換參數(shù)
String mSelectionClause = "var = ?"; String[] selectionArgs = {""}; selectionArgs[0] = mUserInput;
反例:
// 拼接用戶輸入內(nèi)容和列名
String mSelectionClause = "var = " + mUserInput;
17.【強(qiáng)制】png 圖片使用 TinyPNG 或者類似工具壓縮處理,減少包體積亿遂。
18.【推薦】應(yīng)根據(jù)實(shí)際展示需要,壓縮圖片,而不是直接顯示原圖浓若。手機(jī)屏幕比較小,直接顯示原圖,并不會增加視覺上的收益,但是卻會耗費(fèi)大量寶貴的內(nèi)存。
正例:
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);
// 然后根據(jù)圖片分辨率以及我們實(shí)際需要展示的大小,計算壓縮率
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 設(shè)置壓縮率,并解碼
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
19.【強(qiáng)制】在 Activity#onPause()或 Activity#onStop()回調(diào)中,關(guān)閉當(dāng)前 activity 正在執(zhí) 行的的動畫蛇数。
正例:
public class MyActivity extends Activity {
ImageView mImageView;
Animation mAnimation;
Button mBtn;
@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);
}
};
}
@Override
public void onPause() {
//頁面退出,及時清理動畫資源
mImageView.clearAnimation();
}
}
20.【推薦】使用 RGB_565 代替 RGB_888,在不怎么降低視覺效果的前提下,減少內(nèi)存占用挪钓。
說明:
android.graphics.Bitmap.Config 類中關(guān)于圖片顏色的存儲方式定義:
- ALPHA_8 代表 8 位 Alpha 位圖;
- ARGB_4444 代表 16 位 ARGB 位圖;
- ARGB_8888 代表 32 位 ARGB 位圖;
- RGB_565 代表 8 位 RGB 位圖。
位圖位數(shù)越高,存儲的顏色信息越多,圖像也就越逼真耳舅。大多數(shù)場景使用的是ARGB_8888 和 RGB_565,RGB_565 能夠在保證圖片質(zhì)量的情況下大大減少內(nèi)存的開銷,是解決 OOM 的一種方法碌上。
但是一定要注意 RGB_565 是沒有透明度的,如果圖片本身需要保留透明度,那么就不能使用 RGB_565。
正例:
Config config = drawableSave.getOpacity() != PixelFormat.OPAQUE ? Config.ARGB_8565 : Config.RGB_565;
Bitmap bitmap = Bitmap.createBitmap(w, h, config);
反例:
Bitmap newb = Bitmap.createBitmap(width, height, Config.ARGB_8888);
21.【推薦】在有強(qiáng)依賴 onAnimationEnd 回調(diào)的交互時,如動畫播放完畢才能操作頁面,onAnimationEnd 可能會因各種異常沒被回調(diào)(參考: https://stackoverflow.com/questions/5474923/onanimationend-is-not-getting-calle d-onanimationstart-works-fine ), 建 議 加 上 超 時 保 護(hù) 或 通 過 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);
22.【推薦】當(dāng) View Animation 執(zhí)行結(jié)束時,調(diào)用 View.clearAnimation()釋放相關(guān)資源馏予。
正例:
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);
總結(jié)
說真的天梧,這手冊總結(jié)得挺好的,雖然內(nèi)容少了點(diǎn)霞丧,但是才1.0.1版本呢岗,還會繼續(xù)修改完善的。
我覺得上面的第8點(diǎn)寫得不太合理:
8.【推薦】文本大小使用單位 dp,View 大小使用單位 dp蛹尝。對于 TextView,如果在文 字大小確定的情況下推薦使用 wrap_content 布局避免出現(xiàn)文字顯示不全的適配問 題敷燎。
說明:
之所以文本大小也推薦使用 dp 而非 sp,因?yàn)?sp 是 Android 早期推薦使用的,但其 實(shí) sp 不僅和 dp 一樣受屏幕密度的影響,還受到系統(tǒng)設(shè)置里字體大小的影響,所以使用 dp 對于應(yīng)用開發(fā)會更加保證 UI 的一致性和還原度。
我覺得:如果用戶設(shè)置了系統(tǒng)字體大小箩言,那么肯定是希望系統(tǒng)整體字體變大或變小硬贯,而你的APP卻不怎么變,這看起來一來不協(xié)調(diào)陨收,二來沒有達(dá)到用戶修改系統(tǒng)字體大小的目的饭豹,感覺這樣的做法有點(diǎn)破壞系統(tǒng)的生態(tài),不推薦這樣做务漩。