1. 程式人生 > >淺談Android效能優化方案

淺談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版本
             5、使用者反饋分析:                將使用者效能方面的反饋,測試人員進行分析,以郵件形式傳送給技術負責人,進行分析;                反饋等級:                   --預警機制                  --使用者分類                  --功能分類                  -- 縱向對比              6、anr日誌分析:               --精確定位 : 堆疊資訊比較清晰                --資料量化                 主執行緒超時(5s ---> 1s)
              -- 暴漏更多蕾體               -- 精確定位問題               -- 方便使用者聯調               -- 如果一個按鈕響應時間超過800ms,使用者感知起來就會很難受了。              7、全域性監控 -- Looper Hook              -- 監測系統訊息迴圈              --計算訊息耗時              -- 定位耗時點              卡頓:無非就是主執行緒被卡住了,就是主執行緒的訊息迴圈裡面的某一幀執行時間非常長,導致後續的訊息無法來得及執行,              資料指標卡頓率: 卡頓使用者數/日活總數              8、 問題回顧:              -- 下載介面展開卡頓: 分段載入              -- 二維碼介面展示慢: 延時載入、先出介面在初始化相機              -- 啟動完成後操作卡: 執行緒槍戰,低優先順序後臺程序+佇列              -- 共享儲存卡頓: sharedPerference( 主執行緒IO , commit(主) ---> apply(子))              -- 視訊播放控制卡頓(API相容性問題,非同步化,視訊播放停止暫停執行緒)              -- 獲取網路代理卡頓(IPC異常【程序間通訊】,非同步DNS+快取)              -- 第三方反饋卡死(韌體問題,shield Activity,全部採用一個新的activity去做,這樣不會對原來activity產生影響)              --  網頁滑屏操作卡頓(GPU加速 開啟硬體加速)              -- So載入/jni註冊卡(非同步載入 + 時序控制)              -- 安全軟體事件攔截(溝通反饋)       五、經驗推廣:          禁止:          -- 主執行緒檔案IO(標記檔案讀寫外)          -- 主執行緒耗CPU操作          -- 主執行緒同步IPC呼叫(時間不可預期)          推薦:          -- 非同步化             【1】、 產品及程式設計 : 載入肯定是需要時間的,不可能實時展現;             【2】、預載入 (資料必備,功能執行之前將這些事先資料準備好)             【3】、閒時載入: 利用cpu的閒時做一些事情,主執行緒會設定一個ido handler,主執行緒所有訊息操作完成之後會回撥一個handler             【4】、按需載入          -- 執行緒管理              1、執行緒量限制 + 任務佇列              2、非主執行緒優先順序調低          --壓力測試          -- 防禦式程式設計          -- 全域性效能檢測      六、延伸          -- 精確化 & 自動化             使用者反饋、卡頓日誌          -- 新監控方案              Api Hook          -- 新優化方案              卡頓率 --> 幀率              低端機優化                    -----------------------------------------------------UC瀏覽器方案結束------------------------------------------------------- 個人性能優化方案

Android效能優化程式碼規範

編碼之初準備篇:

l 對於佈局內容的數量要求:

單個Activity顯示的檢視一般情況少於20,層數少於4

        對於Adapter控制元件,如ListView ,item的佈局層數一般情況為2,不得超過3.

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

禁止(避免)操作篇:

核心:少的物件建立,意味著少的GC操作。 杜絕引起記憶體溢位、記憶體抖動的操作行為

禁止在單例模式中引用Activity的context:
禁止使用列舉:

使用列舉訪問速度要比static變數慢4,列舉將造成大量的記憶體浪費;

禁止使用非同步回撥:

非同步回撥被執行的時間不確定,很有可能發生在activity已經被銷燬之後,

這不僅僅很容易引起crash,還很容易發生記憶體洩露。

禁止static引用資源耗費過多的例項:

例如:context  , Activity

對於某些不得不出現static引用context的情況,在onDestroy()方法中,解除Activity與static的繫結關係,

從而去除static對Activity的引用,使Context能夠被回收;

避免在迴圈(for、while、listView - getView方法、onDraw)裡建立物件:

對於onDraw中 Paint 我們可以這樣優化

      private Paint paint = new Paint();

      public on Draw(){

          paint.setColor(mBorderColor);

     }

避免使用static成員物件:

static生命週期過長,對於需要傳遞的物件,使用(Intent)(Handler)

避免使用浮點數:

浮點數會比整型慢兩倍

避免Timer.schedule,對於延時操作,可用以下方式代替:
  ScheduledExecutorService, 
   handler.postDelayed, 
   handler.postAtTime , 
   handler.sendMessageDelayed ,  
   View.postDelayed,      
   AlarmManager

避免載入過大圖片。壓縮或者使用物件池後再使用

避免使用遞迴

避免使用輪詢

l 避免長週期內部類、匿名內部類長時間持有外部類物件導致相關資源無法釋放。如:Handler, Thread , AsyncTask

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 當資料量在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" ;

檔案、網路IO快取,使用有快取機制的輸入流

BufferedInputStream替代InputStream

BufferedReader替代Reader

BufferedReader替代BufferedInputStream. 

考慮使用Webp代替傳統png圖片。對於某些使用JPEG即可實現的效果,儘量採用JPEG

png雖能提供無損的圖片,但相對於JPEG過大。Webp是既保持png優點,又能減少圖片大小的新型格式.

儘量使用區域性變數:
如果沒有特殊需求,使用基本資料型別,而非物件型別

基本類似指:int , double , char等。

對於使用超過兩次的物件成員, 將成員快取到本地

反覆使用的變數,儲存到本地成為臨時變數活成員變數後進行操作。尤其是在迴圈中

    例:多次比較目標時間和當前時間差。  

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

及時釋放不用的物件
  a = new Object();
  當a不為空時,應改寫為:
  a = null;
  a = new Object();

不在使用的變數,手動置為null

  通常對於物件成員如此使用,區域性變數不需要

  this.object = null;

常量用 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

預設不會顯示的佈局使用 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); // 獲取 展開後的佈局 
對於兩次以上相同的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;
對於重複出現超過2-3次的子佈局,用 include 實現複用

  <include layout="@layout/foot.xml" />

當複用的佈局中子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> 

資料壓縮:

傳輸資料經過壓縮 目前大部門網站都支援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);  

---------------希望對大家能有幫助,如果有不好的地方希望大家給予建議------------------