淺談Android效能優化方案
經過前一階段的調查,大概對效能優化已經有了初步的解決方案:
先給大家介紹一下UC公司的效能優化指標以及部分方案:
一、效能優化六項指標: 效能、記憶體、穩定性、流量、電量、安裝包大小; 二、背景 ---- Android程式卡頓產生原因: 1、Android系統低效 --渲染執行緒、同步介面、廣播機制 :沒有獨立的渲染執行緒 :廣播機制引入,可能同時又幾百個廣播機制在後臺執行 2、執行環境惡劣 --後臺程序、安全軟體 3、低端機佔比高 --低記憶體、弱cpu、IO瓶頸 :開源平臺,導致高中低端的機型普遍存在;; :低記憶體影響最大,一般可用記憶體在小於50M,意味著會由於小於50M就會殺死一些程序來維護記憶體的大小 : GPU是其次; :讀寫速度比較慢,在有的手機上; 4、產品考慮不足 --功能定義簡陋、功能堆積嚴重 : 一般的產品只會考慮需求,我要做什麼,而並沒有把整個閉環考慮清楚; :在版本迭代的過程,在不注意間可能啟動過程會越來越慢; 5、技術考慮不足 --很多 三、使用者反饋應用卡頓怎麼辦? 困難: 1、復現性 -- 使用者描述模糊、不穩定出現(復現率比較低); 2、定位難 -- 不同機型、韌體、系統狀態表現不一 --程式細節多、可疑面廣 3、衡量難 -- 卡頓嚴重程度難以量化 -- 卡頓問題不便分類 : 是有一點卡、非常卡、還是什麼 : 沒有針對性的目標,提升百分之多少等等,不知道極限在哪裡; 四、解決思路 1、卡 vs 頓,卡為主,頓為輔。卡和頓沒有一個明顯的界限,大部分頓的問題當環境足夠惡劣時就會表現為卡。所以抓住卡,就能解決很多 問題。2、打點統計 vs 全域性監控: 短期目標:主路徑效能保障,打點統計; 長期目標:整體的卡頓優化,全域性監控;
3、 線下分析 vs 線上監控: 線下分析:實驗室除錯去復現一個問題,精確定位、粒度細; 線上監控:指標衡量、粒度粗。
4、打點統計分析: (1)、啟動速度 (2)、響應速度 (3)、版本比對 : app版本、Android版本
Android效能優化程式碼規範
l 編碼之初準備篇:
l 對於佈局內容的數量要求:
單個Activity顯示的檢視一般情況少於20,層數少於4
對於Adapter控制元件,如ListView ,item的佈局層數一般情況為2,不得超過3.
l 將Acitivity 中的Window 的背景圖設定為空:
getWindow().setBackgroundDrawable(null);
android的預設背景不為空。
l 將Activity的背景放到Activity的Theme中設定。同時避免fragment和activity背景重複設定:
Theme設定屬性
<item name="android:windowBackground">src_image</item>
l 採用硬體加速:
androidmanifest.xml中application新增
android:hardwareAccelerated="true"。
需要注意的是:android 3.0以上才可以使用。
l 使用ProGuard去除不必要的程式碼:
#刪除無用的類
-assumenosideeffects class android.util.Log {
public static *** d(...);
public static *** v(...);
public static *** e(...);
public static *** i(...);
public static *** w(...);
}
l apk打包簽名時,使用zipalign工具對齊:
zipAlignEnabled true
l 後臺可以處理的邏輯不要放在前臺,這樣可能會有預料不到的問題
l 記憶體洩露引入三方框架LeakCanary :使用超級方便:
l Android程式冷啟動優化(第一次啟動應用):
1、在logoactivity設定一個theme,設定windowBackground屬性,避免黑屏階段。
2、對app進行延遲啟動控制,採用延遲載入技術
private Handler handler = new Handler();
//延遲載入 runnable
private Runnable delayLoadRunnable = new Runnable() {
@Override
public void run() {
Logger.d("start delayLoadRunnable ");
init();
}
};
//優化的DelayLoad : 採用延遲載入策略
window.getDecorView().post(new Runnable() {
@Override
public void run() {
handler.post(delayLoadRunnable);
}
});
Activity 在啟動時,會在第二次執行 performTraversals 才會去真正的繪製,原因在於第一次執行 performTraversals 的時候,會走到 Egl 初始化的邏輯,然後會重新執行一次 performTraversals 。
所以有人問為何在 run 方法裡面還要 post 一次,如果在 run 方法裡面直接執行 updateText 方法 ,那麼 updateText 就會在第一個 performTraversals 之後就執行,而不是在第一幀繪製完成後才去執行,所以我們又 Post 了一次 。所以大概的處理步驟如下:
第一步:Activity.onCreate –> Activity.onStart –> Activity.onResume
第二步:ViewRootImpl.performTraversals –>Runnable
第三步:Runnable –> ViewRootImpl.performTraversals
第四步:ViewRootImpl.performTraversals –> init();
第五步:init();
l 禁止(避免)操作篇:
核心:少的物件建立,意味著少的GC操作。 杜絕引起記憶體溢位、記憶體抖動的操作行為;
l 禁止在單例模式中引用Activity的context:
l 禁止使用列舉:
使用列舉訪問速度要比static變數慢4倍,列舉將造成大量的記憶體浪費;
l 禁止使用非同步回撥:
非同步回撥被執行的時間不確定,很有可能發生在activity已經被銷燬之後,
這不僅僅很容易引起crash,還很容易發生記憶體洩露。
l 禁止static引用資源耗費過多的例項:
例如:context , Activity
對於某些不得不出現static引用context的情況,在onDestroy()方法中,解除Activity與static的繫結關係,
從而去除static對Activity的引用,使Context能夠被回收;
l 避免在迴圈(for、while、listView - getView方法、onDraw)裡建立物件:
對於onDraw中 Paint 我們可以這樣優化
private Paint paint = new Paint();
public on Draw(){
paint.setColor(mBorderColor);
}
l 避免使用static成員物件:
static生命週期過長,對於需要傳遞的物件,使用(Intent)和(Handler)
l 避免使用浮點數:
浮點數會比整型慢兩倍
l 避免Timer.schedule,對於延時操作,可用以下方式代替:
ScheduledExecutorService,
handler.postDelayed,
handler.postAtTime ,
handler.sendMessageDelayed ,
View.postDelayed,
AlarmManager
l 避免載入過大圖片。壓縮或者使用物件池後再使用
l 避免使用遞迴
l 避免使用輪詢
l 避免長週期內部類、匿名內部類長時間持有外部類物件導致相關資源無法釋放。如:Handler, Thread , AsyncTask
l 避免使用三方庫,不需要的東西需要剔除
l 避免使用註解框架,畢竟是反射
l 非必要情況下,少用抽象
l 避免頻繁網路請求
訪問server端時,建立連線本身比傳輸需要跟多的時間,如非必要,不要將一互動可以做的事情分成多次互動(這需要與Server端協調好)。有效管理Service 後臺服務就相當於一個持續執行的Acitivity,如果開發的程式後臺都會一個service不停的去伺服器上更新資料,在不更新資料的時候就讓它sleep,這種方式是非常耗電的,通常情況下,可以使用AlarmManager來定時啟動服務。如下所示,第30分鐘執行一次。
1. AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALAR M_SERVICE);
2. Intent intent = new Intent(context, MyService.class);
3. PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0);
4. long interval = DateUtils.MINUTE_IN_MILLIS * 30;
5. long firstWake = System.currentTimeMillis() + interval;
6. am.setRepeating(AlarmManager.RTC,firstWake, interval, pendingIntent);
l 優化操作建議篇:
l 當資料量在100以內時,使用ArrayMap代替HashMap
l 為了避免自動裝箱,當數量在1000以下時,使用如下容器
a)SparseBoolMap <bool , obj>
b)SparseIntMap <int , obj>
c)SparseLongMap <long , obj>
d)LongSparseMap <long ,obj>
l 字串拼接用StringBuilder或StringBuffer
//這種string第一次初始化的情況下,下面得效率更高
String str1 = "abc"+“def”+"hij";
//非併發情況 , StringBuilder效率更優
StringBuilder str2 = str3 + str1 + "builder" ;
//併發情況使用 StringBuffer
StringBuffer str2 = str1 + "buffer" ;
l 檔案、網路IO快取,使用有快取機制的輸入流
BufferedInputStream替代InputStream
BufferedReader替代Reader
BufferedReader替代BufferedInputStream.
l 考慮使用Webp代替傳統png圖片。對於某些使用JPEG即可實現的效果,儘量採用JPEG
png雖能提供無損的圖片,但相對於JPEG過大。Webp是既保持png優點,又能減少圖片大小的新型格式.
l 儘量使用區域性變數:
l 如果沒有特殊需求,使用基本資料型別,而非物件型別
基本類似指:int , double , char等。
l 對於使用超過兩次的物件成員, 將成員快取到本地
反覆使用的變數,儲存到本地成為臨時變數活成員變數後進行操作。尤其是在迴圈中
例:多次比較目標時間和當前時間差。
l 當new的物件並不是100%一定會被用到時,在使用時建立,有效減少不必要的物件生成
例如: Object ob = new Object();
int value;
if(i>0) value = ob.getVlaue();
改寫為:int value;
if(i>0){
Object ob = new Object(); //用到時載入
value = ob.getVlaue();
}
l 及時釋放不用的物件
a = new Object();
當a不為空時,應改寫為:
a = null;
a = new Object();
l 不在使用的變數,手動置為null
通常對於物件成員如此使用,區域性變數不需要
this.object = null;
l 常量用 static final修飾
l 對bitmap進行恰當的操作:
讀取圖片之前先檢視其大小:
1. BitmapFactory.Options opts = new BitmapFactory.Options();
2. opts.inJustDecodeBounds = true;
3. Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);
使用得到的圖片原始寬高計算適合自己的smaplesize:
1. BitmapFactory.Options opts = new BitmapFactory.Options();
2. opts.inSampleSize = 4 ;// 4就代表容量變為以前容量的1/4
Bitmap bitmap = BitmapFactory.decodeFile(imageFile, opts);
對於過時的Bitmap物件一定要及時recycle,並且把此物件賦值為null:
1. bitmap.recycle();
2. bitmap = null;
l 佈局用Java完成比XML快
l 預設不會顯示的佈局使用 viewstub 標籤
<ViewStub
android:id="@+id/network_error_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout="@layout/network_error" />
//非顯示的轉換ViewStub 獲取
View viewStub = findViewById(R.id.network_error_layout);
viewStub.setVisibility(View.VISIBLE); // ViewStub被展開後的佈局所替換
networkErrorView = findViewById(R.id.network_error_layout); // 獲取 展開後的佈局
l 對於兩次以上相同的infalte操作,用成員變數代替區域性變數,避免重複載入
l 正確使用fragment
介面繪製儘量使用fragment代替activity,fragment根據情況使用hide與add方式,還是replace
if (!showFragment.isAdded()) { // 先判斷是否被add過
transaction.hide(currentFragment).add(R.id.fl_content, showFragment)
.commitAllowingStateLoss(); // 隱藏當前的fragment,add下一個到Activity中
} else {
// 隱藏 當前的fragment,顯示下一個
transaction.hide(currentFragment).show(showFragment).commitAllowingStateLoss();
}
this.currentFragment = showFragment;
l 對於重複出現超過2-3次的子佈局,用 include 實現複用
<include layout="@layout/foot.xml" />
l 當複用的佈局中子View對所依賴的根節點要求不高時,使用 merge 作為根節點
要求不高標準:非複雜結構佈局,無Background,padding等屬性,且子View數量較少
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_above="@+id/text"/>
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_40"
android:layout_alignParentBottom="true"
android:text="@string/app_name" />
</merge>
l 資料壓縮:
傳輸資料經過壓縮 目前大部門網站都支援GZIP壓縮,所以在進行大資料量下載時,儘量使用GZIP方式下載,可以減少網路流量,一般是壓縮前資料大小的30%左右。
1. HttpGet request = new HttpGet("http://example.com/gzipcontent");
2. HttpResponse resp = new DefaultHttpClient().execute(request);
3. HttpEntity entity = response.getEntity();
4. InputStream compressed = entity.getContent();
5. InputStream rawData = new GZIPInputStream(compressed);
---------------希望對大家能有幫助,如果有不好的地方希望大家給予建議------------------