將從以下幾個(gè)方面總結(jié)Android的應(yīng)用性能優(yōu)化
性能
- 框架API
- UI 性能
- I/O性能
- 屏幕滾動性能
內(nèi)存
- Android 如何管理內(nèi)存
- OOM終結(jié) & 低內(nèi)存終結(jié)
- 應(yīng)用內(nèi)存使用監(jiān)測
- 識別內(nèi)存泄露
- 最佳實(shí)踐
糟糕的用戶體驗(yàn)
- Activity 啟動時(shí)間過長
- 應(yīng)用無反應(yīng)(ANR)
- 幀速率差
關(guān)于幀
幀速率
- 為了保證能達(dá)到60fps认罩,最多只有16ms去處理每一幀
- 而保證能達(dá)到24fps躯枢,最多只有41ms去處理每一幀
常見操作耗時(shí)
- Binder RPC 調(diào)用大約花費(fèi) 0.12ms
- 從閃存讀取一個(gè)字節(jié)大約花費(fèi) 0.0x ~ 5ms(一個(gè)文件只讀一個(gè)字節(jié)有可能大于1ms)
- 寫內(nèi)容到閃存大約花費(fèi) 1-100ms(一個(gè)文件只寫一個(gè)字節(jié)有可能小于1ms)
- TCP 初始化加上HTTP提取通臣⒋桑花費(fèi)秒級的時(shí)間(此處指的是建立鏈接并獲取鏈接返回的數(shù)據(jù)的時(shí)間)
讀寫操作在性能差一些的機(jī)子上可能時(shí)間會有出入
往磁盤寫內(nèi)容的時(shí)候氓奈,會隨著磁盤的剩余空間的較少而導(dǎo)致寫速率不斷減低
永遠(yuǎn)不要做阻塞UI線程的事情,用一個(gè)新的線程去做可能會影響UI體驗(yàn)的事情
四種可以異步的實(shí)現(xiàn):
- Runnable
- Thread
- Future
- ExecutorService
- 使用Thread
new Thread(new Runnable() {
@Override
public void run() {
// do some heavy work
}
}).start();
- 使用內(nèi)置AsyncTask
new AsyncTask<URL, Integer, Integer>() {
protected Long doInBackground(URL... urls) {
final int count = urls.length;
for ( int i = 0; i < count; i++ ) {
Downloader.download(url);
publishProgress(i);
}
return count;
}
protected void onProgressUpdate(Integer... progress) {
setProgress(progress[0]);
}
protected void onPostExecute(Integer result) {
showDialog(“Downloaded “ + result + “ files”);
}
}
- 使用HandlerThread
HandlerThread mHandlerThread = new HandlerThread("WorkerThread");
Handler handler = new Handler(mHandlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case JOB_1:
// do job #1
break;
case JOB_2:
// do job #2
break;
}
}
};
handler.sendEmptyMessage(JOB_1);
handler.sendEmptyMessage(JOB_2);
handler.post(new Runnable() {
@Override
public void run() {
// do more work
}
});
@Override
protected void onDestroy() {
mHandlerThread.quit();
super.onDestroy();
}
- 使用AsyncQueryHandler
new AsyncQueryHandler(getContentResolver()) {
@Override
protected void onQueryComplete(int token, Object cookie,
Cursor cursor) {
if (token == 0) {
// get data from cursor
}
}
}.startQuery(0, // token
null, // cookie
RawContacts.CONTENT_URI, null, // projection
RawContacts.CONTACT_ID + "<?", // selection
new String[] { "888" }, // selectionArgs
RawContacts.DISPLAY_NAME_PRIMARY + " ASC" // orderby
);
- 使用IntentService
public class WorkerService extends IntentService {
public WorkerService() {
super("WorkerThread");
}
@Override
protected void onHandleIntent(Intent intent) {
String action = intent.getAction();
if ("com.test.DO_JOB_1".equals(action)) {
// do job #1
}
}
}
startService(new Intent("com.test.DO_JOB_1"));
UI線程性能總結(jié)
- Activity or Fragment
- AsyncTask
- Handler,HandlerThread
- AsyncTaskLoader
- ContentProvider
- AsyncQueryHandler
- CursorLoader
- Service
- IntentService
- Parcel.writeStrongBinder(IBinder binder)
View Hierarchy
- Measure
- Layout
- Draw
- Key Events
- Trackball Events
- Touch Evnets
Tips:
降低布局層次結(jié)構(gòu)的復(fù)雜性
使用層次結(jié)構(gòu)查看器來檢查是否存在瓶頸
使用RelativeLayout或者GridLayout來簡化復(fù)雜布局的層次嵌套
使用<merge />標(biāo)簽來較少布局層次
-
使用<ViewStub />標(biāo)簽來延遲該標(biāo)簽下的布局的渲染
<ViewStub android:id="@+id/stub_import" android:inflatedId="@+id/panel_import" android:layout="@layout/progress_overlay" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" />
((ViewStub) findViewById(R.id.stub_import)).setVisibility(View.VISIBLE); // or View importPanel = ((ViewStub) findViewById(R.id.stub_import)).inflate();
使用layoutopt檢測常見問題
I/O性能優(yōu)化
-
異步寫SharedPreferences
SharedPreferences.Editor.apply(); // 異步 SharedPreferences.Editor.commit(); // 同步
數(shù)據(jù)庫query查詢語句中的 * 替換成具體的列值
使用TraceView配置您的數(shù)據(jù)庫查詢
使用LIMIT子句減少選擇行
最小化完整窗口時(shí)間
使用索引優(yōu)化數(shù)據(jù)庫查詢
預(yù)編譯常用的SQL語句
String sql = “INSERT INTO table VALUES (?, ?)”;
SQLiteStatement stmt = mDatabase.compileStatement(sql);
DatabaseUtils.bindObjectToProgram(stmt, 1, 1);
DatabaseUtils.bindObjectToProgram(stmt, 2, 2);
stmt.execute();
stmt.close();
//或者使用 PreparaStatement
- 推遲ContentObserver.onChange()中的自動重新檢查
getContentResolver().registerContentObserver(uri, true,
new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange) {
mDirty = true;
}
});
@Override
protected void onResume() {
super.onResume();
if (mDirty) {
// start query again
mDirty = false;
}
}
-
在事務(wù)中使用批量操作
- ContentProviderOperation!
- ContentProviderOperation.Builder!
- ContentResolver.applyBatch()
-
在一個(gè)比較長的事務(wù)中允許偶爾的事務(wù)提前
SQLiteDatabase.yieldIfContendedSafely()
-
使用事件日志調(diào)試
adb logcat -b events content_query_sample:I *:S
adb logcat -b events content_update_sample:I *:S
adb logcat -b events db_sample:I *:S
滑動性能優(yōu)化(List)
-
ListView : 通過復(fù)用view來避免不必要的inflate操作
@Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.main, parent, false); } // .... }
-
通過ViewHolder緩存v試圖蜓肆,而避免不必要的findViewByI'd
@Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mInflater.inflate(R.layout.main, parent, false); ViewHolder holder = new ViewHolder(); holder.img = (ImageView) convertView.findViewById(R.id.image); holder.txt = (TextView) convertView.findViewById(R.id.text); convertView.setTag(holder); } ViewHolder holder = (ViewHolder) convertView.getTag(); holder.img.setImageResource(R.drawable.icon); holder.txt.setText(R.string.hello); return convertView; } private static class ViewHolder { ImageView img; TextView txt; }
-
避免view的不必要繪制(例如背景的重復(fù)繪制)
Android 中 會繪制每一個(gè)父view即使它被覆蓋在一個(gè)不透明的子view之下
當(dāng)你有一個(gè)父view并且是永遠(yuǎn)不可見的颜凯,那么不要繪制它(包括他的背景)
-
大多數(shù)的情況下你不需要繪制window的背景
//Activity中 getWindow().setBackgroundDrawable(null); //style中 android:windowBackground="@null"
避免在運(yùn)行時(shí)進(jìn)行圖片縮放(特殊業(yè)務(wù)需求除外)
-
避免在視圖(ListView等)滾動的時(shí)候進(jìn)行動畫,如果業(yè)務(wù)要求使用動畫仗扬,那么請關(guān)閉繪制緩存
ListView.setDrawableCacheEnabled(false)
使用Allocation Tracker(內(nèi)存分配追蹤器)檢測并避免頻繁的垃圾回收
考慮使用Object Pool 症概,StringBuilder等封裝類型
緩存的時(shí)候考慮使用SoftReference
-
在調(diào)試模式的時(shí)候啟用StrictMode(可以檢查大部分不規(guī)范,不安全操作)
public void onCreate() { if (DEVELOPER_MODE) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskReads() .detectDiskWrites() .detectNetwork() .penaltyLog() .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() .penaltyLog() .penaltyDeath() .build()); } super.onCreate(); }
-
檢查主線程Looper是否有不必要的活動
Looper.setMessageLogging();
Memory
在系統(tǒng)級別早芭,Android使用低內(nèi)存驅(qū)動程序運(yùn)行修改過的OOM Killer
包括:
- Linux OOM killer
- OOM_ADJ
- Android Low Memory Killer
Android中的低內(nèi)存閾值(init.rc中)
# Define the memory thresholds at which the above process classes will
# be killed. These numbers are in pages (4k).
setprop ro.FOREGROUND_APP_MEM 2048
setprop ro.VISIBLE_APP_MEM 3072
setprop ro.PERCEPTIBLE_APP_MEM 4096
setprop ro.HEAVY_WEIGHT_APP_MEM 4096
setprop ro.SECONDARY_SERVER_MEM 6144
setprop ro.BACKUP_APP_MEM 6144
setprop ro.HOME_APP_MEM 6144
setprop ro.HIDDEN_APP_MEM 7168
setprop ro.EMPTY_APP_MEM 8192
OOM_ADJ基于重要性級別(init.rc中)
# Define the oom_adj values for the classes of processes that can be
# killed by the kernel. These are used in ActivityManagerService.
setprop ro.FOREGROUND_APP_ADJ 0
setprop ro.VISIBLE_APP_ADJ 1
setprop ro.PERCEPTIBLE_APP_ADJ 2
setprop ro.HEAVY_WEIGHT_APP_ADJ 3
setprop ro.SECONDARY_SERVER_ADJ 4
setprop ro.BACKUP_APP_ADJ 5
setprop ro.HOME_APP_ADJ 6
setprop ro.HIDDEN_APP_MIN_ADJ 7
setprop ro.EMPTY_APP_ADJ 15
進(jìn)程重要性級別
- Persistent(持續(xù)存在)
- OOM_ADJ < 0
- system_server (-16) , com.android.phone (-12)
- Foreground(前臺進(jìn)程)
- FOREGROUND_APP_ADJ = 0
- 運(yùn)行前臺Activity
- 運(yùn)行一個(gè)Service穴豫,執(zhí)行onCreate(),onStartCommand(),onDestroy()
- 托管由前臺Activity或前臺進(jìn)程綁定的Service
- 運(yùn)行一個(gè)BroadcastReceiver精肃,執(zhí)行onReceive()
- 托管由持續(xù)或前臺進(jìn)程使用的ContentProvider
- Visible(可見進(jìn)程)
- VISIBLE_APP_ADJ = 1
- 運(yùn)行可見的Activity(不在前臺秤涩,也就是,不是正在和用戶交互的)
- 運(yùn)行由startService()啟動的Service司抱,Service使用startForeground()
使自己處于前臺狀態(tài)
- Service(服務(wù)進(jìn)程)
- SECONDARY_SERVER_ADJ = 4
- 運(yùn)行由startService()啟動Service筐眷,并且不是可見進(jìn)程
- Background(后臺進(jìn)程)
- HIDDEN_APP_MIN_ADJ (7) .. EMPTY_APP_ADJ (15)
- 不包含任何活動應(yīng)用程序組件的進(jìn)程
Low Memory 回調(diào)
Activity.onLowMemory()
Fragment.onLowMemory()
Activity.onSaveInstanceState(Bundle)
Service.onLowMemory()
ContentProvider.onLowMemory()
在應(yīng)用程序級別,Android限制了多少內(nèi)存可以分配給每個(gè)應(yīng)用程序习柠。
Android為每個(gè)應(yīng)用程序定義了一個(gè)堆限制,并指示何時(shí)拋出OutOfMemoryError
Android Studio 中 Heap窗口中的相關(guān)術(shù)語
術(shù)語 | 解釋 |
---|---|
Heap limit | 應(yīng)用在Dalvik堆中的最大允許占用空間 |
Heap size | 當(dāng)前Dalvik堆的大小 |
Allocated | 應(yīng)用在Dalvik堆上分配的字節(jié)總數(shù) |
Free | Heap size – Allocated |
% Used | Allocated / Heap size * 100% |
External allocation (3.0之前) | Bitmap byte[] |
ActivityManager.getMemoryClass() 可以查看當(dāng)前應(yīng)用Heap size limit
OOM 發(fā)生的情形
- 2.3之前
Heap size + external allocation + new allocation request >= Heap limit
- 2.3(包括)之后
Heap size + new allocation request >= Heap limit
new allocation request : 新的內(nèi)存開辟請求大小
不代表進(jìn)程內(nèi)存使用的情形
- 每個(gè)進(jìn)程從zygote fork出來后匀谣,都會有2mb以上的開銷
- 在使用native的時(shí)候會開辟更多的內(nèi)存:
- Android應(yīng)用程序運(yùn)行在Dalvik VM中,同時(shí)通過JNI加載本地庫
- 由應(yīng)用程序調(diào)用的Dalvik級API可以代表申請人使用本機(jī)庫资溃。
- 如果你啟用了硬件加速(4.0中默認(rèn)開啟)武翎,那么會多有8mb的內(nèi)存去使用OpenGL
查看內(nèi)存使用情況
- 根據(jù)進(jìn)程內(nèi)存使用情況排序:
adb shell procrank -p
PID Vss Rss Pss Uss cmdline!
3156 80272K 80220K 59228K 57624K com.htc.launcher
1455 94540K 58728K 37488K 36060K system_server
9000 55224K 55200K 33900K 32412K com.roguso.plurk
6713 47912K 47880K 27719K 26788K tw.anddev.aplurk
1624 44804K 44760K 24954K 24200K android.process.acore
2081 44992K 44960K 23205K 21628K com.htc.android.mail
1604 41288K 41248K 22393K 21752K com.htc.android.htcime
1594 40912K 40844K 21588K 20284K com.htc.weatheridlescreen
1622 39904K 39872K 21297K 20696K com.android.phone
VSS(Virtual Set Size):進(jìn)程可以訪問的頁面總數(shù)
RSS(Resident Set Size): RAM中進(jìn)程可以訪問的頁總數(shù)
PSS(Proportion Set Size):進(jìn)程在RAM中使用的頁面總數(shù),其中每個(gè)頁面的大小是頁面總數(shù)除以共享它的進(jìn)程數(shù)
USS(Unique Set Size):進(jìn)程可以訪問的非共享頁面的數(shù)量
-
列出進(jìn)程的虛擬內(nèi)存區(qū)域
adb shell procmem -p <pid>
Vss Rss Pss Uss ShCl ShDi PrCl PrDi Name
------- ------- ------- ------- ------- ------- ------- -------
4K 4K 0K 0K 4K 0K 0K 0K /system/bin/app_process
4K 4K 0K 0K 4K 0K 0K 0K /system/bin/app_process
13908K 13908K 11571K 11508K 2400K 0K 11508K 0K [heap]
0K 0K 0K 0K 0K 0K 0K 0K [heap]
4K 4K 4K 4K 0K 0K 4K 0K [heap]
36K 36K 0K 0K 0K 36K 0K 0K /dev/__properties__
.......
adb shell dumpsys meminfo <pid>
Applications Memory Usage (kB):
Uptime: 89133197 Realtime: 106110266
** MEMINFO in pid 11961 [com.htc.friendstream] **
native dalvik other total limit bitmap nativeBmp
size: 15032 8535 N/A 23567 32768 N/A N/A
allocated: 14565 5697 N/A 20262 N/A 4669 1918
free: 162 2838 N/A 3000 N/A N/A N/A
(Pss): 4105 2550 13952 20607 N/A N/A N/A
(shared dirty): 2440 1928 5532 9900 N/A N/A N/A
(priv dirty): 4044 708 12716 17468 N/A N/A N/A
Objects
Views: 0 ViewRoots: 0
AppContexts: 0 Activities: 0
Assets: 7 AssetManagers: 7
Local Binders: 11 Proxy Binders: 15
Death Recipients: 1
OpenSSL Sockets: 0!
Private Dirty = USS
無法分頁到磁盤并且不與任何其他進(jìn)程共享的進(jìn)程內(nèi)部RAM量
當(dāng)進(jìn)程消失時(shí)溶锭,系統(tǒng)可以使用的RAM
- 一些重要的虛擬內(nèi)存區(qū)域
/dev/ashmem/dalvik-heap : 在Dalvik級別為堆分配的匿名頁面
[heap], [anonymous] : 由malloc()在本機(jī)級別分配的匿名頁面
/system/framework/*.odex (release build)
/data/dalvik-cache/*.odex (debug build) : 文件支持的mmap頁面
Garbage collection(垃圾收集)
-
2.3之前的GC
收集垃圾的時(shí)候會停止其他所有的工作
對整個(gè)堆進(jìn)行收集
造成的暫停時(shí)間一般都大于100ms
-
2.3及其之后
不會暫停其他工作宝恶,而是與其他工作同時(shí)進(jìn)行(絕大部分是這樣的)
一次垃圾收集只是對堆的一部分而已
造成的暫停時(shí)間一般小于5ms
Memory leaks(內(nèi)存泄露)
- GC并不能避免內(nèi)存泄露
- 有一個(gè)指向長期存在的且未使用的對象的應(yīng)用,導(dǎo)致這個(gè)不被使用的對象不能被回收
- 在Android中趴捅,通常發(fā)生內(nèi)存泄露的是對Context或者Activity的引用
常見的因?yàn)镃ontext 或著 Activity造成的內(nèi)存泄露
-
在Activity中存在長期存在的指向非靜態(tài)內(nèi)部類實(shí)例對象的引用
public class TestActivity extends Activity{ static LeakyTest leaky = null; class LeakyTest{ void doSoming(){ //doing } } @Override protected void onCreate(Bundle saveInstanceStates){ super.onCreate(saveInstanceStates); if(leaky==null) leaky = new LeakyTest(); //.... } //..... }
-
在Activity中有超出Activity生命周期且長期存活的線程
new Thread(new Runnable(){ @Override public void run(){ //do long-live works } }).start();
有用的方法
- 使用logcat檢查是否有內(nèi)存隨著時(shí)間的推移而不斷增加(尤其注意某些方法的執(zhí)行步驟5姹小)
例如得到的日志信息:
D/dalvikvm(9050):GC_CONCURRENT free 2049k, 65% free 3571k/9991k, external 4703k/5261k, paused 2ms+2ms
D/dalvikvm(9050): <u>GC_CONCURRENT</u> free 2049k, 65% free 3571k/9991k, external 4703k/5261k, paused 2ms+2ms
下劃線處GC的原因:
GC_CONCURRENT
GC_FOR_MALLOC
GC_EXTERNAL_ALLOC
GC_HPROF_DUMP_HEAP
GC_EXPLICIT
D/dalvikvm(9050): GC_CONCURRENT <u>free 2049k</u>, 65% free 3571k/9991k, external 4703k/5261k, paused 2ms+2ms
下劃線處GC的原因:
內(nèi)存釋放
D/dalvikvm(9050): GC_CONCURRENT free 2049k, <u>65% free 3571k/9991k</u>, external 4703k/5261k, paused 2ms+2ms
下劃線處GC的原因:
內(nèi)存釋放
堆進(jìn)行信息統(tǒng)計(jì)
D/dalvikvm(9050): GC_CONCURRENT free 2049k, 65% free 3571k/9991k, <u>external 4703k/5261k,</u> paused 2ms+2ms
下劃線處GC的原因:
內(nèi)存釋放
堆進(jìn)行信息統(tǒng)計(jì)
內(nèi)部內(nèi)存進(jìn)行信息統(tǒng)計(jì)
D/dalvikvm(9050): GC_CONCURRENT free 2049k, 65% free 3571k/9991k, external 4703k/5261k, <u>paused 2ms+2ms</u>
下劃線處GC的原因:
內(nèi)存釋放
堆進(jìn)行信息統(tǒng)計(jì)
內(nèi)部內(nèi)存進(jìn)行信息統(tǒng)計(jì)
時(shí)間暫停
- 使用分配跟蹤器查看是否有隨著時(shí)間分配未預(yù)料的對象(Android Studio中的logcat窗口中有對應(yīng)的按鈕)
- 使用(Histogram view)直方圖視圖查看活動實(shí)例的數(shù)量。 有多于一個(gè)Activity的一個(gè)實(shí)例拱绑,那么這是一個(gè)強(qiáng)烈的Activity / Context泄露的跡象综芥。
- 按保留大小排序的Dominator Tree視圖有助于識別保留了大量的內(nèi)存且不能被釋放的對象。 他們通常是找到內(nèi)存泄漏的好起點(diǎn)猎拨。
強(qiáng)烈推薦郭神關(guān)于內(nèi)存泄露分析的文章:
Android最佳性能實(shí)踐(二)——分析內(nèi)存的使用情況
其他優(yōu)化建議
-
造成OutOfMemoryError的原因通常是Bitmap或者對象進(jìn)行了太多的內(nèi)存分配
加載圖片的時(shí)候盡可能不要加載原尺寸的大圖膀藐,可以使用縮略圖
回收已經(jīng)不使用的Bitmap資源bitmap.recycle().
2.3(包括)之前,Bitmap的引用是放在堆中的红省,而Bitmap的數(shù)據(jù)部分是放在棧中的额各,需要用戶調(diào)用recycle方法手動進(jìn)行內(nèi)存回收 ,2.3之后类腮,整個(gè)Bitmap,包括數(shù)據(jù)和引用蛉加,都放在了堆中蚜枢,這樣,整個(gè)Bitmap的回收就全部交給GC了针饥,這個(gè)recycle方法就再也不需要使用了厂抽。
列表中加載圖片注意使用小圖,以及做好緩存工作
盡可能的避免碎片化
減少Java在應(yīng)用堆空間堆快滿的時(shí)候再堆分配
緩存中使用SoftReference
使用WeakReference避免堆存泄露
//縮放圖片
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, opts);
final int originalWidth = opts.outWidth;
final int originalHeight = opts.outHeight;
final int originalDim = Math.max(originalWidth, originalHeight);
opts = new BitmapFactory.Options();
opts.inSampleSize = 1;
while ( originalDim > MAX_IMAGE_DIM ) {
opts.inSampleSize *= 2;
originalDim /= 2;
}
return BitmapFactory.decodeFile(path, opts);
//對比之間的例子丁眼,這里改成了靜態(tài)內(nèi)部類
public class MainActivity extends Activity {
static Leaky leak = null;
static class Leaky {
void doSomething() {
//doing
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (leak == null) {
leak = new Leaky();
}
}
}
public class MainActivity extends Activity {
static Leaky leak = null;
static class Leaky {
private final Context mContext; //final修飾
public Leaky(Context context) {
super();
mContext = context;
doSomethingWithOuterInstance();
}
void doSomethingWithOuterInstance() {
String text = mContext.getString(R.string.hello);
System.out.println(text);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (leak == null) {
leak = new Leaky(this);
}
}
}
public class MainActivity extends Activity {
static Leaky leak = null;
static class Leaky {
private final WeakReference<Context> mContext;//使用了弱引用
public Leaky(Context context) {
super();
mContext = new WeakReference<Context>(context);
doSomethingWithOuterInstance();
}
void doSomethingWithOuterInstance() {
Context context = mContext.get();
if (context != null) {
String text = context.getString(R.string.hello);
System.out.println(text);
}
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (leak == null) {
leak = new Leaky(this);
}
}
}
更多優(yōu)化建議筷凤,請移步郭神博客