Alibaba Android 開發(fā)手冊總結(jié)

Android 資源文件命名與使用

  1. 【推薦】資源文件需帶模塊前綴漱抓。

  2. 【推薦】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 開頭

  3. 【推薦】drawable 資源名稱以小寫單詞+下劃線的方式命名咆繁,根據(jù)分辨率不同存放在
    不同的drawable 目錄下蚓挤,如果介意包大小建議只使用一套,系統(tǒng)去進行縮放搬素。采用
    規(guī)則如下:
    模塊名_業(yè)務功能描述_控件描述_控件狀態(tài)限定詞
    如:module_login_btn_pressed,module_tabs_icon_home_normal

  4. 【推薦】anim 資源名稱以小寫單詞+下劃線的方式命名境输,采用以下規(guī)則:
    模塊名_邏輯名稱_[方向|序號]
    Tween 動畫(使用簡單圖像變換的動畫剂跟,例如縮放蜕衡、平移)資源:盡可能以通用的
    動畫名稱命名,如module_fade_in , module_fade_out, module_push_down_in (動 畫+方向)
    Frame 動畫(按幀順序播放圖像的動畫)資源:盡可能以模塊+功能命名+序號。如
    module_loading_grey_001陶衅。

  5. 【推薦】color 資源使用#AARRGGBB 格式,寫入module_colors.xml 文件中直晨,命名
    格式采用以下規(guī)則:
    模塊名_邏輯名稱_顏色

如:
<color name="module_btn_bg_color">#33b5e5e5</color>

  1. 【推薦】dimen 資源以小寫單詞+下劃線方式命名搀军,寫入module_dimens.xml 文件中,
    采用以下規(guī)則:
    模塊名_描述信息
    如:
    <dimen name="module_horizontal_line_height">1dp</dimen>

  2. 【推薦】style 資源采用父style 名稱.當前style 名稱方式命名勇皇,寫入
    module_styles.xml 文件中罩句,首字母大寫。如:
    <style name="ParentTheme.ThisActivityTheme"> … </style>

  3. 【推薦】string資源文件或者文本用到字符需要全部寫入module_strings.xml 文件中敛摘,
    字符串以小寫單詞+下劃線的方式命名门烂,采用以下規(guī)則:
    模塊名_邏輯名稱
    如:moudule_login_tips,module_homepage_notice_desc

  4. 【推薦】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

其它控件的縮寫推薦使用小寫字母并用下劃線進行分割诅福,例如:ProgressBar 對應
的縮寫為progress_bar;DatePicker 對應的縮寫為date_picker拖叙。

10.【推薦】圖片根據(jù)其分辨率氓润,放在不同屏幕密度的drawable 目錄下管理,否則可能
在低密度設備上導致內(nèi)存占用增加薯鳍,又可能在高密度設備上導致圖片顯示不夠清晰咖气。
說明:
為了支持多種屏幕尺寸和密度,Android 提供了多種通用屏幕密度來適配挖滤。常用的
如下崩溪。
ldpi - 120dpi
mdpi - 160dpi
hdpi - 240dpi
xhdpi - 320dpi
xxhdpi - 480dpi
xxxhdpi - 640dpi

Android 的屏幕分辨率和密度并不存在嚴格的對應關系,應盡量避免直接基于分辨
率來開發(fā)斩松,而是通過適配不同的屏幕密度來保證控件和圖片的顯示效果伶唯。不同密度
drawable 目錄中的圖片分辨率設置,參考不同密度的dpi 比例關系惧盹。
正例:
為顯示某個圖標乳幸,將48 x 48 的圖標文件放在drawable-mdpi 目錄(160dpi)下;
將72 x 72 的圖標文件放在drawable-hdpi 目錄(240dpi)下钧椰;將96 x 96 的圖標
文件放在drawable-xhdpi 目錄(320dpi)下粹断;將144 x 144 的圖標文件放在
drawable-xxhdpi 目錄(480dpi)下。
反例:
上述圖標嫡霞,只有一個144 x 144 的圖標文件放在drawable 目錄下瓶埋。

Android 基本組件

Android 基本組件指ActivityFragment诊沪、Service养筒、BroadcastReceiver
ContentProvider 等等端姚。

  1. 【強制】Activity 間的數(shù)據(jù)通信闽颇,對于數(shù)據(jù)量比較大的,避免使用Intent + Parcelable
    的方式寄锐,可以考慮EventBus等替代方案兵多,以免造成TransactionTooLargeException

  2. 【推薦】Activity#onSaveInstanceState()方法不是Activity 生命周期方法橄仆,也不保證
    一定會被調(diào)用剩膘。它是用來在Activity 被意外銷毀時保存UI 狀態(tài)的,只能用于保存臨
    時性數(shù)據(jù)盆顾,例如UI 控件的屬性等怠褐,不能跟數(shù)據(jù)的持久化存儲混為一談。持久化存儲
    應該在Activity#onPause()/onStop()中實行您宪。

  3. 【強制】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();
}
  1. 【強制】避免在Service#onStartCommand()/onBind()方法中執(zhí)行耗時操作溜畅,如果確
    實有需求,應改用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. 【強制】避免在BroadcastReceiver#onReceive()中執(zhí)行耗時操作慈格,如果有耗時工作,
    應該創(chuàng)建IntentService 完成遥金,而不應該在BroadcastReceiver 內(nèi)創(chuàng)建子線程去做浴捆。
    說明:
    由于該方法是在主線程執(zhí)行,如果執(zhí)行耗時操作會導致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
    }
};
  1. 【強制】避免使用隱式Intent 廣播敏感信息餐茵,信息可能被其他注冊了對應
    BroadcastReceiver 的App 接收。
    說明:
    通過Context#sendBroadcast()發(fā)送的隱式廣播會被所有感興趣的receiver 接收述吸,惡
    意應用注冊監(jiān)聽該廣播的receiver 可能會獲取到Intent 中傳遞的敏感信息忿族,并進行
    其他危險操作。如果發(fā)送的廣播為使用Context#sendOrderedBroadcast()方法發(fā)送
    的有序廣播蝌矛,優(yōu)先級較高的惡意receiver 可能直接丟棄該廣播道批,造成服務不可用,
    或者向廣播結(jié)果塞入惡意數(shù)據(jù)入撒。
    如果廣播僅限于應用內(nèi)隆豹,則可以使用LocalBroadcastManager#sendBroadcast()
    現(xiàn),避免敏感信息外泄和Intent 攔截的風險茅逮。
    正例:
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);

以上廣播可能被其他應用的如下receiver 接收導致敏感信息泄漏

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);
            }
        }
    }
}
  1. 【推薦】添加Fragment 時璃赡, 確保FragmentTransaction#commit()
    Activity#onPostResume()或者FragmentActivity#onResumeFragments()內(nèi)調(diào)用。
    不要隨意使用FragmentTransaction#commitAllowingStateLoss()來代替献雅,任何
    commitAllowingStateLoss()的使用必須經(jīng)過code review碉考,確保無負面影響。
    說明:
    Activity 可能因為各種原因被銷毀挺身, Android 支持頁面被銷毀前通過
    Activity#onSaveInstanceState()保存自己的狀態(tài)侯谁。但如果
    FragmentTransaction.commit()發(fā)生在Activity 狀態(tài)保存之后,就會導致Activity 重
    建、恢復狀態(tài)時無法還原頁面狀態(tài)墙贱,從而可能出錯热芹。為了避免給用戶造成不好的體驗,系統(tǒng)會拋出IllegalStateExceptionStateLoss異常惨撇。推薦的做法是在Activity 的
    onPostResume()onResumeFragments() ( 對FragmentActivity ) 里執(zhí)行
    FragmentTransaction.commit()伊脓,如有必要也可在onCreate()里執(zhí)行。不要隨意改用
    FragmentTransaction.commitAllowingStateLoss()或者直接使用try-catch 避免
    crash串纺,這不是問題的根本解決之道丽旅,當且僅當你確認Activity 重建椰棘、恢復狀態(tài)時纺棺,
    本次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();
    }
}

反例:

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();
    }
}
  1. 【推薦】不要在Activity#onDestroy()內(nèi)執(zhí)行釋放資源的工作邪狞,例如一些工作線程的
    銷毀和停止祷蝌,因為onDestroy()執(zhí)行的時機可能較晚》浚可根據(jù)實際需要巨朦,在
    Activity#onPause()/onStop()中結(jié)合isFinishing()的判斷來執(zhí)行。

  2. 【推薦】如非必須剑令,避免使用嵌套的Fragment糊啡。
    說明:
    嵌套Fragment 是在Android API 17添加到SDK以及Support 庫中的功能,F(xiàn)ragment
    嵌套使用會有一些坑吁津,容易出現(xiàn)bug棚蓄,比較常見的問題有如下幾種:

  1. onActivityResult()方法的處理錯亂,內(nèi)嵌的Fragment 可能收不到該方法的回調(diào)碍脏,
    需要由宿主Fragment 進行轉(zhuǎn)發(fā)處理梭依;
  2. 突變動畫效果;
  3. 被繼承的setRetainInstance()典尾,導致在Fragment 重建時多次觸發(fā)不必要的邏
    輯役拴。
    非必須的場景盡可能避免使用嵌套Fragment,如需使用請注意上述問題钾埂。
    正例:
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();
  1. 【推薦】總是使用顯式Intent 啟動或者綁定Service河闰,且不要為服務聲明Intent Filter,
    保證應用的安全性褥紫。如果確實需要使用隱式調(diào)用淤击,則可為Service 提供Intent Filter
    并從Intent 中排除相應的組件名稱,但必須搭配使用Intent#setPackage()方法設置
    Intent 的指定包名故源,這樣可以充分消除目標服務的不確定性污抬。

11.【推薦】Service 需要以多線程來并發(fā)處理多個啟動請求,建議使用IntentService,
可避免各種復雜的設置印机。
說明:
Service 組件一般運行主線程矢腻,應當避免耗時操作,如果有耗時操作應該在Worker
線程執(zhí)行射赛《喔蹋可以使用IntentService 執(zhí)行后臺任務。
正例:

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.【推薦】對于只用于應用內(nèi)的廣播楣责,優(yōu)先使用LocalBroadcastManager 來進行注冊
和發(fā)送竣灌,LocalBroadcastManager 安全性更好,同時擁有更高的運行效率秆麸。
說明:
對于使用Context#sendBroadcast()等方法發(fā)送全局廣播的代碼進行提示初嘹。如果該廣
播僅用于應用內(nèi),則可以使用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(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);
  1. 【推薦】當前Activity 的onPause 方法執(zhí)行結(jié)束后才會創(chuàng)建(onCreate)或恢復
    (onRestart)別的Activity,所以在onPause 方法中不適合做耗時較長的工作房铭,這
    會影響到頁面之間的跳轉(zhuǎn)效率驻龟。

  2. 【強制】Activity 或者Fragment 中動態(tài)注冊BroadCastReceiver 時,registerReceiver()
    unregisterReceiver()要成對出現(xiàn)缸匪。
    說明:
    如果registerReceiver()unregisterReceiver()不成對出現(xiàn)翁狐,則可能導致已經(jīng)注冊的
    receiver 沒有在合適的時機注銷,導致內(nèi)存泄漏凌蔬,占用內(nèi)存空間露懒,加重SystemService
    負擔。
    部分華為的機型會對receiver 進行資源管控龟梦,單個應用注冊過多receiver 會觸發(fā)管
    控模塊拋出異常隐锭,應用直接崩潰。
    正例:

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 的生命周期不對應计贰,可能出現(xiàn)多次onResume 造成receiver 注冊多個钦睡,但
最終只注銷一個,其余receiver 產(chǎn)生內(nèi)存泄漏躁倒。

15.【強制】Android 基礎組件如果使用隱式調(diào)用荞怒,應在 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 與布局

  1. 【強制】布局中不得不使用ViewGroup 多重嵌套時,不要使用LinearLayout 嵌套啦撮,
    改用RelativeLayout谭网,可以有效降低嵌套數(shù)。
    說明:
    Android 應用頁面上任何一個View 都需要經(jīng)過 measure赃春、layout愉择、draw 三個步驟
    才能被正確的渲染。從xml layout 的頂部節(jié)點開始進行measure织中,每個子節(jié)點都需
    要向自己的父節(jié)點提供自己的尺寸來決定展示的位置锥涕,在此過程中可能還會重新
    measure(由此可能導致measure 的時間消耗為原來的2-3 倍)。節(jié)點所處位置越深狭吼,
    嵌套帶來的measure 越多层坠,計算就會越費時。這就是為什么扁平的View 結(jié)構(gòu)會性
    能更好搏嗡。
    同時窿春,頁面擁上的View 越多拉一,measure采盒、layout、draw 所花費的時間就越久蔚润。要縮
    短這個時間磅氨,關鍵是保持View 的樹形結(jié)構(gòu)盡量扁平,而且要移除所有不需要渲染的
    View嫡纠。理想情況下烦租,總共的measure,layout除盏,draw 時間應該被很好的控制在16ms
    以內(nèi)叉橱,以保證滑動屏幕時UI 的流暢。
    要找到那些多余的View(增加渲染延遲的view)者蠕,可以用Android Studio Monitor
    里的Hierarchy 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. 【推薦】源文件統(tǒng)一采用UTF-8 的形式進行編碼探膊。

  2. 【強制】禁止在非UI 線程進行View 相關操作。

  3. 【推薦】文本大小使用單位dp待榔,View 大小使用單位dp逞壁。對于TextView,如果在文
    字大小確定的情況下推薦使用wrap_content 布局避免出現(xiàn)文字顯示不全的適配問
    題。
    說明:
    之所以文本大小也推薦使用dp 而非sp腌闯,因為sp 是Android 早期推薦使用的袭灯,但其
    實sp 不僅和dp 一樣受屏幕密度的影響,還受到系統(tǒng)設置里字體大小的影響绑嘹,所以
    使用dp 對于應用開發(fā)會更加保證UI 的一致性和還原度稽荧。

  4. 【強制】禁止在設計布局時多次為子View 和父View 設置同樣背景進而造成頁面過
    度繪制,推薦將不需要顯示的布局進行及時隱藏工腋。
    正例:

<?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);
}
  1. 【推薦】靈活使用布局姨丈,推薦mergeViewStub 來優(yōu)化布局擅腰,盡可能多的減少UI
    布局層級蟋恬,推薦使用FrameLayoutLinearLayout趁冈、RelativeLayout 次之歼争。

  2. 【推薦】在需要時刻刷新某一區(qū)域的組件時,建議通過以下方式避免引發(fā)全局layout
    刷新:

  1. 設置固定的View 大小的寬高渗勘,如倒計時組件等沐绒;
  2. 調(diào)用View 的layout 方法修改位置,如彈幕組件等旺坠;
  3. 通過修改Canvas 位置并且調(diào)用invalidate(int l, int t, int r, int b)等方式限定刷新
    區(qū)域乔遮;
  4. 通過設置一個是否允許requestLayout 的變量,然后重寫控件的requestlayout取刃、
    onSizeChanged 方法蹋肮, 判斷控件的大小沒有改變的情況下, 當進入
    requestLayout 的時候,直接返回而不調(diào)用super 的requestLayout 方法。
  1. 【推薦】不能在Activity 沒有完全顯示時顯示PopupWindow 和Dialog究恤。
    說明:
    Android Activity 創(chuàng)建時的生命周期,按照onCreate() -> onStart() -> onResume() -> onAttachedToWindow() -> onWindowFocusChanged() 的順序漆魔, 其中在
    Activity#onAttachedToWindow() 時,Activity 會與它的 Window 關聯(lián)啦膜,這時 UI 才
    會開始繪制有送,在 Activity#onWindowFocusChanged() 時,UI 才變成可交互狀態(tài)僧家,
    可以提示用戶使用雀摘。如果在 Window 未關聯(lián)時就創(chuàng)建對話框,UI 可能顯示異常八拱。
    推薦的做法是在 Activity#onAttachedToWindow() 之后( 其實最好是
    Activity#onWindowFocusChanged() 之后)才創(chuàng)建對話框阵赠。

10.【推薦】盡量不要使用AnimationDrawable涯塔,它在初始化的時候就將所有圖片加載
到內(nèi)存中,特別占內(nèi)存清蚀,并且還不能釋放匕荸,釋放之后下次進入再次加載時會報錯。
說明:
Android 的幀動畫可以使用AnimationDrawable 實現(xiàn)枷邪,但是如果你的幀動畫中如果
包含過多幀圖片榛搔,一次性加載所有幀圖片所導致的內(nèi)存消耗會使低端機發(fā)生OOM
異常。幀動畫所使用的圖片要注意降低內(nèi)存消耗东揣,當圖片比較大時践惑,容易出現(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>

上述如此多圖片的動畫就不建議使用AnimationDrawable 了尔觉。

11.【強制】不能使用ScrollView包裹ListView/GridView/ExpandableListVIew;因為這
樣會把ListView 的所有Item 都加載到內(nèi)存中,要消耗巨大的內(nèi)存和cpu 去繪制圖
面芥吟。
說明:
ScrollView 中嵌套List 或RecyclerView 的做法官方明確禁止侦铜。除了開發(fā)過程中遇到
的各種視覺和交互問題,這種做法對性能也有較大損耗钟鸵。ListView 等UI 組件自身有
垂直滾動功能钉稍,也沒有必要在嵌套一層ScrollView。目前為了較好的UI 體驗携添,更貼
近Material Design 的設計嫁盲,推薦使用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ù)烈掠。基礎組件之間的數(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());
    }
}

13.【推薦】使用Toast 時,建議定義一個全局的Toast 對象俐镐,這樣可以避免連續(xù)顯示
Toast 時不能取消上一次Toast 消息的情況矫限。即使需要連續(xù)彈出Toast,也應避免直
接調(diào)用Toast#makeText佩抹。

14.【強制】使用Adapter 的時候叼风,如果你使用了ViewHolder 做緩存,在getView()的
方法中無論這項convertView 的每個子控件是否需要設置屬性(比如某個TextView
設置的文本可能為null棍苹,某個按鈕的背景色為透明无宿,某控件的顏色為透明等),都需
要為其顯式設置屬性(Textview 的文本為空也需要設置setText("")枢里,背景透明也需要
設置)孽鸡,否則在滑動的過程中蹂午,因為adapter item 復用的原因,會出現(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;
}

進程豆胸、線程與消息通信

  1. 【強制】不要通過Intent 在Android 基礎組件之間傳遞大數(shù)據(jù)(binder transaction
    緩存為1MB),可能導致OOM巷疼。

  2. 【強制】在Application 的業(yè)務初始化代碼加入進程判斷晚胡,確保只在自己需要的進程
    初始化。特別是后臺進程減少不必要的業(yè)務初始化嚼沿。
    正例:

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        //在所有進程中初始化
        ....
        //僅在主進程中初始化
        if (mainProcess) {
            ...
        }
        //僅在后臺進程中初始化
        if (bgProcess) {
            ...
        }
    }
}
  1. 【強制】新建線程時搬泥,必須通過線程池提供(AsyncTask 或者ThreadPoolExecutor或者其他形式自定義的線程池),不允許在應用中自行顯式創(chuàng)建線程伏尼。
    說明:
    使用線程池的好處是減少在創(chuàng)建和銷毀線程上所花的時間以及系統(tǒng)資源的開銷忿檩,解決資源不足的問題。如果不使用線程池爆阶,有可能造成系統(tǒng)創(chuàng)建大量同類線程而導致消耗完內(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í)行任務
executorService.execute(new Runnnable() {
    ...
});

反例:

new Thread(new Runnable() {
    @Override
    public void run() {
        //操作語句
        ...
    }
}).start();
  1. 【強制】線程池不允許使用Executors 去創(chuàng)建班套,而是通過ThreadPoolExecutor 的方
    式,這樣的處理方式讓寫的同學更加明確線程池的運行規(guī)則故河,規(guī)避資源耗盡的風險吱韭。
    說明:
    Executors 返回的線程池對象的弊端如下:
  1. FixedThreadPool 和SingleThreadPool : 允許的請求隊列長度為Integer.MAX_VALUE,可能會堆積大量的請求鱼的,從而導致OOM理盆;
  2. CachedThreadPool 和ScheduledThreadPool : 允許的創(chuàng)建線程數(shù)量為Integer.MAX_VALUE,可能會創(chuàng)建大量的線程凑阶,從而導致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();
  1. 【強制】子線程中不能更新界面,更新界面必須在主線程中進行宙橱,網(wǎng)絡操作不能在
    主線程中調(diào)用姨俩。

  2. 【推薦】盡量減少不同APP 之間的進程間通信及拉起行為。拉起導致占用系統(tǒng)資源师郑,
    影響用戶體驗环葵。

  3. 【推薦】新建線程時,定義能識別自己業(yè)務的線程名稱宝冕,便于性能優(yōu)化和問題排查张遭。
    正例:

public class MyThread extends Thread {
    public MyThread(){
        super.setName("ThreadName");
        …
    }
}
  1. 【推薦】ThreadPoolExecutor 設置線程存活時間(setKeepAliveTime),確扁剩空閑時
    線程能被釋放帝璧。

  2. 【推薦】禁止在多進程之間用SharedPreferences 共享數(shù)據(jù)先誉, 雖然可以
    (MODE_MULTI_PROCESS),但官方已不推薦的烁。

10.【推薦】謹慎使用Android 的多進程褐耳,多進程雖然能夠降低主進程的內(nèi)存壓力,但
會遇到如下問題:

  1. 首次進入新啟動進程的頁面時會有延時的現(xiàn)象(有可能黑屏渴庆、白屏幾秒铃芦,是白
    屏還是黑屏和新Activity 的主題有關);
  2. 應用內(nèi)多進程時襟雷,Application 實例化多次刃滓,需要考慮各個模塊是否都需要在所
    有進程中初始化。

文件與數(shù)據(jù)庫

  1. 【強制】任何時候不要硬編碼文件路徑耸弄,請使用Android 文件系統(tǒng)API 訪問咧虎。
    說明:
    Android 應用提供內(nèi)部和外部存儲,分別用于存放應用自身數(shù)據(jù)以及應用產(chǎn)生的用
    戶數(shù)據(jù)计呈∨樗校可以通過相關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;
}

反例:

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;
}
  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_READ_ONLY.equals(state)) {
        return true;
    }
    return false;
}
  1. 【強制】應用間共享文件時,不要通過放寬文件系統(tǒng)權(quán)限的方式去實現(xiàn)善镰,而應使用
    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 分享文件給別的應用,包括但不限于Intent
    getAlbumImageIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(image));
    startActivityForResult(takePhotoIntent, REQUEST_GET_ALBUMIMAGE);
}
  1. 【推薦】SharedPreference 中只能存儲簡單數(shù)據(jù)類型(int媳禁、boolean眠副、String 等),
    復雜數(shù)據(jù)類型建議使用文件竣稽、數(shù)據(jù)庫等其他方式存儲。
    正例:
public void updateSettings() {
    SharedPreferences mySharedPreferences = getSharedPreferences("settings",Activity.MODE_PRIVATE);
    SharedPreferences.Editor editor = mySharedPreferences.edit();
    editor.putString("id", "foo");
    editor.putString("nick", "bar");
    //不要把復雜數(shù)據(jù)類型轉(zhuǎn)成String 存儲
    editor.apply();
}
  1. 【推薦】SharedPreference 提交數(shù)據(jù)時霍弹, 盡量使用Editor#apply() 毫别, 而非
    Editor#commit()。一般來講典格,僅當需要確定提交結(jié)果岛宦,并據(jù)此有后續(xù)操作時,才使
    用Editor#commit()耍缴。
    說明:
    SharedPreference 相關修改使用apply 方法進行提交會先寫入內(nèi)存砾肺,然后異步寫入
    磁盤挽霉, commit 方法是直接寫入磁盤。如果頻繁操作的話apply 的性能會優(yōu)于commit变汪,
    apply 會將最后修改內(nèi)容寫入磁盤侠坎。但是如果希望立刻獲取存儲操作的結(jié)果,并據(jù)此
    做相應的其他操作裙盾,應當使用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();
  1. 【強制】數(shù)據(jù)庫Cursor 必須確保使用完后關閉,以免內(nèi)存泄漏番官。
    說明:
    Cursor 是對數(shù)據(jù)庫查詢結(jié)果集管理的一個類庐完,當查詢的結(jié)果集較小時,消耗內(nèi)存不
    易察覺徘熔。但是當結(jié)果集較大门躯,長時間重復操作會導致內(nèi)存消耗過大,需要開發(fā)者在
    操作完成后手動關閉Cursor酷师。
    數(shù)據(jù)庫Cursor 在創(chuàng)建及使用時生音,可能發(fā)生各種異常,無論程序是否正常結(jié)束窒升,必須
    在最后確保Cursor 正確關閉缀遍,以避免內(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 不關閉
}
  1. 【強制】多線程操作寫入數(shù)據(jù)庫時蓉媳,需要使用事務譬挚,以免出現(xiàn)同步問題。
    說明:
    通過SQLiteOpenHelper 獲取數(shù)據(jù)庫SQLiteDatabase 實例酪呻,Helper 中會自動緩存已經(jīng)打開的SQLiteDatabase 實例减宣,單個App 中應使用SQLiteOpenHelper 的單例模式確保數(shù)據(jù)庫連接唯一。由于SQLite 自身是數(shù)據(jù)庫級鎖玩荠,單個數(shù)據(jù)庫操作是保證線程安全的(不能同時寫入)漆腌,transaction 是一次原子操作,因此處于事務中的操作是線程安全的阶冈。
    若同時打開多個數(shù)據(jù)庫連接闷尿,并通過多線程寫入數(shù)據(jù)庫,會導致數(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);
}
  1. 【推薦】大數(shù)據(jù)寫入數(shù)據(jù)庫時,請使用事務或其他能夠提高I/O 效率的機制匆骗,保證執(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();
    }
}
  1. 【強制】執(zhí)行SQL 語句時誉简,應使用SQLiteDatabase#insert()、update()盟广、delete()闷串,
    不要使用SQLiteDatabase#execSQL(),以免SQL 注入風險衡蚂。
    正例:
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ù)庫中,應該避免將不受
信任的外部數(shù)據(jù)直接拼接在原始SQL 語句中毛甲。
正例:

// 使用一個可替換參數(shù)
String mSelectionClause = "var = ?";
String[] selectionArgs = {""};
selectionArgs[0] = mUserInput;

反例:

// 拼接用戶輸入內(nèi)容和列名
String mSelectionClause = "var = " + mUserInput;

Bitmap年叮、Drawable 與動畫

  1. 【強制】加載大圖片或者一次性加載多張圖片,應該在異步線程中進行玻募。圖片的加
    載只损,涉及到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");
    }
});
  1. 【強制】在ListView,ViewPager艾栋,RecyclerView爆存,GirdView 等組件中使用圖片時,
    應做好圖片的緩存蝗砾,避免始終持有圖片導致內(nèi)存溢出先较,也避免重復創(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;
    }
    ...
}

反例:
沒有存儲,每次都需要解碼瀑凝,或者有緩存但是沒有合適的淘汰機制序芦,導致緩存效果
很差,依然經(jīng)常需要重新解碼粤咪。

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

  2. 【推薦】應根據(jù)實際展示需要寥枝,壓縮圖片宪塔,而不是直接顯示原圖。手機屏幕比較小囊拜,
    直接顯示原圖某筐,并不會增加視覺上的收益,但是卻會耗費大量寶貴的內(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ù)圖片分辨率以及我們實際需要展示的大小南誊,計算壓縮率
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
    // 設置壓縮率,并解碼
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

反例:
不經(jīng)壓縮顯示原圖蜜托。

  1. 【強制】使用完畢的圖片抄囚,應該及時回收,釋放寶貴的內(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;

反例:
使用完成圖片,始終不釋放資源棠涮。

  1. 【強制】在Activity#onPause()或Activity#onStop()回調(diào)中谬哀,關閉當前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()
    }
}

反例:
頁面退出時史煎,不關閉該頁面相關的動畫。

  1. 【推薦】在動畫或者其他異步任務結(jié)束時诬垂,應該考慮回調(diào)時刻的環(huán)境是否還支持業(yè)
    務處理劲室。例如Activity 的onStop()函數(shù)已經(jīng)執(zhí)行,且在該函數(shù)中主動釋放了資源结窘,
    此時回調(diào)中如果不做判斷就會空指針崩潰很洋。
    正例:
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);
    }
}

反例:
動畫結(jié)束回調(diào)中,直接使用資源不加判斷隧枫,導致異常喉磁。

  1. 【推薦】使用 inBitmap 重復利用內(nèi)存空間,避免重復開辟新內(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)中運行协怒,嘗試使用inBitmap
    if (Utils.hasHoneycomb()) {
        addInBitmapOptions(options, cache);
    }
    ...
    return BitmapFactory.decodeFile(filename, options);
}

private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {
    // inBitmap 只處理可變的位圖,所以強制返回可變的位圖
    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 類中關于圖片顏色的存儲方式定義:

  1. ALPHA_8 代表8 位Alpha 位圖;
  2. ARGB_4444 代表16 位ARGB 位圖妖滔;
  3. ARGB_8888 代表32 位ARGB 位圖隧哮;
  4. RGB_565 代表8 位RGB 位圖。
    位圖位數(shù)越高座舍,存儲的顏色信息越多沮翔,圖像也就越逼真。大多數(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.【推薦】謹慎使用gif 圖片责静,注意限制每個頁面允許同時播放的gif 圖片,以及單個
gif 圖片的大小盖桥。
12.【參考】大圖片資源不要直接打包到apk灾螃,可以考慮通過文件倉庫遠程下載,減小包
體積揩徊。
13.【推薦】根據(jù)設備性能腰鬼,選擇性開啟復雜動畫,以實現(xiàn)一個整體較優(yōu)的性能和體驗塑荒;
14.【推薦】在有強依賴 onAnimationEnd 回調(diào)的交互時熄赡,如動畫播放完畢才能操作頁
面, onAnimationEnd 可能會因各種異常沒被回調(diào)( 參考:
https://stackoverflow.com/questions/5474923/onanimationend-is-not-getting-calle
d-onanimationstart-works-fine )齿税, 建議加上超時保護或通過 postDelay 替代
onAnimationEnd彼硫。
正例:

View v = findViewById(R.id.xxxViewID);
final FadeUpAnimation anim = new FadeUpAnimation(v);
anim.setInterpolator(new AccelerateInterpolator());
anim.setDuration(1000);
anim.setFillAfter(true);
new Handler().postDelayed(new Runnable() {
    public void run() {
        if (v != null) {
            v.clearAnimation();
        }
    }
}, anim.getDuration());
v.startAnimation(anim);

15.【推薦】當View Animation 執(zhí)行結(jié)束時,調(diào)用View.clearAnimation()釋放相關資源凌箕。
正例:

View v = findViewById(R.id.xxxViewID);
final FadeUpAnimation anim = new FadeUpAnimation(v);
anim.setInterpolator(new AccelerateInterpolator());
anim.setDuration(1000);
anim.setFillAfter(true);
anim.setAnimationListener(new AnimationListener() {
    @Override
    public void onAnimationEnd(Animation arg0) {
        //判斷一下資源是否被釋放了
        if (v != null) {
            v.clearAnimation();
        }
    }
});
v.startAnimation(anim);

安全

  1. 【強制】禁止使用常量初始化矢量參數(shù)構(gòu)建IvParameterSpec黄绩,建議IV 通過隨機方
    式產(chǎn)生深员。
    說明:
    使用常量初始化向量尖昏,密碼文本的可預測性會高得多赖歌,容易受到字典式攻擊。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());
  1. 【強制】將android:allowbackup 屬性必須設置為false,阻止應用數(shù)據(jù)被導出把篓。
    說明:
    android:allowBackup 原本是 Android 提供的 adb 調(diào)試功能纫溃,如果設置為 true腰涧,
    可以導出應用數(shù)據(jù)備份并在任意設備上恢復韧掩。這對應用安全性和用戶數(shù)據(jù)隱私構(gòu)成
    極大威脅,所以必須設置為 false窖铡,防止數(shù)據(jù)泄露疗锐。
    正例:
<application
    android:allowBackup="false"
    android:largeHeap="true"
    android:icon="@drawable/test_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
  1. 【強制】如果使用自定義HostnameVerifier 實現(xiàn)類,必須在verify()方法中校驗服務
    器主機名的合法性费彼,否則可能受到中間人攻擊滑臊。
    說明:
    在與服務器建立 https 連接時,如果 URL 的主機名和服務器的主機名不匹配箍铲,則
    可通過該回調(diào)接口來判斷是否應該允許建立連接雇卷。如果回調(diào)內(nèi)實現(xiàn)不恰當,沒有有
    效校驗主機名颠猴,甚至默認接受所有主機名关划,會大大增加安全風險。
    反例:
HostnameVerifier hnv = new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
        // 不做校驗翘瓮,接受任意域名服務器
        return true;
    }
};
HttpsURLConnection.setDefaultHostnameVerifier(hnv);
  1. 【強制】如果使用自定義X509TrustManager 實現(xiàn)類贮折,必須在checkServerTrusted()
    方法中校驗服務端證書的合法性,否則可能受到中間人攻擊资盅。
    說明:
    常見誤區(qū)是checkServerTrusted()方法根本沒有實現(xiàn)调榄,這將導致 X509TrustManager
    形同虛設。該方法中需要實現(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今穿,接受任意服務端證書
    }
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
};
sslContext.init(null, new TrustManager[] { tm }, null);
  1. 【強制】在SDK 支持的情況下缤灵,Android 應用必須使用V2 簽名,這將對APK 文件的修改做更多的保護荣赶。

  2. 【強制】所有的 Android 基本組件(Activity凤价、Service、BroadcastReceiver拔创、ContentProvider 等)都不應在沒有嚴格權(quán)限控制的情況下利诺,將 android:exported 設置為 true。

  3. 【強制】WebView 應設置 WebView#getSettings()#setAllowFileAccess(false)剩燥、
    WebView#getSettings()#setAllowFileAccessFromFileURLs(false) 慢逾、
    WebView#getSettings()#setAllowUniversalAccessFromFileURLs(false)立倍,阻止 file
    scheme URL 的訪問。

8.【強制】不要把敏感信息打印到log 中侣滩。
說明:
在開發(fā)過程中口注,為了方便調(diào)試,通常會使用log 函數(shù)輸出一些關鍵流程的信息君珠,這
些信息中通常會包含敏感內(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)試信息,日志沒有
關閉唯竹,攻擊者可以直接從Logcat 中讀取這些敏感信息乐导。所以在產(chǎn)品的線上版本中關
閉調(diào)試接口,不要輸出敏感信息浸颓。

9.【強制】確保應用發(fā)布版本的android:debuggable 屬性設置為false物臂。

10.【強制】本地加密秘鑰不能硬編碼在代碼中,更不能使用 SharedPreferences 等本
地持久化機制存儲产上。應選擇Android 自身的秘鑰庫(KeyStore)機制或者其他安全
性更高的安全解決方案保存棵磷。
說明:
應用程序在加解密時,使用硬編碼在程序中的密鑰蒂秘,攻擊者通過反編譯拿到密鑰可
以輕易解密APP 通信數(shù)據(jù)泽本。

11.【建議】addJavascriptInterface() 可以添加JS 對本地Java 方法的調(diào)用,但這本身
會導致惡意代碼的攻擊姻僧。在Android 4.2(API Level 17)以下规丽,不應再使用這樣的
調(diào)用方式。在Android 4.2 及以上撇贺,需要對本地被遠程調(diào)用的方法顯式添加
@JavascriptInterface annotation赌莺。

12.【強制】使用Android 的AES/DES/DESede 加密算法時,不要使用ECB 加密模式松嘶,
應使用CBC 或CFB 加密模式艘狭。
說明:
加密模式有 ECB、CBC翠订、CFB巢音、OFB 等,其中 ECB 的安全性較弱尽超,如果使用固
定的密鑰官撼,相同的明文將會生成相同的密文,容易受到字典攻擊似谁,建議使用 CBC傲绣、
CFB 或OFB 等模式掠哥。

  1. ECB:Electronic codebook,電子密碼本模式
  2. CBC:Cipher-block chaining秃诵,密碼分組鏈接模式
  3. CFB:Cipher feedback续搀,密文反饋模式
  4. OFB:Output feedback,輸出反饋模式

13.【強制】Android APP 在HTTPS 通信中菠净,驗證策略需要改成嚴格模式禁舷。
說明:
Android APP 在HTTPS 通信中,使用ALLOW_ALL_HOSTNAME_VERIFIER嗤练,表
示允許和所有的HOST 建立SSL 通信榛了,這會存在中間人攻擊的風險,最終導致敏感
信息可能會被劫持煞抬,以及其他形式的攻擊。
反例:

SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

ALLOW_ALL_HOSTNAME_VERIFIER 關閉host 驗證构哺,允許和所有的host 建立
SSL 通信革答,BROWSER_COMPATIBLE_HOSTNAME_VERIFIER 和瀏覽器兼容的
驗證策略,即通配符能夠匹配所有子域名 曙强,STRICT_HOSTNAME_VERIFIER 嚴
格匹配模式残拐,hostname 必須匹配第一個CN 或者任何一個subject-alts,以上例子
使用了ALLOW_ALL_HOSTNAME_VERIFIER碟嘴,需要改成STRICT_HOSTNAME_
VERIFIER溪食。

14.【推薦】在Android 4.2(API Level 17)及以上,對安全性要求較高的應用可在Activity
中娜扇,對 Activity 所關聯(lián)的 Window 應用 WindowManager.LayoutParams.FLAG_
SECURE错沃,防止被截屏、錄屏雀瓢。但要注意的是枢析,一個 Activity 關聯(lián)的 Window 可
能不止一個,如果使用了 Dialog / DialogFragment 等控件彈出對話框刃麸,它們本身
也會創(chuàng)建一個新的 Window醒叁,也一樣需要保護。

15.【推薦】zip 中不要包含 ../../file 這樣的路徑泊业,可能被篡改目錄結(jié)構(gòu)把沼,造成攻擊。
說明:
當zip 壓縮包中允許存在"../"的字符串吁伺,攻擊者可以利用多個"../"在解壓時改變zip 文
件存放的位置饮睬,當文件已經(jīng)存在是就會進行覆蓋,如果覆蓋掉的文件是so箱蝠、dex 或
者odex 文件续捂,就有可能造成嚴重的安全問題垦垂。
正例:
對路徑進行判斷,存在".."時拋出異常牙瓢。

//對重要的Zip 壓縮包文件進行數(shù)字簽名校驗劫拗,校驗通過才進行解壓
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 算法矾克,有一定的安全性页慷,
但不能代替加密算法。敏感信息的存儲和傳輸胁附,需要使用專業(yè)的加密機制酒繁。

其他

  1. 【強制】不能使用System.out.println 打印log。
    正例:
Log.d(TAG, "Some Android Debug info ...");

反例:

System.out.println("System out println ...");
  1. 【強制】Log 的tag 不能是" "控妻。
    說明:
    日志的tag 是空字符串沒有任何意義州袒,也不利于過濾日志。
    正例:
private static String TAG = "LoginActivity";
Log.e(TAG, "Login failed!");

反例:

Log.e("", "Login failed!");

參考文獻

[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 多個Fragment 嵌套導致的三大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.reibang.com/p/57eb08fe071d

---The end---

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市菇存,隨后出現(xiàn)的幾起案子夸研,更是在濱河造成了極大的恐慌,老刑警劉巖依鸥,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件亥至,死亡現(xiàn)場離奇詭異,居然都是意外死亡贱迟,警方通過查閱死者的電腦和手機姐扮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來关筒,“玉大人溶握,你說我怎么就攤上這事≌舨ィ” “怎么了睡榆?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長袍榆。 經(jīng)常有香客問我胀屿,道長,這世上最難降的妖魔是什么包雀? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任宿崭,我火速辦了婚禮,結(jié)果婚禮上才写,老公的妹妹穿的比我還像新娘葡兑。我一直安慰自己奖蔓,他們只是感情好,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布讹堤。 她就那樣靜靜地躺著吆鹤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪洲守。 梳的紋絲不亂的頭發(fā)上疑务,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天,我揣著相機與錄音梗醇,去河邊找鬼知允。 笑死,一個胖子當著我的面吹牛叙谨,可吹牛的內(nèi)容都是我干的温鸽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼唉俗,長吁一口氣:“原來是場噩夢啊……” “哼嗤朴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起虫溜,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎股缸,沒想到半個月后衡楞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡敦姻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年瘾境,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片镰惦。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡迷守,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出旺入,到底是詐尸還是另有隱情兑凿,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布茵瘾,位于F島的核電站礼华,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏拗秘。R本人自食惡果不足惜圣絮,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雕旨。 院中可真熱鬧扮匠,春花似錦捧请、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至帮非,卻和暖如春氧吐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背末盔。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工筑舅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人陨舱。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓翠拣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親游盲。 傳聞我的和親對象是個殘疾皇子误墓,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容