閱讀《阿里巴巴Android開發手冊1.0.1》筆記

瘋震震發表於2018-03-10

背景

2018春節餘味尚未消,阿里巴巴為移動開發者們準備了一份遲到的新年禮物——《阿里巴巴Android開發手冊》1.0.1版本。

在此寫下我的閱讀筆記,記錄下自己平時沒有注意的一些問題,規範自己。

正文

1.【強制】Activity 間通過隱式 Intent 的跳轉,在發出 Intent 之前必須通過 resolveActivity 檢查,避免找不到合適的呼叫元件,造成 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.【強制】避免在 BroadcastReceiver#onReceive()中執行耗時操作,如果有耗時工作, 應該建立 IntentService 完成,而不應該在 BroadcastReceiver 內建立子執行緒去做。

說明:

由於該方法是在主執行緒執行,如果執行耗時操作會導致 UI 不流暢。可以使用IntentService 、 創 建 HandlerThread 或 者 調 用 Context#registerReceiver (BroadcastReceiver, IntentFilter, String, Handler)方法等方式,在其他 Wroker 執行緒執行 onReceive 方法。BroadcastReceiver#onReceive()方法耗時超過 10 秒鐘,可能會被系統殺死。

3.【 推 薦 】 添 加 Fragment 時 , 確 保 FragmentTransaction#commit() 在 Activity#onPostResume()或者 FragmentActivity#onResumeFragments()內呼叫。 不要隨意使用 FragmentTransaction#commitAllowingStateLoss()來代替,任何 commitAllowingStateLoss()的使用必須經過 code review,確保無負面影響。

說明:

Activity 可能因為各種原因被銷燬,Android 支援頁面被銷燬前通過Activity#onSaveInstanceState() 保 存 自 己 的 狀 態 。 但 如 果FragmentTransaction.commit()發生在 Activity 狀態儲存之後,就會導致 Activity 重 建、恢復狀態時無法還原頁面狀態,從而可能出錯。為了避免給使用者造成不好的體驗,系統會丟擲 IllegalStateExceptionStateLoss 異常。推薦的做法是在 Activity 的onPostResume() 或 onResumeFragments() ( 對 FragmentActivity ) 裡 執 行 FragmentTransaction.commit(),如有必要也可在 onCreate()裡執行。不要隨意改用FragmentTransaction.commitAllowingStateLoss() 或 者 直 接 使 用 try-catch 避 免 crash,這不是問題的根本解決之道,當且僅當你確認 Activity 重建、恢復狀態時,本次 commit 丟失不會造成影響時才可這麼做。

4.【推薦】不要在 Activity#onDestroy()內執行釋放資源的工作,例如一些工作執行緒的 銷燬和停止,因為 onDestroy()執行的時機可能較晚。可根據實際需要,在 Activity#onPause()/onStop()中結合 isFinishing()的判斷來執行。

5.【推薦】總是使用顯式Intent啟動或者繫結Service,且不要為服務宣告IntentFilter, 保證應用的安全性。如果確實需要使用隱式呼叫,則可為 Service 提供 Intent Filter 並從 Intent 中排除相應的元件名稱,但必須搭配使用 Intent#setPackage()方法設定 Intent 的指定包名,這樣可以充分消除目標服務的不確定性。

6.【推薦】對於只用於應用內的廣播,優先使用 LocalBroadcastManager 來進行註冊 和傳送,LocalBroadcastManager 安全性更好,同時擁有更高的執行效率。

說明:

對於使用 Context#sendBroadcast()等方法傳送全域性廣播的程式碼進行提示。如果該廣播僅用於應用內,則可以使用 LocalBroadcastManager 來避免廣播洩漏以及廣播被攔截等安全問題,同時相對全域性廣播本地廣播的更高效。

7.【推薦】當前 Activity 的 onPause 方法執行結束後才會建立(onCreate)或恢復 (onRestart)別的 Activity,所以在 onPause 方法中不適合做耗時較長的工作,這 會影響到頁面之間的跳轉效率。

8.【推薦】文字大小使用單位 dp,View 大小使用單位 dp。對於 TextView,如果在文 字大小確定的情況下推薦使用 wrap_content 佈局避免出現文字顯示不全的適配問 題。

說明:

之所以文字大小也推薦使用 dp 而非 sp,因為 sp 是 Android 早期推薦使用的,但其 實 sp 不僅和 dp 一樣受螢幕密度的影響,還受到系統設定裡字型大小的影響,所以使用 dp 對於應用開發會更加保證 UI 的一致性和還原度。

9.【推薦】使用 Toast 時,建議定義一個全域性的 Toast 物件,這樣可以避免連續顯示 Toast 時不能取消上一次 Toast 訊息的情況。即使需要連續彈出 Toast,也應避免直 接呼叫 Toast#makeText。

10.【強制】執行緒池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方 式,這樣的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險。

說明:

Executors 返回的執行緒池物件的弊端如下:

  1. FixedThreadPool 和 SingleThreadPool : 允 許 的 請 求 隊 列 長 度 為Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM;

  2. CachedThreadPool 和 ScheduledThreadPool : 允 許 的 創 建 線 程 數 量 為Integer.MAX_VALUE,可能會建立大量的執行緒,從而導致 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 設定執行緒存活時間(setKeepAliveTime),確保空閒時 執行緒能被釋放.

12. 【推薦】禁止在多程式之間用 SharedPreferences 共享資料,雖然可以 (MODE_MULTI_PROCESS),但官方已不推薦。

13. 【強制】任何時候不要硬編碼檔案路徑,請使用 Android 檔案系統 API 訪問。

說明:

Android 應用提供內部和外部儲存,分別用於存放應用自身資料以及應用產生的用 戶資料。可以通過相關 API 介面獲取對應的目錄,進行檔案操作。

  1. android.os.Environment#getExternalStorageDirectory()

  2. android.os.Environment#getExternalStoragePublicDirectory()

  3. android.content.Context#getFilesDir()

  4. 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 更容易出現適配問題 
	File file = new File("/mnt/sdcard/Download/Album", alName);
	if (!file.mkdirs()) {
		Log.e(LOG_TAG, "Directory not created");
	}
	return file;
}

複製程式碼

14.【強制】當使用外部儲存時,必須檢查外部儲存的可用性

正例:

// 讀/寫檢查
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.【強制】應用間共享檔案時,不要通過放寬檔案系統許可權的方式去實現,而應使用 FileProvider。

16.【強制】如果 ContentProvider 管理的資料儲存在 SQL 資料庫中,應該避免將不受 信任的外部資料直接拼接在原始 SQL 語句中。

???這是個什麼梗,都沒說清楚???

正例:

// 使用一個可替換引數
String mSelectionClause = "var = ?"; String[] selectionArgs = {""}; selectionArgs[0] = mUserInput;

複製程式碼

反例:

// 拼接使用者輸入內容和列名
String mSelectionClause = "var = " + mUserInput;

複製程式碼

17.【強制】png 圖片使用 TinyPNG 或者類似工具壓縮處理,減少包體積。

18.【推薦】應根據實際展示需要,壓縮圖片,而不是直接顯示原圖。手機螢幕比較小,直接顯示原圖,並不會增加視覺上的收益,但是卻會耗費大量寶貴的記憶體。

正例:

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);
}

複製程式碼

19.【強制】在 Activity#onPause()或 Activity#onStop()回撥中,關閉當前 activity 正在執 行的的動畫。

正例:

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,在不怎麼降低視覺效果的前提下,減少記憶體佔用。

說明:

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_8565 : Config.RGB_565;
Bitmap bitmap = Bitmap.createBitmap(w, h, config);

複製程式碼

反例:

Bitmap newb = Bitmap.createBitmap(width, height, Config.ARGB_8888);

複製程式碼

21.【推薦】在有強依賴 onAnimationEnd 回撥的互動時,如動畫播放完畢才能操作頁面,onAnimationEnd 可能會因各種異常沒被回撥(參考: https://stackoverflow.com/questions/5474923/onanimationend-is-not-getting-calle d-onanimationstart-works-fine ), 建 議 加 上 超 時 保 護 或 通 過 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.【推薦】當 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);

複製程式碼

總結

說真的,這手冊總結得挺好的,雖然內容少了點,但是才1.0.1版本,還會繼續修改完善的。

我覺得上面的第8點寫得不太合理:

8.【推薦】文字大小使用單位 dp,View 大小使用單位 dp。對於 TextView,如果在文 字大小確定的情況下推薦使用 wrap_content 佈局避免出現文字顯示不全的適配問 題。

說明:

之所以文字大小也推薦使用 dp 而非 sp,因為 sp 是 Android 早期推薦使用的,但其 實 sp 不僅和 dp 一樣受螢幕密度的影響,還受到系統設定裡字型大小的影響,所以使用 dp 對於應用開發會更加保證 UI 的一致性和還原度。

我覺得:如果使用者設定了系統字型大小,那麼肯定是希望系統整體字型變大或變小,而你的APP卻不怎麼變,這看起來一來不協調,二來沒有達到使用者修改系統字型大小的目的,感覺這樣的做法有點破壞系統的生態,不推薦這樣做。

相關文章