看看泓洋老師發(fā)布的:安卓開發(fā)者的鐵血軍規(guī)《阿里巴巴 Android 開發(fā)手冊》

《阿里巴巴 Android 開發(fā)手冊》是阿里巴巴集團各大 Android 開發(fā)團隊的集體智慧結(jié)晶和經(jīng)驗總結(jié),將淘寶瓮恭、天貓能庆、閑魚讶请、釘釘?shù)?App 長期開發(fā)迭代和優(yōu)化經(jīng)驗系統(tǒng)地整理成冊,以指導 Android 開發(fā)者更加高效俗他、高質(zhì)量地進行 App 開發(fā)脖捻,呈現(xiàn)給用戶體驗好、性能優(yōu)兆衅、穩(wěn)定性佳地沮、安全性高的產(chǎn)品嗜浮。
《阿里巴巴 Android 開發(fā)手冊》作為阿里巴巴開發(fā)規(guī)約重要的一環(huán),我們的目標是:
防患未然摩疑,提升質(zhì)量意識危融,降低故障率和維護成本;
標準統(tǒng)一雷袋,提升協(xié)作效率吉殃;
追求卓越的工匠精神,打磨精品代碼楷怒。
本手冊以開發(fā)者為中心視角分為 Java 語言規(guī)范(遵循《阿里巴巴 Java 開發(fā)手冊》)蛋勺, Android 資源文件命名與使用,Android 基本組件鸠删,UI 與布局抱完,進程、線程與消息通信刃泡,文件與數(shù)據(jù)庫巧娱,Bitmap、Drawable 與動畫捅僵,安全家卖,其他等九大部分,根據(jù)約束力強弱庙楚,規(guī)約依次分為強制上荡、推薦、參考三大類:
【強制】必須遵守馒闷,違反本約定或?qū)饑乐氐暮蠊?br> 【推薦】盡量遵守酪捡,長期遵守有助于系統(tǒng)穩(wěn)定性和合作效率的提升;
【參考】充分理解纳账,技術(shù)意識的引導逛薇,是個人學習、團隊溝通疏虫、項目合作的方向永罚。
對于規(guī)約條目的延伸信息中,“說明”對內(nèi)容做了適當擴展和解釋卧秘;“正例”提倡什么樣的編碼和實現(xiàn)方式呢袱;“反例”說明需要提防的雷區(qū),以及錯誤案例翅敌。

一.Android 基本組件

Android 基本組件指 Activity 羞福、 Fragment 、 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ù)的持久化存儲混為一談粥喜。持久化存儲應(yīng)該在 Activity#onPause()/onStop()中實行凸主。
3.【強制】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();
}

4.【強制】避免在 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) {
            }
        }
    }
}

5.【強制】避免在 BroadcastReceiver#onReceive()中執(zhí)行耗時操作毯焕,如果有耗時工作衍腥,應(yīng)該創(chuàng)建 IntentService 完成,而不應(yīng)該在 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
    }
};

6.【強制】 避免使用隱式 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 攔截的風險。

正例:
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 接收導致敏感信息泄漏
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);
            }
        }
    }
}

7.【 推 薦 】 添 加 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();
    }
}

8.【推薦】不要在 Activity#onDestroy()內(nèi)執(zhí)行釋放資源的工作,例如一些工作線程的
銷毀和停止矩父,因為 onDestroy() 執(zhí)行的時機可能較晚 锉桑。 可根據(jù)實際需要,在Activity#onPause()/onStop()中結(jié)合 isFinishing()的判斷來執(zhí)行窍株。
9.【推薦】如非必須民轴,避免使用嵌套的 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();

10【.推薦】總是使用顯式 Intent 啟動或者綁定 Service凶杖,且不要為服務(wù)聲明 Intent Filter,保證應(yīng)用的安全性款筑。如果確實需要使用隱式調(diào)用智蝠,則可為 Service 提供 Intent Filter 并從 Intent 中排除相應(yīng)的組件名稱,但必須搭配使用 Intent#setPackage()方法設(shè)置 Intent 的指定包名奈梳,這樣可以充分消除目標服務(wù)的不確定性杈湾。
11.【推薦】Service 需要以多線程來并發(fā)處理多個啟動請求,建議使用 IntentService颈嚼,可避免各種復雜的設(shè)置毛秘。
說明:
Service 組件一般運行主線程,應(yīng)當避免耗時操作阻课,如果有耗時操作應(yīng)該在 Worker 線程執(zhí)行叫挟。可以使用 IntentService 執(zhí)行后臺任務(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.【推薦】對于只用于應(yīng)用內(nèi)的廣播抹恳,優(yōu)先使用 LocalBroadcastManager 來進行注冊和發(fā)送,LocalBroadcastManager 安全性更好署驻,同時擁有更高的運行效率奋献。
說明:
對于使用 Context#sendBroadcast()等方法發(fā)送全局廣播的代碼進行提示。如果該廣播僅用于應(yīng)用內(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);

13.【推薦】當前 Activity 的 onPause 方法執(zhí)行結(jié)束后才會創(chuàng)建(onCreate)或恢復(onRestart)別的 Activity宣吱,所以在 onPause 方法中不適合做耗時較長的工作窃这,這會影響到頁面之間的跳轉(zhuǎn)效率。
14.【強制】Activity 或者 Fragment 中動態(tài)注冊 BroadCastReceiver 時征候,registerReceiver()
和unregisterReceiver()要成對出現(xiàn)杭攻。說明:
如果 registerReceiver()和 unregisterReceiver()不成對出現(xiàn),則可能導致已經(jīng)注冊的
receiver 沒有在合適的時機注銷疤坝,導致內(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 中使用或在代碼中使用 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 應(yīng)用頁面上任何一個 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 所花費的時間就越久拼窥。要縮短這個時間益楼,關(guān)鍵是保持 View 的樹形結(jié)構(gòu)盡量扁平,而且要移除所有不需要渲染的View年鸳。理想情況下,總共的 measure丸相,layout搔确,draw 時間應(yīng)該被很好的控制在 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);
}

3.【推薦】源文件統(tǒng)一采用 UTF-8 的形式進行編碼蜘拉。
4.【強制】禁止在非 UI 線程進行 View 相關(guān)操作。
5.【推薦】文本大小使用單位 dp有鹿,View 大小使用單位 dp旭旭。對于 TextView,如果在文字大小確定的情況下推薦使用 wrap_content 布局避免出現(xiàn)文字顯示不全的適配問題葱跋。
說明:
之所以文本大小也推薦使用 dp 而非 sp持寄,因為 sp 是 Android 早期推薦使用的,但其
實sp 不僅和 dp 一樣受屏幕密度的影響娱俺,還受到系統(tǒng)設(shè)置里字體大小的影響稍味,所以使用 dp 對于應(yīng)用開發(fā)會更加保證 UI 的一致性和還原度。
6.【強制】禁止在設(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);
}

7.【推薦】靈活使用布局,推薦 merge僵朗、ViewStub 來優(yōu)化布局赖欣,盡可能多的減少 UI
布局層級,推薦使用 FrameLayout验庙,LinearLayout顶吮、RelativeLayout 次之。
8.【推薦】在需要時刻刷新某一區(qū)域的組件時粪薛,建議通過以下方式避免引發(fā)全局 layout 刷新:
1)設(shè)置固定的 View 大小的寬高悴了,如倒計時組件等;
2)調(diào)用 View 的 layout 方法修改位置违寿,如彈幕組件等湃交;
3)通過修改 Canvas 位置并且調(diào)用 invalidate(int l, int t, int r, int b)等方式限定刷新區(qū)域;
4)通過設(shè)置一個是否允許 requestLayout 的變量藤巢,然后重寫控件的 requestlayout搞莺、 onSizeChanged 方法,判斷控件的大小沒有改變的情況下掂咒,當進入 requestLayout 的時候才沧,直接返回而不調(diào)用 super 的 requestLayout 方法迈喉。
9.【推薦】不能在 Activity 沒有完全顯示時顯示 PopupWindow 和 Dialog。
說明:
Android Activity 創(chuàng)建時的生命周期温圆,按照 onCreate() -> onStart() -> onResume() -> onAttachedToWindow() -> onWindowFocusChanged() 的 順 序 挨摸, 其 中 在 Activity#onAttachedToWindow() 時,Activity 會與它的 Window 關(guān)聯(lián)岁歉,這時 UI 才會開始繪制得运,在 Activity#onWindowFocusChanged() 時,UI 才變成可交互狀態(tài)刨裆,可以提示用戶使用澈圈。如果在 Window 未關(guān)聯(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 的設(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());
}
}

13.【推薦】使用 Toast 時,建議定義一個全局的 Toast 對象渐白,這樣可以避免連續(xù)顯示 Toast 時不能取消上一次 Toast 消息的情況尊浓。即使需要連續(xù)彈出 Toast,也應(yīng)避免直接調(diào)用 Toast#makeText纯衍。
14.【強制】使用 Adapter 的時候栋齿,如果你使用了 ViewHolder 做緩存,在 getView()的方法中無論這項 convertView 的每個子控件是否需要設(shè)置屬性(比如某個 TextView 設(shè)置的文本可能為 null襟诸,某個按鈕的背景色為透明瓦堵,某控件的顏色為透明等),都需要為其顯式設(shè)置屬性(Textview 的文本為空也需要設(shè)置 setText("")歌亲,背景透明也需要設(shè)置)菇用,否則在滑動的過程中,因為 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 基礎(chǔ)組件之間傳遞大數(shù)據(jù)(binder transaction
緩存為 1MB)悍缠,可能導致 OOM卦绣。
2.【強制】在 Application 的業(yè)務(wù)初始化代碼加入進程判斷,確保只在自己需要的進程初始化飞蚓。特別是后臺進程減少不必要的業(yè)務(wù)初始化滤港。

正例:
public class MyApplication extends Application {
@Override
public void onCreate() {
    //在所有進程中初始化
    ....
    //僅在主進程中初始化
    if (mainProcess) {
        ...
    }
    //僅在后臺進程中初始化
    if (bgProcess) {
        ...
    }
}
}

3.【強制】新建線程時,必須通過線程池提供(AsyncTask 或者 ThreadPoolExecutor
或者其他形式自定義的線程池)趴拧,不允許在應(yīng)用中自行顯式創(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í)行任務(wù)
executorService.execute(new Runnnable() {
...
}
);
反例:
new Thread(new Runnable() {
@Override
public void run() {
    //操作語句
    ...
}
}
).start();

4.【強制】線程池不允許使用 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();

5.【強制】子線程中不能更新界面,更新界面必須在主線程中進行脚曾,網(wǎng)絡(luò)操作不能在主線程中調(diào)用东且。
6.【推薦】盡量減少不同 APP 之間的進程間通信及拉起行為。拉起導致占用系統(tǒng)資源本讥,影響用戶體驗珊泳。
7.【推薦】新建線程時,定義能識別自己業(yè)務(wù)的線程名稱拷沸,便于性能優(yōu)化和問題排查色查。

正例:
public class MyThread extends Thread {
public MyThread(){
    super.setName("ThreadName");
    …
}
}

8.【推薦】ThreadPoolExecutor 設(shè)置線程存活時間(setKeepAliveTime),確弊采郑空閑時線程能被釋放秧了。
9.【推薦】 禁止在多進程之間用 SharedPreferences 共享數(shù)據(jù),雖然可以
(MODE_MULTI_PROCESS)序无,但官方已不推薦示惊。
10.【推薦】謹慎使用 Android 的多進程,多進程雖然能夠降低主進程的內(nèi)存壓力愉镰,但會遇到如下問題:
1)首次進入新啟動進程的頁面時會有延時的現(xiàn)象(有可能黑屏、白屏幾秒钧汹,是白屏還是黑屏和新 Activity 的主題有關(guān))丈探;
2)應(yīng)用內(nèi)多進程時,Application 實例化多次拔莱,需要考慮各個模塊是否都需要在所有進程中初始化碗降。

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

1.【強制】任何時候不要硬編碼文件路徑,請使用 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;
}

2.【強制】當使用外部存儲時,必須檢查外部存儲的可用性京痢。

正例:
//讀/寫檢查
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;
}

3.【強制】應(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);
}

4.【推薦】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();
}

5.【推薦】 SharedPreference 提交數(shù)據(jù)時橄霉,盡量使用 Editor#apply() 窃爷,而非
Editor#commit()。一般來講姓蜂,僅當需要確定提交結(jié)果按厘,并據(jù)此有后續(xù)操作時,才使用 Editor#commit()钱慢。
說明:
SharedPreference 相關(guān)修改使用 apply 方法進行提交會先寫入內(nèi)存逮京,然后異步寫入磁盤,commit 方法是直接寫入磁盤束莫。如果頻繁操作的話 apply 的性能會優(yōu)于 commit懒棉, apply 會將最后修改內(nèi)容寫入磁盤。但是如果希望立刻獲取存儲操作的結(jié)果览绿,并據(jù)此做相應(yīng)的其他操作策严,應(yī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();

6.【強制】數(shù)據(jù)庫 Cursor 必須確保使用完后關(guān)閉饿敲,以免內(nèi)存泄漏妻导。說明:
Cursor 是對數(shù)據(jù)庫查詢結(jié)果集管理的一個類,當查詢的結(jié)果集較小時怀各,消耗內(nèi)存不易察覺倔韭。但是當結(jié)果集較大,長時間重復操作會導致內(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)閉
}

7.【強制】多線程操作寫入數(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ù)庫,會導致數(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);
}

8.【推薦】大數(shù)據(jù)寫入數(shù)據(jù)庫時,請使用事務(wù)或其他能夠提高 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();
}
}
9.【強制】執(zhí)行 SQL 語句時,應(yīng)使用 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ù)庫中机蔗,應(yīng)該避免將不受信任的外部數(shù)據(jù)直接拼接在原始 SQL 語句中妻顶。

正例:
//使用一個可替換參數(shù)
String mSelectionClause =    "var = ?";
String[] selectionArgs = {""};
selectionArgs[0] = mUserInput;
反例:
//拼接用戶輸入內(nèi)容和列名
String mSelectionClause =    "var = " + mUserInput;

五.Bitmap、Drawable 與動畫

1.【強制】加載大圖片或者一次性加載多張圖片蜒车,應(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");
}
}
);

2.【強制】在 ListView嬉挡,ViewPager,RecyclerView汇恤,GirdView 等組件中使用圖片時庞钢,應(yīng)做好圖片的緩存,避免始終持有圖片導致內(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)常需要重新解碼侄刽。

3.【強制】png 圖片使用 TinyPNG 或者類似工具壓縮處理,減少包體積拓挥。
4.【推薦】應(yīng)根據(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);
//設(shè)置壓縮率赁炎,并解碼
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
反例:
不經(jīng)壓縮顯示原圖醉箕。

5.【強制】使用完畢的圖片,應(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;
反例:
使用完成圖片吴旋,始終不釋放資源损肛。

6.【強制】在 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)的動畫笆焰。

7.【推薦】在動畫或者其他異步任務(wù)結(jié)束時劫谅,應(yīng)該考慮回調(diào)時刻的環(huán)境是否還支持業(yè)務(wù)處理。例如 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)中,直接使用資源不加判斷未檩,導致異常戴尸。

8.【推薦】使用 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 類中關(guān)于圖片顏色的存儲方式定義:
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ù)設(shè)備性能步悠,選擇性開啟復雜動畫签杈,以實現(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()釋放相關(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);

六.安全

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());

2.【強制】將 android:allowbackup 屬性必須設(shè)置為 false盔腔,阻止應(yīng)用數(shù)據(jù)被導出。說明:
android:allowBackup 原本是 Android 提供的 adb 調(diào)試功能月褥,如果設(shè)置為 true弛随,可以導出應(yīng)用數(shù)據(jù)備份并在任意設(shè)備上恢復。這對應(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" >

3.【強制】如果使用自定義 HostnameVerifier 實現(xiàn)類礁击,必須在 verify()方法中校驗服務(wù)器主機名的合法性盐杂,否則可能受到中間人攻擊。
說明:
在與服務(wù)器建立 https 連接時哆窿,如果 URL 的主機名和服務(wù)器的主機名不匹配链烈,則可通過該回調(diào)接口來判斷是否應(yīng)該允許建立連接。如果回調(diào)內(nèi)實現(xiàn)不恰當挚躯,沒有有效校驗主機名强衡,甚至默認接受所有主機名,會大大增加安全風險码荔。

反例:
HostnameVerifier hnv = new HostnameVerifier() {
@Override
public Boolean verify(String hostname, SSLSession session) {
//不做校驗漩勤,接受任意域名服務(wù)器
return true;
}
}
;
HttpsURLConnection.setDefaultHostnameVerifier(hnv);

4.【強制】如果使用自定義 X509TrustManager 實現(xiàn)類,必須在 checkServerTrusted()
方法中校驗服務(wù)端證書的合法性缩搅,否則可能受到中間人攻擊越败。
說明:
常見誤區(qū)是 checkServerTrusted()方法根本沒有實現(xiàn),這將導致 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);

5.【強制】在 SDK 支持的情況下媒峡,Android 應(yīng)用必須使用 V2 簽名,這將對 APK 文件的修改做更多的保護葵擎。
6.【強制】 所有的 Android 基本組件( Activity谅阿、 Service 、BroadcastReceiver、
ContentProvider 等)都不應(yīng)在沒有嚴格權(quán)限控制的情況下,將 android:exported 設(shè)置為 true磷瘤。
7.【強制】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ù)。
11.【建議】addJavascriptInterface() 可以添加 JS 對本地 Java 方法的調(diào)用队腐,但這本身
會導致惡意代碼的攻擊蚕捉。在 Android 4.2(API Level 17)以下,不應(yīng)再使用這樣的調(diào)用方式 香到。 在 Android 4.2 及以上,需要對本地被遠程調(diào)用的方法顯式添加 @JavascriptInterface annotation。
12【.強制】使用 Android 的 AES/DES/DESede 加密算法時悠就,不要使用 ECB 加密模式千绪,應(yīng)使用 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 關(guān)閉 host 驗證骤宣,允許和所有的 host 建立 SSL 通信,BROWSER_COMPATIBLE_HOSTNAME_VERIFIER 和瀏覽器兼容的驗證策略序愚,即通配符能夠匹配所有子域名 憔披,STRICT_HOSTNAME_VERIFIER 嚴格匹配模式,hostname 必須匹配第一個 CN 或者任何一個 subject-alts爸吮,以上例子使用了 ALLOW_ALL_HOSTNAME_VERIFIER芬膝,需要改成 STRICT_HOSTNAME_
VERIFIER。

4【.推薦】在 Android 4.2(API Level 17)及以上形娇,對安全性要求較高的應(yīng)用可在 Activity
中锰霜,對 Activity 所關(guān)聯(lián)的 Window 應(yīng)用 WindowManager.LayoutParams.FLAG_ SECURE,防止被截屏桐早、錄屏癣缅。但要注意的是厨剪,一個 Activity 關(guān)聯(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();
}
}

6.【推薦】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 ...");

2.【強制】Log 的 tag 不能是" "挖滤。說明:
日志的 tag 是空字符串沒有任何意義,也不利于過濾日志浅役。

正例:
private static String TAG = "LoginActivity";
Log.e(TAG, "Login failed!");
反例:
Log.e("", "Login failed!");

不得不說 泓洋老師的 厲害斩松,看看人家阿里寫代碼的規(guī)范,實在是太牛逼了觉既,怪不得大家都有阿里夢惧盹,加入的不止是實力的回報而是榮譽的獲得,加油吧瞪讼。

好久沒有寫文章了钧椰,渣渣一枚。符欠。嫡霞。。希柿。诊沪。996沒有時間养筒。。端姚。闽颇。。寄锐。。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末尖啡,一起剝皮案震驚了整個濱河市橄仆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌衅斩,老刑警劉巖盆顾,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異畏梆,居然都是意外死亡您宪,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門奠涌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宪巨,“玉大人,你說我怎么就攤上這事溜畅∧笞浚” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵慈格,是天一觀的道長怠晴。 經(jīng)常有香客問我,道長浴捆,這世上最難降的妖魔是什么蒜田? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮选泻,結(jié)果婚禮上冲粤,老公的妹妹穿的比我還像新娘。我一直安慰自己滔金,他們只是感情好色解,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著餐茵,像睡著了一般科阎。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上忿族,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天锣笨,我揣著相機與錄音蝌矛,去河邊找鬼。 笑死错英,一個胖子當著我的面吹牛入撒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播椭岩,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼茅逮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了判哥?” 一聲冷哼從身側(cè)響起献雅,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎塌计,沒想到半個月后挺身,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡锌仅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年章钾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片热芹。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡贱傀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伊脓,到底是詐尸還是另有隱情窍箍,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布丽旅,位于F島的核電站椰棘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏榄笙。R本人自食惡果不足惜邪狞,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茅撞。 院中可真熱鬧帆卓,春花似錦、人聲如沸米丘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拄查。三九已至吁津,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堕扶,已是汗流浹背碍脏。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工梭依, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人典尾。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓役拴,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钾埂。 傳聞我的和親對象是個殘疾皇子河闰,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355