將從以下幾個方面總結(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)存泄露
- 最佳實踐
糟糕的用戶體驗
- Activity 啟動時間過長
- 應(yīng)用無反應(yīng)(ANR)
- 幀速率差
關(guān)于幀
幀速率
- 為了保證能達到60fps,最多只有16ms去處理每一幀
- 而保證能達到24fps,最多只有41ms去處理每一幀
常見操作耗時
- Binder RPC 調(diào)用大約花費 0.12ms
- 從閃存讀取一個字節(jié)大約花費 0.0x ~ 5ms(一個文件只讀一個字節(jié)有可能大于1ms)
- 寫內(nèi)容到閃存大約花費 1-100ms(一個文件只寫一個字節(jié)有可能小于1ms)
- TCP 初始化加上HTTP提取通?;ㄙM秒級的時間(此處指的是建立鏈接并獲取鏈接返回的數(shù)據(jù)的時間)
讀寫操作在性能差一些的機子上可能時間會有出入
往磁盤寫內(nèi)容的時候,會隨著磁盤的剩余空間的較少而導致寫速率不斷減低
永遠不要做阻塞UI線程的事情,用一個新的線程去做可能會影響UI體驗的事情
四種可以異步的實現(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)的復雜性
使用層次結(jié)構(gòu)查看器來檢查是否存在瓶頸
使用RelativeLayout或者GridLayout來簡化復雜布局的層次嵌套
使用<merge />標簽來較少布局層次
-
使用<ViewStub />標簽來延遲該標簽下的布局的渲染
<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子句減少選擇行
最小化完整窗口時間
使用索引優(yōu)化數(shù)據(jù)庫查詢
預編譯常用的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()
-
在一個比較長的事務(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 : 通過復用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的不必要繪制(例如背景的重復繪制)
Android 中 會繪制每一個父view即使它被覆蓋在一個不透明的子view之下
當你有一個父view并且是永遠不可見的,那么不要繪制它(包括他的背景)
-
大多數(shù)的情況下你不需要繪制window的背景
//Activity中 getWindow().setBackgroundDrawable(null); //style中 android:windowBackground="@null" 避免在運行時進行圖片縮放(特殊業(yè)務(wù)需求除外)
-
避免在視圖(ListView等)滾動的時候進行動畫,如果業(yè)務(wù)要求使用動畫,那么請關(guān)閉繪制緩存
ListView.setDrawableCacheEnabled(false) 使用Allocation Tracker(內(nèi)存分配追蹤器)檢測并避免頻繁的垃圾回收
考慮使用Object Pool ,StringBuilder等封裝類型
緩存的時候考慮使用SoftReference
-
在調(diào)試模式的時候啟用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ū)動程序運行修改過的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
進程重要性級別
- Persistent(持續(xù)存在)
- OOM_ADJ < 0
- system_server (-16) , com.android.phone (-12)
- Foreground(前臺進程)
- FOREGROUND_APP_ADJ = 0
- 運行前臺Activity
- 運行一個Service,執(zhí)行onCreate(),onStartCommand(),onDestroy()
- 托管由前臺Activity或前臺進程綁定的Service
- 運行一個BroadcastReceiver,執(zhí)行onReceive()
- 托管由持續(xù)或前臺進程使用的ContentProvider
- Visible(可見進程)
- VISIBLE_APP_ADJ = 1
- 運行可見的Activity(不在前臺,也就是,不是正在和用戶交互的)
- 運行由startService()啟動的Service,Service使用startForeground()
使自己處于前臺狀態(tài)
- Service(服務(wù)進程)
- SECONDARY_SERVER_ADJ = 4
- 運行由startService()啟動Service,并且不是可見進程
- Background(后臺進程)
- HIDDEN_APP_MIN_ADJ (7) .. EMPTY_APP_ADJ (15)
- 不包含任何活動應(yīng)用程序組件的進程
Low Memory 回調(diào)
Activity.onLowMemory()
Fragment.onLowMemory()
Activity.onSaveInstanceState(Bundle)
Service.onLowMemory()
ContentProvider.onLowMemory()
在應(yīng)用程序級別,Android限制了多少內(nèi)存可以分配給每個應(yīng)用程序。
Android為每個應(yīng)用程序定義了一個堆限制,并指示何時拋出OutOfMemoryError
Android Studio 中 Heap窗口中的相關(guān)術(shù)語
| 術(shù)語 | 解釋 |
|---|---|
| Heap limit | 應(yīng)用在Dalvik堆中的最大允許占用空間 |
| Heap size | 當前Dalvik堆的大小 |
| Allocated | 應(yīng)用在Dalvik堆上分配的字節(jié)總數(shù) |
| Free | Heap size – Allocated |
| % Used | Allocated / Heap size * 100% |
| External allocation (3.0之前) | Bitmap byte[] |
ActivityManager.getMemoryClass() 可以查看當前應(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)存開辟請求大小
不代表進程內(nèi)存使用的情形
- 每個進程從zygote fork出來后,都會有2mb以上的開銷
- 在使用native的時候會開辟更多的內(nèi)存:
- Android應(yīng)用程序運行在Dalvik VM中,同時通過JNI加載本地庫
- 由應(yīng)用程序調(diào)用的Dalvik級API可以代表申請人使用本機庫。
- 如果你啟用了硬件加速(4.0中默認開啟),那么會多有8mb的內(nèi)存去使用OpenGL
查看內(nèi)存使用情況
- 根據(jù)進程內(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):進程可以訪問的頁面總數(shù)
RSS(Resident Set Size): RAM中進程可以訪問的頁總數(shù)
PSS(Proportion Set Size):進程在RAM中使用的頁面總數(shù),其中每個頁面的大小是頁面總數(shù)除以共享它的進程數(shù)
USS(Unique Set Size):進程可以訪問的非共享頁面的數(shù)量
-
列出進程的虛擬內(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
無法分頁到磁盤并且不與任何其他進程共享的進程內(nèi)部RAM量
當進程消失時,系統(tǒng)可以使用的RAM
- 一些重要的虛擬內(nèi)存區(qū)域
/dev/ashmem/dalvik-heap : 在Dalvik級別為堆分配的匿名頁面
[heap], [anonymous] : 由malloc()在本機級別分配的匿名頁面
/system/framework/*.odex (release build)
/data/dalvik-cache/*.odex (debug build) : 文件支持的mmap頁面
Garbage collection(垃圾收集)
-
2.3之前的GC
收集垃圾的時候會停止其他所有的工作
對整個堆進行收集
造成的暫停時間一般都大于100ms
-
2.3及其之后
不會暫停其他工作,而是與其他工作同時進行(絕大部分是這樣的)
一次垃圾收集只是對堆的一部分而已
造成的暫停時間一般小于5ms
Memory leaks(內(nèi)存泄露)
- GC并不能避免內(nèi)存泄露
- 有一個指向長期存在的且未使用的對象的應(yīng)用,導致這個不被使用的對象不能被回收
- 在Android中,通常發(fā)生內(nèi)存泄露的是對Context或者Activity的引用
常見的因為Context 或著 Activity造成的內(nèi)存泄露
-
在Activity中存在長期存在的指向非靜態(tài)內(nèi)部類實例對象的引用
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)存隨著時間的推移而不斷增加(尤其注意某些方法的執(zhí)行步驟?。?/li>
例如得到的日志信息:
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)存釋放
堆進行信息統(tǒng)計
D/dalvikvm(9050): GC_CONCURRENT free 2049k, 65% free 3571k/9991k, <u>external 4703k/5261k,</u> paused 2ms+2ms
下劃線處GC的原因:
內(nèi)存釋放
堆進行信息統(tǒng)計
內(nèi)部內(nèi)存進行信息統(tǒng)計
D/dalvikvm(9050): GC_CONCURRENT free 2049k, 65% free 3571k/9991k, external 4703k/5261k, <u>paused 2ms+2ms</u>
下劃線處GC的原因:
內(nèi)存釋放
堆進行信息統(tǒng)計
內(nèi)部內(nèi)存進行信息統(tǒng)計
時間暫停
- 使用分配跟蹤器查看是否有隨著時間分配未預料的對象(Android Studio中的logcat窗口中有對應(yīng)的按鈕)
- 使用(Histogram view)直方圖視圖查看活動實例的數(shù)量。 有多于一個Activity的一個實例,那么這是一個強烈的Activity / Context泄露的跡象。
- 按保留大小排序的Dominator Tree視圖有助于識別保留了大量的內(nèi)存且不能被釋放的對象。 他們通常是找到內(nèi)存泄漏的好起點。
強烈推薦郭神關(guān)于內(nèi)存泄露分析的文章:
Android最佳性能實踐(二)——分析內(nèi)存的使用情況
其他優(yōu)化建議
-
造成OutOfMemoryError的原因通常是Bitmap或者對象進行了太多的內(nèi)存分配
加載圖片的時候盡可能不要加載原尺寸的大圖,可以使用縮略圖
回收已經(jīng)不使用的Bitmap資源bitmap.recycle().
2.3(包括)之前,Bitmap的引用是放在堆中的,而Bitmap的數(shù)據(jù)部分是放在棧中的,需要用戶調(diào)用recycle方法手動進行內(nèi)存回收 ,2.3之后,整個Bitmap,包括數(shù)據(jù)和引用,都放在了堆中,這樣,整個Bitmap的回收就全部交給GC了,這個recycle方法就再也不需要使用了。
列表中加載圖片注意使用小圖,以及做好緩存工作
盡可能的避免碎片化
減少Java在應(yīng)用堆空間堆快滿的時候再堆分配
緩存中使用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)化建議,請移步郭神博客