Android 資源文件命名與使用
【推薦】資源文件需帶模塊前綴。
【推薦】layout 文件的命名方式。
Activity的layout 以module_activity開頭
Fragment的layout 以module_fragment開頭
Dialog的layout 以module_dialog開頭
include的layout 以module_include開頭
ListView的行l(wèi)ayout 以module_list_item開頭
RecyclerView的item layout 以module_recycle_item開頭
GridView的item layout 以module_grid_item開頭【推薦】drawable 資源名稱以
小寫單詞+下劃線的方式命名,根據(jù)分辨率不同存放在
不同的drawable 目錄下,如果介意包大小建議只使用一套,系統(tǒng)去進(jìn)行縮放。采用
規(guī)則如下:
模塊名_業(yè)務(wù)功能描述_控件描述_控件狀態(tài)限定詞
如:module_login_btn_pressed,module_tabs_icon_home_normal【推薦】anim 資源名稱以
小寫單詞+下劃線的方式命名,采用以下規(guī)則:
模塊名_邏輯名稱_[方向|序號(hào)]
Tween 動(dòng)畫(使用簡單圖像變換的動(dòng)畫,例如縮放、平移)資源:盡可能以通用的
動(dòng)畫名稱命名,如module_fade_in,module_fade_out,module_push_down_in (動(dòng) 畫+方向)。
Frame 動(dòng)畫(按幀順序播放圖像的動(dòng)畫)資源:盡可能以模塊+功能命名+序號(hào)。如
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 對(duì)應(yīng)
的縮寫為progress_bar;DatePicker 對(duì)應(yīng)的縮寫為date_picker。
10.【推薦】圖片根據(jù)其分辨率,放在不同屏幕密度的drawable 目錄下管理,否則可能
在低密度設(shè)備上導(dǎo)致內(nèi)存占用增加,又可能在高密度設(shè)備上導(dǎo)致圖片顯示不夠清晰。
說明:
為了支持多種屏幕尺寸和密度,Android 提供了多種通用屏幕密度來適配。常用的
如下。
ldpi - 120dpi
mdpi - 160dpi
hdpi - 240dpi
xhdpi - 320dpi
xxhdpi - 480dpi
xxxhdpi - 640dpi
Android 的屏幕分辨率和密度并不存在嚴(yán)格的對(duì)應(yīng)關(guān)系,應(yīng)盡量避免直接基于分辨
率來開發(fā),而是通過適配不同的屏幕密度來保證控件和圖片的顯示效果。不同密度
drawable 目錄中的圖片分辨率設(shè)置,參考不同密度的dpi 比例關(guān)系。
正例:
為顯示某個(gè)圖標(biāo),將48 x 48 的圖標(biāo)文件放在drawable-mdpi 目錄(160dpi)下;
將72 x 72 的圖標(biāo)文件放在drawable-hdpi 目錄(240dpi)下;將96 x 96 的圖標(biāo)
文件放在drawable-xhdpi 目錄(320dpi)下;將144 x 144 的圖標(biāo)文件放在
drawable-xxhdpi 目錄(480dpi)下。
反例:
上述圖標(biāo),只有一個(gè)144 x 144 的圖標(biāo)文件放在drawable 目錄下。
Android 基本組件
Android 基本組件指Activity 、Fragment、Service、BroadcastReceiver 、
ContentProvider 等等。
【強(qiáng)制】Activity 間的數(shù)據(jù)通信,對(duì)于數(shù)據(jù)量比較大的,避免使用
Intent + Parcelable
的方式,可以考慮EventBus等替代方案,以免造成TransactionTooLargeException。【推薦】
Activity#onSaveInstanceState()方法不是Activity 生命周期方法,也不保證
一定會(huì)被調(diào)用。它是用來在Activity 被意外銷毀時(shí)保存UI 狀態(tài)的,只能用于保存臨
時(shí)性數(shù)據(jù),例如UI 控件的屬性等,不能跟數(shù)據(jù)的持久化存儲(chǔ)混為一談。持久化存儲(chǔ)
應(yīng)該在Activity#onPause()/onStop()中實(shí)行。【強(qiáng)制】Activity 間通過隱式Intent 的跳轉(zhuǎn),在發(fā)出Intent 之前必須通過
resolveActivity
檢查,避免找不到合適的調(diào)用組件,造成ActivityNotFoundException的異常。
正例:
public void viewUrl(String action, String url, String mimeType) {
Intent intent = new Intent(!TextUtils.isEmpty(action) ? action : Intent.ACTION_VIEW);
if (!TextUtils.isEmpty(url) && !TextUtils.isEmpty(mimeType)) {
intent.setDataAndType(Uri.parse(url), mimeType);
} else if (!TextUtils.isEmpty(url)) {
intent.setData(Uri.parse(url));
} else if (!TextUtils.isEmpty(mimeType)) {
intent.setType(mimeType);
}
if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) {
startActivity(intent);
} else {
// 找不到指定的 Activity
Toast.makeText(this, "找不到指定的Activity", Toast.LENGTH_SHORT).show();
}
}
反例:
Intent intent = new Intent();
intent.setAction("com.example.DemoIntent ");
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
- 【強(qiáng)制】避免在
Service#onStartCommand()/onBind()方法中執(zhí)行耗時(shí)操作,如果確
實(shí)有需求,應(yīng)改用IntentService 或采用其他異步機(jī)制完成。
正例:
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) {
}
}
}
}
- 【強(qiáng)制】避免在
BroadcastReceiver#onReceive()中執(zhí)行耗時(shí)操作,如果有耗時(shí)工作,
應(yīng)該創(chuàng)建IntentService 完成,而不應(yīng)該在BroadcastReceiver 內(nèi)創(chuàng)建子線程去做。
說明:
由于該方法是在主線程執(zhí)行,如果執(zhí)行耗時(shí)操作會(huì)導(dǎo)致UI 不流暢??梢允褂?br>IntentService、創(chuàng)建HandlerThread或者調(diào)用Context#registerReceiver (BroadcastReceiver, IntentFilter, String, Handler)方法等方式,在其他Wroker 線程
執(zhí)行onReceive方法。BroadcastReceiver#onReceive()方法耗時(shí)超過10 秒鐘,可
能會(huì)被系統(tǒng)殺死。
正例:
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, UserHomeService.class);
this.startService(userHomeIntent);
}
};
反例
mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
MyDatabaseHelper myDB = new MyDatabaseHelper(context);
myDB.initData();
// have more database operation here
}
};
- 【強(qiáng)制】避免使用隱式Intent 廣播敏感信息,信息可能被其他注冊(cè)了對(duì)應(yīng)
BroadcastReceiver 的App 接收。
說明:
通過Context#sendBroadcast()發(fā)送的隱式廣播會(huì)被所有感興趣的receiver 接收,惡
意應(yīng)用注冊(cè)監(jiān)聽該廣播的receiver 可能會(huì)獲取到Intent 中傳遞的敏感信息,并進(jìn)行
其他危險(xiǎn)操作。如果發(fā)送的廣播為使用Context#sendOrderedBroadcast()方法發(fā)送
的有序廣播,優(yōu)先級(jí)較高的惡意receiver 可能直接丟棄該廣播,造成服務(wù)不可用,
或者向廣播結(jié)果塞入惡意數(shù)據(jù)。
如果廣播僅限于應(yīng)用內(nèi),則可以使用LocalBroadcastManager#sendBroadcast()實(shí)
現(xiàn),避免敏感信息外泄和Intent 攔截的風(fēng)險(xiǎn)。
正例:
Intent intent = new Intent("my-sensitive-event");
intent.putExtra("event", "this is a test event");
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
反例:
Intent intent = new Intent();
v1.setAction("com.sample.action.server_running");
v1.putExtra("local_ip", v0.h);
v1.putExtra("port", v0.i);
v1.putExtra("code", v0.g);
v1.putExtra("connected", v0.s);
v1.putExtra("pwd_predefined", v0.r);
if (!TextUtils.isEmpty(v0.t)) {
v1.putExtra("connected_usr", v0.t);
}
context.sendBroadcast(v1);
以上廣播可能被其他應(yīng)用的如下receiver 接收導(dǎo)致敏感信息泄漏
final class MyReceiver extends BroadcastReceiver {
public final void onReceive(Context context, Intent intent) {
if (intent != null && intent.getAction() != null) {
String s = intent.getAction();
if (s.equals("com.sample.action.server_running") {
String ip = intent.getStringExtra("local_ip");
String pwd = intent.getStringExtra("code");
String port = intent.getIntExtra("port", 8888);
boolean status = intent.getBooleanExtra("connected", false);
}
}
}
}
- 【推薦】添加Fragment 時(shí), 確保
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)保存之后,就會(huì)導(dǎo)致Activity 重
建、恢復(fù)狀態(tài)時(shí)無法還原頁面狀態(tài),從而可能出錯(cuò)。為了避免給用戶造成不好的體驗(yàn),系統(tǒng)會(huì)拋出IllegalStateExceptionStateLoss異常。推薦的做法是在Activity 的
onPostResume()或onResumeFragments() ( 對(duì)FragmentActivity )里執(zhí)行
FragmentTransaction.commit(),如有必要也可在onCreate()里執(zhí)行。不要隨意改用
FragmentTransaction.commitAllowingStateLoss()或者直接使用try-catch 避免
crash,這不是問題的根本解決之道,當(dāng)且僅當(dāng)你確認(rèn)Activity 重建、恢復(fù)狀態(tài)時(shí),
本次commit 丟失不會(huì)造成影響時(shí)才可這么做。
正例:
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();
}
}
反例:
public class MainActivity extends FragmentActivity {
FragmentManager fragmentManager;
@Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState){
super.onSaveInstanceState(outState, outPersistentState);
fragmentManager = getSupportFragmentManager();
FragmentTransaction ft = fragmentManager.beginTransaction();
MyFragment fragment = new MyFragment();
ft.replace(R.id.fragment_container, fragment);
ft.commit();
}
}
【推薦】不要在
Activity#onDestroy()內(nèi)執(zhí)行釋放資源的工作,例如一些工作線程的
銷毀和停止,因?yàn)閛nDestroy()執(zhí)行的時(shí)機(jī)可能較晚??筛鶕?jù)實(shí)際需要,在
Activity#onPause()/onStop()中結(jié)合isFinishing()的判斷來執(zhí)行。【推薦】如非必須,避免使用嵌套的Fragment。
說明:
嵌套Fragment 是在Android API 17添加到SDK以及Support 庫中的功能,F(xiàn)ragment
嵌套使用會(huì)有一些坑,容易出現(xiàn)bug,比較常見的問題有如下幾種:
- onActivityResult()方法的處理錯(cuò)亂,內(nèi)嵌的Fragment 可能收不到該方法的回調(diào),
需要由宿主Fragment 進(jìn)行轉(zhuǎn)發(fā)處理; - 突變動(dòng)畫效果;
- 被繼承的setRetainInstance(),導(dǎo)致在Fragment 重建時(shí)多次觸發(fā)不必要的邏
輯。
非必須的場景盡可能避免使用嵌套Fragment,如需使用請(qǐng)注意上述問題。
正例:
FragmentManager fragmentManager = getFragmentManager();
Fragment fragment = fragmentManager.findFragmentByTag(FragmentB.TAG);
if (null == fragment) {
FragmentB fragmentB = new FragmentB();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fragment_container, fragmentB, FragmentB.TAG).
commit();
}
反例:
Fragment videoFragment = new VideoPlayerFragment();
FragmentTransaction transaction = currentFragment.getChildFragmentManager().beginTransaction();
transaction.add(R.id.video_fragment, videoFragment).commit();
- 【推薦】總是使用顯式Intent 啟動(dòng)或者綁定Service,且不要為服務(wù)聲明Intent Filter,
保證應(yīng)用的安全性。如果確實(shí)需要使用隱式調(diào)用,則可為Service 提供Intent Filter
并從Intent 中排除相應(yīng)的組件名稱,但必須搭配使用Intent#setPackage()方法設(shè)置
Intent 的指定包名,這樣可以充分消除目標(biāo)服務(wù)的不確定性。
11.【推薦】Service 需要以多線程來并發(fā)處理多個(gè)啟動(dòng)請(qǐng)求,建議使用IntentService,
可避免各種復(fù)雜的設(shè)置。
說明:
Service 組件一般運(yùn)行主線程,應(yīng)當(dāng)避免耗時(shí)操作,如果有耗時(shí)操作應(yīng)該在Worker
線程執(zhí)行??梢允褂肐ntentService 執(zhí)行后臺(tái)任務(wù)。
正例:
public class SingleIntentService extends IntentService {
public SingleIntentService() {
super("single-service thread");
}
@Override
protected void onHandleIntent(Intent intent) {
try {
......
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
反例:
public class HelloService extends Service {
...
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override
public void run() {
//操作語句
}
}).start();
...
}
}
12.【推薦】對(duì)于只用于應(yīng)用內(nèi)的廣播,優(yōu)先使用LocalBroadcastManager 來進(jìn)行注冊(cè)
和發(fā)送,LocalBroadcastManager 安全性更好,同時(shí)擁有更高的運(yùn)行效率。
說明:
對(duì)于使用Context#sendBroadcast()等方法發(fā)送全局廣播的代碼進(jìn)行提示。如果該廣
播僅用于應(yīng)用內(nèi),則可以使用LocalBroadcastManager 來避免廣播泄漏以及廣播被
攔截等安全問題,同時(shí)相對(duì)全局廣播本地廣播的更高效。
正例:
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(savedInstsanceState);
context = this;
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
}
}
}
反例:
所有廣播都使用全局廣播
//In activity, sending broadcast
Intent intent = new Intent("com.example.broadcastreceiver.SOME_ACTION");
sendBroadcast(intent);
【推薦】當(dāng)前Activity 的onPause 方法執(zhí)行結(jié)束后才會(huì)創(chuàng)建(onCreate)或恢復(fù)
(onRestart)別的Activity,所以在onPause 方法中不適合做耗時(shí)較長的工作,這
會(huì)影響到頁面之間的跳轉(zhuǎn)效率。【強(qiáng)制】Activity 或者Fragment 中動(dòng)態(tài)注冊(cè)BroadCastReceiver 時(shí),
registerReceiver()
和unregisterReceiver()要成對(duì)出現(xiàn)。
說明:
如果registerReceiver()和unregisterReceiver()不成對(duì)出現(xiàn),則可能導(dǎo)致已經(jīng)注冊(cè)的
receiver 沒有在合適的時(shí)機(jī)注銷,導(dǎo)致內(nèi)存泄漏,占用內(nèi)存空間,加重SystemService
負(fù)擔(dān)。
部分華為的機(jī)型會(huì)對(duì)receiver 進(jìn)行資源管控,單個(gè)應(yīng)用注冊(cè)過多receiver 會(huì)觸發(fā)管
控模塊拋出異常,應(yīng)用直接崩潰。
正例:
public class MainActivity extends AppCompatActivity {
private static MyReceiver myReceiver = new MyReceiver();
...
@Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter("com.example.myservice");
registerReceiver(myReceiver, filter);
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(myReceiver);
}
...
}
反例:
public class MainActivity extends AppCompatActivity {
private static MyReceiver myReceiver;
@Override
protected void onResume() {
super.onResume();
myReceiver = new MyReceiver();
IntentFilter filter = new IntentFilter("com.example.myservice");
registerReceiver(myReceiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(myReceiver);
}
}
Activity 的生命周期不對(duì)應(yīng),可能出現(xiàn)多次onResume 造成receiver 注冊(cè)多個(gè),但
最終只注銷一個(gè),其余receiver 產(chǎn)生內(nèi)存泄漏。
15.【強(qiáng)制】Android 基礎(chǔ)組件如果使用隱式調(diào)用,應(yīng)在 AndroidManifest.xml 中使用
<intent-filter> 或在代碼中使用 IntentFilter 增加過濾。
說明:
如果瀏覽器支持Intent Scheme Uri 語法,如果過濾不當(dāng),那么惡意用戶可能通過瀏
覽器js 代碼進(jìn)行一些惡意行為,比如盜取cookie 等。如果使用了Intent.parseUri
函數(shù),獲取的intent 必須嚴(yán)格過濾。
正例:
// 將intent scheme URL 轉(zhuǎn)換為intent 對(duì)象
Intent intent = Intent.parseUri(uri);
// 禁止沒有BROWSABLE category 的情況下啟動(dòng)activity
intent.addCategory("android.intent.category.BROWSABLE");
intent.setComponent(null);
intent.setSelector(null);
// 使用intent 啟動(dòng)activity
context.startActivityIfNeeded(intent, -1)
反例:
Intent intent = Intent.parseUri(uri.toString().trim().substring(15), 0);
intent.addCategory("android.intent.category.BROWSABLE");
context.startActivity(intent);
UI 與布局
【強(qiáng)制】布局中不得不使用ViewGroup 多重嵌套時(shí),不要使用LinearLayout 嵌套,
改用RelativeLayout,可以有效降低嵌套數(shù)。
說明:
Android 應(yīng)用頁面上任何一個(gè)View 都需要經(jīng)過 measure、layout、draw 三個(gè)步驟
才能被正確的渲染。從xml layout 的頂部節(jié)點(diǎn)開始進(jìn)行measure,每個(gè)子節(jié)點(diǎn)都需
要向自己的父節(jié)點(diǎn)提供自己的尺寸來決定展示的位置,在此過程中可能還會(huì)重新
measure(由此可能導(dǎo)致measure 的時(shí)間消耗為原來的2-3 倍)。節(jié)點(diǎn)所處位置越深,
嵌套帶來的measure 越多,計(jì)算就會(huì)越費(fèi)時(shí)。這就是為什么扁平的View 結(jié)構(gòu)會(huì)性
能更好。
同時(shí),頁面擁上的View 越多,measure、layout、draw 所花費(fèi)的時(shí)間就越久。要縮
短這個(gè)時(shí)間,關(guān)鍵是保持View 的樹形結(jié)構(gòu)盡量扁平,而且要移除所有不需要渲染的
View。理想情況下,總共的measure,layout,draw 時(shí)間應(yīng)該被很好的控制在16ms
以內(nèi),以保證滑動(dòng)屏幕時(shí)UI 的流暢。
要找到那些多余的View(增加渲染延遲的view),可以用Android Studio Monitor
里的Hierarchy Viewer 工具,可視化的查看所有的view。【推薦】在Activity 中顯示對(duì)話框或彈出浮層時(shí),盡量使用DialogFragment,而非
Dialog/AlertDialog,這樣便于隨Activity生命周期管理對(duì)話框/彈出浮層的生命周期。
正例:
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);
}
【推薦】源文件統(tǒng)一采用
UTF-8的形式進(jìn)行編碼。【強(qiáng)制】禁止在非UI 線程進(jìn)行View 相關(guān)操作。
【推薦】文本大小使用單位
dp,View 大小使用單位dp。對(duì)于TextView,如果在文
字大小確定的情況下推薦使用wrap_content 布局避免出現(xiàn)文字顯示不全的適配問
題。
說明:
之所以文本大小也推薦使用dp 而非sp,因?yàn)閟p 是Android 早期推薦使用的,但其
實(shí)sp 不僅和dp 一樣受屏幕密度的影響,還受到系統(tǒng)設(shè)置里字體大小的影響,所以
使用dp 對(duì)于應(yīng)用開發(fā)會(huì)更加保證UI 的一致性和還原度。【強(qiáng)制】禁止在設(shè)計(jì)布局時(shí)多次為子View 和父View 設(shè)置同樣背景進(jìn)而造成頁面過
度繪制,推薦將不需要顯示的布局進(jìn)行及時(shí)隱藏。
正例:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello" />
<Button
android:id="@+id/btn_mybuttom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="click it !" />
<ImageView
android:id="@+id/img"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:src="@drawable/youtube" />
<TextView
android:text="it is an example!"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
反例:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
mPaint.setColor(Color.GRAY);
canvas.drawRect(0, 0, width, height, mPaint);
mPaint.setColor(Color.CYAN);
canvas.drawRect(0, height/4, width, height, mPaint);
mPaint.setColor(Color.DKGRAY);
canvas.drawRect(0, height/3, width, height, mPaint);
mPaint.setColor(Color.LTGRAY);
canvas.drawRect(0, height/2, width, height, mPaint);
}
【推薦】靈活使用布局,推薦
merge、ViewStub來優(yōu)化布局,盡可能多的減少UI
布局層級(jí),推薦使用FrameLayout,LinearLayout、RelativeLayout次之。【推薦】在需要時(shí)刻刷新某一區(qū)域的組件時(shí),建議通過以下方式避免引發(fā)全局layout
刷新:
- 設(shè)置固定的View 大小的寬高,如倒計(jì)時(shí)組件等;
- 調(diào)用View 的layout 方法修改位置,如彈幕組件等;
- 通過修改Canvas 位置并且調(diào)用invalidate(int l, int t, int r, int b)等方式限定刷新
區(qū)域; - 通過設(shè)置一個(gè)是否允許requestLayout 的變量,然后重寫控件的requestlayout、
onSizeChanged 方法, 判斷控件的大小沒有改變的情況下, 當(dāng)進(jìn)入
requestLayout 的時(shí)候,直接返回而不調(diào)用super 的requestLayout 方法。
- 【推薦】不能在Activity 沒有完全顯示時(shí)顯示PopupWindow 和Dialog。
說明:
Android Activity 創(chuàng)建時(shí)的生命周期,按照onCreate() -> onStart() -> onResume() -> onAttachedToWindow() -> onWindowFocusChanged()的順序, 其中在
Activity#onAttachedToWindow() 時(shí),Activity 會(huì)與它的 Window 關(guān)聯(lián),這時(shí) UI 才
會(huì)開始繪制,在 Activity#onWindowFocusChanged() 時(shí),UI 才變成可交互狀態(tài),
可以提示用戶使用。如果在 Window 未關(guān)聯(lián)時(shí)就創(chuàng)建對(duì)話框,UI 可能顯示異常。
推薦的做法是在 Activity#onAttachedToWindow() 之后( 其實(shí)最好是
Activity#onWindowFocusChanged() 之后)才創(chuàng)建對(duì)話框。
10.【推薦】盡量不要使用AnimationDrawable,它在初始化的時(shí)候就將所有圖片加載
到內(nèi)存中,特別占內(nèi)存,并且還不能釋放,釋放之后下次進(jìn)入再次加載時(shí)會(huì)報(bào)錯(cuò)。
說明:
Android 的幀動(dòng)畫可以使用AnimationDrawable 實(shí)現(xiàn),但是如果你的幀動(dòng)畫中如果
包含過多幀圖片,一次性加載所有幀圖片所導(dǎo)致的內(nèi)存消耗會(huì)使低端機(jī)發(fā)生OOM
異常。幀動(dòng)畫所使用的圖片要注意降低內(nèi)存消耗,當(dāng)圖片比較大時(shí),容易出現(xiàn)OOM。
正例:
圖片數(shù)量較少的AnimationDrawable 還是可以接受的。
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot ="true">
<item android:duration="500" android:drawable="@drawable/ic_heart_100"/>
<item android:duration="500" android:drawable="@drawable/ic_heart_75"/>
<item android:duration="500" android:drawable="@drawable/ic_heart_50"/>
<item android:duration="500" android:drawable="@drawable/ic_heart_25"/>
<item android:duration="500" android:drawable="@drawable/ic_heart_0"/>
</animation-list>
反例:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot ="false">
<item android:drawable="@drawable/soundwave_new_1_40" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_41" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_42" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_43" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_44" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_45" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_46" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_47" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_48" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_49" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_50" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_51" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_52" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_53" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_54" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_55" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_56" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_57" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_58" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_59" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_60" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_61" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_62" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_63" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_64" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_65" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_66" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_67" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_68" android:duration="100" />
<item android:drawable="@drawable/soundwave_new_1_69" android:duration="100" />
</animation-list>
上述如此多圖片的動(dòng)畫就不建議使用AnimationDrawable 了。
11.【強(qiáng)制】不能使用ScrollView包裹ListView/GridView/ExpandableListVIew;因?yàn)檫@
樣會(huì)把ListView 的所有Item 都加載到內(nèi)存中,要消耗巨大的內(nèi)存和cpu 去繪制圖
面。
說明:
ScrollView 中嵌套List 或RecyclerView 的做法官方明確禁止。除了開發(fā)過程中遇到
的各種視覺和交互問題,這種做法對(duì)性能也有較大損耗。ListView 等UI 組件自身有
垂直滾動(dòng)功能,也沒有必要在嵌套一層ScrollView。目前為了較好的UI 體驗(yàn),更貼
近Material Design 的設(shè)計(jì),推薦使用NestedScrollView。
正例:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout>
<android.support.v4.widget.NestedScrollView>
<LinearLayout>
<ImageView/>
...
<android.support.v7.widget.RecyclerView/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</LinearLayout>
反例:
<ScrollView>
<LinearLayout>
<TextView/>
...
<ListView/>
<TextView />
</LinearLayout>
</ScrollView>
12.【強(qiáng)制】不要在Android 的Application 對(duì)象中緩存數(shù)據(jù)?;A(chǔ)組件之間的數(shù)據(jù)共享
請(qǐng)使用Intent 等機(jī)制,也可使用SharedPreferences 等數(shù)據(jù)持久化機(jī)制。
反例:
class MyApplication extends Application {
String username;
String getUsername() {
return username;
}
void setUsername(String username) {
this.username = username;
}
}
class SetUsernameActivity extends Activity {
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.set_username);
MyApplication app = (MyApplication) getApplication();
app.setUsername("tester1");
startActivity(new Intent(this, GetUsernameActivity.class));
}
}
class GetUsernameActivity extends Activity {
TextView tv;
@Override
void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.get_username);
tv = (TextView) findViewById(R.id.username);
}
@Override
void onResume() {
super.onResume();
MyApplication app = (MyApplication) getApplication();
tv.setText("Welcome back ! " + app.getUsername().toUpperCase());
}
}
13.【推薦】使用Toast 時(shí),建議定義一個(gè)全局的Toast 對(duì)象,這樣可以避免連續(xù)顯示
Toast 時(shí)不能取消上一次Toast 消息的情況。即使需要連續(xù)彈出Toast,也應(yīng)避免直
接調(diào)用Toast#makeText。
14.【強(qiáng)制】使用Adapter 的時(shí)候,如果你使用了ViewHolder 做緩存,在getView()的
方法中無論這項(xiàng)convertView 的每個(gè)子控件是否需要設(shè)置屬性(比如某個(gè)TextView
設(shè)置的文本可能為null,某個(gè)按鈕的背景色為透明,某控件的顏色為透明等),都需
要為其顯式設(shè)置屬性(Textview 的文本為空也需要設(shè)置setText(""),背景透明也需要
設(shè)置),否則在滑動(dòng)的過程中,因?yàn)閍dapter item 復(fù)用的原因,會(huì)出現(xiàn)內(nèi)容的顯示錯(cuò)
亂。
正例:
@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;
}
進(jìn)程、線程與消息通信
【強(qiáng)制】不要通過Intent 在Android 基礎(chǔ)組件之間傳遞大數(shù)據(jù)(binder transaction
緩存為1MB),可能導(dǎo)致OOM。【強(qiáng)制】在Application 的業(yè)務(wù)初始化代碼加入進(jìn)程判斷,確保只在自己需要的進(jìn)程
初始化。特別是后臺(tái)進(jìn)程減少不必要的業(yè)務(wù)初始化。
正例:
public class MyApplication extends Application {
@Override
public void onCreate() {
//在所有進(jìn)程中初始化
....
//僅在主進(jìn)程中初始化
if (mainProcess) {
...
}
//僅在后臺(tái)進(jìn)程中初始化
if (bgProcess) {
...
}
}
}
- 【強(qiáng)制】新建線程時(shí),必須通過線程池提供(AsyncTask 或者ThreadPoolExecutor或者其他形式自定義的線程池),不允許在應(yīng)用中自行顯式創(chuàng)建線程。
說明:
使用線程池的好處是減少在創(chuàng)建和銷毀線程上所花的時(shí)間以及系統(tǒng)資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導(dǎo)致消耗完內(nèi)存或者“過度切換”的問題。另外創(chuàng)建匿名線程不便于后續(xù)的資源使用分析,
對(duì)性能分析等會(huì)造成困擾。
正例:
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());
//執(zhí)行任務(wù)
executorService.execute(new Runnnable() {
...
});
反例:
new Thread(new Runnable() {
@Override
public void run() {
//操作語句
...
}
}).start();
- 【強(qiáng)制】線程池不允許使用Executors 去創(chuàng)建,而是通過ThreadPoolExecutor 的方
式,這樣的處理方式讓寫的同學(xué)更加明確線程池的運(yùn)行規(guī)則,規(guī)避資源耗盡的風(fēng)險(xiǎn)。
說明:
Executors 返回的線程池對(duì)象的弊端如下:
- FixedThreadPool 和SingleThreadPool : 允許的請(qǐng)求隊(duì)列長度為Integer.MAX_VALUE,可能會(huì)堆積大量的請(qǐng)求,從而導(dǎo)致OOM;
- CachedThreadPool 和ScheduledThreadPool : 允許的創(chuàng)建線程數(shù)量為Integer.MAX_VALUE,可能會(huì)創(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();
【強(qiáng)制】子線程中不能更新界面,更新界面必須在主線程中進(jìn)行,網(wǎng)絡(luò)操作不能在
主線程中調(diào)用。【推薦】盡量減少不同APP 之間的進(jìn)程間通信及拉起行為。拉起導(dǎo)致占用系統(tǒng)資源,
影響用戶體驗(yàn)。【推薦】新建線程時(shí),定義能識(shí)別自己業(yè)務(wù)的線程名稱,便于性能優(yōu)化和問題排查。
正例:
public class MyThread extends Thread {
public MyThread(){
super.setName("ThreadName");
…
}
}
【推薦】ThreadPoolExecutor 設(shè)置線程存活時(shí)間(setKeepAliveTime),確??臻e時(shí)
線程能被釋放。【推薦】禁止在多進(jìn)程之間用SharedPreferences 共享數(shù)據(jù), 雖然可以
(MODE_MULTI_PROCESS),但官方已不推薦。
10.【推薦】謹(jǐn)慎使用Android 的多進(jìn)程,多進(jìn)程雖然能夠降低主進(jìn)程的內(nèi)存壓力,但
會(huì)遇到如下問題:
- 首次進(jìn)入新啟動(dòng)進(jìn)程的頁面時(shí)會(huì)有延時(shí)的現(xiàn)象(有可能黑屏、白屏幾秒,是白
屏還是黑屏和新Activity 的主題有關(guān)); - 應(yīng)用內(nèi)多進(jìn)程時(shí),Application 實(shí)例化多次,需要考慮各個(gè)模塊是否都需要在所
有進(jìn)程中初始化。
文件與數(shù)據(jù)庫
- 【強(qiáng)制】任何時(shí)候不要硬編碼文件路徑,請(qǐng)使用Android 文件系統(tǒng)API 訪問。
說明:
Android 應(yīng)用提供內(nèi)部和外部存儲(chǔ),分別用于存放應(yīng)用自身數(shù)據(jù)以及應(yīng)用產(chǎn)生的用
戶數(shù)據(jù)。可以通過相關(guān)API 接口獲取對(duì)應(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) {
// 任何時(shí)候都不要硬編碼文件路徑,這不僅存在安全隱患,也讓app 更容易出現(xiàn)適配問題
File file = new File("/mnt/sdcard/Download/Album", alName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
- 【強(qiáng)制】當(dāng)使用外部存儲(chǔ)時(shí),必須檢查外部存儲(chǔ)的可用性。
正例:
// 讀/寫檢查
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;
}
- 【強(qiáng)制】應(yīng)用間共享文件時(shí),不要通過放寬文件系統(tǒng)權(quán)限的方式去實(shí)現(xiàn),而應(yīng)使用
FileProvider。
正例:
<!-- AndroidManifest.xml -->
<manifest>
...
<application>
...
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
...
</application>
</manifest>
<!-- res/xml/provider_paths.xml -->
<paths>
<files-path path="album/" name="myimages" />
</paths>
void getAlbumImage(String imagePath) {
File image = new File(imagePath);
Intent getAlbumImageIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Uri imageUri = FileProvider.getUriForFile(this,"com.example.provider",image);
getAlbumImageIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(takePhotoIntent, REQUEST_GET_ALBUMIMAGE);
}
反例:
void getAlbumImage(String imagePath) {
File image = new File(imagePath);
Intent getAlbumImageIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//不要使用file://的URI 分享文件給別的應(yīng)用,包括但不限于Intent
getAlbumImageIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(image));
startActivityForResult(takePhotoIntent, REQUEST_GET_ALBUMIMAGE);
}
- 【推薦】SharedPreference 中只能存儲(chǔ)簡單數(shù)據(jù)類型(int、boolean、String 等),
復(fù)雜數(shù)據(jù)類型建議使用文件、數(shù)據(jù)庫等其他方式存儲(chǔ)。
正例:
public void updateSettings() {
SharedPreferences mySharedPreferences = getSharedPreferences("settings",Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = mySharedPreferences.edit();
editor.putString("id", "foo");
editor.putString("nick", "bar");
//不要把復(fù)雜數(shù)據(jù)類型轉(zhuǎn)成String 存儲(chǔ)
editor.apply();
}
- 【推薦】SharedPreference 提交數(shù)據(jù)時(shí), 盡量使用Editor#apply() , 而非
Editor#commit()。一般來講,僅當(dāng)需要確定提交結(jié)果,并據(jù)此有后續(xù)操作時(shí),才使
用Editor#commit()。
說明:
SharedPreference 相關(guān)修改使用apply 方法進(jìn)行提交會(huì)先寫入內(nèi)存,然后異步寫入
磁盤, commit 方法是直接寫入磁盤。如果頻繁操作的話apply 的性能會(huì)優(yōu)于commit,
apply 會(huì)將最后修改內(nèi)容寫入磁盤。但是如果希望立刻獲取存儲(chǔ)操作的結(jié)果,并據(jù)此
做相應(yīng)的其他操作,應(yīng)當(dāng)使用commit。
正例:
public void updateSettingsAsync() {
SharedPreferences mySharedPreferences = getSharedPreferences("settings",
Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = mySharedPreferences.edit();
editor.putString("id", "foo");
editor.apply();
}
public void updateSettings() {
SharedPreferences mySharedPreferences = getSharedPreferences("settings",
Activity.MODE_PRIVATE);
SharedPreferences.Editor editor = mySharedPreferences.edit();
editor.putString("id", "foo");
if (!editor.commit()) {
Log.e(LOG_TAG, "Failed to commit setting changes");
}
}
反例:
editor.putLong("key_name", "long value");
editor.commit();
- 【強(qiáng)制】數(shù)據(jù)庫Cursor 必須確保使用完后關(guān)閉,以免內(nèi)存泄漏。
說明:
Cursor 是對(duì)數(shù)據(jù)庫查詢結(jié)果集管理的一個(gè)類,當(dāng)查詢的結(jié)果集較小時(shí),消耗內(nèi)存不
易察覺。但是當(dāng)結(jié)果集較大,長時(shí)間重復(fù)操作會(huì)導(dǎo)致內(nèi)存消耗過大,需要開發(fā)者在
操作完成后手動(dòng)關(guān)閉Cursor。
數(shù)據(jù)庫Cursor 在創(chuàng)建及使用時(shí),可能發(fā)生各種異常,無論程序是否正常結(jié)束,必須
在最后確保Cursor 正確關(guān)閉,以避免內(nèi)存泄漏。同時(shí),如果Cursor 的使用還牽涉
多線程場景,那么需要自行保證操作同步。
正例:
public void handlePhotos(SQLiteDatabase db, String userId) {
Cursor cursor;
try {
cursor = db.query(TUserPhoto, new String[]{"userId", "content"}, "userId=?", new
String[]{userId}, null, null, null);
while (cursor.moveToNext()) {
// TODO
}
} catch (Exception e) {
// TODO
} finally {
if (cursor != null) {
cursor.close();
}
}
}
反例:
public void handlePhotos(SQLiteDatabase db, String userId) {
Cursor cursor = db.query(TUserPhoto, new String[] { "userId", "content" }, "userId=?", new String[] { userId }, null, null, null);
while (cursor.moveToNext()) {
// TODO
}
// 不能放任cursor 不關(guān)閉
}
- 【強(qiáng)制】多線程操作寫入數(shù)據(jù)庫時(shí),需要使用事務(wù),以免出現(xiàn)同步問題。
說明:
通過SQLiteOpenHelper 獲取數(shù)據(jù)庫SQLiteDatabase 實(shí)例,Helper 中會(huì)自動(dòng)緩存已經(jīng)打開的SQLiteDatabase 實(shí)例,單個(gè)App 中應(yīng)使用SQLiteOpenHelper 的單例模式確保數(shù)據(jù)庫連接唯一。由于SQLite 自身是數(shù)據(jù)庫級(jí)鎖,單個(gè)數(shù)據(jù)庫操作是保證線程安全的(不能同時(shí)寫入),transaction 是一次原子操作,因此處于事務(wù)中的操作是線程安全的。
若同時(shí)打開多個(gè)數(shù)據(jù)庫連接,并通過多線程寫入數(shù)據(jù)庫,會(huì)導(dǎo)致數(shù)據(jù)庫異常,提示數(shù)據(jù)庫已被鎖住。
正例:
public void insertUserPhoto(SQLiteDatabase db, String userId, String content) {
ContentValues cv = new ContentValues();
cv.put("userId", userId);
cv.put("content", content);
db.beginTransaction();
try {
db.insert(TUserPhoto, null, cv);
// 其他操作
db.setTransactionSuccessful();
} catch (Exception e) {
// TODO
} finally {
db.endTransaction();
}
}
反例:
public void insertUserPhoto(SQLiteDatabase db, String userId, String content) {
ContentValues cv = new ContentValues();
cv.put("userId", userId);
cv.put("content", content);
db.insert(TUserPhoto, null, cv);
}
- 【推薦】大數(shù)據(jù)寫入數(shù)據(jù)庫時(shí),請(qǐng)使用事務(wù)或其他能夠提高I/O 效率的機(jī)制,保證執(zhí)
行速度。
正例:
public void insertBulk(SQLiteDatabase db, ArrayList<UserInfo> users) {
db.beginTransaction();
try {
for (int i = 0; i < users.size; i++) {
ContentValues cv = new ContentValues();
cv.put("userId", users[i].userId);
cv.put("content", users[i].content);
db.insert(TUserPhoto, null, cv);
}
// 其他操作
db.setTransactionSuccessful();
} catch (Exception e) {
// TODO
} finally {
db.endTransaction();
}
}
- 【強(qiáng)制】執(zhí)行SQL 語句時(shí),應(yīng)使用SQLiteDatabase#insert()、update()、delete(),
不要使用SQLiteDatabase#execSQL(),以免SQL 注入風(fēng)險(xiǎn)。
正例:
public int updateUserPhoto(SQLiteDatabase db, String userId, String content) {
ContentValues cv = new ContentValues();
cv.put("content", content);
String[] args = {String.valueOf(userId)};
return db.update(TUserPhoto, cv, "userId=?", args);
}
反例:
public void updateUserPhoto(SQLiteDatabase db, String userId, String content) {
String sqlStmt = String.format("UPDATE %s SET content=%s WHERE userId=%s",TUserPhoto, userId, content);
//請(qǐng)?zhí)岣甙踩庾R(shí),不要直接執(zhí)行字符串作為SQL 語句
db.execSQL(sqlStmt);
}
10.【強(qiáng)制】如果ContentProvider 管理的數(shù)據(jù)存儲(chǔ)在SQL 數(shù)據(jù)庫中,應(yīng)該避免將不受
信任的外部數(shù)據(jù)直接拼接在原始SQL 語句中。
正例:
// 使用一個(gè)可替換參數(shù)
String mSelectionClause = "var = ?";
String[] selectionArgs = {""};
selectionArgs[0] = mUserInput;
反例:
// 拼接用戶輸入內(nèi)容和列名
String mSelectionClause = "var = " + mUserInput;
Bitmap、Drawable 與動(dòng)畫
- 【強(qiáng)制】加載大圖片或者一次性加載多張圖片,應(yīng)該在異步線程中進(jìn)行。圖片的加
載,涉及到IO 操作,以及CPU 密集操作,很可能引起卡頓。
正例:
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
...
// 在后臺(tái)進(jìn)行圖片解碼
@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");
}
});
- 【強(qiáng)制】在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)等圖片庫。
正例:
例如使用系統(tǒng)LruCache 緩存,參考:
https://developer.android.com/topic/performance/graphics/cache-bitmap.html
private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// 獲取可用內(nèi)存的最大值,使用內(nèi)存超出這個(gè)值將拋出OutOfMemory 異常。LruCache 通
過構(gòu)造函數(shù)傳入緩存值,以KB 為單位。
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
// 把最大可用內(nèi)存的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> {
...
// 在后臺(tái)進(jìn)行圖片解碼
@Override
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(),
params[0], 100, 100));
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
return bitmap;
}
...
}
反例:
沒有存儲(chǔ),每次都需要解碼,或者有緩存但是沒有合適的淘汰機(jī)制,導(dǎo)致緩存效果
很差,依然經(jīng)常需要重新解碼。
【強(qiáng)制】png 圖片使用TinyPNG 或者類似工具壓縮處理,減少包體積。
【推薦】應(yīng)根據(jù)實(shí)際展示需要,壓縮圖片,而不是直接顯示原圖。手機(jī)屏幕比較小,
直接顯示原圖,并不會(huì)增加視覺上的收益,但是卻會(huì)耗費(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í)際需要展示的大小,計(jì)算壓縮率
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
// 設(shè)置壓縮率,并解碼
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
反例:
不經(jīng)壓縮顯示原圖。
- 【強(qiáng)制】使用完畢的圖片,應(yīng)該及時(shí)回收,釋放寶貴的內(nèi)存。
正例:
Bitmap bitmap = null;
loadBitmapAsync(new OnResult(result){
bitmap = result;
});
...使用該bitmap...
// 使用結(jié)束,在2.3.3 及以下需要調(diào)用recycle()函數(shù),在2.3.3 以上GC 會(huì)自動(dòng)管理,除非你明
確不需要再用。
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
bitmap.recycle();
}
bitmap = null;
反例:
使用完成圖片,始終不釋放資源。
- 【強(qiáng)制】在Activity#onPause()或Activity#onStop()回調(diào)中,關(guān)閉當(dāng)前activity 正在執(zhí)
行的的動(dòng)畫。
正例:
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() {
//頁面退出,及時(shí)清理動(dòng)畫資源
mImageView.clearAnimation()
}
}
反例:
頁面退出時(shí),不關(guān)閉該頁面相關(guān)的動(dòng)畫。
- 【推薦】在動(dòng)畫或者其他異步任務(wù)結(jié)束時(shí),應(yīng)該考慮回調(diào)時(shí)刻的環(huán)境是否還支持業(yè)
務(wù)處理。例如Activity 的onStop()函數(shù)已經(jīng)執(zhí)行,且在該函數(shù)中主動(dòng)釋放了資源,
此時(shí)回調(diào)中如果不做判斷就會(huì)空指針崩潰。
正例:
public class MyActivity extends Activity {
private ImageView mImageView;
private Animation mAnimation;
@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);
}
}
反例:
動(dòng)畫結(jié)束回調(diào)中,直接使用資源不加判斷,導(dǎo)致異常。
- 【推薦】使用 inBitmap 重復(fù)利用內(nèi)存空間,避免重復(fù)開辟新內(nèi)存。
正例:
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;
}
}
}
9.【推薦】使用RGB_565 代替RGB_888,在不怎么降低視覺效果的前提下,減少內(nèi)
存占用。
說明:
android.graphics.Bitmap.Config 類中關(guān)于圖片顏色的存儲(chǔ)方式定義:
- ALPHA_8 代表8 位Alpha 位圖;
- ARGB_4444 代表16 位ARGB 位圖;
- ARGB_8888 代表32 位ARGB 位圖;
- RGB_565 代表8 位RGB 位圖。
位圖位數(shù)越高,存儲(chǔ)的顏色信息越多,圖像也就越逼真。大多數(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);
10【. 推薦】盡量減少 Bitmap(BitmapDrawable)的使用,盡量使用純色(ColorDrawable)、
漸變色(GradientDrawable)、StateSelector(StateListDrawable)等與Shape 結(jié)
合的形式構(gòu)建繪圖。
11.【推薦】謹(jǐn)慎使用gif 圖片,注意限制每個(gè)頁面允許同時(shí)播放的gif 圖片,以及單個(gè)
gif 圖片的大小。
12.【參考】大圖片資源不要直接打包到apk,可以考慮通過文件倉庫遠(yuǎn)程下載,減小包
體積。
13.【推薦】根據(jù)設(shè)備性能,選擇性開啟復(fù)雜動(dòng)畫,以實(shí)現(xiàn)一個(gè)整體較優(yōu)的性能和體驗(yàn);
14.【推薦】在有強(qiáng)依賴 onAnimationEnd 回調(diào)的交互時(shí),如動(dòng)畫播放完畢才能操作頁
面, onAnimationEnd 可能會(huì)因各種異常沒被回調(diào)( 參考:
https://stackoverflow.com/questions/5474923/onanimationend-is-not-getting-calle
d-onanimationstart-works-fine ), 建議加上超時(shí)保護(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);
15.【推薦】當(dāng)View Animation 執(zhí)行結(jié)束時(shí),調(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);
安全
- 【強(qiáng)制】禁止使用常量初始化矢量參數(shù)構(gòu)建IvParameterSpec,建議IV 通過隨機(jī)方
式產(chǎn)生。
說明:
使用常量初始化向量,密碼文本的可預(yù)測性會(huì)高得多,容易受到字典式攻擊。iv 的
作用主要是用于產(chǎn)生密文的第一個(gè)block,以使最終生成的密文產(chǎn)生差異(明文相同
的情況下),使密碼攻擊變得更為困難。
正例:
byte[] rand = new byte[16];
SecureRandom r = new SecureRandom();
r.nextBytes(rand);
IvParameterSpec iv = new IvParameterSpec(rand);
反例:
IvParameterSpec iv_ = new IvParameterSpec("1234567890".getBytes());
System.out.println(iv.getIV());
- 【強(qiáng)制】將android:allowbackup 屬性必須設(shè)置為false,阻止應(yīng)用數(shù)據(jù)被導(dǎo)出。
說明:
android:allowBackup 原本是 Android 提供的 adb 調(diào)試功能,如果設(shè)置為 true,
可以導(dǎo)出應(yīng)用數(shù)據(jù)備份并在任意設(shè)備上恢復(fù)。這對(duì)應(yīng)用安全性和用戶數(shù)據(jù)隱私構(gòu)成
極大威脅,所以必須設(shè)置為 false,防止數(shù)據(jù)泄露。
正例:
<application
android:allowBackup="false"
android:largeHeap="true"
android:icon="@drawable/test_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
- 【強(qiáng)制】如果使用自定義HostnameVerifier 實(shí)現(xiàn)類,必須在verify()方法中校驗(yàn)服務(wù)
器主機(jī)名的合法性,否則可能受到中間人攻擊。
說明:
在與服務(wù)器建立 https 連接時(shí),如果 URL 的主機(jī)名和服務(wù)器的主機(jī)名不匹配,則
可通過該回調(diào)接口來判斷是否應(yīng)該允許建立連接。如果回調(diào)內(nèi)實(shí)現(xiàn)不恰當(dāng),沒有有
效校驗(yàn)主機(jī)名,甚至默認(rèn)接受所有主機(jī)名,會(huì)大大增加安全風(fēng)險(xiǎn)。
反例:
HostnameVerifier hnv = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
// 不做校驗(yàn),接受任意域名服務(wù)器
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
- 【強(qiáng)制】如果使用自定義X509TrustManager 實(shí)現(xiàn)類,必須在checkServerTrusted()
方法中校驗(yàn)服務(wù)端證書的合法性,否則可能受到中間人攻擊。
說明:
常見誤區(qū)是checkServerTrusted()方法根本沒有實(shí)現(xiàn),這將導(dǎo)致 X509TrustManager
形同虛設(shè)。該方法中需要實(shí)現(xiàn)完備的校驗(yàn)邏輯, 對(duì)于證書錯(cuò)誤拋出
CertificateException 。
正例:
HostnameVerifier hnv = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
if("yourhostname".equals(hostname)){
return true;
} else {
HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
return hv.verify(hostname, session);
}
}
};
反例:
TrustManager tm = new X509TrustManager() {
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
//do nothing,接受任意客戶端證書
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
//do nothing,接受任意服務(wù)端證書
}
public X509Certificate[] getAcceptedIssuers() {
return null;
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
【強(qiáng)制】在SDK 支持的情況下,Android 應(yīng)用必須使用V2 簽名,這將對(duì)APK 文件的修改做更多的保護(hù)。
【強(qiáng)制】所有的 Android 基本組件(Activity、Service、BroadcastReceiver、ContentProvider 等)都不應(yīng)在沒有嚴(yán)格權(quán)限控制的情況下,將 android:exported 設(shè)置為 true。
【強(qiáng)制】WebView 應(yīng)設(shè)置 WebView#getSettings()#setAllowFileAccess(false)、
WebView#getSettings()#setAllowFileAccessFromFileURLs(false) 、
WebView#getSettings()#setAllowUniversalAccessFromFileURLs(false),阻止 file
scheme URL 的訪問。
8.【強(qiáng)制】不要把敏感信息打印到log 中。
說明:
在開發(fā)過程中,為了方便調(diào)試,通常會(huì)使用log 函數(shù)輸出一些關(guān)鍵流程的信息,這
些信息中通常會(huì)包含敏感內(nèi)容,讓攻擊者更加容易了解APP 內(nèi)部結(jié)構(gòu),方便破解和
攻擊,甚至直接獲取到有價(jià)值的敏感信息。
反例:
String username = "log_leak";
String password = "log_leak_pwd";
Log.d("MY_APP", "usesname" + username);
Log.v("MY_APP", "send message to server ");
以上代碼使用Log.d Log.v 打印程序的執(zhí)行過程的username 等調(diào)試信息,日志沒有
關(guān)閉,攻擊者可以直接從Logcat 中讀取這些敏感信息。所以在產(chǎn)品的線上版本中關(guān)
閉調(diào)試接口,不要輸出敏感信息。
9.【強(qiáng)制】確保應(yīng)用發(fā)布版本的android:debuggable 屬性設(shè)置為false。
10.【強(qiáng)制】本地加密秘鑰不能硬編碼在代碼中,更不能使用 SharedPreferences 等本
地持久化機(jī)制存儲(chǔ)。應(yīng)選擇Android 自身的秘鑰庫(KeyStore)機(jī)制或者其他安全
性更高的安全解決方案保存。
說明:
應(yīng)用程序在加解密時(shí),使用硬編碼在程序中的密鑰,攻擊者通過反編譯拿到密鑰可
以輕易解密APP 通信數(shù)據(jù)。
11.【建議】addJavascriptInterface() 可以添加JS 對(duì)本地Java 方法的調(diào)用,但這本身
會(huì)導(dǎo)致惡意代碼的攻擊。在Android 4.2(API Level 17)以下,不應(yīng)再使用這樣的
調(diào)用方式。在Android 4.2 及以上,需要對(duì)本地被遠(yuǎn)程調(diào)用的方法顯式添加
@JavascriptInterface annotation。
12.【強(qiáng)制】使用Android 的AES/DES/DESede 加密算法時(shí),不要使用ECB 加密模式,
應(yīng)使用CBC 或CFB 加密模式。
說明:
加密模式有 ECB、CBC、CFB、OFB 等,其中 ECB 的安全性較弱,如果使用固
定的密鑰,相同的明文將會(huì)生成相同的密文,容易受到字典攻擊,建議使用 CBC、
CFB 或OFB 等模式。
- ECB:Electronic codebook,電子密碼本模式
- CBC:Cipher-block chaining,密碼分組鏈接模式
- CFB:Cipher feedback,密文反饋模式
- OFB:Output feedback,輸出反饋模式
13.【強(qiáng)制】Android APP 在HTTPS 通信中,驗(yàn)證策略需要改成嚴(yán)格模式。
說明:
Android APP 在HTTPS 通信中,使用ALLOW_ALL_HOSTNAME_VERIFIER,表
示允許和所有的HOST 建立SSL 通信,這會(huì)存在中間人攻擊的風(fēng)險(xiǎn),最終導(dǎo)致敏感
信息可能會(huì)被劫持,以及其他形式的攻擊。
反例:
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ALLOW_ALL_HOSTNAME_VERIFIER 關(guān)閉host 驗(yàn)證,允許和所有的host 建立
SSL 通信,BROWSER_COMPATIBLE_HOSTNAME_VERIFIER 和瀏覽器兼容的
驗(yàn)證策略,即通配符能夠匹配所有子域名 ,STRICT_HOSTNAME_VERIFIER 嚴(yán)
格匹配模式,hostname 必須匹配第一個(gè)CN 或者任何一個(gè)subject-alts,以上例子
使用了ALLOW_ALL_HOSTNAME_VERIFIER,需要改成STRICT_HOSTNAME_
VERIFIER。
14.【推薦】在Android 4.2(API Level 17)及以上,對(duì)安全性要求較高的應(yīng)用可在Activity
中,對(duì) Activity 所關(guān)聯(lián)的 Window 應(yīng)用 WindowManager.LayoutParams.FLAG_
SECURE,防止被截屏、錄屏。但要注意的是,一個(gè) Activity 關(guān)聯(lián)的 Window 可
能不止一個(gè),如果使用了 Dialog / DialogFragment 等控件彈出對(duì)話框,它們本身
也會(huì)創(chuàng)建一個(gè)新的 Window,也一樣需要保護(hù)。
15.【推薦】zip 中不要包含 ../../file 這樣的路徑,可能被篡改目錄結(jié)構(gòu),造成攻擊。
說明:
當(dāng)zip 壓縮包中允許存在"../"的字符串,攻擊者可以利用多個(gè)"../"在解壓時(shí)改變zip 文
件存放的位置,當(dāng)文件已經(jīng)存在是就會(huì)進(jìn)行覆蓋,如果覆蓋掉的文件是so、dex 或
者odex 文件,就有可能造成嚴(yán)重的安全問題。
正例:
對(duì)路徑進(jìn)行判斷,存在".."時(shí)拋出異常。
//對(duì)重要的Zip 壓縮包文件進(jìn)行數(shù)字簽名校驗(yàn),校驗(yàn)通過才進(jìn)行解壓
String entryName = entry.getName();
if (entryName.contains("..")){
throw new Exception("unsecurity zipfile!");
}
反例:
BufferedOutputStream dest = null;
try {
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream
("/Users/yunmogong/Documents/test/test.zip")));
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
int count;
byte data[] = new byte[BUFFER];
String entryName = entry.getName();
FileOutputStream fos = new FileOutputStream(entryName);
//System.out.println("Extracting:" + entry);
dest = new BufferedOutputStream(fos, BUFFER);
while ((count = zis.read(data, 0, BUFFER)) != -1) {
dest.write(data, 0, count);
}
dest.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
dest.close();
} catch (IOException e) {
e.printStackTrace();
}
}
16.【推薦】MD5 和SHA-1、SHA-256 等常用算法是Hash 算法,有一定的安全性,
但不能代替加密算法。敏感信息的存儲(chǔ)和傳輸,需要使用專業(yè)的加密機(jī)制。
其他
- 【強(qiáng)制】不能使用System.out.println 打印log。
正例:
Log.d(TAG, "Some Android Debug info ...");
反例:
System.out.println("System out println ...");
- 【強(qiáng)制】Log 的tag 不能是" "。
說明:
日志的tag 是空字符串沒有任何意義,也不利于過濾日志。
正例:
private static String TAG = "LoginActivity";
Log.e(TAG, "Login failed!");
反例:
Log.e("", "Login failed!");
參考文獻(xiàn)
[1] Google. Developer Guides [EB/OL].
https://developer.android.com/guide/index.html
[2] Google. Class Index [EB/OL].
https://developer.android.com/reference/classes.html
[3] Alex Lockwood. Android Design Patterns [EB/OL].
https://www.androiddesignpatterns.com/
[4] O'Reilly. High Performance Android Apps by Doug Sillars [EB/OL].
https://www.safaribooksonline.com/library/view/high-performance-android/97814
91913994/ch04.html#figure-story_tree
[5] Takeshi Terada. Whitepaper – Attacking Android browsers via intent scheme
URLs [EB/OL].
https://www.mbsd.jp/Whitepaper/IntentScheme.pdf
[6] 張明云. Android 開發(fā)中,有哪些坑需要注意? [EB/OL].
https://zhuanlan.zhihu.com/p/20309921
[7] MegatronKing. Android 多個(gè)Fragment 嵌套導(dǎo)致的三大BUG [EB/OL].
http://blog.csdn.net/megatronkings/article/details/51417510
[8] Nfrolov. Android: SQLiteDatabase locking and multi-threading [EB/OL].
https://nfrolov.wordpress.com/2014/08/16/android-sqlitedatabase-locking-and-m
ulti-threading
[9] gcoder_io. Android 數(shù)據(jù)庫模塊搭建方案 [EB/OL].
http://www.itdecent.cn/p/57eb08fe071d
---The end---