Android開發(fā)規(guī)范
命名規(guī)范
- 資源文件需要帶模塊名前綴(模塊化實(shí)行暫無需),以小寫加下劃線方式命名.
- layout文件命名方式
Activity 的 layout以module_activity開頭
Fragment 的 layout 以 module_fragment 開頭
Dialog 的 layout 以 module_dialog 開頭
include 的 layout 以 module_include 開頭
ListView 的行 layout 以 module_list_item 開頭
RecyclerView 的 item layout 以 module_recycle_item 開頭
GridView 的 item layout 以 module_grid_item 開頭
- drawable 資源命名方式
模塊名_業(yè)務(wù)功能描述_控件描述_控件狀態(tài)限定詞
如:module_login_btn_pressed,module_tabs_icon_home_normal
- anim 資源命名方式
模塊名_邏輯名稱_[方向|序號]
module_fade_in , module_fade_out , module_push_down_in
module_loading_grey_001
- color 資源使用#AARRGGBB 格式,寫入 module_colors.xml 文件中留拾,命名格式采用以下規(guī)則:
模塊名_邏輯名稱_顏色
<color name="module_btn_bg_color">#33b5e5e5</color>
- dimen 寫入 module_dimens.xml 文件中羹奉,采用以下規(guī)則:
模塊名_描述信息
<dimen name="module_horizontal_line_height">1dp</dimen>
- style 資源采用“父 style 名稱.當(dāng)前 style 名稱”方式命名,寫入module_styles.xml文件中煤辨,首字母大寫:
<style name="ParentTheme.ThisActivityTheme">
…
</style>
- string資源文件或者文本用到字符需要全部寫入module_strings.xml文件中裳涛,采用以下規(guī)則:
模塊名_邏輯名稱
moudule_login_tips,module_homepage_notice_desc
- Id 資源原則上以駝峰法命名木张,View 組件的資源 id 建議以 View 的縮寫作為
前綴。常用縮寫表如下:
控件 縮寫
LinearLayout ll
RelativeLayout rl
ConstraintLayout cl
ListView lv
ScollView sv
TextView tv
Button btn
ImageView iv
CheckBox cb
RadioButton rb
EditText et
其它控件的縮寫推薦使用小寫字母并用下劃線進(jìn)行分割端三,例如:ProgressBar 對應(yīng)
的縮寫為 progress_bar舷礼;DatePicker 對應(yīng)的縮寫為 date_picker。
基本組件規(guī)范
- 討論Activity 間的數(shù)據(jù)通信郊闯,對于數(shù)據(jù)量比較大的且轨,避免使用 Intent + Parcelable的方式,可以考慮EventBus等替代方案虚婿,以免造成TransactionTooLargeException.
- Activity#onSaveInstanceState()方法不是 Activity 生命周期方法旋奢,它是用來在 Activity 被意外銷毀時(shí)保存 UI 狀態(tài)的,只能用于保存臨時(shí)性數(shù)據(jù)然痊,例如UI控件的屬性等至朗,不能跟數(shù)據(jù)的持久化存儲混為一談。持久化存儲應(yīng)該在 Activity#onPause()/onStop()中實(shí)行.
- Activity 間通過隱式 Intent 的跳轉(zhuǎn)剧浸,在發(fā)出 Intent 之前必須通過 resolveActivity 檢查锹引,避免找不到合適的調(diào)用組件,造成 ActivityNotFoundException 的異常唆香。
- 避免在 Service#onStartCommand()/onBind()方法中執(zhí)行耗時(shí)操作嫌变,如果有需求,應(yīng)改用 IntentService 或采用其他異步機(jī)制完成.
- 避免在 BroadcastReceiver#onReceive()中執(zhí)行耗時(shí)操作躬它,如果有耗時(shí)工作腾啥,應(yīng)該創(chuàng)建 IntentService 完成,而不應(yīng)該在 BroadcastReceiver 內(nèi)創(chuàng)建子線程去做.
- 避免使用隱式 Intent 廣播敏感信息冯吓,信息可能被其他注冊了對應(yīng) BroadcastReceiver 的 App 接收,如果廣播僅限于應(yīng)用內(nèi)倘待,則可以使用 LocalBroadcastManager#sendBroadcast()實(shí)現(xiàn),避免敏感信息外泄和 Intent 攔截的風(fēng)險(xiǎn).
- 添 加 Fragment 時(shí)组贺,確保 FragmentTransaction#commit() 在 Activity#onPostResume()或者 FragmentActivity#onResumeFragments()內(nèi)調(diào)用凸舵。不要隨意使用 FragmentTransaction#commitAllowingStateLoss()來代替.
- 不要在 Activity#onDestroy()內(nèi)執(zhí)行釋放資源的工作,例如一些工作線程的銷毀和停止失尖,因?yàn)?onDestroy()執(zhí)行的時(shí)機(jī)可能較晚啊奄。可根據(jù)實(shí)際需要掀潮,在 Activity#onPause()/onStop() 中結(jié)合 isFinishing()的判斷來執(zhí)行.
- 如非必須菇夸,避免使用嵌套的 Fragment.
-
總是使用顯式 Intent 啟動或者綁定 Service,且不要為服務(wù)聲明 Intent Filter胧辽,
保證應(yīng)用的安全性峻仇。如確實(shí)需要使用隱式調(diào)用公黑,則可為 Service 提供 Intent Filter 并從 Intent 中排除相應(yīng)的組件名稱邑商,必須搭配使用 Intent#setPackage()方法設(shè)置 Intent 的指定包名摄咆,消除目標(biāo)服務(wù)的不確定性. - Service 需要以多線程來并發(fā)處理多個(gè)啟動請求,建議使用 IntentService人断,避免復(fù)雜的設(shè)置吭从。Service 組件一般運(yùn)行主線程,避免耗時(shí)操作恶迈,如果有耗時(shí)操作應(yīng)該在 Worker 線程執(zhí)行,推薦使用 IntentService 執(zhí)行后臺任務(wù).
- 對于只用于應(yīng)用內(nèi)的廣播涩金,優(yōu)先使用 LocalBroadcastManager 來進(jìn)行注冊和發(fā)送,LocalBroadcastManager 安全性更好暇仲,同時(shí)擁有更高的運(yùn)行效率.
- 當(dāng)前 Activity 的 onPause 方法執(zhí)行結(jié)束后才會創(chuàng)建(onCreate)或恢復(fù)(onRestart)別的 Activity步做,所以在 onPause 方法中不適合做耗時(shí)較長的工作,這會影響頁面之間的跳轉(zhuǎn)效率.(注:所有耗時(shí)任務(wù)都不應(yīng)在主線程進(jìn)行,在頁面跳轉(zhuǎn)的時(shí)候涉及到流暢性問題,所以需要格外注意).
- Activity或者Fragment中動態(tài)注冊BroadCastReceiver時(shí)奈附,registerReceiver() 和 unregisterReceiver()要成對(生命周期對應(yīng))出現(xiàn).Activity 的生命周期不對應(yīng)全度,可能出現(xiàn)多次 onResume 造成 receiver 注冊多個(gè),但最終只注銷一個(gè)斥滤,其余 receiver 產(chǎn)生內(nèi)存泄漏将鸵。
- Android 基礎(chǔ)組件如果使用隱式調(diào)用,應(yīng)在 AndroidManifest.xml 中使用 <intent-filter> 或在代碼中使用 IntentFilter 增加過濾.
UI 與布局
- 使用 ConstraintLayout,盡量不要嵌套,降低布局層級(ViewStub,merge).
- 在 Activity 中顯示對話框或彈出浮層時(shí)佑颇,盡量使用 DialogFragment顶掉,而非 Dialog/AlertDialog,這樣便于隨Activity生命周期管理對話框/彈出浮層的生命周期.
- 討論: 文本大小使用單位 dp挑胸,View 大小使用單位 dp痒筒。對于 TextView,如果在文字大小確定的情況下推薦使用 wrap_content 布局避免出現(xiàn)文字顯示不全的適配問題茬贵。
- 禁止在設(shè)計(jì)布局時(shí)多次為子 View 和父 View 設(shè)置同樣背景進(jìn)而造成頁面過度繪制凸克,無需顯示的布局及時(shí)隱藏.
- 在需要時(shí)刻刷新某一區(qū)域的組件時(shí),建議通過以下方式避免引發(fā)全局 layout 刷新:
1) 設(shè)置固定的 View 大小的寬高闷沥,如倒計(jì)時(shí)組件等萎战;
2) 調(diào)用 View 的 layout 方法修改位置,如彈幕組件等舆逃;
3) 通過修改 Canvas 位置并且調(diào)用 invalidate(int l, int t, int r, int b)等方式限定刷新區(qū)域蚂维;
4) 通過設(shè)置一個(gè)是否允許requestLayout的變量,然后重寫控件的requestlayout路狮、 onSizeChanged
方法虫啥,判斷控件的大小沒有改變的情況下,當(dāng)進(jìn)入 requestLayout 的時(shí)候奄妨,直接返回而不調(diào)用 super
的 requestLayout 方法涂籽。
- 不能在 Activity 沒有完全顯示時(shí)顯示 PopupWindow 和 Dialog,推薦在 Activity#onAttachedToWindow()/Activity#onWindowFocusChanged() 之后創(chuàng)建對話框.
- 盡量不要使用 AnimationDrawable,它在初始化的時(shí)候就將所有圖片加載到內(nèi)存中砸抛,特別占內(nèi)存评雌,并且還不能釋放树枫,釋放之后下次進(jìn)入再次加載時(shí)會報(bào)錯(cuò).
- 不能使用 ScrollView 包裹 ListView/GridView/ExpandableListVIew;這樣會把 ListView 的所有 Item 加載到內(nèi)存中,會消耗巨大的內(nèi)存和 cpu 資源,推薦使用 NestedScrollView.
- 不要在 Android 的 Application 對象中緩存數(shù)據(jù)景东。
討論:
1. 基礎(chǔ)組件之間的數(shù)據(jù)共享使用 Intent 等機(jī)制
2. 使用 SharedPreferences 等數(shù)據(jù)持久化機(jī)制.
3. 使用kotlin object 單例全局對象.
- 使用 Toast 時(shí)砂轻,建議定義一個(gè)全局的 Toast 對象,這樣可以避免連續(xù)顯示 Toast 時(shí)不能取消上一次 Toast 消息的情況斤吐。即使需要連續(xù)彈出 Toast搔涝,也應(yīng)避免直接調(diào)用 Toast#makeText.
- 使用 Adapter 的時(shí)候,如果使用 ViewHolder 做緩存和措,在 getView()方法中無論 convertView 的每個(gè)子控件是否需要設(shè)置屬性(比如某個(gè) TextView 設(shè)置的文本為 null庄呈,背景色為透明),都需要為其顯式設(shè)置屬性派阱,否則在滑動過程中抒痒,因?yàn)?adapter item 復(fù)用的原因,會出現(xiàn)內(nèi)容的顯示錯(cuò)亂.
進(jìn)程颁褂、線程與消息通信
- 不要通過 Intent 在 Android 基礎(chǔ)組件之間傳遞大數(shù)據(jù)(binder transaction 緩存為 1MB)故响,可能導(dǎo)致 OOM,TransactionTooLargeException等.
- 在 Application 的業(yè)務(wù)初始化代碼加入進(jìn)程判斷,確保只在自己需要的進(jìn)程初始化.
- 新建線程時(shí)颁独,必須通過線程池提供(AsyncTask,ThreadPoolExecutor,Rxjava,kotlin 協(xié)程,或其他形式自定義線程池,不能直接使用Executors)彩届,不允許在應(yīng)用中自行顯式創(chuàng)建線程.
- 新建線程時(shí),定義能識別自己業(yè)務(wù)的線程名稱誓酒,便于性能優(yōu)化和問題排查.
- ThreadPoolExecutor 設(shè)置線程存活時(shí)間(setKeepAliveTime)樟蠕,確保空閑時(shí)線程能被釋放.
- 禁止在多進(jìn)程之間用 SharedPreferences 共享數(shù)據(jù)靠柑,雖然可以(MODE_MULTI_PROCESS)寨辩,但官方已不推薦。
文件與數(shù)據(jù)庫
- 不要硬編碼文件路徑歼冰,使用 Android 文件系統(tǒng) API 訪問
android.os.Environment#getExternalStorageDirectory()
android.os.Environment#getExternalStoragePublicDirectory()
android.content.Context#getFilesDir()
android.content.Context#getCacheDir
- 當(dāng)使用外部存儲時(shí)靡狞,必須檢查外部存儲的可用性
// 讀/寫檢查
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;
}
- 應(yīng)用間共享文件時(shí),不要通過放寬文件系統(tǒng)權(quán)限的方式去實(shí)現(xiàn)隔嫡,而應(yīng)使用 FileProvider.
- SharedPreference 中只能存儲簡單數(shù)據(jù)類型(int甸怕、boolean、String 等)腮恩,復(fù)雜數(shù)據(jù)類型建議使用文件梢杭、數(shù)據(jù)庫等其他方式存儲.
- SharedPreference 提交數(shù)據(jù)時(shí),盡量使用 Editor#apply() 秸滴,而非 Editor#commit()武契。一般來講,僅當(dāng)需要確定提交結(jié)果,并據(jù)此有后續(xù)操作時(shí)咒唆,才使用 Editor#commit(),commit 會直接讀寫磁盤,頻繁操作性能不好.
- 數(shù)據(jù)庫 Cursor 必須確保使用完后關(guān)閉届垫,以免內(nèi)存泄漏.
- 多線程操作寫入數(shù)據(jù)庫時(shí),需要使用事務(wù)钧排,以免出現(xiàn)同步問題.
- 大數(shù)據(jù)寫入數(shù)據(jù)庫時(shí)敦腔,使用事務(wù)或其他能夠提高 I/O 效率的機(jī)制均澳,保證執(zhí)行速度.
- 執(zhí)行 SQL 語句時(shí)恨溜,應(yīng)使用 SQLiteDatabase#insert()、update()找前、delete()糟袁,不要使用 SQLiteDatabase#execSQL(),以免 SQL 注入風(fēng)險(xiǎn).
Bitmap躺盛、Drawable 與動畫
- 在 ListView项戴,ViewPager,RecyclerView槽惫,GirdView 等組件中使用圖片時(shí)周叮,應(yīng)做好圖片的緩存,避免始終持有圖片導(dǎo)致內(nèi)存溢出界斜,也避免重復(fù)創(chuàng)建圖片仿耽,引起性能問題。建議 使 用 Fresco( https://github.com/facebook/fresco )各薇、 Glide ( https://github.com/bumptech/glide )等圖片庫.
- png 圖片使用 TinyPNG 或者類似工具壓縮處理项贺,減少包體積.
- 應(yīng)根據(jù)實(shí)際展示需要,壓縮圖片峭判,而不是直接顯示原圖.(Glide)
- 使用完畢的圖片开缎,應(yīng)該及時(shí)回收,釋放寶貴的內(nèi)存.(Glide)
- 在 #onPause()或 #onStop()回調(diào)中林螃,關(guān)閉當(dāng)前正在執(zhí)行的的動畫.
- 在動畫或者其他異步任務(wù)結(jié)束時(shí)奕删,應(yīng)該考慮回調(diào)時(shí)刻的環(huán)境是否還支持業(yè)務(wù)處理。例如 Activity 的 onStop()函數(shù)已經(jīng)執(zhí)行疗认,且在該函數(shù)中主動釋放了資源急侥,此時(shí)回調(diào)中如果不做判斷就會空指針崩潰(Kotlin ?. !!.)
- 使用 inBitmap 重復(fù)利用內(nèi)存空間,避免重復(fù)開辟新內(nèi)存(Glide 待確認(rèn))
public static Bitmap decodeSampledBitmapFromFile(String filename, int reqWidth, int
reqHeight, ImageCache cache) {
final BitmapFactory.Options options = new BitmapFactory.Options();
...
BitmapFactory.decodeFile(filename, options);
...
// 如果在 Honeycomb 或更新版本系統(tǒng)中運(yùn)行侮邀,嘗試使用 inBitmap
if (Utils.hasHoneycomb()) {
addInBitmapOptions(options, cache);
}
...
return BitmapFactory.decodeFile(filename, options);
}
private static void addInBitmapOptions(BitmapFactory.Options options,
ImageCache cache) {
// inBitmap 只處理可變的位圖坏怪,所以強(qiáng)制返回可變的位圖
options.inMutable = true;
if (cache != null) {
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
if (inBitmap != null) {
options.inBitmap = inBitmap;
}
}
}
- 使用 RGB_565 代替 RGB_888,在不怎么降低視覺效果的前提下绊茧,減少內(nèi)存占用.(Glide).
- 盡量減少 Bitmap(BitmapDrawable)的使用铝宵,盡量使用純色(ColorDrawable)、漸變色(GradientDrawable)、StateSelector(StateListDrawable)等與 Shape 結(jié)合的形式構(gòu)建繪圖.
- 謹(jǐn)慎使用 gif 圖片鹏秋,注意限制每個(gè)頁面允許同時(shí)播放的 gif 圖片尊蚁,以及單個(gè) gif 圖片的大小.
- 非首次加載必須的大圖片資源不要直接打包到 apk,可以考慮通過文件倉庫遠(yuǎn)程下載侣夷,減小包體積.
- 在有強(qiáng)依賴 onAnimationEnd 回調(diào)的交互時(shí)横朋,如動畫播放完畢才能操作頁面 , onAnimationEnd 可能會因各種異常沒被回調(diào) (參考:https://stackoverflow.com/questions/5474923/onanimationend-is-not-getting-called-onanimationstart-works-fine )百拓,建議加上超時(shí)保護(hù)或通過 postDelay 替代 onAnimationEnd.
- View Animation 執(zhí)行結(jié)束時(shí)琴锭,調(diào)用 View.clearAnimation()釋放相關(guān)資源.
安全
- 將 android:allowbackup 屬性必須設(shè)置為 false,阻止應(yīng)用數(shù)據(jù)被導(dǎo)出.
<application
android:allowBackup="false">
- 如果使用自定義 HostnameVerifier 實(shí)現(xiàn)類衙传,必須在 verify()方法中校驗(yàn)服務(wù)器主機(jī)名的合法性决帖,否則可能受到中間人攻擊.
- 如果使用自定義 X509TrustManager 實(shí)現(xiàn)類,必須在 checkServerTrusted() 方法中校驗(yàn)服務(wù)端證書的合法性蓖捶,否則可能受到中間人攻擊.
- 在 SDK 支持的情況下地回,Android 應(yīng)用必須使用 V2 簽名,這將對 APK 文件的修改做更多的保護(hù)(默認(rèn)已開啟,不能手動去關(guān)閉)
- 所有的 Android 基本組件(Activity俊鱼、Service刻像、BroadcastReceiver萍肆、阿里巴巴 Android 開發(fā)手冊ContentProvider 等)都不應(yīng)在沒有嚴(yán)格權(quán)限控制的情況下梳庆,將 android:exported 設(shè)置為 true.
- WebView 應(yīng)設(shè)置
WebView#getSettings()#setAllowFileAccess(false)惰爬、WebView#getSettings()#setAllowFileAccessFromFileURLs(false)喊积、WebView#getSettings()#setAllowUniversalAccessFromFileURLs(false)
梢夯,阻止 filescheme URL 的訪問.
- 不要把敏感信息打印到 log 中.
- 確保應(yīng)用發(fā)布版本的 android:debuggable 屬性設(shè)置為 false.
- 本地加密秘鑰不能硬編碼在代碼中痕寓,更不能使用 SharedPreferences 等本地持久化機(jī)制存儲清酥。應(yīng)選擇 Android 自身的秘鑰庫(KeyStore)機(jī)制或者其他安全性更高的安全解決方案保存讶踪。
- 使用 Android 的 AES/DES/DESede 加密算法時(shí)购公,不要使用 ECB 加密模式萌京,應(yīng)使用 CBC 或 CFB 加密模式.
- Android APP 在 HTTPS 通信中,驗(yàn)證策略需要改成嚴(yán)格模式
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
- zip 中不要包含 ../../file 這樣的路徑宏浩,可能被篡改目錄結(jié)構(gòu)知残,造成攻擊。
//對重要的 Zip 壓縮包文件進(jìn)行數(shù)字簽名校驗(yàn)比庄,校驗(yàn)通過才進(jìn)行解壓
String entryName = entry.getName();
if (entryName.contains("..")){
throw new Exception("unsecurity zipfile!");
}
- MD5 和 SHA-1求妹、SHA-256 等常用算法是 Hash 算法,有一定的安全性佳窑,但不能代替加密算法制恍。敏感信息的存儲和傳輸,需要使用專業(yè)的加密機(jī)制.
其他
- 不能使用 System.out.println 打印 log.
- Log 的 tag 不能是" ".