三系草、Android基本組件
1刻炒、【強制】Activity間的數(shù)據(jù)通信罐寨,對于數(shù)據(jù)量比較大的,避免使用Intent + Parcelable 的方式纵柿,可以考慮EventBus等替代方案蜈抓,以免造成TransactionTooLargeException。
- 【強制】Activity間通過隱式Intent的跳轉(zhuǎn)昂儒,在發(fā)出Intent之前必須通過resolveActivity 檢查沟使,避免找不到合適的調(diào)用組件,造成ActivityNotFoundException 的異常渊跋。
正例:
public void viewUrl(String url, String mimeType) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(url), mimeType);
if (getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ ONLY) != null) {
startActivity(intent);
}else {
// 找不到指定的Activity
}
}
反例:
Intent intent = new Intent();
intent.setAction("com.example.DemoIntent ");
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
}
- 【強制】避免在Service#onStartCommand()/onBind()方法中執(zhí)行耗時操作腊嗡,如果確 實有需求着倾,應(yīng)改用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) {
}
}
}
}
- 【強制】避免在BroadcastReceiver#onReceive()中執(zhí)行耗時操作燕少,如果有耗時工作卡者, 應(yīng)該創(chuàng)建IntentService 完成妥曲,而不應(yīng)該在BroadcastReceiver 內(nèi)創(chuàng)建子線程去做下面。
說明:
由于該方法是在主線程執(zhí)行,如果執(zhí)行耗時操作會導(dǎo)致 UI 不流暢祷安∠庹可以使用 IntentService 嗽桩、創(chuàng)建 HandlerThread 或者調(diào)用 Context#registerReceiver (BroadcastReceiver, IntentFilter, String, Handler)方法等方式,在其他Wroker線程 執(zhí)行 onReceive 方法凄敢。BroadcastReceiver#onReceive()方法耗時超過 10 秒鐘碌冶,可 能會被系統(tǒng)殺死。
正例:
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
}
};
- 【強制】避免使用隱式 Intent 廣播敏感信息涝缝,信息可能被其他注冊了對應(yīng) BroadcastReceiver的App接收扑庞。
說明:
通過Context#sendBroadcast()發(fā)送的隱式廣播會被所有感興趣的receiver接收,惡 意應(yīng)用注冊監(jiān)聽該廣播的 receiver 可能會獲取到 Intent 中傳遞的敏感信息拒逮,并進行 其他危險操作罐氨。如果發(fā)送的廣播為使用 Context#sendOrderedBroadcast()方法發(fā)送 的有序廣播,優(yōu)先級較高的惡意 receiver 可能直接丟棄該廣播滩援,造成服務(wù)不可用栅隐, 或者向廣播結(jié)果塞入惡意數(shù)據(jù)。
如果廣播僅限于應(yīng)用內(nèi)玩徊,則可以使用 LocalBroadcastManager#sendBroadcast()實 現(xiàn)租悄,避免敏感信息外泄和Intent攔截的風(fēng)險。
正例:
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);
}
}
}
}
- 【強制】Activity或者Fragment中動態(tài)注冊BroadCastReceiver時恩袱,registerReceiver() 和unregisterReceiver()要成對出現(xiàn)泣棋。
說明:
如果registerReceiver()和unregisterReceiver()不成對出現(xiàn),則可能導(dǎo)致已經(jīng)注冊的 receiver沒有在合適的時機注銷畔塔,導(dǎo)致內(nèi)存泄漏潭辈,占用內(nèi)存空間,加重SystemService 負擔澈吨。
部分華為的機型會對 receiver 進行資源管控把敢,單個應(yīng)用注冊過多 receiver 會觸發(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 的生命周期不對應(yīng)技竟,可能出現(xiàn)多次 onResume 造成 receiver 注冊多個冰肴,但 終只注銷一個屈藐,其余receiver產(chǎn)生內(nèi)存泄漏榔组。
15.【強制】Android 基礎(chǔ)組件如果使用隱式調(diào)用,應(yīng)在 AndroidManifest.xml 中使用 <intent-filter> 或在代碼中使用 IntentFilter 增加過濾联逻。
說明:
如果瀏覽器支持Intent Scheme Uri語法搓扯,如果過濾不當,那么惡意用戶可能通過瀏 覽器 js 代碼進行一些惡意行為包归,比如盜取 cookie 等锨推。如果使用了 Intent.parseUri 函數(shù),獲取的intent必須嚴格過濾公壤。
正例:
// 將 intent scheme URL 轉(zhuǎn)換為 intent 對象
Intent intent = Intent.parseUri(uri);
// 禁止沒有 BROWSABLE category 的情況下啟動activity
intent.addCategory("android.intent.category.BROWSABLE");
intent.setComponent(null);
intent.setSelector(null);
// 使用 intent 啟動 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與布局
- 【強制】布局中不得不使用 ViewGroup 多重嵌套時,不要使用 LinearLayout 嵌套厦幅, 改用RelativeLayout沾鳄,可以有效降低嵌套數(shù)。
說明:
Android 應(yīng)用頁面上任何一個 View 都需要經(jīng)過 measure确憨、layout译荞、draw 三個步驟 才能被正確的渲染。從xml layout的頂部節(jié)點開始進行measure休弃,每個子節(jié)點都需 要向自己的父節(jié)點提供自己的尺寸來決定展示的位置吞歼,在此過程中可能還會重新 measure(由此可能導(dǎo)致measure的時間消耗為原來的2-3倍)。節(jié)點所處位置越深塔猾, 嵌套帶來的 measure 越多篙骡,計算就會越費時。這就是為什么扁平的 View 結(jié)構(gòu)會性 能更好桥帆。
同時医增,頁面擁上的View越多,measure老虫、layout叶骨、draw所花費的時間就越久。要縮 短這個時間祈匙,關(guān)鍵是保持View的樹形結(jié)構(gòu)盡量扁平忽刽,而且要移除所有不需要渲染的 View。理想情況下夺欲,總共的measure跪帝,layout,draw時間應(yīng)該被很好的控制在16ms 以內(nèi)些阅,以保證滑動屏幕時UI的流暢伞剑。
要找到那些多余的 View(增加渲染延遲的 view),可以用 Android Studio Monitor 里的Hierarchy Viewer工具市埋,可視化的查看所有的view黎泣。 - 【強制】禁止在非UI線程進行View相關(guān)操作恕刘。
- 【強制】禁止在設(shè)計布局時多次為子 View 和父 View 設(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);
}
11.【強制】不能使用 ScrollView 包裹 ListView/GridView/ExpandableListVIew;因為這 樣會把 ListView 的所有 Item 都加載到內(nèi)存中褐着,要消耗巨大的內(nèi)存和 cpu 去繪制圖 面。
說明:
ScrollView中嵌套List或RecyclerView的做法官方明確禁止托呕。除了開發(fā)過程中遇到 的各種視覺和交互問題含蓉,這種做法對性能也有較大損耗。ListView等UI組件自身有 垂直滾動功能项郊,也沒有必要在嵌套一層ScrollView馅扣。目前為了較好的UI體驗,更貼 近Material Design的設(shè)計着降,推薦使用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.【強制】不要在 Android 的 Application 對象中緩存數(shù)據(jù)∪蛋基礎(chǔ)組件之間的數(shù)據(jù)共享 請使用Intent等機制厌殉,也可使用SharedPreferences等數(shù)據(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());
}
}
14.【強制】使用 Adapter 的時候侈咕,如果你使用了 ViewHolder 做緩存公罕,在 getView()的 方法中無論這項 convertView 的每個子控件是否需要設(shè)置屬性(比如某個 TextView 設(shè)置的文本可能為 null,某個按鈕的背景色為透明耀销,某控件的顏色為透明等)楼眷,都需 要為其顯式設(shè)置屬性(Textview的文本為空也需要設(shè)置setText(""),背景透明也需要 設(shè)置)熊尉,否則在滑動的過程中罐柳,因為adapter item復(fù)用的原因,會出現(xiàn)內(nèi)容的顯示錯 亂狰住。
正例:
@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;
}
五张吉、進程、線程與消息通信
- 【強制】不要通過 Intent 在 Android 基礎(chǔ)組件之間傳遞大數(shù)據(jù)(binder transaction 緩存為1MB)催植,可能導(dǎo)致OOM肮蛹。
- 【強制】在 Application 的業(yè)務(wù)初始化代碼加入進程判斷,確保只在自己需要的進程 初始化创南。特別是后臺進程減少不必要的業(yè)務(wù)初始化伦忠。
正例:
public class MyApplication extends Application {
@Override
public void onCreate() {
//在所有進程中初始化
....
//僅在主進程中初始化
if (mainProcess) {
...
}
//僅在后臺進程中初始化
if (bgProcess) {
...
}
}
}
- 【強制】新建線程時,必須通過線程池提供(AsyncTask 或者 ThreadPoolExecutor 或者其他形式自定義的線程池)稿辙,不允許在應(yīng)用中自行顯式創(chuàng)建線程昆码。
說明:
使用線程池的好處是減少在創(chuàng)建和銷毀線程上所花的時間以及系統(tǒng)資源的開銷,解 決資源不足的問題。如果不使用線程池赋咽,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導(dǎo)致消耗完內(nèi)存或者“過度切換”的問題笔刹。另外創(chuàng)建匿名線程不便于后續(xù)的資源使用分析, 對性能分析等會造成困擾冬耿。
正例:
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();
- 【強制】線程池不允許使用Executors去創(chuàng)建,而是通過ThreadPoolExecutor的方 式萌壳,這樣的處理方式讓寫的同學(xué)更加明確線程池的運行規(guī)則亦镶,規(guī)避資源耗盡的風(fēng)險。
說明:
Executors返回的線程池對象的弊端如下:
- FixedThreadPool 和 SingleThreadPool : 允許的請求隊列長度Integer.MAX_VALUE袱瓮,可能會堆積大量的請求缤骨,從而導(dǎo)致OOM;
- CachedThreadPool 和 ScheduledThreadPool:允許的創(chuàng)建線程數(shù)量為 Integer.MAX_VALUE尺借,可能會創(chuàng)建大量的線程绊起,從而導(dǎo)致OOM。
正例:
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
int KEEP_ALIVE_TIME = 1;
TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>();
ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES,
NUMBER_OF_CORES*2, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT,
taskQueue, new BackgroundThreadFactory(), new DefaultRejectedExecutionHandler());
反例:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
- 【強制】子線程中不能更新界面燎斩,更新界面必須在主線程中進行虱歪,網(wǎng)絡(luò)操作不能在 主線程中調(diào)用。
六栅表、文件與數(shù)據(jù)庫
- 【強制】任何時候不要硬編碼文件路徑笋鄙,請使用Android文件系統(tǒng)API訪問。
說明:
Android 應(yīng)用提供內(nèi)部和外部存儲怪瓶,分別用于存放應(yīng)用自身數(shù)據(jù)以及應(yīng)用產(chǎn)生的用 戶數(shù)據(jù)萧落。可以通過相關(guān)API接口獲取對應(yīng)的目錄洗贰,進行文件操作找岖。
android.os.Environment#getExternalStorageDirectory()
android.os.Environment#getExternalStoragePublicDirectory()
android.content.Context#getFilesDir()
android.content.Context#getCacheDir
正例:
public File getDir(String alName) {
File file = new File(Environment.getExternalStoragePublicDirectory(Environment. DIRECTORY_PICTURES), alName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
反例:
public File getDir(String alName) {
// 任何時候都不要硬編碼文件路徑,這不僅存在安全隱患敛滋,也讓 app更容易出現(xiàn)適配問題
File file = new File("/mnt/sdcard/Download/Album", alName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
- 【強制】當使用外部存儲時许布,必須檢查外部存儲的可用性。
正例:
// 讀/寫檢查
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)用間共享文件時绎晃,不要通過放寬文件系統(tǒng)權(quán)限的方式去實現(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);
}
- 【強制】數(shù)據(jù)庫Cursor必須確保使用完后關(guān)閉灵妨,以免內(nèi)存泄漏。
說明:
Cursor 是對數(shù)據(jù)庫查詢結(jié)果集管理的一個類落竹,當查詢的結(jié)果集較小時泌霍,消耗內(nèi)存不 易察覺。但是當結(jié)果集較大,長時間重復(fù)操作會導(dǎo)致內(nèi)存消耗過大朱转,需要開發(fā)者在 操作完成后手動關(guān)閉Cursor蟹地。
數(shù)據(jù)庫Cursor在創(chuàng)建及使用時,可能發(fā)生各種異常藤为,無論程序是否正常結(jié)束怪与,必須 在后確保 Cursor 正確關(guān)閉,以避免內(nèi)存泄漏缅疟。同時分别,如果 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)閉
}
- 【強制】多線程操作寫入數(shù)據(jù)庫時耘斩,需要使用事務(wù),以免出現(xiàn)同步問題桅咆。
說明:
通過 SQLiteOpenHelper 獲取數(shù)據(jù)庫 SQLiteDatabase 實例括授,Helper 中會自動緩存已經(jīng)打開的 SQLiteDatabase 實例,單個 App 中應(yīng)使用 SQLiteOpenHelper 的單例 模式確保數(shù)據(jù)庫連接唯一岩饼。由于SQLite自身是數(shù)據(jù)庫級鎖荚虚,單個數(shù)據(jù)庫操作是保證 線程安全的(不能同時寫入),transaction是一次原子操作籍茧,因此處于事務(wù)中的操作 是線程安全的曲管。
若同時打開多個數(shù)據(jù)庫連接,并通過多線程寫入數(shù)據(jù)庫硕糊,會導(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);
}
- 【強制】執(zhí)行 SQL 語句時简十,應(yīng)使用 SQLiteDatabase#insert()檬某、update()、delete()螟蝙, 不要使用SQLiteDatabase#execSQL()恢恼,以免SQL注入風(fēng)險。
正例:
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);
//請?zhí)岣甙踩庾R胰默,不要直接執(zhí)行字符串作為SQL 語句
db.execSQL(sqlStmt);
}
10.【強制】如果 ContentProvider 管理的數(shù)據(jù)存儲在 SQL 數(shù)據(jù)庫中场斑,應(yīng)該避免將不受 信任的外部數(shù)據(jù)直接拼接在原始SQL語句中。
正例:
// 使用一個可替換參數(shù)
String mSelectionClause = "var = ?";
String[] selectionArgs = {""};
selectionArgs[0] = mUserInput;
反例:
// 拼接用戶輸入內(nèi)容和列名
String mSelectionClause = "var = " + mUserInput;
七牵署、Bitmap漏隐、Drawable與動畫
- 【強制】加載大圖片或者一次性加載多張圖片,應(yīng)該在異步線程中進行奴迅。圖片的加 載青责,涉及到IO操作,以及CPU密集操作,很可能引起卡頓脖隶。
正例:
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
...
// 在后臺進行圖片解碼
@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");
}
});
- 【強制】在ListView扁耐,ViewPager,RecyclerView产阱,GirdView等組件中使用圖片時婉称, 應(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)存超出這個值將拋出 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> {
...
// 在后臺進行圖片解碼
@Override
protected Bitmap doInBackground(Integer... params) {
final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100));
addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
return bitmap;
}
...
}
反例:
沒有存儲,每次都需要解碼拙毫,或者有緩存但是沒有合適的淘汰機制依许,導(dǎo)致緩存效果 很差,依然經(jīng)常需要重新解碼缀蹄。
【強制】png圖片使用TinyPNG或者類似工具壓縮處理峭跳,減少包體積。
【強制】使用完畢的圖片缺前,應(yīng)該及時回收蛀醉,釋放寶貴的內(nèi)存。
正例:
Bitmap bitmap = null;
loadBitmapAsync(new OnResult(result){
bitmap = result;
});
...使用該 bitmap...
// 使用結(jié)束衅码,在 2.3.3及以下需要調(diào)用 recycle()函數(shù)拯刁,在 2.3.3以上 GC 會自動管理,除非你明 確不需要再用逝段。
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
bitmap.recycle();
}
bitmap = null;
反例:
使用完成圖片垛玻,始終不釋放資源。
- 【強制】在Activity#onPause()或Activity#onStop()回調(diào)中奶躯,關(guān)閉當前activity正在執(zhí) 行的的動畫帚桩。
正例:
public class MyActivity extends Activity {
ImageView mImageView;
Animation mAnimation;
Button mBtn;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mImageView = (ImageView)findViewById(R.id.ImageView01);
mAnimation = AnimationUtils.loadAnimation(this, R.anim.anim);
mBtn= (Button)findViewById(R.id.Button01);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mImageView.startAnimation(mAnimation);
}
});
}
@Override
public void onPause() {
//頁面退出,及時清理動畫資源
mImageView.clearAnimation()
}
}
反例:
頁面退出時嘹黔,不關(guān)閉該頁面相關(guān)的動畫朗儒。
八、安全
- 【強制】禁止使用常量初始化矢量參數(shù)構(gòu)建 IvParameterSpec,建議 IV 通過隨機方 式產(chǎn)生醉锄。
說明:
使用常量初始化向量乏悄,密碼文本的可預(yù)測性會高得多,容易受到字典式攻擊恳不。iv 的 作用主要是用于產(chǎn)生密文的第一個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());
- 【強制】將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ù)。這對應(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" >
- 【強制】如果使用自定義HostnameVerifier實現(xiàn)類畜疾,必須在verify()方法中校驗服務(wù) 器主機名的合法性赴邻,否則可能受到中間人攻擊。
說明:
在與服務(wù)器建立 https 連接時啡捶,如果 URL 的主機名和服務(wù)器的主機名不匹配姥敛,則 可通過該回調(diào)接口來判斷是否應(yīng)該允許建立連接。如果回調(diào)內(nèi)實現(xiàn)不恰當瞎暑,沒有有 效校驗主機名彤敛,甚至默認接受所有主機名,會大大增加安全風(fēng)險了赌。
反例:
HostnameVerifier hnv = new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
// 不做校驗臊泌,接受任意域名服務(wù)器
return true;
}
};
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
- 【強制】如果使用自定義X509TrustManager實現(xiàn)類,必須在checkServerTrusted() 方法中校驗服務(wù)端證書的合法性揍拆,否則可能受到中間人攻擊渠概。
說明:
常見誤區(qū)是checkServerTrusted()方法根本沒有實現(xiàn),這將導(dǎo)致 X509TrustManager 形同虛設(shè)嫂拴。該方法中需要實現(xiàn)完備的校驗邏輯播揪,對于證書錯誤拋出
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);
- 【強制】在 SDK 支持的情況下,Android 應(yīng)用必須使用 V2 簽名辩恼,這將對 APK 文 件的修改做更多的保護雇庙。
- 【強制】所有的 Android 基本組件(Activity谓形、Service、BroadcastReceiver疆前、ContentProvider等)都不應(yīng)在沒有嚴格權(quán)限控制的情況下寒跳,將 android:exported 設(shè) 置為 true。
- 【強制】WebView 應(yīng)設(shè)置 WebView#getSettings()#setAllowFileAccess(false)竹椒、 WebView#getSettings()#setAllowFileAccessFromFileURLs(false) 童太、 WebView#getSettings()#setAllowUniversalAccessFromFileURLs(false),阻止 file scheme URL 的訪問胸完。
8.【強制】不要把敏感信息打印到log中书释。
說明:
在開發(fā)過程中,為了方便調(diào)試赊窥,通常會使用 log 函數(shù)輸出一些關(guān)鍵流程的信息爆惧,這 些信息中通常會包含敏感內(nèi)容,讓攻擊者更加容易了解APP內(nèi)部結(jié)構(gòu)锨能,方便破解和 攻擊扯再,甚至直接獲取到有價值的敏感信息。
反例:
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.【強制】確保應(yīng)用發(fā)布版本的android:debuggable屬性設(shè)置為false窃页。
10.【強制】本地加密秘鑰不能硬編碼在代碼中跺株,更不能使用 SharedPreferences 等本 地持久化機制存儲。應(yīng)選擇 Android 自身的秘鑰庫(KeyStore)機制或者其他安全 性更高的安全解決方案保存脖卖。
說明:
應(yīng)用程序在加解密時乒省,使用硬編碼在程序中的密鑰,攻擊者通過反編譯拿到密鑰可 以輕易解密APP通信數(shù)據(jù)畦木。
12.【強制】使用Android的AES/DES/DESede加密算法時袖扛,不要使用ECB加密模式, 應(yīng)使用CBC或CFB加密模式十籍。
說明:
加密模式有 ECB蛆封、CBC、CFB勾栗、OFB 等惨篱,其中 ECB 的安全性較弱,如果使用固 定的密鑰围俘,相同的明文將會生成相同的密文砸讳,容易受到字典攻擊琢融,建議使用 CBC、 CFB 或OFB 等模式簿寂。
- ECB:Electronic codebook漾抬,電子密碼本模式
- CBC:Cipher-block chaining,密碼分組鏈接模式
- CFB:Cipher feedback陶耍,密文反饋模式
- OFB:Output feedback奋蔚,輸出反饋模式
13.【強制】Android APP在HTTPS通信中,驗證策略需要改成嚴格模式烈钞。
說明:
Android APP在HTTPS通信中泊碑,使用ALLOW_ALL_HOSTNAME_VERIFIER,表 示允許和所有的HOST建立SSL通信毯欣,這會存在中間人攻擊的風(fēng)險馒过,終導(dǎo)致敏感 信息可能會被劫持,以及其他形式的攻擊酗钞。
反例:
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
ALLOW_ALL_HOSTNAME_VERIFIER 關(guān)閉 host 驗證腹忽,允許和所有的 host 建立 SSL 通信,BROWSER_COMPATIBLE_HOSTNAME_VERIFIER 和瀏覽器兼容的 驗證策略砚作,即通配符能夠匹配所有子域名 窘奏,STRICT_HOSTNAME_VERIFIER 嚴 格匹配模式,hostname 必須匹配第一個 CN 或者任何一個 subject-alts葫录,以上例子 使用了 ALLOW_ALL_HOSTNAME_VERIFIER着裹,需要改成 STRICT_HOSTNAME_ VERIFIER。
九米同、其他
- 【強制】不能使用System.out.println打印log骇扇。
正例:
Log.d(TAG, "Some Android Debug info ...");
反例:
System.out.println("System out println ...");
- 【強制】Log的tag不能是" "。
說明:
日志的tag是空字符串沒有任何意義面粮,也不利于過濾日志少孝。
正例:
private static String TAG = "LoginActivity";
Log.e(TAG, "Login failed!");
反例:
Log.e("", "Login failed!");