Android 回爐 阿里巴巴Android開發規范

初衷:

開發 Android 更加高效、高質量地進行 App 開發,呈現給用戶體驗好、性能優、穩定性佳、安全性高的產品。

目的:
  1. 防患未然,提升質量意識,降低故障率和維護成本;
  2. 標準統一,提升協作效率;
  3. 追求卓越的工匠精神,打磨精品代碼。
一、Android 資源文件命名與使用
  1. layout 命名方式:模塊+下劃線+功能 如: module_activity,module_fragment,module_recycle_item。
  2. drawable 命名方式:模塊名業務功能描述控件描述_控件狀態限定詞 如:module_login_btn_pressed
  3. anim 命名方式:模塊名邏輯名稱[方向|序號] 如:module_fade_in ,module_fade_out
  4. color 命名方式:模塊名邏輯名稱顏色 如: <color name="module_btn_bg_color">#33b5e5e5</color>
  5. dimen 命名方式:module_dimens.xml 模塊名_描述信息 如: <dimen name="module_horizontal_line_height">1dp</dimen>
  6. style 命名方式:module_styles.xml 父style 名稱點當前 style 名稱 如:ParentTheme.ThisActivityTheme
  7. string 命名方式:module_string.xml 模塊名_邏輯名稱 如:moudule_login_tips
  8. Id 資源原則上以駝峰法命名,View組件的資源 id 需要以 View 的縮寫作為前綴
  9. Android為多種屏幕提供不同的資源目錄進行適配。為不同屏幕密度提供不同的位圖可繪制對象,可用于密度特定資源的配置限定符(在下面詳述) 包括:ldpi(低)、mdpi(中)、 hdpi(高)、xhdpi(超高)、xxhdpi(超超高)和 xxxhdpi(超超超高)
二、Android 基本組件
(1)Activity

注意:

  1. Activity間的數據通信,對于數據量比較大的,避免使用 Intent + Parcelable的方式,可以考慮EventBus等替代方案,以免造TransactionTooLargeException。
  2. 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_DEFAULONLY) != null) { 
        try { 
         startActivity(intent); 
        } catch (ActivityNotFoundException e) { 
            if (Config.LOGD) { 
                Log.d(LOGTAG, "activity not found for " + mimeType + " over " + 
                Uri.parse(url). getScheme(), e); 
            } 
        } 
    } 
}
  1. Activity#onSaveInstanceState()方法不是Activity生命周期方法,也不保證一定會被調用。它是用來在 Activity 被意外銷毀時保存 UI 狀態的,只能用于保存臨時性數據,例如 UI 控件的屬性等,不能跟數據的持久化存儲混為一談。持久化存儲應該在Activity#onPause()/onStop()中實行。
  2. 不要在 Activity#onDestroy()內執行釋放資源的工作,例如一些工作線程的銷毀和停止,因為 onDestroy()執行的時機可能較晚。可根據實際需要,在Activity#onPause()/onStop()中結合isFinishing()的判斷來執行。
  3. Activity或者Fragment中動態注冊BroadCastReceiver時,registerReceiver()和unregisterReceiver()要成對出現。
(2)Service
  1. 避免在 Service#onStartCommand()/onBind()方法中執行耗時操作,如果確實有需求,應改用 IntentService 或采用其他異步機制完成。
    例:
public class MainActivity extends Activity { 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.main); 
    } 
public void startIntentService(View source) { 
    Intent intent = new Intent(this, MyIntentService.class); 
    startService(intent); 
    } 
} 
public class MyIntentService extends IntentService { 
    public MyIntentService() { 
        super("MyIntentService"); 
    } 
    @Override 
    protected void onHandleIntent(Intent intent) { 
        synchronized (this) { 
            try { 
                ...... 
            } catch (Exception e) { 
            } 
      }
   }
}
  1. 總是使用顯式Intent 啟動或者綁定Service,且不要為服務聲明Intent Filter,
    保證應用的安全性。如果確實需要使用隱式調用,則可為Service 提供Intent Filter
    并從Intent 中排除相應的組件名稱,但必須搭配使用Intent#setPackage()方法設置
    Intent 的指定包名,這樣可以充分消除目標服務的不確定性。
  2. Service 需要以多線程來并發處理多個啟動請求,建議使用 IntentService,可避免各種復雜的設置。
    說明: Service 組件一般運行主線程,應當避免耗時操作,如果有耗時操作應該在 Worker線程執行。 可以使用IntentService 執行后臺任務。
    例:
public class SingleIntentService extends IntentService { 
    public SingleIntentService() { 
        super("single-service thread"); 
    } 
    @Override 
    protected void onHandleIntent(Intent intent) { 
        try { 
            ...... 
        } catch (InterruptedException e) { 
        e.printStackTrace(); 
        } 
    } 
} 
  1. 避免在BroadcastReceiver#onReceive()中執行耗時操作,如果有耗時工作,應該創建IntentService 完成,而不應該在 BroadcastReceiver 內創建子線程去做。
    說明:由于該方法是在主線程執行,如果執行耗時操作會導致 UI 不流暢。
    例:
IntentFilter filter = new IntentFilter(); 
filter.addAction(LOGIN_SUCCESS); 
this.registerReceiver(mBroadcastReceiver, filter);  
mBroadcastReceiver = new BroadcastReceiver() { 
    @Override 
    public void onReceive(Context context, Intent intent) { 
        Intent userHomeIntent = new Intent(); 
        userHomeIntent.setClass(this, UseHomeActivity.class); 
        this.startActivity(userHomeIntent); 
    } 
};

(3)廣播(BroadcastReceiver)

  1. 避免使用隱式 Intent 廣播敏感信息,信息可能被其他注冊了對應
    BroadcastReceiver 的App 接收。
    說明: 通過Context#sendBroadcast()發送的隱式廣播會被所有感興趣的 receiver接收,意應用注冊監聽該廣播的 receiver 可能會獲取到 Intent 中傳遞的敏感信息,并進其他危險操作。如果發送的廣播為使用 Context#sendOrderedBroadcast()方法發
    的有序廣播,優先級較高的惡意 receiver 可能直接丟棄該廣播,造成服務不可用
    或者向廣播結果塞入惡意數據。 如果廣播僅限于應用內,則可以使用LocalBroadcastManager#sendBroadcast()實現,避免敏感信息外泄和 Intent 攔截的風險。
    例:
Intent intent = new Intent("my-sensitive-event"); 
intent.putExtra("event", "this is a test event"); 
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
  1. 對于只用于應用內的廣播,優先使用 LocalBroadcastManager 來進行注冊
    和發送,LocalBroadcastManager 安全性更好,同時擁有更高的運行效率。
    說明:對于使用Context#sendBroadcast()等方法發送全局廣播的代碼進行提示。如果該廣
    播僅用于應用內,則可以使用 LocalBroadcastManager 來避免廣播泄漏以及廣播被
    攔截等安全問題,同時相對全局廣播本地廣播的更高效。
    例:
public class MainActivity extends ActionBarActivity { 
    private MyReceiver receiver; 
    private IntentFilter filter; 
    private Context context; 
    private static final String MY_BROADCAST_TAG = "com.example.localbroadcast"; 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
        receiver = new MyReceiver(); 
        filter = new IntentFilter(); 
        filter.addAction(MY_BROADCAST_TAG); 
        Button button = (Button) findViewById(R.id.button); 
        button.setOnClickListener(new View.OnClickListener() { 
            @Override 
            public void onClick(View view) { 
                Intent intent = new Intent(); 
                intent.setAction(MY_BROADCAST_TAG); 
                LocalBroadcastManager.getInstance(context).sendBroadcast(intent); 
                           } 
        }); 
    } 
    @Override 
    protected void onResume() { 
        super.onResume(); 
        LocalBroadcastManager.getInstance(context).registerReceiver(receiver, filter); 
    } 
    @Override 
    protected void onPause() { 
        super.onPause(); 
        LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver); 
    } 
    class MyReceiver extends BroadcastReceiver { 
        @Override 
        public void onReceive(Context arg0, Intent arg1) { 
            // message received 
        } 
    } 
}

(4)其他注意

  1. 不要在 Android 的 Application 對象中緩存數據。基礎組件之間的數據共享
    請使用Intent 等機制,也可使用 SharedPreferences 等數據持久化機制。
  2. 添加 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 丟失不會造成影響時才可這么做。
    例:
public class MainActivity extends FragmentActivity { 
    FragmentManager fragmentManager;  
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main2);  
        fragmentManager = getSupportFragmentManager(); 
        FragmentTransaction ft = fragmentManager.beginTransaction(); 
        MyFragment fragment = new MyFragment(); 
        ft.replace(R.id.fragment_container, fragment);  
        ft.commit(); 
    } 
} 
  1. 如非必須,避免使用嵌套的 Fragment。
    說明:嵌套 Fragment 是在 Android API 17 添加到 SDK 以及 Support 庫中的功能,
    Fragment 嵌套使用會有一些坑,容易出現 bug,比較常見的問題有如下幾種:
  1. onActivityResult()方法的處理錯亂,內嵌的 Fragment 可能收不到該方法的回調
    需要由宿主 Fragment進行轉發處理;
  2. 突變動畫效果;
  3. 被繼承的 setRetainInstance(),導致在 Fragment 重建時多次觸發不必要的邏
    輯。
    非必須的場景盡可能避免使用嵌套 Fragment,如需使用請注意上述問題。
    例:
FragmentManager fragmentManager = getFragmentManager(); 
Fragment fragment = fragmentManager.findFragmentByTag(FragmentB.TAG); 
if (null == fragment) { 
    FragmentB fragmentB = new FragmentB(); 
    FragmentTransaction fragmentTransaction = fragmentManager.beginTransactio
    fragmentTransaction.add(R.id.fragment_container, fragmentB, 
FragmentB.TAG).commit(); 
} 
  1. 使用 Toast 時,建議定義一個全局的 Toast 對象,這樣可以避免連續顯示Toast 時不能取消上一次 Toast 消息的情況(如果你有連續彈出 Toast 的情況,避免使用Toast.makeText)。
  2. 使用 Adapter 的時候,如果你使用了 ViewHolder 做緩存,在 getView()的方法中無論這項 convertView 的每個子控件是否需要設置屬性(比如某個 TextView設置的文本可能為 null,某個按鈕的背景色為透明,某控件的顏色為透明等),都需要為其顯式設置屬性(Textview的文本為空也需要設置 setText(""),背景透明也需要設置),否則在滑動的過程中,因為 adapter item 復用的原因,會出現內容的顯示錯亂。
    例:
@Override   
public View getView(int position, View convertView, ViewGroup parent) { 
    ViewHolder myViews; 
    if (convertView == null) { 
        myViews = new ViewHolder(); 
        convertView = mInflater.inflate(R.layout.list_item, null); 
        myViews.mUsername = (TextView)convertView.findViewById(R.id.username);
        convertView.setTag(myViews); 
    } else { 
    myViews = (ViewHolder)convertView.getTag(); 
    } 
    Info p = infoList.get(position); 
    String dn = p.getDisplayName; 
        myViews.mUsername.setText(StringUtils.isEmpty(dn) ? "" : dn); 
    return convertView; 
} 
static class ViewHolder { 
    private TextView mUsername; 
} 
三、UI 與布局
  1. 布局中不得不使用 ViewGroup 多重嵌套時,不要使用 LinearLayout 嵌套,
    改用RelativeLayout,可以有效降低嵌套數。
    說明: Android 應用頁面上任何一個 View 都需要經過 measure、layout、draw 三個步驟才能被正確的渲染。從 xml layout 的頂部節點開始進行 measure,每個子節點都需要向自己的父節點提供自己的尺寸來決定展示的位置,在此過程中可能還會重新measure(由此可能導致 measure 的時間消耗為原來的 2-3 倍)。節點所處位置越深,套嵌帶來的 measure 越多,計算就會越費時。這就是為什么扁平的 View 結構會性能更好。
    同時,頁面擁上的 View越多,measure、layout、draw所花費的時間就越久。要縮短這個時間,關鍵是保持 View的樹形結構盡量扁平,而且要移除所有不需要渲染的View。理想情況下,總共的 measure,layout,draw時間應該被很好的控制在 16ms以內,以保證滑動屏幕時 UI 的流暢。 要找到那些多余的 View(增加渲染延遲的 view),可以用 Android Studio Monitor里的Hierarachy Viewer 工具,可視化的查看所有的 view。
  2. 在 Activity 中顯示對話框或彈出浮層時,盡量使用 DialogFragment,而非Dialog/AlertDialog,這樣便于隨Activity生命周期管理對話框/彈出浮層的生命周期。
    例:
public void showPromptDialog(String text){ 
    DialogFragment promptDialog = new DialogFragment() { 
        @Override 
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle 
savedInstanceState) { 
            getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); 
            View view = inflater.inflate(R.layout.fragment_prompt, container); 
            return view; 
        } 
    }; 
    promptDialog.show(getFragmentManager(), text); 
} `
  1. 禁止在非 ui線程進行view相關操作。
  2. 文本大小使用單位 dp,view 大小使用單位 dp。對于 Textview,如果在文字大小確定的情況下推薦使用 wrap_content 布局避免出現文字顯示不全的適配問題。
  3. 禁止在設計布局時多次設置子 view 和父 view 中為同樣的背景造成頁面過度繪制,推薦將不需要顯示的布局進行及時隱藏。
  4. 靈活使用布局,推薦 Merge、ViewStub 來優化布局,盡可能多的減少 UI布局層級,推薦使用 FrameLayout,LinearLayout、RelativeLayout 次之。
  5. 在需要時刻刷新某一區域的組件時,建議通過以下方式避免引發全局 layout刷新:
    1. 設置固定的 view大小的高寬,如倒計時組件等;
    1. 調用view的 layout 方式修改位置,如彈幕組件等;
    1. 通過修改canvas 位置并且調用invalidate(int l, int t, int r, int b)等方式限定刷新區域;
    1. 通過設置一個是否允許 requestLayout 的變量,然后重寫控件的 requestlayout、onSizeChanged 方法,判斷控件的大小沒有改變的情況下,當進入requestLayout 的時候,直接返回而不調用 super的requestLayout 方法。
  1. 不能在 Activity沒有完全顯示時顯示 PopupWindow和Dialog。
  2. 盡量不要使用 AnimationDrawable,它在初始化的時候就將所有圖片加載到內存中,特別占內存,并且還不能釋放,釋放之后下次進入再次加載時會報錯。
    說明: Android 的幀動畫可以使用 AnimationDrawable 實現,但是如果你的幀動畫中如果包含過多幀圖片,一次性加載所有幀圖片所導致的內存消耗會使低端機發生OOM異常。幀動畫所使用的圖片要注意降低內存消耗,當圖片比較大時,容易出現OOM。
  3. 不能使用 ScrollView 包裹 ListView/GridView/ExpandableListVIew;因為這樣會把 ListView 的所有 Item 都加載到內存中,要消耗巨大的內存和 cpu 去繪制圖面。
    說明:ScrollView中嵌套 List 或RecyclerView的做法官方明確禁止。除了開發過程中遇到的各種視覺和交互問題,這種做法對性能也有較大損耗。ListView等UI 組件自身有垂直滾動功能,也沒有必要在嵌套一層 ScrollView。目前為了較好的 UI 體驗,更貼近Material Design 的設計,推薦使用 NestedScrollView。
四、進程、線程與消息通信
  1. 不要通過 Intent 在 Android 基礎組件之間傳遞大數據(binder transaction緩存為1MB),可能導致 OOM。
  2. 在 Application 的業務初始化代碼加入進程判斷,確保只在自己需要的進程初始化。特別是后臺進程減少不必要的業務初始化。
    例:
public class MyApplication extends Application { 
    @Override 
    public void onCreate() { 
        //在所有進程中初始化 
        .... 
        //僅在主進程中初始化 
        if (mainProcess) { 
        ... 
        }  
        //僅在后臺進程中初始化 
        if (bgProcess) { 
            ... 
        } 
    } 
} 
  1. 新建線程時,必須通過線程池提供(AsyncTask 或者 ThreadPoolExecutor或者其他形式自定義的線程池),不允許在應用中自行顯式創建線程。
    說明: 使用線程池的好處是減少在創建和銷毀線程上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。另外創建匿名線程不便于后續的資源使用分析,對性能分析等會造成困擾。
  2. 線程池不允許使用 Executors 去創建,而是通過ThreadPoolExecutor 的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
    說明: Executors 返回的線程池對象的弊端如下:
    1. FixedThreadPool 和 SingleThreadPool : 允 許 的 請 求 隊 列 長 度 為Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM;
    1. CachedThreadPool 和 ScheduledThreadPool:允許的創建線程數量為Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。
  1. 子線程中不能更新界面,更新界面必須在主線程中進行,網絡操作不能在主線程中調用。
  2. 不要在非UI 線程中初始化ViewStub,否則會返回null。]
  3. 盡量減少不同 APP 之間的進程間通信及拉起行為。拉起導致占用系統資源,影響用戶體驗。
  4. 新建線程時,定義能識別自己業務的線程名稱,便于性能優化和問題排查
  5. ThreadPoolExecutor 設置線程存活時間(setKeepAliveTime),確保空閑時線程能被釋放。
  6. 禁止在多進程之間用 SharedPreferences 共享數據,雖然可以(MODE_MULTI_PROCESS),但官方已不推薦。
  7. 謹慎使用 Android 的多進程,多進程雖然能夠降低主進程的內存壓力,但會遇到如下問題:
    1. 不能實現完全退出所有 Activity的功能;
    1. 首次進入新啟動進程的頁面時會有延時的現象(有可能黑屏、白屏幾秒,是白屏還是黑屏和新 Activity的主題有關);
    1. 應用內多進程時,Application 實例化多次,需要考慮各個模塊是否都需要在所有進程中初始化;
    1. 多進程間通過 SharedPreferences 共享數據時不穩定。
五、文件與數據庫
  1. 任何時候不要硬編碼文件路徑,請使用 Android 文件系統 API 訪問。 Android 應用提供內部和外部存儲,分別用于存放應用自身數據以及應用產生的用戶數據。可以通過相關 API 接口獲取對應的目錄,進行文件操作。
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; 
} 
  1. 當使用外部存儲時,必須檢查外部存儲的可用性。
// 讀/寫檢查  
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.equals(state) || 
        Environment.MEDIA_MOUNTED_READ_ONLY.equ
        return true; 
    } 
    return false; 
} 
  1. 應用間共享文件時,不要通過放寬文件系統權限的方式去實現,而應使用FileProvider。
  2. SharedPreference 中只能存儲簡單數據類型(int、boolean、String 等),復雜數據類型建議使用文件、數據庫等其他方式存儲。
  3. SharedPreference 提交數據時,盡量使用 Editor#apply(),而非Editor#commit()。一般來講,僅當需要確定提交結果,并據此有后續操作時,才使用Editor#commit()。 SharedPreference 相關修改使用apply方法進行提交會先寫入內存,然后異步寫入磁盤,commit方法是直接寫入磁盤。如果頻繁操作的話apply的性能會優于commit,apply會將最后修改內容寫入磁盤。但是如果希望立刻獲取存儲操作的結果,并據此做相應的其他操作,應當使用 commit。
  4. 數據庫 Cursor必須確保使用完后關閉,以免內存泄漏。Cursor是對數據庫查詢結果集管理的一個類,當查詢的結果集較小時,消耗內存不易察覺。但是當結果集較大,長時間重復操作會導致內存消耗過大,需要開發者在操作完成后手動關閉 Cursor。 數據庫Cursor在創建及使用時,可能發生各種異常,無論程序是否正常結束,必須在最后確保 Cursor 正確關閉,以避免內存泄漏。同時,如果 Cursor 的使用還牽涉多線程場景,那么需要自行保證操作同步。
  5. 多線程操作寫入數據庫時,需要使用事務,以免出現同步問題。Android 的通過SQLiteOpenHelper 獲取數據庫 SQLiteDatabase 實例,Helper中會自動緩存已經打開的SQLiteDatabase實例,單個App中應使用SQLiteOpenHelper的單例模式確保數據庫連接唯一。由于 SQLite 自身是數據庫級鎖,單個數據庫操作是保證線程安全的(不能同時寫入),transaction 時一次原子操作,因此處于事務中的操作是線程安全的。若同時打開多個數據庫連接,并通過多線程寫入數據庫,會導致數據庫異常,提示數據庫已被鎖住。
  6. 大數據寫入數據庫時,請使用事務或其他能夠提高 I/O 效率的機制,保證執行速度。
  7. 執行 SQL 語句時,應使用 SQLiteDatabase#insert()、update()、delete(),不要使用SQLiteDatabase#execSQL(),以免SQL 注入風險。
  8. 如果 ContentProvider 管理的數據存儲在 SQL 數據庫中,應該避免將不受信任的外部數據直接拼接在原始 SQL 語句中,可使用一個用于將 ? 作為可替換參數的選擇子句以及一個單獨的選擇參數數組,會避免 SQL 注入。
六、Bitmap、Drawable與動畫
  1. 加載大圖片或者一次性加載多張圖片,應該在異步線程中進行。圖片的加載,涉及到IO 操作,以及 CPU密集操作,很可能引起卡頓。
  2. 在ListView,ViewPager,RecyclerView,GirdView等組件中使用圖片時,應做好圖片的緩存,避免始終持有圖片導致內存泄露,也避免重復創建圖片,引起性 能 問題 。建 議 使用 Fresco ( https://github.com/facebook/fresco )、 Glide(https://github.com/bumptech/glide)等圖片庫
  3. png圖片使用 tinypng或者類似工具壓縮處理,減少包體積。
  4. 應根據實際展示需要,壓縮圖片,而不是直接顯示原圖。手機屏幕比較小,直接顯示原圖,并不會增加視覺上的收益,但是卻會耗費大量寶貴的內存。
  5. 使用完畢的圖片,應該及時回收,釋放寶貴的內存。
  6. 針對不同的屏幕密度,提供對應的圖片資源,使內存占用和顯示效果達到合理的平衡。如果為了節省包體積,可以在不影響 UI 效果的前提下,省略低密度圖片。
  7. 在 Activity.onPause()或 Activity.onStop()回調中,關閉當前 activity 正在執行的的動畫。
  8. 在動畫或者其他異步任務結束時,應該考慮回調時刻的環境是否還支持業務處理。例如 Activity 的 onStop()函數已經執行,且在該函數中主動釋放了資源,此時回調中如果不做判斷就會空指針崩潰。
  9. 使用 inBitmap 重復利用內存空間,避免重復開辟新內存。
  10. 使用ARGB_565 代替ARGB_888,在不怎么降低視覺效果的前提下,減少內存占用。
  11. 盡量減少 Bitmap(BitmapDrawable)的使用,盡量使用純色(ColorDrawable)、漸變色(GradientDrawable)、StateSelector(StateListDrawable)等與 Shape 結合的形式構建繪圖。
  12. 謹慎使用 gif 圖片,注意限制每個頁面允許同時播放的 gif 圖片,以及單個gif 圖片的大小。
  13. 大圖片資源不要直接打包到 apk,可以考慮通過文件倉庫遠程下載,減小包體積。
  14. 根據設備性能,選擇性開啟復雜動畫,以實現一個整體較優的性能和體驗;
  15. 在有強依賴 onAnimationEnd 回調的交互時,如動畫播放完畢才能操作頁面, onAnimationEnd 可 能 會 因 各 種 異 常 沒 被 回 調 ( 參 考 :https://stackoverflow.com/questions/5474923/onanimationend-is-not-getting-called-onanimationstart-works-fine),建議加上超時保護或通過 postDelay 替代onAnimationEnd。
  16. 當View Animation 執行結束時,調用 View.clearAnimation()釋放相關資源。
七、安全
  1. 使用PendingIntent 時,禁止使用空 intent,同時禁止使用隱式 Intent
    說明:
  1. 使用 PendingIntent 時,使用了空 Intent,會導致惡意用戶劫持修改 Intent 的內容。禁止使用一個空 Intent 去構造 PendingIntent,構造PendingIntent 的Intent一定要設置 ComponentName 或者action。
  2. PendingIntent 可以讓其他APP 中的代碼像是運行自己 APP 中。PendingIntent的intent接收方在使用該intent時與發送方有相同的權限。在使用PendingIntent時,PendingIntent 中包裝的 intent 如果是隱式的 Intent,容易遭到劫持,導致信息泄露。
  1. 禁止使用常量初始化矢量參數構建 IvParameterSpec,建議 IV 通過隨機方式產生。 使用固定初始化向量,結果密碼文本可預測性會高得多,容易受到字典式攻擊。iv的作用主要是用于產生密文的第一個 block,以使最終生成的密文產生差異(明文相同的情況下),使密碼攻擊變得更為困難,除此之外 iv 并無其它用途。因此 iv 通過隨機方式產生是一種十分簡便、有效的途徑。
  2. 將android:allowbackup 屬性設置為 false,防止adb backup 導出數據。 在AndroidManifest.xml 文件中為了方便對程序數據的備份和恢復在 Android APIlevel 8 以后增加了 android:allowBackup 屬性值。默認情況下這個屬性值為true,故當allowBackup 標志值為true 時,即可通過 adb backup 和adb restore 來備份和恢復應用程序數據。
  3. 在實現的 HostnameVerifier 子類中,需要使用 verify 函數效驗服務器主機名的合法性,否則會導致惡意程序利用中間人攻擊繞過主機名效驗。在握手期間,如果 URL 的主機名和服務器的標識主機名不匹配,則驗證機制可以回調此接口的實現程序來確定是否應該允許此連接。如果回調內實現不恰當,默認接受所有域名,則有安全風險。
  4. 利用 X509TrustManager 子類中的 checkServerTrusted 函數效驗服務器端證書的合法性。在實現的 X509TrustManager 子類中未對服務端的證書做檢驗,這樣會導致不被信任的證書繞過證書效驗機制。
  5. META-INF 目錄中不能包含如.apk,.odex,.so 等敏感文件,該文件夾沒有經過簽名,容易被惡意替換。
  6. Receiver/Provider 不能在毫無權限控制的情況下,將 android:export 設置為true。
  7. 數據存儲在 Sqlite 或者輕量級存儲需要對數據進行加密,取出來的時候進行解密。
  8. 阻止webview通過file:schema 方式訪問本地敏感數據。
  9. 不要廣播敏感信息,只能在本應用使用 LocalBroadcast,避免被別的應用收到,或者setPackage 做限制。
  10. 不要把敏感信息打印到 log中。
  11. 對于內部使用的組件,顯示設置組件的"android:exported"屬性為false。Android應用使用 Intent 機制在組件之間傳遞數據,如果應用在使用 getIntent(),getAction(),Intent.getXXXExtra()獲取到空數據、異常或者畸形數據時沒有進行異常捕獲,應用就會發生 Crash,應用不可使用(本地拒絕服務)。惡意應用可通向受害者應用發送此類空數據、異常或者畸形數據從而使應用產生本地拒絕服務。
  12. 應用發布前確保 android:debuggable 屬性設置為false。
  13. 使用Intent Scheme URL 需要做過濾。如果瀏覽器支持 Intent Scheme Uri 語法,如果過濾不當,那么惡意用戶可能通過瀏覽器 js 代碼進行一些惡意行為,比如盜取 cookie 等。如果使用了 Intent.parseUri函 數 , 獲 取 的 intent 必須 嚴格過濾, intent 至少包含addCategory(“android.intent.category.BROWSABLE”) , setComponent(null) ,setSelector(null)3 個策略。
  14. 密鑰加密存儲或者經過變形處理后用于加解密運算,切勿硬編碼到代碼中。應用程序在加解密時,使用硬編碼在程序中的密鑰,攻擊者通過反編譯拿到密鑰可以輕易解密APP 通信數據。
  15. 將所需要動態加載的文件放置在 apk 內部,或應用私有目錄中,如果應用必須要把所加載的文件放置在可被其他應用讀寫的目錄中(比如 sdcard),建議對不可信的加載源進行完整性校驗和白名單處理,以保證不被惡意代碼注入。
  16. 除非min API level >=17,請注意 addJavascriptInterface 的使用,APIlevel>=17,允許js 被調用的函數必須以@JavascriptInterface 進行注解,因此不受影響; 對于 API level < 17,盡量不要使用 addJavascriptInterface,如果一定要用,那么:
  1. 使用https 協議加載 URL,使用證書校驗,防止訪問的頁面被篡改掛馬;
  2. 對加載URL 做白名單過濾、完整性校驗等防止訪問的頁面被篡改;
  3. 如果加載本地 html,應該會HTML 內置在APK 中,以及對 HTML 頁面進行完整性校驗。
  1. 使用Android 的AES/DES/DESede 加密算法時,不要使用默認的加密模式ECB,應顯示指定使用 CBC或CFB加密模式。 加密模式ECB、CBC、CFB、OFB等,其中 ECB 的安全性較弱,會使相同的銘文在不同的時候產生相同的密文,容易遇到字典攻擊,建議使用 CBC或CFB模式。
  1. ECB:Electronic codebook,電子密碼本模式
  2. CBC:Cipher-block chaining,密碼分組鏈接模式
  3. CFB:Cipher feedback,密文反饋模式
  4. OFB:Output feedback,輸出反饋模式
  1. 不要使用 loopback 來通信敏感信息。
  2. 對于不需要使用 File 協議的應用,禁用 File 協議,顯式設置 webView. getSettings().setAllowFileAccess(false),對于需要使用File 協議的應用,禁止File協議調用JavaScript,顯式設置webView.getSettings().setJavaScriptEnabled(false)。
  3. Android APP 在HTTPS 通信中,驗證策略需要改成嚴格模式。 Android APP 在HTTPS 通信中,使用ALLOW_ALL_HOSTNAME_VERIFIER,表示允許和所有的HOST 建立 SSL 通信,這會存在中間人攻擊的風險,最終導致敏感信息可能會被劫持,以及其他形式的攻擊。
  4. Android5.0 以后安全性要求較高的應用應該使用 window.setFlag (LayoutParam.FLAG_SECURE) 禁止錄屏
  5. zip 中不建議允許../../file 這樣的路徑,可能被篡改目錄結構,造成攻擊。 說明:當 zip 壓縮包中允許存在"../"的字符串,攻擊者可以利用多個"../"在解壓時改變zip 文件存放的位置,當文件已經存在是就會進行覆蓋,如果覆蓋掉的文件是 so、dex或者odex文件,就有可能造成嚴重的安全問題。
  6. 開放的 activity/service/receiver 等需要對傳入的 intent 做合法性校驗
  7. 加密算法:使用不安全的 Hash 算法(MD5/SHA-1)加密信息,存在被破解的風險,建議使用 SHA-256 等安全性更高的Hash 算法。
  8. Android WebView組件加載網頁發生證書認證錯誤時,采用默認的處理方法handler.cancel(),停止加載問題頁面。Android WebView組件加載網頁發生證書認證錯誤時,會調用WebViewClient 類的onReceivedSslError 方法,如果該方法實現調用了 handler.proceed()來忽略該證書錯誤,則會受到中間人攻擊的威脅,可能導致隱私泄露.
  9. 直接傳遞命令字或者間接處理有敏感信息或操作時,避免使用 socket 實現,使用能夠控制權限校驗身份的方式通訊。
八、其他
  1. 不要通過Msg傳遞大的對象,會導致內存問題。
  2. 不能使用System.out.println 打印 log。
  3. Log的 tag不能是" "。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,098評論 25 708
  • ¥開啟¥ 【iAPP實現進入界面執行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個線程,因...
    小菜c閱讀 6,510評論 0 17
  • 你穿了我喜歡的顏色,我移不開眼 你做了我喜歡的選擇,我移不開眼 你吃了我喜歡的食物,我移不開眼 你有了我喜歡的世界...
    鯤珂閱讀 171評論 0 1
  • 今天我們主要來講一講金葉榆種植后幫助其成活的4個方法。 一、保水劑:保水劑具有吸水、保水、釋水作用,無毒,呈中性反...
    A錦皓工作室閱讀 421評論 0 0
  • 世界本不是我原本認知的那樣。可我還是習慣性的像原來一樣去想,難道只有我是這樣嗎?難道我把人都想得和我一樣,不對嗎?...
    沒有意義的名字閱讀 251評論 0 0