Android效能優化 筆記

幻海流心發表於2019-03-04

說明

這篇文章是將很久以來看過的文章,包括自己寫的一些測試程式碼的總結.屬於筆記的性質,沒有面面俱到,一些自己相對熟悉的點可能會略過.
最開始看到的效能優化的文章,就是胡凱的優化典範系列,後來又陸續看過一些人寫的,個人覺得anly_jun和胡凱的質量最好.
文章大的框架也是先把優化典範過一遍,記錄個人認為重要的點,然後是anly_jun的系列,將之前未覆蓋的補充進去,也包括HenCoder的一些課程相關內容.
當然除了上面幾位,還有很多其他大神的文章,時間久了也記不太清,在此一併謝過.

筆記內容引用來源

  1. 胡凱
  2. anly_jun
  3. HenCoder

1.Android效能優化之渲染篇

1.VSYNC

  1. 幀率:GPU在1秒內繪製操作的幀數.如60fps.
    • 我們通常都會提到60fps與16ms,這是因為人眼與大腦之間的協作無法感知超過60fps的畫面更新.
    • 開發app的效能目標就是保持60fps,這意味著每一幀只有16ms=1000/60的時間來處理所有的任務
  2. 重新整理率:螢幕在1秒內重新整理螢幕的次數.如60Hz,每16ms重新整理1次螢幕.
  3. GPU獲取圖形資料進行渲染,然後螢幕將渲染後的內容展示在螢幕上.
  4. 大多數手機螢幕的重新整理率是60Hz,如果GPU渲染1幀的時間低於1000/60=16ms,那麼在螢幕重新整理時候都有最新幀可顯示.如果GPU渲染某1幀 f 的時間超過16ms,在螢幕重新整理時候,f並沒有被GPU渲染完成則無法展示,螢幕只能繼續展示f的上1幀的內容.這就是掉幀,造成了UI介面的卡頓.
    下面展示了幀率正常和幀率低於重新整理率(掉幀)的情形

Android效能優化 筆記

Android效能優化 筆記

2.GPU渲染:GPU渲染依賴2個元件:CPU和GPU

Android效能優化 筆記

Android效能優化 筆記

  1. CPU負責Measure,Layout,Record,Execute操作.
  2. GPU負責Rasterization(柵格化)操作.
    • Resterization柵格化是繪製那些Button,Shape,Path,String,Bitmap等元件最基礎的操作.它把元件拆分到不同的畫素上進行顯示.這是一個很費時的操作.
    • CPU負責把UI元件計算成Polygons(多邊形),Texture(紋理),然後交給GPU進行柵格化渲染.
  3. 為了App流暢,我們需要確保在16ms內完成所有CPU和GPU的工作.

3.過度繪製

Overdraw過度繪製是指螢幕上的某個畫素在同一幀的時間內被繪製了多次.過度繪製會大量浪費CPU及GPU資源/佔用CPU和GPU的處理時間

  • 過度繪製的原因
    1. UI佈局存在大量重疊
    2. 非必須的背景重疊.
      • 如Activity有背景,Layout又有背景,子View又有背景.僅僅移除非必要背景就可以顯著提升效能.
    3. 子View在onDraw中存在重疊部分繪製的情況,比如Bitmap重疊繪製

4.如何提升渲染效能

  1. 移除XML佈局檔案中非必要的Background
  2. 保持佈局扁平化,儘量避免佈局巢狀
  3. 在任何時候都避免呼叫requestLayout(),呼叫requestLayout會導致該layout的所有父節點都發生重新layout的操作
  4. 在自定義View的onDraw中避免過度繪製.
    程式碼例項:
public class OverdrawView extends View {
    public OverdrawView(Context context) {
        super(context);
        init();
    }

    public OverdrawView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public OverdrawView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    private Bitmap bitmap1,bitmap2,bitmap3;
    private void init(){
        paint.setStyle(Paint.Style.FILL);
        bitmap1 = BitmapFactory.decodeResource(getResources(),R.mipmap.png1);
        bitmap2 = BitmapFactory.decodeResource(getResources(),R.mipmap.png2);
        bitmap3 = BitmapFactory.decodeResource(getResources(),R.mipmap.png3);
    }

    int w,h;
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        w = getMeasuredWidth();
        h = getMeasuredHeight();
    }

    private boolean Overdraw = true;
    @Override
    protected void onDraw(Canvas canvas) {
        if(Overdraw){
            //預設會出現過度繪製
            canvas.drawBitmap(bitmap1,0,0,paint);
            canvas.drawBitmap(bitmap2,w/3,0,paint);
            canvas.drawBitmap(bitmap3,w*2/3,0,paint);
        }else{
            //使用Canvas.clipRect避免過度繪製
            canvas.save();
            canvas.clipRect(0,0,w/3,h);
            canvas.drawBitmap(bitmap1,0,0,paint);
            canvas.restore();
            canvas.save();
            canvas.clipRect(w/3,0,w*2/3,h);
            canvas.drawBitmap(bitmap2,w/3,0,paint);
            canvas.restore();
            canvas.save();
            canvas.clipRect(w*2/3,0,w,h);
            canvas.drawBitmap(bitmap3,w*2/3,0,paint);
            canvas.restore();
        }
    }
    //切換是否避免過度繪製
    public void toggleOverdraw(){
        Overdraw = !Overdraw;
        invalidate();
    }
}
複製程式碼


效果圖:

過度繪製

避免過度繪製

2.Android效能優化之記憶體篇

1.Android虛擬機器的 分代堆記憶體/Generational Heap Memory模型

Android效能優化 筆記

Android效能優化 筆記

  1. 和JVM不同:Android的堆記憶體多了1個永久代/Permanent Generation.
  2. 和JVM類似:
    1. 新建立的物件儲存在新生代/Young Generation
    2. GC所佔用的時間和它是哪一個Generation有關,Young Generation的每次GC操作時間是最短的,Old Generation其次,Permanent Generation最長
    3. 無論哪一代,觸發GC後,所有非垃圾回收執行緒暫停,GC結束後所有執行緒恢復執行
  3. 如果短時間內進行過多GC,多次暫停執行緒進行垃圾回收的累積時間就會增大.佔用過多的幀間隔時間/16ms,導致CPU和GPU用於計算渲染的時間不足,導致卡頓/掉幀.

2.記憶體洩漏和記憶體溢位

記憶體洩漏就是無用物件佔據的記憶體空間沒有及時釋放,導致記憶體空間浪費的情況.memory leak.
記憶體溢位是App為1個物件申請記憶體空間,記憶體空間不足的情況.out of memory.
記憶體洩漏數量足夠大,就會引起記憶體溢位.或者說記憶體洩漏是記憶體溢位的原因之一.

3.Android效能優化典範-第2季

1.提升動畫效能

  1. Bitmap的縮放,旋轉,裁剪比較耗效能.例如在一個圓形的鐘表圖上,我們把時鐘的指標摳出來當做單獨的圖片進行旋轉會比旋轉一張完整的圓形圖效能好.
    Android效能優化 筆記
  2. 儘量減少每次重繪的元素可以極大提升效能.可以把複雜的View拆分會更小的View進行組合,在需要重新整理介面時候僅對指定View進行重繪.
    • 假如鐘錶介面上有很多元件,可以把這些元件做拆分,背景圖片單獨拎出來設定為一個獨立的View,通過setLayerType()方法使得這個View強制用Hardware來進行渲染.至於介面上哪些元素需要做拆分,他們各自的更新頻率是多少,需要有針對性的單獨討論

2.物件池

  1. 短時間內大量物件被建立然後很快被銷燬,會多次觸發Android虛擬機器在Young generation進行GC,使用AS檢視記憶體曲線,會看到記憶體曲線劇烈起伏,稱為"記憶體抖動".
  2. GC會暫停其他執行緒,短時間多次GC/記憶體抖動會引起CPU和GPU在16ms內無法完成當前幀的渲染,引起介面卡頓.
  3. 避免記憶體抖動,可以使用物件池
    • 物件池的作用:減少頻繁建立和銷燬物件帶來的成本,實現物件的快取和複用
    • 1 2 3 4
  4. 例項
    
    public class User {
        public String id;
        public String name;
        //物件池例項
        private static final SynchronizedPool<User> sPool = new SynchronizedPool<User>(10);
    
        public static User obtain() {
            User instance = sPool.acquire();
            return (instance != null) ? instance : new User();
        }
        public void recycle() {
            sPool.release(this);
        }
    }
    複製程式碼

3.for index,for simple,iterator三種遍歷效能比較

public class ForTest {
    public static void main(String[] args) {
        Vector<Integer> v = new Vector<>();
        ArrayList<Integer> a = new ArrayList<>();
        LinkedList<Integer> l = new LinkedList<>();
        int time = 1000000;
        for(int i = 0; i< time; i++){
            Integer item = new Random().nextInt(time);
            v.add(item);
            a.add(item);
            l.add(item);
        }
        //測試3種遍歷效能
        long start = System.currentTimeMillis();
        for(int i = 0;i<v.size();i++){
            Integer item = v.get(i);
        }
        long end = System.currentTimeMillis();
        System.out.println("for index Vector耗時:"+(end-start)+"ms");
        start = System.currentTimeMillis();
        for(int i = 0;i<a.size();i++){
            Integer item = a.get(i);
        }
        end = System.currentTimeMillis();
        System.out.println("for index ArrayList耗時:"+(end-start)+"ms");
        start = System.currentTimeMillis();
        for(int i = 0;i<l.size();i++){
            Integer item = l.get(i);
        }
        end = System.currentTimeMillis();
        System.out.println("for index LinkedList耗時:"+(end-start)+"ms");
        start = System.currentTimeMillis();
        for(Integer item:v){
            Integer i = item;
        }
        end = System.currentTimeMillis();
        System.out.println("for simple Vector耗時:"+(end-start)+"ms");
        start = System.currentTimeMillis();
        for(Integer item:a){
            Integer i = item;
        }
        end = System.currentTimeMillis();
        System.out.println("for simple ArrayList耗時:"+(end-start)+"ms");
        start = System.currentTimeMillis();
        for(Integer item:l){
            Integer i = item;
        }
        end = System.currentTimeMillis();
        System.out.println("for simple LinkedList耗時:"+(end-start)+"ms");
        start = System.currentTimeMillis();
        for(Iterator i = v.iterator();i.hasNext();){
            Integer item = (Integer) i.next();
        }
        end = System.currentTimeMillis();
        System.out.println("for Iterator Vector耗時:"+(end-start)+"ms");
        start = System.currentTimeMillis();
        for(Iterator i = a.iterator();i.hasNext();){
            Integer item = (Integer) i.next();
        }
        end = System.currentTimeMillis();
        System.out.println("for Iterator ArrayList耗時:"+(end-start)+"ms");
        start = System.currentTimeMillis();
        for(Iterator i = l.iterator();i.hasNext();){
            Integer item = (Integer) i.next();
        }
        end = System.currentTimeMillis();
        System.out.println("for Iterator LinkedList耗時:"+(end-start)+"ms");
    }
}

列印結果:
for index Vector耗時:28ms
for index ArrayList耗時:14ms
LinkedList就不能用for index方式進行遍歷.
for simple Vector耗時:68ms
for simple ArrayList耗時:11ms
for simple LinkedList耗時:34ms
for Iterator Vector耗時:49ms
for Iterator ArrayList耗時:12ms
for Iterator LinkedList耗時:0ms
複製程式碼
  1. 不要用for index去遍歷連結串列,因為LinkedList在get任何一個位置的資料的時候,都會把前面的資料走一遍.應該使用Iterator去遍歷
    1. get(0),直接拿到0位的Node0的地址,拿到Node0裡面的資料
    2. get(1),直接拿到0位的Node0的地址,從0位的Node0中找到下一個1位的Node1的地址,找到Node1,拿到Node1裡面的資料
    3. get(2),直接拿到0位的Node0的地址,從0位的Node0中找到下一個1位的Node1的地址,找到Node1,從1位的Node1中找到下一個2位的Node2的地址,找到Node2,拿到Node2裡面的資料
  2. Vector和ArrayList,使用for index遍歷效率較高

4.Merge:通過Merge減少1個View層級

  1. 可以將merge當做1個ViewGroup v,如果v的型別和v的父控制元件的型別一致,那麼v其實沒必要存在,因為白白增加了佈局的深度.所以merge使用時必須保證merge中子控制元件所應該在的ViewGroup型別和merge所在的父控制元件型別一致.

  2. Merge的使用場景有2個:

    1. Activity的佈局檔案的根佈局是FrameLayout,則將FrameLayout替換為merge
      • 因為setContentView本質就是將佈局檔案inflate後載入到了id為android.id.content的FrameLayout上.
    2. merge作為根佈局的佈局檔案通過include標籤被引入其他佈局檔案中.這時候include所在的父控制元件,必須和merge所在的佈局檔案"原本根佈局"一致.
  3. 程式碼示例
    merge作為根佈局的佈局檔案,用於Activity的setContentView:

    activity_merge.xml
    
    <?xml version="1.0" encoding="utf-8"?>
    <merge xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="0dp"
            android:text="111111"
            />
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="100dp"
            android:layout_marginLeft="40dp"
            android:text="222222"
            />
    </merge>
    複製程式碼

    merge作為根佈局的佈局檔案,被include標籤引入其他佈局檔案中:
    activity_merge_include.xml
    
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical" android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="merge被include引用"
            />
        <include
            layout="@layout/activity_merge"
            />
    </LinearLayout>
    複製程式碼

5.使用.9.png作為背景

  • 典型場景是1個ImageView需要新增1個背景圖作為邊框.這樣邊框所在矩形的中間部分和實際顯示的圖片就好重疊發生Overdraw.
  • 可以將背景圖製作成.9.png.和前景圖重疊部分設定為透明.Android的2D渲染器會優化.9.png的透明區域.

6.減少透明區域對效能的影響

  • 不透明的View,顯示它只需要渲染一次;如果View設定了alpha值,會至少需要渲染兩次,效能不好
    • 設定透明度setAlpha的時候,會把當前view繪製到offscreen buffer中,然後再顯示出來.offscreen buffer是 一個臨時緩衝區,把View放進來並做透明度的轉化,然後顯示到螢幕上,這個過程效能差,所以應該儘量避免這個過程
  • 如何避免使用offscreen buffer
    1. 對於不存在過度繪製的View,如沒有背景的TextView,就可以直接設定文字顏色;ImageView設定圖片透明度setImageAlpha;自定義View設定繪製時的paint的透明度
    2. 如果是自定義View,確定不存在過度繪製,可以重寫hasOverlappingRendering返回false即可.這樣設定alpha時android會自動優化,避免使用offscreen buffer.
      @Override
      public boolean hasOverlappingRendering() {
          return false;
      }
      複製程式碼
    3. 如果不是1,2兩種情況,要設定View的透明度,則需要讓GPU來渲染指定View,然後再設定透明度.
      View v = findViewById(R.id.root);
      //通過setLayerType的方法來指定View應該如何進行渲染
      //開啟硬體加速
      v.setLayerType(View.LAYER_TYPE_HARDWARE,null);
      v.setAlpha(0.60F);
      //透明度設定完畢後關閉硬體加速
      v.setLayerType(View.LAYER_TYPE_NONE,null);
      複製程式碼

4.Android效能優化典範-第3季

1.避免使用列舉,用註解進行替代

  1. 列舉的問題
    1. 每個列舉值都是1個物件,相比較Integer和String常量,列舉的記憶體開銷至少是其2倍.
    2. 過多列舉會增加dex大小及其中的方法數量,增加App佔用的空間及引發65536機率
  2. 如何替代列舉:使用註解
    1. android.support.annotation中的@IntDef,@StringDef來包裝Integer和String常量.
    2. 3個步驟
      1. 首先定義常量
      2. 然後自定義註解,設定取值範圍就是剛剛定義的常量,並設定自定義註解的保留範圍為原始碼時/SOURCE
      3. 位指定的屬性及方法新增自定義註解.
    3. 程式碼例項
      public class MainActivity extends Activity {
          //1:首先定義常量
          public static final int MALE = 0;
          public static final int FEMALE = 1;
          
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.main_activity);
              Person person = new Person();
              person.setSex(MALE);
              ((Button) findViewById(R.id.test)).setText(person.getSexDes());
          }
          class Person {
              //3.為指定的屬性及方法新增自定義註解
              @SEX
              private int sex;
              //3.為指定的屬性及方法新增自定義註解
              public void setSex(@SEX int sex) {
                  this.sex = sex;
              }
              //3.為指定的屬性及方法新增自定義註解
              @SEX
              public int getSex() {
                  return sex;
              }
              public String getSexDes() {
                  if (sex == MALE) {
                      return "男";
                  } else {
                      return "女";
                  }
              }
          }
          //2:然後建立自定義註解,設定取值範圍就是剛剛定義的常量,並設定自定義註解的保留範圍是原始碼時
          @IntDef({MALE, FEMALE})
          @Retention(RetentionPolicy.SOURCE)
          public @interface SEX {
          }
      }
      複製程式碼

5.Android記憶體優化之OOM

如何避免OOM:

  1. 減小物件的記憶體佔用
  2. 記憶體物件複用防止重建
  3. 避免記憶體洩漏
  4. 記憶體使用策略優化

1.減小物件的記憶體佔用

  1. 避免使用列舉,用註解替代
  2. 減小建立的Bitmap的記憶體,使用合適的縮放比例及解碼格式
    1. inSampleSize:縮放比例
    2. decode format:解碼格式
  3. 現在很多圖片資源的URL都可以新增圖片尺寸作為引數.在通過網路獲取圖片時選擇合適的尺寸,減小網路流量消耗,並減小生成的Bitmap的大小.

2.記憶體物件的重複利用

  1. 物件池技術:減少頻繁建立和銷燬物件帶來的成本,實現物件的快取和複用
  2. 儘量使用Android系統內建資源,可降低APK大小,在一定程度降低記憶體開銷
  3. ConvertView的複用
  4. LRU的機制實現Bitmap的快取(圖片載入框架的必備機制)
  5. 在for迴圈中,用StringBuilder代替String實現字串拼接

3.避免記憶體洩漏

  1. 在App中使用leakcanary檢測記憶體洩漏:leakcanary
  2. Activity的記憶體洩漏
    1. Handler引起Activity記憶體洩漏
      1. 原因:Handler作為Activity的1個非靜態內部類例項,持有Activity例項的引用.若Activity退出後Handler依然有待接收的Message,這時候發生GC,Message-Handler-Activity的引用鏈導致Activity無法被回收.
      2. 2種解決方法
        1. 在onDestroy呼叫Handler.removeCallbacksAndMessages(null)移除該Handler關聯的所有Message及Runnable.再發生GC,Message已經不存在,就可以順利的回收Handler及Activity

          @Override
          protected void onDestroy() {
              super.onDestroy();
              m.removeCallbacksAndMessages(null);
          }
          複製程式碼
        2. 自定義靜態內部類繼承Handler,靜態內部類例項不持有外部Activity的引用.在自定義Handler中定義外部Activity的弱引用,只有弱引用關聯的外部Activity例項未被回收的情況下才繼續執行handleMessage.自定義Handler持有外部Activity的弱引用,發生GC時不耽誤Activity被回收.

              static class M extends Handler{
                  WeakReference<Activity> mWeakReference;
                  public M(Activity activity)
                  {
                      mWeakReference=new WeakReference<Activity>(activity);
                  }
                  @Override
                  public void handleMessage(Message msg) {
                      if(mWeakReference != null){
                          Activity activity=mWeakReference.get();
                          if(activity != null){
                              if(msg.what == 15){
                                  Toast.makeText(activity,"M:15",Toast.LENGTH_SHORT).show();
                              }
                              if(msg.what == 5){
                                  Toast.makeText(activity,"M:5",Toast.LENGTH_SHORT).show();
                              }
                          }
                      }
                  }
              }
              private M m;
              @Override
              protected void onResume() {
                  super.onResume();
                  m = new M(this);
                  m.sendMessageDelayed(m.obtainMessage(15),15000);
                  m.sendMessageDelayed(m.obtainMessage(5),5000);
              }
          複製程式碼
        3. 在避免記憶體洩漏的前提下,如果要求Activity退出就不執行後續動作,用方法1.如果要求後續動作在GC發生前繼續執行,使用方法2

  3. Context:儘量使用Application Context而不是Activity Context,避免不經意的記憶體洩漏
  4. 資源物件要及時關閉

4.記憶體使用策略優化

  1. 圖片選擇合適的資料夾進行存放
    • hdpi/xhdpi/xxhdpi等等不同dpi的資料夾下的圖片在不同的裝置上會經過scale的處理。例如我們只在hdpi的目錄下放置了一張100100的圖片,那麼根據換算關係,xxhdpi的手機去引用那張圖片就會被拉伸到200200。需要注意到在這種情況下,記憶體佔用是會顯著提高的。對於不希望被拉伸的圖片,需要放到assets或者nodpi的目錄下
  2. 謹慎使用依賴注入框架.依賴注入框架會掃描程式碼,需要大量的記憶體空間對映程式碼.
  3. 混淆可以減少不必要的程式碼,類,方法等.降低對映程式碼所需的記憶體空間
  4. onLowMemory()與onTrimMemory():沒想到應該怎麼用
    1. onLowMemory
      • 當所有的background應用都被kill掉的時候,forground應用會收到onLowMemory()的回撥.在這種情況下,需要儘快釋放當前應用的非必須的記憶體資源,從而確保系統能夠繼續穩定執行
    2. onTrimMemory(int level)
      • 當系統記憶體達到某些條件的時候,所有正在執行的應用都會收到這個回撥,同時在這個回撥裡面會傳遞以下的引數,代表不同的記憶體使用情況,收到onTrimMemory()回撥的時候,需要根據傳遞的引數型別進行判斷,合理的選擇釋放自身的一些記憶體佔用,一方面可以提高系統的整體執行流暢度,另外也可以避免自己被系統判斷為優先需要殺掉的應用

6.Android開發最佳實踐

1.注意對隱式Intent的執行時檢查保護

  1. 類似開啟相機等隱式Intent,不一定能夠在所有的Android裝置上都正常執行.
    • 例如系統相機應用被關閉或者不存在相機應用,或者某些許可權被關閉都可能導致丟擲ActivityNotFoundException的異常.
    • 預防這個問題的最佳解決方案是在發出這個隱式Intent之前呼叫resolveActivity做檢查
  2. 程式碼例項
    public class IntentCheckActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_intent_check);
        }
        public void openSBTest(View view) {
            // 跳轉到"傻逼"軟體
            Intent sbIntent = new Intent("android.media.action.IMAGE_GO_SB");
            if (sbIntent.resolveActivity(getPackageManager()) != null) {
                startActivity(sbIntent);
            } else {
                //會彈出這個提示
                Toast.makeText(this,"裝置木有傻逼!",Toast.LENGTH_SHORT).show();
            }
        }
        public void openCameraTest(View view) {
            // 跳轉到系統照相機
            Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
            if (cameraIntent.resolveActivity(getPackageManager()) != null) {
                startActivity(cameraIntent);
                //正常裝置會進入相機並彈出提示
                Toast.makeText(this,"裝置有相機!",Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(this,"裝置木有相機!",Toast.LENGTH_SHORT).show();
            }
        }
    }
    複製程式碼

2.Android 6.0的許可權

3.MD新控制元件的使用:Toolbar替代ActionBar,AppBarLayout,Navigation Drawer, DrawerLayout, NavigationView等

7.Android效能優化典範-第4季

1.網路資料的快取.okHttp,Picasso都支援網路快取

okHttp Picasso
MVP架構實現的Github客戶端(4-加入網路快取)

2.程式碼混淆

2.1.AS中生成keystore.jks應用於APK打包

  • 1:生成keystore.jks

    Android效能優化 筆記

  • 2:檢視.jks檔案的SHA1安全碼
    在AS的Terminal中輸入:
    keytool -list -v -keystore C:\Users\Administrator\Desktop\key.jks
    keytool -list -v -keystore .jks檔案詳細路徑
    回車後,輸入金鑰庫口令/就是.jks的密碼,輸入過程不可見,輸入完畢回車即可!

Android效能優化 筆記

2.2.proguard-rules關鍵字及部分萬用字元含義

關鍵字 描述
keep 保留類和類中的成員,防止它們被混淆或移除
keepnames 保留類和類中的成員,防止它們被混淆,但當成員沒有被引用時會被移除
keepclasseswithmembers 保留類和類中的成員,防止它們被混淆或移除,前提是指名的類中的成員必須存在,如果不存在則還是會混淆
keepclasseswithmembernames 保留類和類中的成員,防止它們被混淆,但當成員沒有被引用時會被移除,前提是指名的類中的成員必須存在,如果不存在則還是會混淆
keepclassmembers 只保留類中的成員,防止它們被混淆或移除
keepclassmembernames 只保留類中的成員,防止它們被混淆,但當成員沒有被引用時會被移除

萬用字元 描述
< field > 匹配類中的所有欄位
< method > 匹配類中的所有方法
< init > 匹配類中的所有建構函式
* 1.*和字串聯合使用,*代表任意長度的不包含包名分隔符(.)的字串:
a.b.c.MainActivity: a.*.*.MainActivity可以匹配;a.*就匹配不上;
2.*單獨使用,就可以匹配所有東西
** 匹配任意長度字串,包含包名分隔符(.)
a.b.**可以匹配a.b包下所有內容,包括子包
*** 匹配任意引數型別.比如:
void set*(***)就能匹配任意傳入的引數型別;
*** get*()就能匹配任意返回值的型別
匹配任意長度的任意型別引數.比如:
void test(…)就能匹配任意void test(String a)或者是void test(int a, String b)

  • keep 完整類名{*;}, 可以對指定類進行完全保留,不混淆類名,變數名,方法名.
    -keep public class a.b.c.TestItem{
        *;
    }
    複製程式碼
  • 在App中,我們會定義很多實體bean.往往涉及到bean例項和json字串間互相轉換.部分json庫會通過反射呼叫bean的set和get方法.因而實體bean的set,get方法不能被混淆,或者說我們自己寫的方法,如果會被第三方庫或其他地方通過反射呼叫,則指定方法要keep避免混淆.
    -keep public class a.**.Bean.**{
        public void set*(***);
        public *** get*();
        # 對應獲取boolean型別屬性的方法
        public *** is*();
    }
    複製程式碼
  • 我們自己寫的使用了反射功能的類,必須keep
    #保留單個包含反射程式碼的類
    -keep public class a.b.c.ReflectUtils{
        *;
    }
    #保留所有包含反射程式碼的類,比如所有涉及反射程式碼的類都在a.b.reflectpackage包及其子包下
    -keep class a.b.reflectpackage.**{
        *;
    } 
    複製程式碼
  • 如果我們要保留繼承了指定類的子類,或者實現了指定介面的類
    -keep class * extends a.b.c.Parent{*;}
    -keep class * implements a.b.c.OneInterface{*;}
    複製程式碼

2.3.proguard-rules.pro通用模板

#####################基本指令##############################################
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
   public *;
}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
-renamesourcefileattribute SourceFile
#程式碼混淆壓縮比,在0~7之間,預設為5,一般不需要改
-optimizationpasses 5
#混淆時不使用大小寫混合,混淆後的類名為小寫
-dontusemixedcaseclassnames
#指定不去忽略非公共的庫的類
-dontskipnonpubliclibraryclasses
#指定不去忽略非公共的庫的類的成員
-dontskipnonpubliclibraryclassmembers
#不做預校驗,preverify是proguard的4個步驟之一
#Android不需要preverify,去掉這一步可加快混淆速度
-dontpreverify
#有了verbose這句話,混淆後就會生成對映檔案
#包含有類名->混淆後類名的對映關係
-verbose
#然後使用printmapping指定對映檔案的名稱
-printmapping mapping.txt
#指定混淆時採用的演算法,後面的引數是一個過濾器,這個過濾器是谷歌推薦的演算法,一般不改變
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
#保護程式碼中的Annotation不被混淆,這在JSON實體對映時非常重要(保留註解引數)
-keepattributes *Annotation*
#避免混淆泛型,這在JSON實體對映時非常重要
-keepattributes Signature
#丟擲異常時保留程式碼行號
-keepattributes SourceFile,LineNumberTable

#忽略所有警告
-ignorewarnings

###################需要保留的東西########################################

#保留反射的方法和類不被混淆================================================
#手動啟用support keep註解
#http://tools.android.com/tech-docs/support-annotations

-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}

#==========================================================================================

#保留所有的本地native方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}
#保留了繼承自Activity、Application這些類的子類
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Fragment
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class * extends com.android.vending.licensing.ILicensingService
-keep class android.support.** {*;}

#保留在Activity中的方法引數是view的方法,從而我們在layout裡面便攜onClick就不會受影響
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}
#列舉類不能被混淆
-keepclassmembers enum *{
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
#保留自定義控制元件不被混淆
-keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
    void set*(***);
    *** get*();
}
#保留Parcelable序列化的類不被混淆
-keep class * implements android.os.Paracelable{
    public static final android.os.Paracelable$Creator *;
}
#保留Serializable序列化的類的如下成員不被混淆
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    !private <fields>;
    !private <methods>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}
#對於R(資源)下的所有類及其方法,都不能被混淆
-keep class **.R$*{
    *;
}
#R檔案中的所有記錄資源id的靜態欄位
-keepclassmembers class **.R$* {
    public static <fields>;
}
#對於帶有回撥函式onXXEvent的,不能被混淆
-keepclassmembers class * {
    void *(**On*Event);
}

#============================針對app的量身定製=============================================

# webView處理,專案中沒有使用到webView忽略即可
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.webView, java.lang.String);
}
複製程式碼

2.4.混淆jar包
郭霖大神部落格有介紹,自己沒試過

2.5.幾條實用的Proguard rules
在上面提供的通用模板上繼續新增下面幾行:

-repackageclasses com
-obfuscationdictionary dict.txt
-classobfuscationdictionary dict.txt
-packageobfuscationdictionary dict.txt

-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(java.lang.String, int);
    public static int v(...);
    public static int i(...);
    public static int w(...);
    public static int d(...);
    public static int e(...);
}
複製程式碼
  1. repackageclasses:除了keep的類,會把我們自己寫的所有類以及所使用到的各種第三方庫程式碼統統移動到我們指定的單個包下.
    • 比如一些比較敏感的被keep的類在包a.b.min下,我們可以使用 -repackageclasses a.b.min,這樣就有成千上萬的被混淆的類和未被混淆的敏感的類在a.b.min下面,正常人根本就找不到關鍵類.尤其是keep的類也只是保留關鍵方法,名字也被混淆過.
  2. -obfuscationdictionary,-classobfuscationdictionary和-packageobfuscationdictionary分別指定變數/方法名,類名,包名混淆後的字串集.
    • 預設我們的程式碼命名會被混淆成字母組合,使用這些配置可以用亂碼或中文內容進行命名.中文命名可以破壞部分反編譯軟體的正常工作,亂碼則極大加大了檢視程式碼的難度.
    • dict.txt:需要放到和app模組的proguard-rules.pro同級目錄.dict.txt具體內容可以自己寫,參考開源專案:一種生成閱讀極其困難的proguard字典的演算法
  3. -assumenosideeffects class android.util.Log是在編譯成 APK 之前把日誌程式碼全部刪掉.

2.6.字串硬編碼

  1. 對於反編譯者來說,最簡單的入手點就是字串搜尋.硬編碼留在程式碼裡的字串值都會在反編譯過程中被原樣恢復,不要使用硬編碼.
  2. 如果一定要使用硬編碼
    1. 新建1個儲存硬編碼的常量類,靜態存放字串常量,即使找到了常量類,反編譯者很難搜尋到哪裡用了這些字串.
    2. 常量類中的靜態常量字串,用名稱作為真正內容,而值用難以理解的編碼表示.
      //1:新建常量類,用於存放字串常量
      public class HardStrings {
          //2:名稱是真正內容,值是難以理解的編碼.
          //這樣即使是必須儲存的Log,被反編譯者看到的也只是難以理解的值,搞不清意義
          public static final String MaxMemory = "001";
          public static final String M = "002";
          public static final String MemoryClass = "003";
          public static final String LargeMemoryClass = "004";
          public static final String 系統總記憶體 = "005";
          public static final String 系統剩餘記憶體 = "006";
          public static final String 系統是否處於低記憶體執行 = "007";
          public static final String 系統剩餘記憶體低於 = "008";
          public static final String M時為低記憶體執行 = "009";
      }
      複製程式碼

2.7.res資源混淆及多渠道打包
簡單講,使用騰訊的2個gradle外掛來實現res資源混淆及多渠道打包.
res資源混淆:AndResGuard
多渠道打包:VasDolly
多渠道打包原理+VasDolly和其他多渠道打包方案對比

具體流程:
AndResGuard使用了chaychan的方法,單獨建立gradle檔案

  1. 專案根目錄下build.gradle中,新增外掛的依賴,具體如下
    buildscript {
        repositories {
            google()
            jcenter()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.1.4'
    
            // NOTE: Do not place your application dependencies here; they belong
            // in the individual module build.gradle files
            //新增AndResGuard
            classpath 'com.tencent.mm:AndResGuard-gradle-plugin:1.2.12'
            //新增VasDolly
            classpath 'com.leon.channel:plugin:2.0.1'
        }
    }
    複製程式碼
  2. 在app目錄下單獨建立gradle檔案and_res_guard.gradle.內容如下
    apply plugin: 'AndResGuard'
    
    andResGuard {
        mappingFile = null
        use7zip = true
        useSign = true
        keepRoot = false
        compressFilePattern = [
                "*.png",
                "*.jpg",
                "*.jpeg",
                "*.gif",
                "resources.arsc"
        ]
        whiteList = [
    //            // your icon
    //            "R.drawable.icon",
    //            // for fabric
    //            "R.string.com.crashlytics.*",
    //            // for umeng update
    //            "R.string.tb_*",
    //            "R.layout.tb_*",
    //            "R.drawable.tb_*",
    //            "R.drawable.u1*",
    //            "R.drawable.u2*",
    //            "R.color.tb_*",
    //            // umeng share for sina
    //            "R.drawable.sina*",
    //            // for google-services.json
    //            "R.string.google_app_id",
    //            "R.string.gcm_defaultSenderId",
    //            "R.string.default_web_client_id",
    //            "R.string.ga_trackingId",
    //            "R.string.firebase_database_url",
    //            "R.string.google_api_key",
    //            "R.string.google_crash_reporting_api_key",
    //
    //            //友盟
    //            "R.string.umeng*",
    //            "R.string.UM*",
    //            "R.layout.umeng*",
    //            "R.drawable.umeng*",
    //            "R.id.umeng*",
    //            "R.anim.umeng*",
    //            "R.color.umeng*",
    //            "R.style.*UM*",
    //            "R.style.umeng*",
    //
    //            //融雲
    //            "R.drawable.u*",
    //            "R.drawable.rc_*",
    //            "R.string.rc_*",
    //            "R.layout.rc_*",
    //            "R.color.rc_*",
    //            "R.id.rc_*",
    //            "R.style.rc_*",
    //            "R.dimen.rc_*",
    //            "R.array.rc_*"
        ]
    
        sevenzip {
            artifact = 'com.tencent.mm:SevenZip:1.2.12'
            //path = "/usr/local/bin/7za"
        }
    }
    複製程式碼
  3. 模組app下的build.gradle檔案新增依賴,具體如下
    apply plugin: 'com.android.application'
    //引入剛剛建立的and_res_guard.gradle
    apply from: 'and_res_guard.gradle'
    //依賴VasDolly
    apply plugin: 'channel'
    
    channel{
        //指定渠道檔案
        channelFile = file("channel.txt")
        //多渠道包的輸出目錄,預設為new File(project.buildDir,"channel")
        baseOutputDir = new File(project.buildDir,"channel")
        //多渠道包的命名規則,預設為:${appName}-${versionName}-${versionCode}-${flavorName}-${buildType}
        apkNameFormat ='${appName}-${versionName}-${versionCode}-${flavorName}-${buildType}'
        //快速模式:生成渠道包時不進行校驗(速度可以提升10倍以上,預設為false)
        isFastMode = true
        //buildTime的時間格式,預設格式:yyyyMMdd-HHmmss
        buildTimeDateFormat = 'yyyyMMdd-HH:mm:ss'
        //低記憶體模式(僅針對V2簽名,預設為false):只把簽名塊、中央目錄和EOCD讀取到記憶體,不把最大頭的內容塊讀取到記憶體,在手機上合成APK時,可以使用該模式
        lowMemory = false
    }
    rebuildChannel {
        //指定渠道檔案
        channelFile = file("channel.txt")
    //    baseDebugApk = new File(project.projectDir, "app-release_7zip_aligned_signed.apk")
        baseReleaseApk = new File(project.projectDir, "app-release_7zip_aligned_signed.apk")
        //預設為new File(project.buildDir, "rebuildChannel/debug")
    //    debugOutputDir = new File(project.buildDir, "rebuildChannel/debug")
        //預設為new File(project.buildDir, "rebuildChannel/release")
        releaseOutputDir = new File(project.buildDir, "rebuildChannel/release")
        //快速模式:生成渠道包時不進行校驗(速度可以提升10倍以上,預設為false)
        isFastMode = false
        //低記憶體模式(僅針對V2簽名,預設為false):只把簽名塊、中央目錄和EOCD讀取到記憶體,不把最大頭的內容塊讀取到記憶體,在手機上合成APK時,可以使用該模式
        lowMemory = false
    }
    
    android {
        signingConfigs {
            tcl {
                keyAlias 'qinghailongxin'
                keyPassword 'huanhailiuxin'
                storeFile file('C:/Users/Administrator/Desktop/key.jks')
                storePassword 'huanhailiuxin'
            }
        }
        compileSdkVersion 28
        defaultConfig {
            applicationId "com.example.administrator.proguardapp"
            minSdkVersion 15
            targetSdkVersion 28
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
            vectorDrawables.useSupportLibrary = true
        }
        buildTypes {
            release {
                minifyEnabled true
                shrinkResources true
                zipAlignEnabled true
                pseudoLocalesEnabled true
                proguardFiles 'proguard-rules.pro'
                signingConfig signingConfigs.tcl
            }
            debug {
                signingConfig signingConfigs.tcl
                minifyEnabled true
                pseudoLocalesEnabled true
                zipAlignEnabled true
            }
        }
    }
    
    dependencies {
        implementation fileTree(include: ['*.jar'], dir: 'libs')
        implementation 'com.android.support:appcompat-v7:28.0.0'
        implementation 'com.android.support.constraint:constraint-layout:1.1.3'
        implementation 'com.android.support:design:28.0.0'
        implementation 'com.android.support:support-vector-drawable:28.0.0'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
        implementation files('libs/litepal-2.0.0.jar')
        //依賴VasDolly
        api 'com.leon.channel:helper:2.0.1'
    }
    複製程式碼
  4. 首先使用AndResGuard實現資源混淆,再使用VasDolly實現多渠道打包
    1. 在Gradle介面中,找到app模組下andresguard的task.

      • 如果想打debug包,則執行resguardDebug指令;
      • 如果想打release包,則執行resguardRelease指令.
      • 此處我們雙擊執行resguardRelease指令,在app目錄下的/build/output/apk/release/AndResGuard_{apk_name}/ 資料夾中找到混淆後的Apk,其中app-release_aligned_signed.apk為進行混淆並簽名過的apk.
      • 我們檢視app-release_aligned_signed.apk,res資料夾更名為r,裡面的目錄名稱以及xml檔案已經被混淆.
        Android效能優化 筆記
    2. 將app-release_aligned_signed.apk放到app模組下,在Gradle介面中,找到app模組下channel的task,執行reBuildChannel指令.

      • 雙擊執行reBuildChannel指令,幾秒鐘就生成了20個通過app-release_aligned_signed.apk的多渠道apk.
        Android效能優化 筆記
      • 通過helper類庫中的ChannelReaderUtil類讀取渠道資訊
        String channel = ChannelReaderUtil.getChannel(getApplicationContext());
        複製程式碼

3.APK瘦身

4.更高效的資料序列化:只是看看從沒用過,Protocal Buffers,Nano-Proto-Buffers,FlatBuffers

5.資料呈現的順序以及結構會對序列化之後的空間產生不小的影響

改變資料結構對記憶體及gzip的影響

  1. gzip
    1. gzip概念:HTTP協議上的GZIP編碼是一種用來改進WEB應用程式效能的技術
      • 一般對純文字內容可壓縮到原大小的40%
      • 減少檔案大小有兩個明顯的好處,一是可以減少儲存空間,二是通過網路傳輸檔案時,可以減少傳輸的時間
    2. okHttp對gzip的支援
      1. okHttp支援gzip自動解壓縮,不需要設定Accept-Encoding為gzip
        1. 開發者手動設定Accept-Encoding,okHttp不負責解壓縮
        2. 開發者沒有設定Accept-Encoding時,則自動新增Accept-Encoding: gzip,自動新增的request,response支援自動解壓
          1. 自動解壓時移除Content-Length,所以上層Java程式碼想要contentLength時為-1
          2. 自動解壓時移除 Content-Encoding
          3. 自動解壓時,如果是分塊傳輸編碼,Transfer-Encoding: chunked不受影響
      2. 我們在向伺服器提交大量資料的時候,希望對post的資料進行gzip壓縮,需要使用自定義攔截器
        import okhttp3.Interceptor;
        import okhttp3.MediaType;
        import okhttp3.Request;
        import okhttp3.RequestBody;
        import okhttp3.Response;
        import okio.BufferedSink;
        import okio.GzipSink;
        import okio.Okio;
        
        public class GzipRequestInterceptor implements Interceptor {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request originalRequest = chain.request();
                if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
                    return chain.proceed(originalRequest);
                }
        
                Request compressedRequest = originalRequest.newBuilder()
                        .header("Content-Encoding", "gzip")
                        .method(originalRequest.method(), gzip(originalRequest.body()))
                        .build();
                return chain.proceed(compressedRequest);
            }
        
            private RequestBody gzip(final RequestBody body) {
                return new RequestBody() {
                    @Override
                    public MediaType contentType() {
                        return body.contentType();
                    }
        
                    @Override
                    public long contentLength() {
                        return -1; // 無法提前知道壓縮後的資料大小
                    }
        
                    @Override
                    public void writeTo(BufferedSink sink) throws IOException {
                        BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
                        body.writeTo(gzipSink);
                        gzipSink.close();
                    }
                };
            }
        }
        
        然後構建OkhttpClient的時候,新增攔截器:
        OkHttpClient okHttpClient = new OkHttpClient.Builder() 
            .addInterceptor(new GzipRequestInterceptor())//開啟Gzip壓縮
            ...
            .build();
        複製程式碼
  2. 改變資料結構,就是將原始集合按照集合中物件的屬性進行拆分,變成多個屬性集合的形式.
    1. 改變資料結構後JSON字串長度有明顯降低
    2. 使用GZIP對JSON字串進行壓縮,在原始集合中元素資料重複率逐漸變大的情況下,GZIP壓縮後的原始JSON字串長度/GZIP壓縮後的改變資料結構的JSON字串會明顯大於1.
    3. 程式碼及測試結果如下,結果僅供參考(ZipUtils是網上蕩的,且沒有使用網上用過的Base64Decoder,Base64Encoder)
      public final class ZipUtils {
      
          /**
           * Gzip 壓縮資料
           *
           * @param unGzipStr
           * @return
           */
          public static String compressForGzip(String unGzipStr) {
      
      //        if (TextUtils.isEmpty(unGzipStr)) {
      //            return null;
      //        }
              try {
                  ByteArrayOutputStream baos = new ByteArrayOutputStream();
                  GZIPOutputStream gzip = new GZIPOutputStream(baos);
                  gzip.write(unGzipStr.getBytes());
                  gzip.close();
                  byte[] encode = baos.toByteArray();
                  baos.flush();
                  baos.close();
      //            return Base64Encoder.encode(encode);
                  return new String(encode);
              } catch (UnsupportedEncodingException e) {
                  e.printStackTrace();
              } catch (IOException e) {
                  e.printStackTrace();
              }
      
              return null;
          }
      
          /**
           * Gzip解壓資料
           *
           * @param gzipStr
           * @return
           */
          public static String decompressForGzip(String gzipStr) {
      //        if (TextUtils.isEmpty(gzipStr)) {
      //            return null;
      //        }
      //        byte[] t = Base64Decoder.decodeToBytes(gzipStr);
              byte[] t = gzipStr.getBytes();
              try {
                  ByteArrayOutputStream out = new ByteArrayOutputStream();
                  ByteArrayInputStream in = new ByteArrayInputStream(t);
                  GZIPInputStream gzip = new GZIPInputStream(in);
                  byte[] buffer = new byte[1024];
                  int n = 0;
                  while ((n = gzip.read(buffer, 0, buffer.length)) > 0) {
                      out.write(buffer, 0, n);
                  }
                  gzip.close();
                  in.close();
                  out.close();
                  return out.toString();
              } catch (IOException e) {
                  e.printStackTrace();
              }
              return null;
          }
          /**
           * Zip 壓縮資料
           *
           * @param unZipStr
           * @return
           */
          public static String compressForZip(String unZipStr) {
      
      //        if (TextUtils.isEmpty(unZipStr)) {
      //            return null;
      //        }
              try {
                  ByteArrayOutputStream baos = new ByteArrayOutputStream();
                  ZipOutputStream zip = new ZipOutputStream(baos);
                  zip.putNextEntry(new ZipEntry("0"));
                  zip.write(unZipStr.getBytes());
                  zip.closeEntry();
                  zip.close();
                  byte[] encode = baos.toByteArray();
                  baos.flush();
                  baos.close();
      //            return Base64Encoder.encode(encode);
                  return new String(encode);
              } catch (UnsupportedEncodingException e) {
                  e.printStackTrace();
              } catch (IOException e) {
                  e.printStackTrace();
              }
      
              return null;
          }
      
          /**
           * Zip解壓資料
           *
           * @param zipStr
           * @return
           */
          public static String decompressForZip(String zipStr) {
      
      //        if (TextUtils.isEmpty(zipStr)) {
      //            return null;
      //        }
      //        byte[] t = Base64Decoder.decodeToBytes(zipStr);
              byte[] t = zipStr.getBytes();
              try {
                  ByteArrayOutputStream out = new ByteArrayOutputStream();
                  ByteArrayInputStream in = new ByteArrayInputStream(t);
                  ZipInputStream zip = new ZipInputStream(in);
                  zip.getNextEntry();
                  byte[] buffer = new byte[1024];
                  int n = 0;
                  while ((n = zip.read(buffer, 0, buffer.length)) > 0) {
                      out.write(buffer, 0, n);
                  }
                  zip.close();
                  in.close();
                  out.close();
                  return out.toString("UTF-8");
              } catch (IOException e) {
                  e.printStackTrace();
              }
              return null;
          }
      }
      
      public class GzipZipTest {
          public static void main(String[] args) {
              GzipZipTest t = new GzipZipTest();
              t.t();
          }
          private void t(){
              /*List<Person> l = new ArrayList<Person>();
              for(int i=0;i<1;i++){
                  for(int j=0;j<6000;j++){
                      Person p = new Person();
                      p.age = j;
                      p.gender = "gender"+j;
                      p.name = "name"+j;
                      l.add(p);
                  }
              }
              Gson gson = new Gson();
              List<String> names = new ArrayList<String>();
              List<String> genders = new ArrayList<String>();
              List<Integer> ages = new ArrayList<Integer>();
              for(Person p:l){
                  names.add(p.name);
                  genders.add(p.gender);
                  ages.add(p.age);
              }
              PersonItemList itemList = new PersonItemList();
              itemList.items = l;
              String jsonDataOri = gson.toJson(itemList);
              System.out.println("原始資料結構 壓縮前json資料長度 ---->" + jsonDataOri.length());
              PersonAttrList attrList = new PersonAttrList();
              attrList.names = names;
              attrList.genders = genders;
              attrList.ages = ages;
              String jsonDataVariety = gson.toJson(attrList);
              System.out.println("變種資料結構 壓縮前json資料長度 ---->" + jsonDataVariety.length());
              System.out.println("===================================================");
      
              for(int i=0;i<100;i++){
                  //1:原始資料結構
                  //Gzip壓縮
                  long start = System.currentTimeMillis();
                  String gzipStr = ZipUtils.compressForGzip(jsonDataOri);
                  long end = System.currentTimeMillis();
                  System.out.println("原始資料結構 Gzip壓縮耗時 cost time---->" + (end - start));
                  System.out.println("原始資料結構 Gzip壓縮後json資料長度 ---->" + gzipStr.length());
                  //Zip壓縮
      //        start = System.currentTimeMillis();
      //        String zipStr = ZipUtils.compressForZip(jsonDataOri);
      //        end = System.currentTimeMillis();
      //        System.out.println("原始資料結構 Zip壓縮耗時 cost time---->" + (end - start));
      //        System.out.println("原始資料結構 Zip壓縮後json資料長度 ---->" + zipStr.length());
      
                  //2:變種資料結構
                  //Gzip壓縮
                  start = System.currentTimeMillis();
                  String gzipStrVariety = ZipUtils.compressForGzip(jsonDataVariety);
                  end = System.currentTimeMillis();
                  System.out.println("變種資料結構 Gzip壓縮耗時 cost time---->" + (end - start));
                  System.out.println("變種資料結構 Gzip壓縮後json資料長度 ---->" + gzipStrVariety.length());
                  //Zip壓縮
      //        start = System.currentTimeMillis();
      //        String zipStrVariety = ZipUtils.compressForZip(jsonDataVariety);
      //        end = System.currentTimeMillis();
      //        System.out.println("變種資料結構 Zip壓縮耗時 cost time---->" + (end - start));
      //        System.out.println("變種資料結構 Zip壓縮後json資料長度 ---->" + zipStrVariety.length());
                  System.out.println("壓縮後 原始結構長度:變種資料結構="+((float)gzipStr.length())/(float)gzipStrVariety.length());
                  System.out.println("===================================================");
              }*/
      
              float repetitionRatio = 0.00F;
              List<Person> l = new ArrayList<Person>();
              for(repetitionRatio = 0.000F; repetitionRatio < 0.500F; repetitionRatio+=0.005F){
                  int reportIndex = (int) (6000 * (1-repetitionRatio));
                  for(int i = 0;i<reportIndex;i++){
                      Person p = new Person();
                      p.age = i;
                      p.gender = "gender"+i;
                      p.name = "name"+i;
                      l.add(p);
                  }
                  if(repetitionRatio > 0.00F){
                      int reportCount = (int) (6000 * repetitionRatio);
                      for(int i = 0;i<reportCount;i++){
                          Person p = new Person();
                          p.age = i;
                          p.gender = "gender"+i;
                          p.name = "name"+i;
                          l.add(p);
                      }
                  }
                  Gson gson = new Gson();
                  List<String> names = new ArrayList<String>();
                  List<String> genders = new ArrayList<String>();
                  List<Integer> ages = new ArrayList<Integer>();
                  for(Person p:l){
                      names.add(p.name);
                      genders.add(p.gender);
                      ages.add(p.age);
                  }
                  PersonItemList itemList = new PersonItemList();
                  itemList.items = l;
                  String jsonDataOri = gson.toJson(itemList);
                  System.out.println("===================================================");
                  System.out.println("原始資料結構 壓縮前json資料長度 ---->" + jsonDataOri.length());
                  PersonAttrList attrList = new PersonAttrList();
                  attrList.names = names;
                  attrList.genders = genders;
                  attrList.ages = ages;
                  String jsonDataVariety = gson.toJson(attrList);
                  System.out.println("變種資料結構 壓縮前json資料長度 ---->" + jsonDataVariety.length());
                  //1:原始資料結構
                  //Gzip壓縮
                  long start = System.currentTimeMillis();
                  String gzipStr = ZipUtils.compressForGzip(jsonDataOri);
                  long end = System.currentTimeMillis();
                  System.out.println("原始資料結構 Gzip壓縮後json資料長度 ---->" + gzipStr.length());
      
                  //2:變種資料結構
                  //Gzip壓縮
                  start = System.currentTimeMillis();
                  String gzipStrVariety = ZipUtils.compressForGzip(jsonDataVariety);
                  end = System.currentTimeMillis();
                  System.out.println("變種資料結構 Gzip壓縮後json資料長度 ---->" + gzipStrVariety.length());
                  System.out.println("重複率為 "+repetitionRatio/(1-repetitionRatio)+" 壓縮後:原始結構長度:變種資料結構="+((float)gzipStr.length())/(float)gzipStrVariety.length());
              }
          }
          public class Person implements Serializable{
              public String name;
              public String gender;
              public int age;
      
              public String getName() {
                  return name;
              }
      
              public void setName(String name) {
                  this.name = name;
              }
      
              public String getGender() {
                  return gender;
              }
      
              public void setGender(String gender) {
                  this.gender = gender;
              }
      
              public int getAge() {
                  return age;
              }
      
              public void setAge(int age) {
                  this.age = age;
              }
          }
          public class PersonItemList implements Serializable{
              public List<Person> items;
      
              public List<Person> getItems() {
                  return items;
              }
      
              public void setItems(List<Person> items) {
                  this.items = items;
              }
          }
          public class PersonAttrList implements Serializable{
              public List<String> names;
              public List<String> genders;
              public List<Integer> ages;
      
              public List<String> getNames() {
                  return names;
              }
      
              public void setNames(List<String> names) {
                  this.names = names;
              }
      
              public List<String> getGenders() {
                  return genders;
              }
      
              public void setGenders(List<String> genders) {
                  this.genders = genders;
              }
      
              public List<Integer> getAges() {
                  return ages;
              }
      
              public void setAges(List<Integer> ages) {
                  this.ages = ages;
              }
          }
      }
      
      首先看當單個物件屬性重複率超過100%的情況下列印結果:
      
      List<Person> l = new ArrayList<Person>();
              for(int i=0;i<1;i++){
                  for(int j=0;j<6000;j++){
                      Person p = new Person();
                      p.age = j;
                      p.gender = "gender"+j;
                      p.name = "name"+j;
                      l.add(p);
                  }
      }
      原始資料結構 壓縮前json資料長度 ---->273011	//因為i和j變動,資料會略有變化
      變種資料結構 壓縮前json資料長度 ---->129032	//因為i和j變動,資料會略有變化
      
      i=x;	j=y;
      
      //x=1,j=6000:代表資料沒有任何重複的情形
      x=1;	j=6000;
      原始資料結構 Gzip壓縮後json資料長度 ---->44215
      變種資料結構 Gzip壓縮後json資料長度 ---->39561
      壓縮後 原始結構長度:變種資料結構=1.1176411
      
      //x=2,j=3000:代表每個物件都存在另1個屬性完全一致的物件.單個物件重複率100%
      x=2;	j=3000;
      原始資料結構 Gzip壓縮後json資料長度 ---->44204
      變種資料結構 Gzip壓縮後json資料長度 ---->27628
      壓縮後 原始結構長度:變種資料結構=1.599971
      
      //餘下的代表每單個物件重複率超過100%的情況
      x=3;	j=2000;
      原始資料結構 Gzip壓縮後json資料長度 ---->43733
      變種資料結構 Gzip壓縮後json資料長度 ---->17020
      壓縮後 原始結構長度:變種資料結構=2.5695064
      
      x=4;	j=1500;
      原始資料結構 Gzip壓縮後json資料長度 ---->43398
      變種資料結構 Gzip壓縮後json資料長度 ---->13914
      壓縮後 原始結構長度:變種資料結構=3.119017
      
      x=6;	j=1000;
      原始資料結構 Gzip壓縮後json資料長度 ---->42166
      變種資料結構 Gzip壓縮後json資料長度 ---->8016
      壓縮後 原始結構長度:變種資料結構=5.2602296
      
      x=7;	j=857;
      原始資料結構 Gzip壓縮後json資料長度 ---->41743
      變種資料結構 Gzip壓縮後json資料長度 ---->7024
      壓縮後 原始結構長度:變種資料結構=5.94291
      
      x=8;	j=750;
      原始資料結構 Gzip壓縮後json資料長度 ---->41561
      變種資料結構 Gzip壓縮後json資料長度 ---->6378
      壓縮後 原始結構長度:變種資料結構=6.516306
      
      x=9;	j=667;
      原始資料結構 Gzip壓縮後json資料長度 ---->41491
      變種資料結構 Gzip壓縮後json資料長度 ---->5870
      壓縮後 原始結構長度:變種資料結構=7.0683136
      
      x=10;	j=600;
      原始資料結構 Gzip壓縮後json資料長度 ---->7552
      變種資料結構 Gzip壓縮後json資料長度 ---->5503
      壓縮後 原始結構長度:變種資料結構=1.3723423
      
      x=12;	j=500;
      原始資料結構 Gzip壓縮後json資料長度 ---->6955
      變種資料結構 Gzip壓縮後json資料長度 ---->4962
      壓縮後 原始結構長度:變種資料結構=1.4016526
      
      x=15;	j=400;
      原始資料結構 Gzip壓縮後json資料長度 ---->6207
      變種資料結構 Gzip壓縮後json資料長度 ---->4179
      壓縮後 原始結構長度:變種資料結構=1.4852836
      
      x=20;	j=300;
      原始資料結構 Gzip壓縮後json資料長度 ---->5117
      變種資料結構 Gzip壓縮後json資料長度 ---->3576
      壓縮後 原始結構長度:變種資料結構=1.4309285
      
      x=30;	j=200;
      原始資料結構 Gzip壓縮後json資料長度 ---->4511
      變種資料結構 Gzip壓縮後json資料長度 ---->3156
      壓縮後 原始結構長度:變種資料結構=1.429341
      
      x=40;	j=150;
      原始資料結構 Gzip壓縮後json資料長度 ---->4359
      變種資料結構 Gzip壓縮後json資料長度 ---->3035
      壓縮後 原始結構長度:變種資料結構=1.4362438
      
      x=60;	j=100;
      原始資料結構 Gzip壓縮後json資料長度 ---->2832
      變種資料結構 Gzip壓縮後json資料長度 ---->1382
      壓縮後 原始結構長度:變種資料結構=2.049204
      
      x=80;	j=75;
      原始資料結構 Gzip壓縮後json資料長度 ---->2581
      變種資料結構 Gzip壓縮後json資料長度 ---->1217
      壓縮後 原始結構長度:變種資料結構=2.1207888
      
      x=150;	j=40;
      原始資料結構 Gzip壓縮後json資料長度 ---->1835
      變種資料結構 Gzip壓縮後json資料長度 ---->890
      壓縮後 原始結構長度:變種資料結構=2.0617979
      
      x=200;	j=30;
      原始資料結構 Gzip壓縮後json資料長度 ---->1744
      變種資料結構 Gzip壓縮後json資料長度 ---->797
      壓縮後 原始結構長度:變種資料結構=2.1882057
      
      x=300;	j=20;
      原始資料結構 Gzip壓縮後json資料長度 ---->1539
      變種資料結構 Gzip壓縮後json資料長度 ---->739
      壓縮後 原始結構長度:變種資料結構=2.082544
      
      x=316;	j=19;
      原始資料結構 Gzip壓縮後json資料長度 ---->1269
      變種資料結構 Gzip壓縮後json資料長度 ---->725
      壓縮後 原始結構長度:變種資料結構=1.7503449
      
      x=400;	j=15;
      原始資料結構 Gzip壓縮後json資料長度 ---->1488
      變種資料結構 Gzip壓縮後json資料長度 ---->662
      壓縮後 原始結構長度:變種資料結構=2.247734
      
      x=500;	j=12;
      原始資料結構 Gzip壓縮後json資料長度 ---->1453
      變種資料結構 Gzip壓縮後json資料長度 ---->563
      壓縮後 原始結構長度:變種資料結構=2.580817
      
      x=600;	j=10;
      原始資料結構 Gzip壓縮後json資料長度 ---->1044
      變種資料結構 Gzip壓縮後json資料長度 ---->573
      壓縮後 原始結構長度:變種資料結構=1.8219895
      
      x=667;	j=9;
      原始資料結構 Gzip壓縮後json資料長度 ---->1291
      變種資料結構 Gzip壓縮後json資料長度 ---->527
      壓縮後 原始結構長度:變種資料結構=2.4497154
      
      x=750;	j=8;
      原始資料結構 Gzip壓縮後json資料長度 ---->1155
      變種資料結構 Gzip壓縮後json資料長度 ---->520
      壓縮後 原始結構長度:變種資料結構=2.2211537
      
      x=1000;	j=6;
      原始資料結構 Gzip壓縮後json資料長度 ---->1269
      變種資料結構 Gzip壓縮後json資料長度 ---->429
      壓縮後 原始結構長度:變種資料結構=2.958042
      
      x=1200;	j=5;
      原始資料結構 Gzip壓縮後json資料長度 ---->1135
      變種資料結構 Gzip壓縮後json資料長度 ---->478
      壓縮後 原始結構長度:變種資料結構=2.374477
      
      x=3000;	j=2;
      原始資料結構 Gzip壓縮後json資料長度 ---->990
      變種資料結構 Gzip壓縮後json資料長度 ---->382
      壓縮後 原始結構長度:變種資料結構=2.591623
      
      x=6000;	j=1;
      原始資料結構 Gzip壓縮後json資料長度 ---->590
      變種資料結構 Gzip壓縮後json資料長度 ---->311
      壓縮後 原始結構長度:變種資料結構=1.897106
      
      當每個物件屬性重複率低於100%的情況下列印結果:
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->314681
      變種資料結構 壓縮前json資料長度 ---->170702
      原始資料結構 Gzip壓縮後json資料長度 ---->44215
      變種資料結構 Gzip壓縮後json資料長度 ---->39561
      重複率為 0.0 壓縮後:原始結構長度:變種資料結構=1.1176411
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->629141
      變種資料結構 壓縮前json資料長度 ---->341162
      原始資料結構 Gzip壓縮後json資料長度 ---->88279
      變種資料結構 Gzip壓縮後json資料長度 ---->66875
      重複率為 0.0050251256 壓縮後:原始結構長度:變種資料結構=1.3200598
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->943421
      變種資料結構 壓縮前json資料長度 ---->511442
      原始資料結構 Gzip壓縮後json資料長度 ---->131892
      變種資料結構 Gzip壓縮後json資料長度 ---->90806
      重複率為 0.01010101 壓縮後:原始結構長度:變種資料結構=1.4524591
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->1257521
      變種資料結構 壓縮前json資料長度 ---->681542
      原始資料結構 Gzip壓縮後json資料長度 ---->175554
      變種資料結構 Gzip壓縮後json資料長度 ---->116973
      重複率為 0.015228426 壓縮後:原始結構長度:變種資料結構=1.5008079
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->1571501
      變種資料結構 壓縮前json資料長度 ---->851522
      原始資料結構 Gzip壓縮後json資料長度 ---->218945
      變種資料結構 Gzip壓縮後json資料長度 ---->142129
      重複率為 0.020408163 壓縮後:原始結構長度:變種資料結構=1.5404668
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->1885341
      變種資料結構 壓縮前json資料長度 ---->1021386
      原始資料結構 Gzip壓縮後json資料長度 ---->262306
      變種資料結構 Gzip壓縮後json資料長度 ---->168725
      重複率為 0.025641024 壓縮後:原始結構長度:變種資料結構=1.5546362
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->2199091
      變種資料結構 壓縮前json資料長度 ---->1191160
      原始資料結構 Gzip壓縮後json資料長度 ---->305678
      變種資料結構 Gzip壓縮後json資料長度 ---->191222
      重複率為 0.030927831 壓縮後:原始結構長度:變種資料結構=1.5985503
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->2512751
      變種資料結構 壓縮前json資料長度 ---->1360844
      原始資料結構 Gzip壓縮後json資料長度 ---->348774
      變種資料結構 Gzip壓縮後json資料長度 ---->219050
      重複率為 0.036269426 壓縮後:原始結構長度:變種資料結構=1.5922118
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->2826321
      變種資料結構 壓縮前json資料長度 ---->1530438
      原始資料結構 Gzip壓縮後json資料長度 ---->391506
      變種資料結構 Gzip壓縮後json資料長度 ---->243066
      重複率為 0.041666664 壓縮後:原始結構長度:變種資料結構=1.6106983
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->3139801
      變種資料結構 壓縮前json資料長度 ---->1699942
      原始資料結構 Gzip壓縮後json資料長度 ---->434274
      變種資料結構 Gzip壓縮後json資料長度 ---->268432
      重複率為 0.047120415 壓縮後:原始結構長度:變種資料結構=1.6178175
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->3453191
      變種資料結構 壓縮前json資料長度 ---->1869356
      原始資料結構 Gzip壓縮後json資料長度 ---->476356
      變種資料結構 Gzip壓縮後json資料長度 ---->291550
      重複率為 0.052631572 壓縮後:原始結構長度:變種資料結構=1.6338742
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->3766491
      變種資料結構 壓縮前json資料長度 ---->2038680
      原始資料結構 Gzip壓縮後json資料長度 ---->518371
      變種資料結構 Gzip壓縮後json資料長度 ---->317122
      重複率為 0.058201052 壓縮後:原始結構長度:變種資料結構=1.6346107
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->4079701
      變種資料結構 壓縮前json資料長度 ---->2207914
      原始資料結構 Gzip壓縮後json資料長度 ---->560526
      變種資料結構 Gzip壓縮後json資料長度 ---->344023
      重複率為 0.06382978 壓縮後:原始結構長度:變種資料結構=1.629327
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->4392821
      變種資料結構 壓縮前json資料長度 ---->2377058
      原始資料結構 Gzip壓縮後json資料長度 ---->602208
      變種資料結構 Gzip壓縮後json資料長度 ---->365983
      重複率為 0.06951871 壓縮後:原始結構長度:變種資料結構=1.6454535
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->4705851
      變種資料結構 壓縮前json資料長度 ---->2546112
      原始資料結構 Gzip壓縮後json資料長度 ---->643532
      變種資料結構 Gzip壓縮後json資料長度 ---->391465
      重複率為 0.07526881 壓縮後:原始結構長度:變種資料結構=1.6439068
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->5018791
      變種資料結構 壓縮前json資料長度 ---->2715076
      原始資料結構 Gzip壓縮後json資料長度 ---->684775
      變種資料結構 Gzip壓縮後json資料長度 ---->415902
      重複率為 0.08108108 壓縮後:原始結構長度:變種資料結構=1.6464816
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->5331691
      變種資料結構 壓縮前json資料長度 ---->2883976
      原始資料結構 Gzip壓縮後json資料長度 ---->725952
      變種資料結構 Gzip壓縮後json資料長度 ---->438987
      重複率為 0.086956516 壓縮後:原始結構長度:變種資料結構=1.6536982
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->5644501
      變種資料結構 壓縮前json資料長度 ---->3052786
      原始資料結構 Gzip壓縮後json資料長度 ---->767578
      變種資料結構 Gzip壓縮後json資料長度 ---->464169
      重複率為 0.09289617 壓縮後:原始結構長度:變種資料結構=1.6536607
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->5957221
      變種資料結構 壓縮前json資料長度 ---->3221506
      原始資料結構 Gzip壓縮後json資料長度 ---->808616
      變種資料結構 Gzip壓縮後json資料長度 ---->488167
      重複率為 0.09890111 壓縮後:原始結構長度:變種資料結構=1.6564331
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->6269851
      變種資料結構 壓縮前json資料長度 ---->3390136
      原始資料結構 Gzip壓縮後json資料長度 ---->848776
      變種資料結構 Gzip壓縮後json資料長度 ---->511159
      重複率為 0.104972385 壓縮後:原始結構長度:變種資料結構=1.6604931
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->6582391
      變種資料結構 壓縮前json資料長度 ---->3558676
      原始資料結構 Gzip壓縮後json資料長度 ---->889184
      變種資料結構 Gzip壓縮後json資料長度 ---->536695
      重複率為 0.11111113 壓縮後:原始結構長度:變種資料結構=1.6567771
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->6894841
      變種資料結構 壓縮前json資料長度 ---->3727126
      原始資料結構 Gzip壓縮後json資料長度 ---->928982
      變種資料結構 Gzip壓縮後json資料長度 ---->557274
      重複率為 0.11731845 壓縮後:原始結構長度:變種資料結構=1.6670111
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->7207201
      變種資料結構 壓縮前json資料長度 ---->3895486
      原始資料結構 Gzip壓縮後json資料長度 ---->968845
      變種資料結構 Gzip壓縮後json資料長度 ---->583064
      重複率為 0.12359552 壓縮後:原始結構長度:變種資料結構=1.6616443
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->7519471
      變種資料結構 壓縮前json資料長度 ---->4063756
      原始資料結構 Gzip壓縮後json資料長度 ---->1013093
      變種資料結構 Gzip壓縮後json資料長度 ---->606056
      重複率為 0.12994352 壓縮後:原始結構長度:變種資料結構=1.6716162
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->7831651
      變種資料結構 壓縮前json資料長度 ---->4231936
      原始資料結構 Gzip壓縮後json資料長度 ---->1057283
      變種資料結構 Gzip壓縮後json資料長度 ---->626963
      重複率為 0.13636366 壓縮後:原始結構長度:變種資料結構=1.6863563
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->8143741
      變種資料結構 壓縮前json資料長度 ---->4400026
      原始資料結構 Gzip壓縮後json資料長度 ---->1101480
      變種資料結構 Gzip壓縮後json資料長度 ---->650165
      重複率為 0.14285716 壓縮後:原始結構長度:變種資料結構=1.6941546
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->8455741
      變種資料結構 壓縮前json資料長度 ---->4568026
      原始資料結構 Gzip壓縮後json資料長度 ---->1145324
      變種資料結構 Gzip壓縮後json資料長度 ---->675800
      重複率為 0.1494253 壓縮後:原始結構長度:變種資料結構=1.6947677
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->8767651
      變種資料結構 壓縮前json資料長度 ---->4735936
      原始資料結構 Gzip壓縮後json資料長度 ---->1189441
      變種資料結構 Gzip壓縮後json資料長度 ---->696474
      重複率為 0.15606937 壓縮後:原始結構長度:變種資料結構=1.7078038
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->9079471
      變種資料結構 壓縮前json資料長度 ---->4903756
      原始資料結構 Gzip壓縮後json資料長度 ---->1233352
      變種資料結構 Gzip壓縮後json資料長度 ---->720694
      重複率為 0.1627907 壓縮後:原始結構長度:變種資料結構=1.7113394
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->9391201
      變種資料結構 壓縮前json資料長度 ---->5071486
      原始資料結構 Gzip壓縮後json資料長度 ---->1277550
      變種資料結構 Gzip壓縮後json資料長度 ---->741108
      重複率為 0.16959064 壓縮後:原始結構長度:變種資料結構=1.7238379
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->9702791
      變種資料結構 壓縮前json資料長度 ---->5239100
      原始資料結構 Gzip壓縮後json資料長度 ---->1321359
      變種資料結構 Gzip壓縮後json資料長度 ---->763320
      重複率為 0.17647058 壓縮後:原始結構長度:變種資料結構=1.7310683
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->10014291
      變種資料結構 壓縮前json資料長度 ---->5406624
      原始資料結構 Gzip壓縮後json資料長度 ---->1365756
      變種資料結構 Gzip壓縮後json資料長度 ---->782468
      重複率為 0.18343192 壓縮後:原始結構長度:變種資料結構=1.7454464
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->10325701
      變種資料結構 壓縮前json資料長度 ---->5574058
      原始資料結構 Gzip壓縮後json資料長度 ---->1409791
      變種資料結構 Gzip壓縮後json資料長度 ---->809521
      重複率為 0.19047616 壓縮後:原始結構長度:變種資料結構=1.7415125
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->10637021
      變種資料結構 壓縮前json資料長度 ---->5741402
      原始資料結構 Gzip壓縮後json資料長度 ---->1453682
      變種資料結構 Gzip壓縮後json資料長度 ---->828981
      重複率為 0.19760476 壓縮後:原始結構長度:變種資料結構=1.753577
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->10948308
      變種資料結構 壓縮前json資料長度 ---->5908713
      原始資料結構 Gzip壓縮後json資料長度 ---->1497843
      變種資料結構 Gzip壓縮後json資料長度 ---->852966
      重複率為 0.20481923 壓縮後:原始結構長度:變種資料結構=1.7560407
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->11259595
      變種資料結構 壓縮前json資料長度 ---->6076024
      原始資料結構 Gzip壓縮後json資料長度 ---->1542039
      變種資料結構 Gzip壓縮後json資料長度 ---->872647
      重複率為 0.21212116 壓縮後:原始結構長度:變種資料結構=1.7670822
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->11570882
      變種資料結構 壓縮前json資料長度 ---->6243335
      原始資料結構 Gzip壓縮後json資料長度 ---->1585781
      變種資料結構 Gzip壓縮後json資料長度 ---->891023
      重複率為 0.21951213 壓縮後:原始結構長度:變種資料結構=1.7797307
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->11882169
      變種資料結構 壓縮前json資料長度 ---->6410646
      原始資料結構 Gzip壓縮後json資料長度 ---->1629443
      變種資料結構 Gzip壓縮後json資料長度 ---->915561
      重複率為 0.2269938 壓縮後:原始結構長度:變種資料結構=1.7797209
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->12193456
      變種資料結構 壓縮前json資料長度 ---->6577957
      原始資料結構 Gzip壓縮後json資料長度 ---->1673135
      變種資料結構 Gzip壓縮後json資料長度 ---->937219
      重複率為 0.23456782 壓縮後:原始結構長度:變種資料結構=1.7852124
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->12504743
      變種資料結構 壓縮前json資料長度 ---->6745268
      原始資料結構 Gzip壓縮後json資料長度 ---->1717525
      變種資料結構 Gzip壓縮後json資料長度 ---->956429
      重複率為 0.24223594 壓縮後:原始結構長度:變種資料結構=1.7957684
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->12816030
      變種資料結構 壓縮前json資料長度 ---->6912579
      原始資料結構 Gzip壓縮後json資料長度 ---->1761849
      變種資料結構 Gzip壓縮後json資料長度 ---->976092
      重複率為 0.24999991 壓縮後:原始結構長度:變種資料結構=1.805003
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->13127317
      變種資料結構 壓縮前json資料長度 ---->7079890
      原始資料結構 Gzip壓縮後json資料長度 ---->1806001
      變種資料結構 Gzip壓縮後json資料長度 ---->995442
      重複率為 0.25786152 壓縮後:原始結構長度:變種資料結構=1.8142705
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->13438604
      變種資料結構 壓縮前json資料長度 ---->7247201
      原始資料結構 Gzip壓縮後json資料長度 ---->1850241
      變種資料結構 Gzip壓縮後json資料長度 ---->1014463
      重複率為 0.26582268 壓縮後:原始結構長度:變種資料結構=1.8238624
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->13749891
      變種資料結構 壓縮前json資料長度 ---->7414512
      原始資料結構 Gzip壓縮後json資料長度 ---->1893946
      變種資料結構 Gzip壓縮後json資料長度 ---->1038690
      重複率為 0.27388522 壓縮後:原始結構長度:變種資料結構=1.8233987
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->14061178
      變種資料結構 壓縮前json資料長度 ---->7581823
      原始資料結構 Gzip壓縮後json資料長度 ---->1938584
      變種資料結構 Gzip壓縮後json資料長度 ---->1064229
      重複率為 0.28205115 壓縮後:原始結構長度:變種資料結構=1.8215854
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->14372465
      變種資料結構 壓縮前json資料長度 ---->7749134
      原始資料結構 Gzip壓縮後json資料長度 ---->1982416
      變種資料結構 Gzip壓縮後json資料長度 ---->1079948
      重複率為 0.29032245 壓縮後:原始結構長度:變種資料結構=1.8356588
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->14683752
      變種資料結構 壓縮前json資料長度 ---->7916445
      原始資料結構 Gzip壓縮後json資料長度 ---->2026663
      變種資料結構 Gzip壓縮後json資料長度 ---->1102001
      重複率為 0.29870114 壓縮後:原始結構長度:變種資料結構=1.8390754
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->14995039
      變種資料結構 壓縮前json資料長度 ---->8083756
      原始資料結構 Gzip壓縮後json資料長度 ---->2070714
      變種資料結構 Gzip壓縮後json資料長度 ---->1125712
      重複率為 0.30718938 壓縮後:原始結構長度:變種資料結構=1.8394705
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->15306326
      變種資料結構 壓縮前json資料長度 ---->8251067
      原始資料結構 Gzip壓縮後json資料長度 ---->2114297
      變種資料結構 Gzip壓縮後json資料長度 ---->1145723
      重複率為 0.3157893 壓縮後:原始結構長度:變種資料結構=1.8453823
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->15617613
      變種資料結構 壓縮前json資料長度 ---->8418378
      原始資料結構 Gzip壓縮後json資料長度 ---->2158166
      變種資料結構 Gzip壓縮後json資料長度 ---->1164141
      重複率為 0.32450312 壓縮後:原始結構長度:變種資料結構=1.8538699
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->15928900
      變種資料結構 壓縮前json資料長度 ---->8585689
      原始資料結構 Gzip壓縮後json資料長度 ---->2201712
      變種資料結構 Gzip壓縮後json資料長度 ---->1189557
      重複率為 0.33333313 壓縮後:原始結構長度:變種資料結構=1.8508672
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->16240187
      變種資料結構 壓縮前json資料長度 ---->8753000
      原始資料結構 Gzip壓縮後json資料長度 ---->2245653
      變種資料結構 Gzip壓縮後json資料長度 ---->1207825
      重複率為 0.3422817 壓縮後:原始結構長度:變種資料結構=1.8592536
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->16551474
      變種資料結構 壓縮前json資料長度 ---->8920311
      原始資料結構 Gzip壓縮後json資料長度 ---->2289778
      變種資料結構 Gzip壓縮後json資料長度 ---->1228716
      重複率為 0.35135114 壓縮後:原始結構長度:變種資料結構=1.8635535
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->16862761
      變種資料結構 壓縮前json資料長度 ---->9087622
      原始資料結構 Gzip壓縮後json資料長度 ---->2333883
      變種資料結構 Gzip壓縮後json資料長度 ---->1248197
      重複率為 0.36054403 壓縮後:原始結構長度:變種資料結構=1.8698034
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->17174048
      變種資料結構 壓縮前json資料長度 ---->9254933
      原始資料結構 Gzip壓縮後json資料長度 ---->2377734
      變種資料結構 Gzip壓縮後json資料長度 ---->1263293
      重複率為 0.3698628 壓縮後:原始結構長度:變種資料結構=1.8821714
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->17485335
      變種資料結構 壓縮前json資料長度 ---->9422244
      原始資料結構 Gzip壓縮後json資料長度 ---->2421204
      變種資料結構 Gzip壓縮後json資料長度 ---->1286647
      重複率為 0.3793101 壓縮後:原始結構長度:變種資料結構=1.8817935
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->17796622
      變種資料結構 壓縮前json資料長度 ---->9589555
      原始資料結構 Gzip壓縮後json資料長度 ---->2464871
      變種資料結構 Gzip壓縮後json資料長度 ---->1307479
      重複率為 0.38888866 壓縮後:原始結構長度:變種資料結構=1.8852088
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->18107909
      變種資料結構 壓縮前json資料長度 ---->9756866
      原始資料結構 Gzip壓縮後json資料長度 ---->2508873
      變種資料結構 Gzip壓縮後json資料長度 ---->1327997
      重複率為 0.39860114 壓縮後:原始結構長度:變種資料結構=1.8892158
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->18419196
      變種資料結構 壓縮前json資料長度 ---->9924177
      原始資料結構 Gzip壓縮後json資料長度 ---->2552954
      變種資料結構 Gzip壓縮後json資料長度 ---->1342020
      重複率為 0.40845042 壓縮後:原始結構長度:變種資料結構=1.9023218
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->18730483
      變種資料結構 壓縮前json資料長度 ---->10091488
      原始資料結構 Gzip壓縮後json資料長度 ---->2596616
      變種資料結構 Gzip壓縮後json資料長度 ---->1369092
      重複率為 0.41843942 壓縮後:原始結構長度:變種資料結構=1.8965971
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->19041770
      變種資料結構 壓縮前json資料長度 ---->10258799
      原始資料結構 Gzip壓縮後json資料長度 ---->2640984
      變種資料結構 Gzip壓縮後json資料長度 ---->1383626
      重複率為 0.42857113 壓縮後:原始結構長度:變種資料結構=1.9087412
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->19353057
      變種資料結構 壓縮前json資料長度 ---->10426110
      原始資料結構 Gzip壓縮後json資料長度 ---->2685199
      變種資料結構 Gzip壓縮後json資料長度 ---->1402782
      重複率為 0.4388486 壓縮後:原始結構長度:變種資料結構=1.9141955
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->19664344
      變種資料結構 壓縮前json資料長度 ---->10593421
      原始資料結構 Gzip壓縮後json資料長度 ---->2729710
      變種資料結構 Gzip壓縮後json資料長度 ---->1418750
      重複率為 0.44927505 壓縮後:原始結構長度:變種資料結構=1.9240247
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->19975631
      變種資料結構 壓縮前json資料長度 ---->10760732
      原始資料結構 Gzip壓縮後json資料長度 ---->2773735
      變種資料結構 Gzip壓縮後json資料長度 ---->1435122
      重複率為 0.45985368 壓縮後:原始結構長度:變種資料結構=1.932752
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->20286918
      變種資料結構 壓縮前json資料長度 ---->10928043
      原始資料結構 Gzip壓縮後json資料長度 ---->2818175
      變種資料結構 Gzip壓縮後json資料長度 ---->1458645
      重複率為 0.47058788 壓縮後:原始結構長度:變種資料結構=1.93205
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->20598205
      變種資料結構 壓縮前json資料長度 ---->11095354
      原始資料結構 Gzip壓縮後json資料長度 ---->2862715
      變種資料結構 Gzip壓縮後json資料長度 ---->1473688
      重複率為 0.4814811 壓縮後:原始結構長度:變種資料結構=1.9425516
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->20909492
      變種資料結構 壓縮前json資料長度 ---->11262665
      原始資料結構 Gzip壓縮後json資料長度 ---->2906140
      變種資料結構 Gzip壓縮後json資料長度 ---->1497577
      重複率為 0.49253693 壓縮後:原始結構長度:變種資料結構=1.9405613
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->21220779
      變種資料結構 壓縮前json資料長度 ---->11429976
      原始資料結構 Gzip壓縮後json資料長度 ---->2951053
      變種資料結構 Gzip壓縮後json資料長度 ---->1513485
      重複率為 0.50375897 壓縮後:原始結構長度:變種資料結構=1.9498396
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->21532066
      變種資料結構 壓縮前json資料長度 ---->11597287
      原始資料結構 Gzip壓縮後json資料長度 ---->2995263
      變種資料結構 Gzip壓縮後json資料長度 ---->1528176
      重複率為 0.5151511 壓縮後:原始結構長度:變種資料結構=1.9600248
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->21843353
      變種資料結構 壓縮前json資料長度 ---->11764598
      原始資料結構 Gzip壓縮後json資料長度 ---->3039623
      變種資料結構 Gzip壓縮後json資料長度 ---->1546990
      重複率為 0.5267171 壓縮後:原始結構長度:變種資料結構=1.9648627
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->22154640
      變種資料結構 壓縮前json資料長度 ---->11931909
      原始資料結構 Gzip壓縮後json資料長度 ---->3083971
      變種資料結構 Gzip壓縮後json資料長度 ---->1563906
      重複率為 0.5384611 壓縮後:原始結構長度:變種資料結構=1.971967
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->22465927
      變種資料結構 壓縮前json資料長度 ---->12099220
      原始資料結構 Gzip壓縮後json資料長度 ---->3128112
      變種資料結構 Gzip壓縮後json資料長度 ---->1580792
      重複率為 0.55038714 壓縮後:原始結構長度:變種資料結構=1.9788258
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->22777214
      變種資料結構 壓縮前json資料長度 ---->12266531
      原始資料結構 Gzip壓縮後json資料長度 ---->3171693
      變種資料結構 Gzip壓縮後json資料長度 ---->1600344
      重複率為 0.5624995 壓縮後:原始結構長度:變種資料結構=1.981882
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->23088501
      變種資料結構 壓縮前json資料長度 ---->12433842
      原始資料結構 Gzip壓縮後json資料長度 ---->3215617
      變種資料結構 Gzip壓縮後json資料長度 ---->1618740
      重複率為 0.57480264 壓縮後:原始結構長度:變種資料結構=1.9864938
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->23399788
      變種資料結構 壓縮前json資料長度 ---->12601153
      原始資料結構 Gzip壓縮後json資料長度 ---->3259832
      變種資料結構 Gzip壓縮後json資料長度 ---->1637726
      重複率為 0.5873011 壓縮後:原始結構長度:變種資料結構=1.9904624
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->23711075
      變種資料結構 壓縮前json資料長度 ---->12768464
      原始資料結構 Gzip壓縮後json資料長度 ---->3304008
      變種資料結構 Gzip壓縮後json資料長度 ---->1652686
      重複率為 0.5999994 壓縮後:原始結構長度:變種資料結構=1.9991747
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->24022362
      變種資料結構 壓縮前json資料長度 ---->12935775
      原始資料結構 Gzip壓縮後json資料長度 ---->3347657
      變種資料結構 Gzip壓縮後json資料長度 ---->1670445
      重複率為 0.61290264 壓縮後:原始結構長度:變種資料結構=2.004051
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->24333649
      變種資料結構 壓縮前json資料長度 ---->13103086
      原始資料結構 Gzip壓縮後json資料長度 ---->3391716
      變種資料結構 Gzip壓縮後json資料長度 ---->1683890
      重複率為 0.62601566 壓縮後:原始結構長度:變種資料結構=2.0142148
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->24644936
      變種資料結構 壓縮前json資料長度 ---->13270397
      原始資料結構 Gzip壓縮後json資料長度 ---->3436086
      變種資料結構 Gzip壓縮後json資料長度 ---->1704452
      重複率為 0.6393436 壓縮後:原始結構長度:變種資料結構=2.0159476
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->24956223
      變種資料結構 壓縮前json資料長度 ---->13437708
      原始資料結構 Gzip壓縮後json資料長度 ---->3480064
      變種資料結構 Gzip壓縮後json資料長度 ---->1719727
      重複率為 0.65289193 壓縮後:原始結構長度:變種資料結構=2.0236142
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->25267510
      變種資料結構 壓縮前json資料長度 ---->13605019
      原始資料結構 Gzip壓縮後json資料長度 ---->3524494
      變種資料結構 Gzip壓縮後json資料長度 ---->1735590
      重複率為 0.666666 壓縮後:原始結構長度:變種資料結構=2.030718
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->25578797
      變種資料結構 壓縮前json資料長度 ---->13772330
      原始資料結構 Gzip壓縮後json資料長度 ---->3569109
      變種資料結構 Gzip壓縮後json資料長度 ---->1757409
      重複率為 0.6806716 壓縮後:原始結構長度:變種資料結構=2.0308926
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->25890084
      變種資料結構 壓縮前json資料長度 ---->13939641
      原始資料結構 Gzip壓縮後json資料長度 ---->3613919
      變種資料結構 Gzip壓縮後json資料長度 ---->1770126
      重複率為 0.6949145 壓縮後:原始結構長度:變種資料結構=2.041617
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->26201371
      變種資料結構 壓縮前json資料長度 ---->14106952
      原始資料結構 Gzip壓縮後json資料長度 ---->3658034
      變種資料結構 Gzip壓縮後json資料長度 ---->1787002
      重複率為 0.70940095 壓縮後:原始結構長度:變種資料結構=2.0470228
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->26512658
      變種資料結構 壓縮前json資料長度 ---->14274263
      原始資料結構 Gzip壓縮後json資料長度 ---->3702835
      變種資料結構 Gzip壓縮後json資料長度 ---->1799515
      重複率為 0.7241371 壓縮後:原始結構長度:變種資料結構=2.057685
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->26823945
      變種資料結構 壓縮前json資料長度 ---->14441574
      原始資料結構 Gzip壓縮後json資料長度 ---->3746980
      變種資料結構 Gzip壓縮後json資料長度 ---->1818417
      重複率為 0.7391296 壓縮後:原始結構長度:變種資料結構=2.0605724
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->27135232
      變種資料結構 壓縮前json資料長度 ---->14608885
      原始資料結構 Gzip壓縮後json資料長度 ---->3790555
      變種資料結構 Gzip壓縮後json資料長度 ---->1836003
      重複率為 0.7543851 壓縮後:原始結構長度:變種資料結構=2.064569
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->27446519
      變種資料結構 壓縮前json資料長度 ---->14776196
      原始資料結構 Gzip壓縮後json資料長度 ---->3834464
      變種資料結構 Gzip壓縮後json資料長度 ---->1851563
      重複率為 0.76991063 壓縮後:原始結構長度:變種資料結構=2.0709336
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->27757806
      變種資料結構 壓縮前json資料長度 ---->14943507
      原始資料結構 Gzip壓縮後json資料長度 ---->3879072
      變種資料結構 Gzip壓縮後json資料長度 ---->1873192
      重複率為 0.7857134 壓縮後:原始結構長度:變種資料結構=2.0708354
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->28069093
      變種資料結構 壓縮前json資料長度 ---->15110818
      原始資料結構 Gzip壓縮後json資料長度 ---->3923316
      變種資料結構 Gzip壓縮後json資料長度 ---->1894024
      重複率為 0.80180085 壓縮後:原始結構長度:變種資料結構=2.0714183
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->28380380
      變種資料結構 壓縮前json資料長度 ---->15278129
      原始資料結構 Gzip壓縮後json資料長度 ---->3967482
      變種資料結構 Gzip壓縮後json資料長度 ---->1916387
      重複率為 0.81818086 壓縮後:原始結構長度:變種資料結構=2.0702927
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->28691667
      變種資料結構 壓縮前json資料長度 ---->15445440
      原始資料結構 Gzip壓縮後json資料長度 ---->4011094
      變種資料結構 Gzip壓縮後json資料長度 ---->1933486
      重複率為 0.8348614 壓縮後:原始結構長度:變種資料結構=2.07454
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->29002954
      變種資料結構 壓縮前json資料長度 ---->15612751
      原始資料結構 Gzip壓縮後json資料長度 ---->4055289
      變種資料結構 Gzip壓縮後json資料長度 ---->1953997
      重複率為 0.8518508 壓縮後:原始結構長度:變種資料結構=2.0753813
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->29314241
      變種資料結構 壓縮前json資料長度 ---->15780062
      原始資料結構 Gzip壓縮後json資料長度 ---->4099592
      變種資料結構 Gzip壓縮後json資料長度 ---->1974066
      重複率為 0.8691578 壓縮後:原始結構長度:變種資料結構=2.076725
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->29625528
      變種資料結構 壓縮前json資料長度 ---->15947373
      原始資料結構 Gzip壓縮後json資料長度 ---->4143573
      變種資料結構 Gzip壓縮後json資料長度 ---->1987771
      重複率為 0.88679135 壓縮後:原始結構長度:變種資料結構=2.0845323
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->29936815
      變種資料結構 壓縮前json資料長度 ---->16114684
      原始資料結構 Gzip壓縮後json資料長度 ---->4187707
      變種資料結構 Gzip壓縮後json資料長度 ---->2014350
      重複率為 0.9047608 壓縮後:原始結構長度:變種資料結構=2.078937
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->30248102
      變種資料結構 壓縮前json資料長度 ---->16281995
      原始資料結構 Gzip壓縮後json資料長度 ---->4232504
      變種資料結構 Gzip壓縮後json資料長度 ---->2034384
      重複率為 0.92307574 壓縮後:原始結構長度:變種資料結構=2.0804844
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->30559389
      變種資料結構 壓縮前json資料長度 ---->16449306
      原始資料結構 Gzip壓縮後json資料長度 ---->4277046
      變種資料結構 Gzip壓縮後json資料長度 ---->2053854
      重複率為 0.94174635 壓縮後:原始結構長度:變種資料結構=2.082449
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->30870676
      變種資料結構 壓縮前json資料長度 ---->16616617
      原始資料結構 Gzip壓縮後json資料長度 ---->4321134
      變種資料結構 Gzip壓縮後json資料長度 ---->2072485
      重複率為 0.960783 壓縮後:原始結構長度:變種資料結構=2.0850012
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->31181963
      變種資料結構 壓縮前json資料長度 ---->16783928
      原始資料結構 Gzip壓縮後json資料長度 ---->4365924
      變種資料結構 Gzip壓縮後json資料長度 ---->2087159
      重複率為 0.9801967 壓縮後:原始結構長度:變種資料結構=2.0918024
      ===================================================
      原始資料結構 壓縮前json資料長度 ---->31493250
      變種資料結構 壓縮前json資料長度 ---->16951239
      原始資料結構 Gzip壓縮後json資料長度 ---->4409476
      變種資料結構 Gzip壓縮後json資料長度 ---->2100664
      重複率為 0.9999986 壓縮後:原始結構長度:變種資料結構=2.0990868
      
      複製程式碼

8.Android效能優化典範-第5季

多執行緒大部分內容源自凱哥的課程,個人覺得比優化典範寫得清晰得多

1.執行緒

  1. 執行緒就是程式碼線性執行,執行完畢就結束的一條線.UI執行緒不會結束是因為其初始化完畢後會執行死迴圈,所以永遠不會執行完畢.

  2. 如何簡單建立新執行緒:

    //1:直接建立Thread,執行其start方法
    Thread t1 = new Thread(){
        @Override
        public void run() {
            System.out.println("Thread:run");
        }
    };
    t1.start();
    //2:使用Runnable例項作為引數建立Thread,執行start
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("Runnable:run");
        }
    };
    Thread t2 = new Thread(runnable);
    t2.start();
    複製程式碼
    • 兩種方式建立新執行緒效能無差別,使用Runnable例項適用於希望Runnable複用的情形
    • 常用的建立執行緒池2種方式
      1. Executors.newCachedThreadPool():一般情況下使用newCachedThreadPool即可.
      2. Executors.newFixedThreadPool(int number):短時批量處理/比如要並行處理多張圖片,可以直接建立包含圖片精確數量的執行緒的執行緒池並行處理.
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("runnable:run()");
            }
        };
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(runnable);
        executorService.execute(runnable);
        executorService.execute(runnable);
        executorService.shutdown();
        //比如有40張圖片要同時處理
        //建立包含40個執行緒的執行緒池,每個執行緒處理一張圖片,處理完畢後shutdown
        ExecutorService service = Executors.newFixedThreadPool(40);
        for(Bitmap item:bitmaps){
            //比如runnable就是處理單張圖片的
            service.execute(runnable);
        }
        service.shutdown();
        複製程式碼
      3. 《阿里巴巴Java開發手冊》規定:
        • 執行緒池不允許使用 Executors 去建立,而是通過 ThreadPoolExecutor 的方式.這樣 的處理方式讓寫的同學更加明確執行緒池的執行規則,規避資源耗盡的風險
        • 看Android中Executors原始碼.Executors.newCachedThreadPool/newScheduledThreadPool允許的建立執行緒數量為 Integer.MAX_VALUE,可能會建立大量的執行緒,從而導致 OOM.而newFixedThreadPool,newSingleThreadExecutor不會存在這種風險.
      4. 如何正確建立ThreadPoolExecutor:有點麻煩,晚點詳述
      5. ExecutorService的shutdown和shutdownNow
        1. shutdown:在呼叫shutdown之前ExecutorService中已經啟動的執行緒,在呼叫shutdown後,執行緒如果執行未結束會繼續執行完畢並結束,但不會再啟動新的執行緒執行新任務.
        2. shutdownNow:首先停止啟動新的執行緒執行新任務;並嘗試結束所有正在執行的執行緒,正在執行的執行緒可能被終止也可能會繼續執行完成.
  3. 如何正確建立ThreadPoolExecutor
    3.1:ThreadPoolExecutor構造引數

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
    複製程式碼
    1. int corePoolSize:該執行緒池中核心執行緒最大數量.預設情況下,即使核心執行緒處於空閒狀態也不會被銷燬.除非通過allowCoreThreadTimeOut(true),則核心執行緒在空閒時間達到keepAliveTime時會被銷燬
    2. int maximumPoolSize:該執行緒池中執行緒最大數量
    3. long keepAliveTime:該執行緒池中非核心執行緒被銷燬前最大空閒時間,時間單位由unit決定.預設情況下核心執行緒即使空閒也不會被銷燬,在呼叫allowCoreThreadTimeOut(true)後,該銷燬時間設定也適用於核心執行緒
    4. TimeUnit unit:keepAliveTime/被銷燬前最大空閒時間的單位
    5. BlockingQueue workQueue:該執行緒池中的任務佇列.維護著等待被執行的Runnable物件.BlockingQueue有幾種型別,下面會詳述
    6. ThreadFactory threadFactory:建立新執行緒的工廠.一般情況使用Executors.defaultThreadFactory()即可.當然也可以自定義.
    7. RejectedExecutionHandler handler:拒絕策略.當需要建立的執行緒數量達到maximumPoolSize並且等待執行的Runnable數量超過了任務佇列的容量,該如何處理.

    3.2:當1個任務被放進執行緒池,ThreadPoolExecutor具體執行策略如下:

    1. 如果執行緒數量沒有達到corePoolSize,有核心執行緒空閒則核心執行緒直接執行,沒有空閒則直接新建核心執行緒執行任務;
    2. 如果執行緒數量已經達到corePoolSize,且核心執行緒無空閒,則將任務新增到等待佇列;
    3. 如果等待佇列已滿,則新建非核心執行緒執行該任務;
    4. 如果等待佇列已滿且匯流排程數量已達到maximumPoolSize,則會交由RejectedExecutionHandler handler處理.

    3.3:阻塞佇列/BlockingQueue workQueue

    1. BlockingQueue有如下幾種:SynchronousQueue/LinkedBlockingQueue/LinkedTransferQueue/ArrayBlockingQueue/PriorityBlockingQueue/DelayQueue.
    2. SynchronousQueue:SynchronousQueue的容量是0,不儲存任何Runnable例項.新任務到來會直接嘗試交給執行緒執行,如所有執行緒都在忙就建立新執行緒執行該任務.
    3. LinkedBlockingQueue:預設情況下沒有容量限制的佇列.
    4. ArrayBlockingQueue:一個有容量限制的佇列.
    5. DelayQueue:一個沒有容量限制的佇列.佇列中的元素必須實現了Delayed介面.元素在佇列中的排序按照當前時間的延遲值,延遲最小/最早要被執行的任務排在佇列頭部,依次排序.延遲時間到達後執行指定任務.
    6. PriorityBlockingQueue:一個沒有容量限制的佇列.佇列中元素必須實現了Comparable介面.佇列中元素排序依賴元素的自然排序/compareTo的比較結果.
    7. 各種BlockingQueue的問題
      1.SynchronousQueue缺點:因為不具備儲存元素的能力,因而當任務很頻繁時候,為了防止執行緒數量超標,我們往往設定maximumPoolSize是Integer.MAX_VALUE,建立過多執行緒會導致OOM.《阿里巴巴Java開發手冊》中強調不能使用Executors直接建立執行緒池,就是對應Android原始碼中newCachedThreadPool和newScheduledThreadPool,本質上就是建立了maximumPoolSize為Integer.MAX_VALUE的ThreadPoolExecutor.
      2.LinkedBlockingQueue因為沒有容量限制,所以我們使用LinkedBlockingQueue建立ThreadPoolExecutor,設定maximumPoolSize是無意義的,如果執行緒數量已經達到corePoolSize,且核心執行緒都在忙,那麼新來的任務會一直被新增到佇列中.只要核心執行緒無空閒則一直得不到被執行機會.
      3.DelayQueue和PriorityBlockingQueue也具有同樣的問題.所以corePoolSize必須設定合理,否則會導致超出核心執行緒數量的任務一直得不到機會被執行.這兩類佇列分別適用於定時及優先順序明確的任務.

    3.4:RejectedExecutionHandler handler/拒絕策略有4種
    1.hreadPoolExecutor.AbortPolicy:丟棄任務,並丟擲RejectedExecutionException異常.ThreadPoolExecutor預設就是使用AbortPolicy.
    2.ThreadPoolExecutor.DiscardPolicy:丟棄任務,但不會丟擲異常.
    3.ThreadPoolExecutor.DiscardOldestPolicy:丟棄排在佇列頭部的任務,不丟擲異常,並嘗試重新執行任務.
    4.ThreadPoolExecutor.CallerRunsPolicy:丟棄任務,但不丟擲異常,並將該任務交給呼叫此ThreadPoolExecutor的執行緒執行.

  4. synchronized 的本質

    1. 保證synchronized方法或者程式碼塊內部資源/資料的互斥訪問
      • 即同一時間,由同一個Monitor監視的程式碼,最多隻有1個執行緒在訪問
    2. 保證執行緒之間對監視資源的資料同步.
      • 任何執行緒在獲取Monitor後,會第一時間將共享記憶體中的資料複製到自己的快取中;
      • 任何執行緒在釋放Monitor後,會第一時間將快取中的資料複製到共享記憶體中
  5. volatile

    1. 保證被volatile修飾的成員的操作具有原子性和同步性.相當於簡化版的synchronized
      • 原子性就是執行緒間互斥訪問
      • 同步性就是執行緒之間對監視資源的資料同步
    2. volatile生效範圍:基本型別的直接複製賦值 + 引用型別的直接賦值
      //引用型別的直接賦值操作有效
      private volatile User u = U1;
      //修改引用型別的屬性,則不是原子性的,volatile無效
      U1.name = "吊炸天"
      //對引用型別的直接賦值是原子性的
      u = U2;
      
      private volatile int a = 0;
      private int b = 100;
      //volatile無法實現++/--的原子性
      a++;
      複製程式碼
      1. volatile型變數自增操作的隱患
        • volatile型別變數每次在讀取的時候,會越過執行緒的工作記憶體,直接從主存中讀取,也就不會產生髒讀
        • ++自增操作,在Java對應的彙編指令有三條
          1. 從主存讀取變數值到cpu暫存器
          2. 暫存器裡的值+1
          3. 暫存器的值寫回主存
        • 如果N個執行緒同時執行到了第1步,那麼最終變數會損失(N-1).第二步第三步只有一個執行緒是執行成功.
      2. 對變數的寫操作不依賴於當前值,才能用volatile修飾.
  6. 針對num++這類複合類的操作,可以使用java併發包中的原子操作類原子操作類:AtomicInteger AtomicBoolean等來保證其原子性.

    public static AtomicInteger num = new AtomicInteger(0);
    num.incrementAndGet();//原子性的num++,通過迴圈CAS方式
    複製程式碼

2.執行緒間互動

  1. 一個執行緒終結另一個執行緒
    1. Thread.stop不要用:
      • 因為執行緒在執行過程中隨時有可能會被暫停切換到其他執行緒,stop的效果相當於切換到其他執行緒繼續執行且以後再也不會切換回來.我們執行A.stop的時候,完全無法預知A的run方法已經執行了多少,執行百分比完全不可控.
      下面的程式碼,每次執行最後列印的結果都不同,即我們完全不可預知呼叫stop時候當前執行緒執行了百分之多少.
      
      private static void t2(){
          Thread t = new Thread(){
              @Override
              public void run() {
                  for(int i=0;i<1000000;i++){
                      System.out.println(""+i);
                  }
              }
          };
          t.start();
          try {
              Thread.sleep(300);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          t.stop();
      }
      複製程式碼
    2. Thread.interrupt:僅僅設定當前執行緒為被中斷狀態.在執行的執行緒依然會繼續執行.
      1. Thread.isInterrupted:獲取當前執行緒是否被中斷
      2. Thread.interrupted():如果執行緒A呼叫了Thread.interrupted()
        1. 如果A之前已經被中斷,呼叫Thread.interrupted()返回false,A已經不是被中斷狀態
        2. 如果A之前不是被中斷狀態,呼叫Thread.interrupted()返回true,A變成被中斷狀態.
      3. 單純呼叫A.interrupt是無效果的,interrupt需要和isInterrupted聯合使用
        • 用於我們希望執行緒處於被中斷狀態時結束執行的場景.
        • interrupt和stop比較的優點:stop後,執行緒直接結束,我們完全無法控制當前執行到哪裡;
          interrupt後執行緒預設會繼續執行,我們通過isInterrupted來獲取被中斷狀態,只有被中斷且滿足我們指定條件才return,可以精確控制執行緒的執行百分比.
        private static void t2(){
            Thread t = new Thread(){
                @Override
                public void run() {
                    for(int i=0;i<1000000;i++){
                        //檢查執行緒是否處於中斷狀態,且檢查是否滿足指定條件
                        //如果不滿足指定條件,即使處於中斷狀態也繼續執行.
                        if(isInterrupted()&&i>800000){
                            //先做收尾工作
                            //return 結束
                            return;
                        }
                        System.out.println(""+i);
                    }
                }
            };
            t.start();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //呼叫了interrupt後,在run中監查是否已經被打斷,如果已經被打斷,且滿足指定條件,
            //就return,執行緒就執行完了
            t.interrupt();
        }
        
        ......
        799999
        800000
        Process finished with exit code 0
        複製程式碼
      4. InterruptedException:
        1. 如果執行緒A在sleep過程中被其他執行緒呼叫A.interrupt(),會觸發InterruptedException.
        2. 如果呼叫A.interrupt()時候,A並不在sleep狀態,後面再呼叫A.sleep,也會立即丟擲InterruptedException.
        private static void t3(){
            Thread thread = new Thread(){
                @Override
                public void run() {
                    long t1 = System.currentTimeMillis();
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        long t2 = System.currentTimeMillis();
                        System.out.println("老子被叫醒了:睡了"+(t2-t1)+"ms");
                        //用於做執行緒收尾工作,然後return
                        return;
                    }
                    System.out.println("AAAAAAAA");
                }
            };
            thread.start();
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            thread.interrupt();
        }
        
        老子被叫醒了:睡了493ms
        Process finished with exit code 0
        複製程式碼
  2. 執行緒等待:wait,notifyAll,notify
    1. wait,notifyAll,notify是屬於Object的方法.用於執行緒等待的場景,需用Monitor進行呼叫
    2. wait:
      • 當1個執行緒A持有Monitor M.
      • 此時呼叫M.wait,A會釋放M並處於等待狀態.並記錄A在當前程式碼執行的位置Position.
    3. notify:
      • 當呼叫M.notify(),就會喚醒1個因為呼叫M.wait()而處於等待狀態的執行緒
      • 如果有A,B,C--多個執行緒都是因為呼叫M.wait()而處於等待狀態,不一定哪個會被喚醒並嘗試獲取M
    4. notifyAll:
      • 當呼叫M.notifyAll(),所有因為呼叫M.wait()而處於等待狀態的執行緒都被喚醒,一起競爭嘗試獲取M
    5. 呼叫notify/notifyAll被喚醒並獲取到M的執行緒A,會接著之前的程式碼執行位置Position繼續執行下去
    private String str = null;
    private synchronized void setStr(String str){
        System.out.println("setStr時間:"+System.currentTimeMillis());
        this.str = str;
        notifyAll();
    }
    private synchronized void printStr(){
        while (str==null){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("執行緒:"+Thread.currentThread().getName()+
        " printStr時間:"+System.currentTimeMillis());
        System.out.println("str:"+str);
    }
    private void t4(){
        (new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                setStr("老子設定一下");
            }
        }).start();
        (new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("執行緒:"+Thread.currentThread().getName()+
                " 嘗試printStr時間:"+System.currentTimeMillis());
                printStr();
            }
        }).start();
        (new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("執行緒:"+Thread.currentThread().getName()+
                " 嘗試printStr時間:"+System.currentTimeMillis());
                printStr();
            }
        }).start();
    }
    
    執行緒:Thread-2 嘗試printStr時間:1539247468146
    執行緒:Thread-1 嘗試printStr時間:1539247468944
    setStr時間:1539247469944
    執行緒:Thread-1 printStr時間:1539247469944
    str:老子設定一下
    執行緒:Thread-2 printStr時間:1539247469944
    str:老子設定一下
    複製程式碼

3.Executor、 AsyncTask、 HandlerThead、 IntentService 如何選擇

  1. HandlerThead就不要用,HandlerThead設計目的就是為了主介面死迴圈重新整理介面,無其他應用場景.
  2. 能用執行緒池就用執行緒池,因為最簡單.
  3. 涉及後臺執行緒推送任務到UI執行緒,可以使用Handler或AsyncTask
  4. Service:就是為了做後臺任務,不要UI介面,需要持續存活.有複雜的需要長期存活/等待的場景使用Service.
  5. IntentService:屬於Service.當我們需要使用Service,且需要後臺程式碼執行完畢後該Service自動被銷燬,使用IntentService.

4.AsyncTask的記憶體洩漏

  1. GC Roots:由堆外指向堆內的引用,包括:
    1. Java方法棧幀中的區域性變數
    2. 已載入類的靜態變數
    3. native程式碼的引用
    4. 執行中的Java執行緒
  2. AsyncTask記憶體洩漏本質:正在執行的執行緒/AsyncTask 在虛擬機器中屬於GC ROOTS,AsyncTask持有外部Activity的引用.被GC ROOTS引用的物件不能被回收.
  3. 所以AsyncTask和其他執行緒工具一樣,只要是使用執行緒,都有可能發生記憶體洩漏,都要及時關閉,AsyncTask並不比其他工具更差.
  4. 如何避免AsyncTask記憶體洩漏:使用弱引用解決AsyncTask在Activity銷燬後依然持有Activity引用的問題

5.RxJava.

講的太多了這裡推薦1個專題RxJava2.x
下面記錄一下自己不太熟的幾點

  1. RxJava整體結構:
    1. 鏈的最上游:生產者Observable
    2. 鏈的最下游:觀察者Observer
    3. 鏈的中間多個節點:雙重角色.即是上一節點的觀察者Observer,也是下一節點的生產者Observable.
  2. Scheduler切換執行緒的原理:原始碼跟蹤下去,實質是通過Excutor實現了執行緒切換.

6.Android M對Profile GPU Rendering工具的更新

Android效能優化 筆記

  1. Swap Buffers:CPU等待GPU處理的時間
  2. Command Issur:OpenGL渲染Display List所需要的時間
  3. Sync&Upload:通常表示的是準備當前介面上有待繪製的圖片所耗費的時間,為了減少該段區域的執行時間,我們可以減少螢幕上的圖片數量或者是縮小圖片本身的大小
  4. Draw:測量繪製Display List的時間
  5. Measure & Layout:這裡表示的是佈局的onMeasure與onLayout所花費的時間.一旦時間過長,就需要仔細檢查自己的佈局是不是存在嚴重的效能問題
  6. Animation:表示的是計算執行動畫所需要花費的時間.包含的動畫有ObjectAnimator,ViewPropertyAnimator,Transition等等.一旦這裡的執行時間過長,就需要檢查是不是使用了非官方的動畫工具或者是檢查動畫執行的過程中是不是觸發了讀寫操作等等
  7. Input Handling:表示的是系統處理輸入事件所耗費的時間,粗略等於對於的事件處理方法所執行的時間.一旦執行時間過長,意味著在處理使用者的輸入事件的地方執行了複雜的操作
  8. Misc/Vsync Delay:如果稍加註意,我們可以在開發應用的Log日誌裡面看到這樣一行提示:I/Choreographer(691): Skipped XXX frames! The application may be doing too much work on its main thread。這意味著我們在主執行緒執行了太多的任務,導致UI渲染跟不上vSync的訊號而出現掉幀的情況

9.Android效能優化典範-第6季

1.啟動閃屏

  1. 當點選桌面圖示啟動APP的時候,App會出現短暫的白屏,一直到第一個Activity的頁面的渲染載入完畢
  2. 為了消除白屏,我們可以為App入口Activity單獨設定theme.
    1. 在單獨設定的theme中設定android:background屬性為App的品牌宣傳圖片背景.
    2. 在程式碼執行到入口Activity的onCreate的時候設定為程式正常的主題.
    styles.xml
    <!-- Base application theme. -->
    //Activity預設主題
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        //預設主題視窗背景設定為白色
        <item name="android:background">@android:color/white</item>
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowFullscreen">true</item>
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>
    //入口Activity的theme單獨設定
    <style name="ThemeSplash" parent="Theme.AppCompat.Light.NoActionBar">
        //入口Activity初始視窗背景設定為品牌宣傳圖片
        <item name="android:background">@mipmap/startbg</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowFullscreen">true</item>
        <item name="windowActionBar">false</item>
        <item name="windowNoTitle">true</item>
    </style>
    
    AndroidManifest.xml
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
            android:theme="@style/ThemeSplash">//為入口Activity單獨指定theme
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </manifest>
    
    public class MainActivity extends AppCompatActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            //在程式碼執行到入口Activity時候設定入口Activity為預設主題
            setTheme(R.style.AppTheme);
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            tv_all = findViewById(R.id.tv_all);
            tv_local = findViewById(R.id.tv_local);
            //註冊全域性廣播
            registerReceiver(globalReceiver,new IntentFilter("global"));
            //註冊本地廣播
            LocalBroadcastManager.getInstance(this).registerReceiver(localBroadReceiver,new IntentFilter("localBroadCast"));
        }
    }
    複製程式碼

2.為App提供對應解析度下的圖片,系統會自動匹配最合適解析度的圖片執行拉伸或壓縮的處理.

  1. 如果是隻有1張圖片,放在mipmap-nodpi,或mipmap-xxxhdpi下
  2. 所有的大背景圖片,統一放在mipmap-nodpi目錄,用一套1080P素材可以解決大部分手機適配問題,不用每個資源目錄下放一套素材
  3. 經過試驗,不論ImageView寬高是否是wrap_content,只要圖片所在資料夾和當前裝置解析度不匹配,都會涉及到放大或壓縮,佔用的記憶體都會相應的變化.尤其對於大圖,放在低解析度資料夾下直接OOM.
    具體原因:
    郭霖:Android drawable微技巧,你所不知道的drawable的那些細節

當我們使用資源id來去引用一張圖片時,Android會使用一些規則來去幫我們匹配最適合的圖片。什麼叫最適合的圖片?比如我的手機螢幕密度是xxhdpi,那麼drawable-xxhdpi資料夾下的圖片就是最適合的圖片。因此,當我引用android_logo這張圖時,如果drawable-xxhdpi資料夾下有這張圖就會優先被使用,在這種情況下,圖片是不會被縮放的。但是,如果drawable-xxhdpi資料夾下沒有這張圖時, 系統就會自動去其它資料夾下找這張圖了,優先會去更高密度的資料夾下找這張圖片,我們當前的場景就是drawable-xxxhdpi資料夾,然後發現這裡也沒有android_logo這張圖,接下來會嘗試再找更高密度的資料夾,發現沒有更高密度的了,這個時候會去drawable-nodpi資料夾找這張圖,發現也沒有,那麼就會去更低密度的資料夾下面找,依次是drawable-xhdpi -> drawable-hdpi -> drawable-mdpi -> drawable-ldpi。 總體匹配規則就是這樣,那麼比如說現在終於在drawable-mdpi資料夾下面找到android_logo這張圖了,但是系統會認為你這張圖是專門為低密度的裝置所設計的,如果直接將這張圖在當前的高密度裝置上使用就有可能會出現畫素過低的情況,於是系統自動幫我們做了這樣一個放大操作。 那麼同樣的道理,如果系統是在drawable-xxxhdpi資料夾下面找到這張圖的話,它會認為這張圖是為更高密度的裝置所設計的,如果直接將這張圖在當前裝置上使用就有可能會出現畫素過高的情況,於是會自動幫我們做一個縮小的操作

3.儘量複用已經存在的圖片.

比如一張圖片O已經存在,如果有View的背景就是O旋轉過後的樣子,可以直接用O建立RotateDrawable.然後將設定給View使用.
注意:RotateDrawable已經重寫了其onLevelChange方法,所以一定要設定level才會生效

@Override
protected boolean onLevelChange(int level) {
    super.onLevelChange(level);

    final float value = level / (float) MAX_LEVEL;
    final float degrees = MathUtils.lerp(mState.mFromDegrees, mState.mToDegrees, value);
    mState.mCurrentDegrees = degrees;

    invalidateSelf();
    return true;
}
複製程式碼

例項:

1.首先建立xml檔案
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@mipmap/close10"
    android:fromDegrees="90"
    android:toDegrees="120"
    android:pivotX="50%"
    android:pivotY="50%"
    >
</rotate>

2.在Java程式碼中獲取該xml對應的Drawable例項,並設定level為10000
Drawable drawable = getResources().getDrawable(R.drawable.rotate_close);
drawable.setLevel(10000);
3.將Drawable設定為View的背景
findViewById(R.id.v).setBackgroundDrawable(drawable);
複製程式碼

4.開啟混淆和資源壓縮:在app模組下的的build.gradle中

    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
複製程式碼

5.對於簡單/規則紋理的圖片,使用VectorDrawable來替代多個解析度圖片.VectorDrawable 有很多注意事項,後面單獨一篇文章總結

10.網路優化

網路優化主要有幾個方面:降低網路請求數量,降低單次請求響應的資料量,在弱網環境下將非必要網路請求延緩至網路環境好的時候.

1.降低網路請求數量:獲取同樣的資料,多次網路請求會增加電量消耗,且多次請求總體上將消耗服務端更多的時間及資源

  1. 介面Api設計要合理.可以將多個介面合併,多次請求顯示1個介面,改造後1個介面即可提供完整資料.
  2. 根據具體場景實時性需求,在App中加入網路快取,在實時性有效區間避免重複請求:主要包括網路框架和圖片載入框架的快取.

2.降低單次請求的資料量

  1. 網路介面Api在設計時候,去除多餘的請求引數及響應資料.
  2. 網路請求及響應資料的傳輸開啟GZIP壓縮,降低傳輸資料量.
    • okHttp對gzip的支援前面已記錄
  3. Protocal Buffers,Nano-Proto-Buffers,FlatBuffers代替GSON執行序列化.
    • Protocal Buffers網上有使用的方法,相對GSON有點繁瑣.如果對網路傳輸量很敏感,可以考慮使用.其他幾種方案的文章不多.
  4. 網路請求圖片,新增圖片寬高引數,避免下載過大圖片增加流量消耗.

3.弱網環境優化這塊沒有經驗,直接看anly_jun的文章

App優化之網路優化
文章中提到:使用者點贊操作, 可以直接給出介面的點贊成功的反饋, 使用JobScheduler在網路情況較好的時候打包請求.

11.電量優化

anly_jun大神的:App優化之電池省著用

12.JobScheduler,AlarmManager和WakeLock

JobScheduler在網路優化中出現過,WakeLock涉及電量優化,AlarmManager和WakeLock有相似,但側重點不同.

  1. WakeLock:比如一段關鍵邏輯T已經在執行,執行未完成Android系統就進入休眠,會導致T執行中斷.WakeLock目的就在於阻止Android系統進入休眠狀態,保證T得以繼續執行.
    • 休眠過程中自定義的Timer、Handler、Thread、Service等都會暫停
  2. AlarmManager:Android系統自帶的定時器,可以將處於休眠狀態的Android系統喚醒
    • 保證Android系統在休眠狀態下被及時喚醒,執行 定時/延時/輪詢任務
  3. JobScheduler:JobScheduler目的在於將當下不緊急的任務延遲到後面更合適的某個時間來執行.我們可以控制這些任務在什麼條件下被執行.
    • JobScheduler可以節約Android裝置當下網路,電量,CPU等資源.在指定資源充裕情況下再執行"不緊要"的任務.

JobScheduler:
Android Jobscheduler使用
Android開發筆記(一百四十三)任務排程JobScheduler
WakeLock:
Android WakeLock詳解
Android PowerManager.WakeLock使用小結
Android的PowerManager和PowerManager.WakeLock用法簡析
AlarmManager和WakeLock使用:
後臺任務 - 保持裝置喚醒狀態

13.效能檢測工具

1.Android Studio 3.2之後,Android Device Monitor已經被移除.Android Device Monitor原先包含的工具由新的方案替代.Android Device Monitor

Android效能優化 筆記

  1. DDMS:由Android Profiler代替.可以進行CPU,記憶體,網路分析.
  2. TraceView:可以通過Debug類在程式碼中呼叫Debug.startMethodTracing(String tracePath)和Debug.stopMethodTracing()來記錄兩者之間所有執行緒及執行緒中方法的耗時,生成.trace檔案.通過abd命令可以將trace檔案匯出到電腦,通過CPU profiler分析.
  3. Systrace:可以通過命令列生成html檔案,通過Chrome瀏覽器進行分析.
    • 生成html檔案已實現.但檔案怎麼分析暫未掌握,看了網上一些文章說實話還是沒搞懂
  4. Hierarchy Viewer:由Layout Inspector代替.但當前版本的Layout Inspector不能檢視每個View具體的onMeasure,onLayout,onDraw耗時,功能是不足的.我們可使用系統提供的Window.OnFrameMetricsAvailableListener來計算指定View的onLayout及onDraw耗時.
  5. Network Traffic tool:由Network Profiler代替.

2.其中Android Profiler如何使用,直接看官網即可.Profile your app performance.

3.TraceView

  1. TraceView可以直接通過CPU profiler中點選Record按鈕後,任意時間後點選Stop按鈕.即可生成trace檔案.並可將.trace檔案匯出.
    Android效能優化 筆記
    Android效能優化 筆記
  2. TraceView也可以通過Debug類在程式碼中精確控制要統計哪個區間程式碼/執行緒的CPU耗時.
    這種用法是 anly_jun大神文章裡學到的
    public class SampleApplication extends Application {
        @Override
        public void onCreate() {
            Debug.startMethodTracing("JetApp");
            super.onCreate();
            LeakCanary.install(this);
            // init logger.
            AppLog.init();
            // init crash helper
            CrashHelper.init(this);
            // init Push
            PushPlatform.init(this);
            // init Feedback
            FeedbackPlatform.init(this);
            Debug.stopMethodTracing();
        }
    複製程式碼
    程式碼執行完畢,會在Android裝置中生成JetApp.trace檔案.通過Device File Explorer,找到sdcard/Android/data/app包名/files/JetApp.trace
    在JetApp.trace上點選右鍵->Copy Path,將trace檔案路徑複製下來.
    Windows下cmd開啟命令列,執行 adb pull 路徑,即可trace檔案匯出到電腦.
    Android效能優化 筆記
  3. trace檔案分析很簡單.我們可以看到每個執行緒及執行緒中每個方法呼叫消耗的時間.

4.Layout Inspector很簡單,在App執行後,點選Tools->Layout Inspector即可.

下面只看Window.OnFrameMetricsAvailableListener怎麼用.

從Android 7.0 (API level 24)開始,Android引入Window.OnFrameMetricsAvailableList介面用於提供每一幀繪製各階段的耗時,資料來源與GPU Profile相同.

public interface OnFrameMetricsAvailableListener {
    void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,int dropCountSinceLastInvocation);
}
/**
 * 包含1幀的週期內,渲染系統各個方法的耗時資料.
 */
public final class FrameMetrics {
    ****
    //通過getMetric獲取layout/measure耗時所用的id
    public static final int LAYOUT_MEASURE_DURATION = 3;
    public static final int DRAW_DURATION = 4;
    /**
    * 獲取當前幀指定id代表的方法/過程的耗時,單位是納秒:1納秒(ns)=10的負6次方毫秒(ms)
    */
    public long getMetric(@Metric int id) {
        ****
    }
}
複製程式碼
  1. 在Activity中使用OnFrameMetricsAvailableListener:
    • 通過呼叫this.getWindow().addOnFrameMetricsAvailableListener(@NonNull OnFrameMetricsAvailableListener listener,Handler handler)來新增監聽.
    • 通過呼叫this.getWindow().removeOnFrameMetricsAvailableListener(OnFrameMetricsAvailableListener listener)取消監聽.
    • 解析ConstraintLayout的效能優勢中引用了Google使用OnFrameMetricsAvailableListener的例子android-constraint-layout-performance.其中Activity是Kotlin寫的,嘗試將程式碼轉為java,解決掉報錯後執行.
      package p1.com.p1;
      
      import android.os.AsyncTask;
      import android.os.Bundle;
      import android.os.Handler;
      import android.support.annotation.RequiresApi;
      import android.support.v7.app.AppCompatActivity;
      import android.util.Log;
      import android.view.FrameMetrics;
      import android.view.View;
      import android.view.View.MeasureSpec;
      import android.view.View.OnClickListener;
      import android.view.ViewGroup;
      import android.view.Window;
      import android.view.Window.OnFrameMetricsAvailableListener;
      import android.widget.Button;
      import android.widget.TextView;
      import org.jetbrains.annotations.NotNull;
      import org.jetbrains.annotations.Nullable;
      import java.lang.ref.WeakReference;
      import java.util.Arrays;
      import kotlin.TypeCastException;
      import kotlin.jvm.internal.Intrinsics;
      
      public final class KtMainActivity extends AppCompatActivity {
          private final Handler frameMetricsHandler = new Handler();
          @RequiresApi(24)
          private final OnFrameMetricsAvailableListener frameMetricsAvailableListener = new OnFrameMetricsAvailableListener() {
              @Override
              public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
                  long costDuration = frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION);
                  Log.d("Jet", "layoutMeasureDurationNs: " + costDuration);
              }
          };
          private static final String TAG = "KtMainActivity";
          private static final int TOTAL = 100;
          private static final int WIDTH = 1920;
          private static final int HEIGHT = 1080;
      
          @RequiresApi(3)
          protected void onCreate(@Nullable Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              this.setContentView(R.layout.activity_for_test);
              final Button traditionalCalcButton = (Button) this.findViewById(R.id.button_start_calc_traditional);
              final Button constraintCalcButton = (Button) this.findViewById(R.id.button_start_calc_constraint);
              final TextView textViewFinish = (TextView) this.findViewById(R.id.textview_finish);
              traditionalCalcButton.setOnClickListener((OnClickListener) (new OnClickListener() {
                  public final void onClick(View it) {
                      Button var10000 = constraintCalcButton;
                      Intrinsics.checkExpressionValueIsNotNull(constraintCalcButton, "constraintCalcButton");
                      var10000.setVisibility(View.INVISIBLE);
                      View var4 = KtMainActivity.this.getLayoutInflater().inflate(R.layout.activity_traditional, (ViewGroup) null);
                      if (var4 == null) {
                          throw new TypeCastException("null cannot be cast to non-null type android.view.ViewGroup");
                      } else {
                          ViewGroup container = (ViewGroup) var4;
                          String var10002 = KtMainActivity.this.getString(R.string.executing_nth_iteration);
                          Intrinsics.checkExpressionValueIsNotNull(var10002, "getString(R.string.executing_nth_iteration)");
                          KtMainActivity.MeasureLayoutAsyncTask asyncTask = new KtMainActivity.MeasureLayoutAsyncTask(var10002, new WeakReference(traditionalCalcButton), new WeakReference(textViewFinish), new WeakReference(container));
                          asyncTask.execute(new Void[0]);
                      }
                  }
              }));
              constraintCalcButton.setOnClickListener((OnClickListener) (new OnClickListener() {
                  public final void onClick(View it) {
                      Button var10000 = traditionalCalcButton;
                      Intrinsics.checkExpressionValueIsNotNull(traditionalCalcButton, "traditionalCalcButton");
                      var10000.setVisibility(View.INVISIBLE);
                      View var4 = KtMainActivity.this.getLayoutInflater().inflate(R.layout.activity_constraintlayout, (ViewGroup) null);
                      if (var4 == null) {
                          throw new TypeCastException("null cannot be cast to non-null type android.view.ViewGroup");
                      } else {
                          ViewGroup container = (ViewGroup) var4;
                          String var10002 = KtMainActivity.this.getString(R.string.executing_nth_iteration);
                          Intrinsics.checkExpressionValueIsNotNull(var10002, "getString(R.string.executing_nth_iteration)");
                          KtMainActivity.MeasureLayoutAsyncTask asyncTask = new KtMainActivity.MeasureLayoutAsyncTask(var10002, new WeakReference(constraintCalcButton), new WeakReference(textViewFinish), new WeakReference(container));
                          asyncTask.execute(new Void[0]);
                      }
                  }
              }));
          }
          @RequiresApi(24)
          protected void onResume() {
              super.onResume();
              this.getWindow().addOnFrameMetricsAvailableListener(this.frameMetricsAvailableListener, this.frameMetricsHandler);
          }
          @RequiresApi(24)
          protected void onPause() {
              super.onPause();
              this.getWindow().removeOnFrameMetricsAvailableListener(this.frameMetricsAvailableListener);
          }
          @RequiresApi(3)
          private static final class MeasureLayoutAsyncTask extends AsyncTask {
              @NotNull
              private final String executingNthIteration;
              @NotNull
              private final WeakReference startButtonRef;
              @NotNull
              private final WeakReference finishTextViewRef;
              @NotNull
              private final WeakReference containerRef;
      
              @Nullable
              protected Void doInBackground(@NotNull Void... voids) {
                  Intrinsics.checkParameterIsNotNull(voids, "voids");
                  int i = 0;
      
                  for (int var3 = KtMainActivity.TOTAL; i < var3; ++i) {
                      this.publishProgress(new Integer[]{i});
      
                      try {
                          Thread.sleep(100L);
                      } catch (InterruptedException var5) {
                          ;
                      }
                  }
                  return null;
              }
              // $FF: synthetic method
              // $FF: bridge method
              public Object doInBackground(Object[] var1) {
                  return this.doInBackground((Void[]) var1);
              }
              protected void onProgressUpdate(@NotNull Integer... values) {
                  Intrinsics.checkParameterIsNotNull(values, "values");
                  Button var10000 = (Button) this.startButtonRef.get();
                  if (var10000 != null) {
                      Button startButton = var10000;
                      Intrinsics.checkExpressionValueIsNotNull(startButton, "startButton");
      //                StringCompanionObject var3 = StringCompanionObject.INSTANCE;
                      String var4 = this.executingNthIteration;
                      Object[] var5 = new Object[]{values[0], KtMainActivity.TOTAL};
                      String var9 = String.format(var4, Arrays.copyOf(var5, var5.length));
                      Intrinsics.checkExpressionValueIsNotNull(var9, "java.lang.String.format(format, *args)");
                      String var7 = var9;
                      startButton.setText((CharSequence) var7);
                      ViewGroup var10 = (ViewGroup) this.containerRef.get();
                      if (var10 != null) {
                          ViewGroup container = var10;
                          Intrinsics.checkExpressionValueIsNotNull(container, "container");
                          this.measureAndLayoutExactLength(container);
                          this.measureAndLayoutWrapLength(container);
                      }
                  }
              }
              // $FF: synthetic method
              // $FF: bridge method
              public void onProgressUpdate(Object[] var1) {
                  this.onProgressUpdate((Integer[]) var1);
              }
              protected void onPostExecute(@Nullable Void aVoid) {
                  TextView var10000 = (TextView) this.finishTextViewRef.get();
                  if (var10000 != null) {
                      TextView finishTextView = var10000;
                      Intrinsics.checkExpressionValueIsNotNull(finishTextView, "finishTextView");
                      finishTextView.setVisibility(View.VISIBLE);
                      Button var4 = (Button) this.startButtonRef.get();
                      if (var4 != null) {
                          Button startButton = var4;
                          Intrinsics.checkExpressionValueIsNotNull(startButton, "startButton");
                          startButton.setVisibility(View.GONE);
                      }
                  }
              }
              // $FF: synthetic method
              // $FF: bridge method
              public void onPostExecute(Object var1) {
                  this.onPostExecute((Void) var1);
              }
              private final void measureAndLayoutWrapLength(ViewGroup container) {
                  int widthMeasureSpec = MeasureSpec.makeMeasureSpec(KtMainActivity.WIDTH, View.MeasureSpec.AT_MOST);
                  int heightMeasureSpec = MeasureSpec.makeMeasureSpec(KtMainActivity.HEIGHT, View.MeasureSpec.AT_MOST);
                  container.measure(widthMeasureSpec, heightMeasureSpec);
                  container.layout(0, 0, container.getMeasuredWidth(), container.getMeasuredHeight());
              }
      
              private final void measureAndLayoutExactLength(ViewGroup container) {
                  int widthMeasureSpec = MeasureSpec.makeMeasureSpec(KtMainActivity.WIDTH, View.MeasureSpec.EXACTLY);
                  int heightMeasureSpec = MeasureSpec.makeMeasureSpec(KtMainActivity.HEIGHT, View.MeasureSpec.EXACTLY);
                  container.measure(widthMeasureSpec, heightMeasureSpec);
                  container.layout(0, 0, container.getMeasuredWidth(), container.getMeasuredHeight());
              }
      
              @NotNull
              public final String getExecutingNthIteration() {
                  return this.executingNthIteration;
              }
      
              @NotNull
              public final WeakReference getStartButtonRef() {
                  return this.startButtonRef;
              }
      
              @NotNull
              public final WeakReference getFinishTextViewRef() {
                  return this.finishTextViewRef;
              }
      
              @NotNull
              public final WeakReference getContainerRef() {
                  return this.containerRef;
              }
      
              public MeasureLayoutAsyncTask(@NotNull String executingNthIteration, @NotNull WeakReference startButtonRef, @NotNull WeakReference finishTextViewRef, @NotNull WeakReference containerRef) {
                  super();
                  Intrinsics.checkParameterIsNotNull(executingNthIteration, "executingNthIteration");
                  Intrinsics.checkParameterIsNotNull(startButtonRef, "startButtonRef");
                  Intrinsics.checkParameterIsNotNull(finishTextViewRef, "finishTextViewRef");
                  Intrinsics.checkParameterIsNotNull(containerRef, "containerRef");
                  this.executingNthIteration = executingNthIteration;
                  this.startButtonRef = startButtonRef;
                  this.finishTextViewRef = finishTextViewRef;
                  this.containerRef = containerRef;
              }
          }
      }
      
      D/Jet: layoutMeasureDurationNs: 267344
      D/Jet: layoutMeasureDurationNs: 47708
      D/Jet: layoutMeasureDurationNs: 647240
      D/Jet: layoutMeasureDurationNs: 59636
      D/Jet: layoutMeasureDurationNs: 50052
      D/Jet: layoutMeasureDurationNs: 49739
      D/Jet: layoutMeasureDurationNs: 75990
      D/Jet: layoutMeasureDurationNs: 296198
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 894375
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 1248021
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 0
      D/Jet: layoutMeasureDurationNs: 1290677
      D/Jet: layoutMeasureDurationNs: 2936563
      D/Jet: layoutMeasureDurationNs: 1387188
      D/Jet: layoutMeasureDurationNs: 2325521
      D/Jet: layoutMeasureDurationNs: 1940052
      D/Jet: layoutMeasureDurationNs: 1539271
      D/Jet: layoutMeasureDurationNs: 803750
      D/Jet: layoutMeasureDurationNs: 1405000
      D/Jet: layoutMeasureDurationNs: 1188437
      D/Jet: layoutMeasureDurationNs: 1748802
      D/Jet: layoutMeasureDurationNs: 3422240
      D/Jet: layoutMeasureDurationNs: 1400677
      D/Jet: layoutMeasureDurationNs: 2416094
      D/Jet: layoutMeasureDurationNs: 1532864
      D/Jet: layoutMeasureDurationNs: 1684063
      D/Jet: layoutMeasureDurationNs: 1092865
      D/Jet: layoutMeasureDurationNs: 1363177
      D/Jet: layoutMeasureDurationNs: 1067188
      D/Jet: layoutMeasureDurationNs: 1358333
      D/Jet: layoutMeasureDurationNs: 2999895
      D/Jet: layoutMeasureDurationNs: 2113021
      D/Jet: layoutMeasureDurationNs: 1957395
      D/Jet: layoutMeasureDurationNs: 1319740
      D/Jet: layoutMeasureDurationNs: 2207239
      D/Jet: layoutMeasureDurationNs: 1514167
      D/Jet: layoutMeasureDurationNs: 949114
      D/Jet: layoutMeasureDurationNs: 1691250
      D/Jet: layoutMeasureDurationNs: 1387448
      D/Jet: layoutMeasureDurationNs: 932552
      D/Jet: layoutMeasureDurationNs: 1223802
      D/Jet: layoutMeasureDurationNs: 2024740
      D/Jet: layoutMeasureDurationNs: 1242292
      D/Jet: layoutMeasureDurationNs: 2228230
      D/Jet: layoutMeasureDurationNs: 1382083
      D/Jet: layoutMeasureDurationNs: 2233282
      D/Jet: layoutMeasureDurationNs: 1907187
      D/Jet: layoutMeasureDurationNs: 2287552
      D/Jet: layoutMeasureDurationNs: 776354
      D/Jet: layoutMeasureDurationNs: 1225000
      D/Jet: layoutMeasureDurationNs: 875417
      D/Jet: layoutMeasureDurationNs: 1271302
      D/Jet: layoutMeasureDurationNs: 1211614
      D/Jet: layoutMeasureDurationNs: 1346459
      D/Jet: layoutMeasureDurationNs: 1978854
      D/Jet: layoutMeasureDurationNs: 2915677
      D/Jet: layoutMeasureDurationNs: 1330573
      D/Jet: layoutMeasureDurationNs: 2195364
      D/Jet: layoutMeasureDurationNs: 775208
      D/Jet: layoutMeasureDurationNs: 2492292
      D/Jet: layoutMeasureDurationNs: 400104
      D/Jet: layoutMeasureDurationNs: 2844375
      D/Jet: layoutMeasureDurationNs: 1563750
      D/Jet: layoutMeasureDurationNs: 3689531
      D/Jet: layoutMeasureDurationNs: 2019323
      D/Jet: layoutMeasureDurationNs: 1663906
      D/Jet: layoutMeasureDurationNs: 1004531
      D/Jet: layoutMeasureDurationNs: 738125
      D/Jet: layoutMeasureDurationNs: 1299166
      D/Jet: layoutMeasureDurationNs: 1223854
      D/Jet: layoutMeasureDurationNs: 1942240
      D/Jet: layoutMeasureDurationNs: 1392396
      D/Jet: layoutMeasureDurationNs: 1906458
      D/Jet: layoutMeasureDurationNs: 691198
      D/Jet: layoutMeasureDurationNs: 2620468
      D/Jet: layoutMeasureDurationNs: 1953229
      D/Jet: layoutMeasureDurationNs: 1120365
      D/Jet: layoutMeasureDurationNs: 3165417
      D/Jet: layoutMeasureDurationNs: 537709
      D/Jet: layoutMeasureDurationNs: 3019531
      D/Jet: layoutMeasureDurationNs: 706250
      D/Jet: layoutMeasureDurationNs: 1129115
      D/Jet: layoutMeasureDurationNs: 539427
      D/Jet: layoutMeasureDurationNs: 1633438
      D/Jet: layoutMeasureDurationNs: 1784479
      D/Jet: layoutMeasureDurationNs: 743229
      D/Jet: layoutMeasureDurationNs: 1851615
      D/Jet: layoutMeasureDurationNs: 851927
      D/Jet: layoutMeasureDurationNs: 1847916
      D/Jet: layoutMeasureDurationNs: 836718
      D/Jet: layoutMeasureDurationNs: 2892552
      D/Jet: layoutMeasureDurationNs: 1230573
      D/Jet: layoutMeasureDurationNs: 3886563
      D/Jet: layoutMeasureDurationNs: 2138281
      D/Jet: layoutMeasureDurationNs: 2198021
      D/Jet: layoutMeasureDurationNs: 1805885
      D/Jet: layoutMeasureDurationNs: 2316927
      D/Jet: layoutMeasureDurationNs: 1990937
      D/Jet: layoutMeasureDurationNs: 2261041
      D/Jet: layoutMeasureDurationNs: 2159010
      D/Jet: layoutMeasureDurationNs: 666562
      D/Jet: layoutMeasureDurationNs: 2332031
      D/Jet: layoutMeasureDurationNs: 1061875
      D/Jet: layoutMeasureDurationNs: 1879062
      D/Jet: layoutMeasureDurationNs: 1411459
      D/Jet: layoutMeasureDurationNs: 154635
      複製程式碼
  2. 在Application中使用OnFrameMetricsAvailableListener,則可以統一設定,不需要每個Activity單獨設定,推薦使用開源專案ActivityFrameMetrics
    1. 在Application的onCreate中設定單幀渲染總時間超過W毫秒和E毫秒,會在Logcat中列印警告和錯誤的Log資訊.
      public class SampleApplication extends Application {
          @Override
          public void onCreate() {
              registerActivityLifecycleCallbacks(new ActivityFrameMetrics.Builder()
                      .warningLevelMs(10)     //default: 17ms
                      .errorLevelMs(10)       //default: 34ms
                      .showWarnings(true)     //default: true
                      .showErrors(true)       //default: true
                      .build());
          }
      }
      複製程式碼
    2. Application設定完成執行App,出現單幀渲染總耗時超過指定時間,即可看到Logcat中的資訊.
      E/FrameMetrics: Janky frame detected on KtMainActivity with total duration: 16.91ms
      Layout/measure: 1.66ms, draw:2.51ms, gpuCommand:3.13ms others:9.61ms
      Janky frames: 72/107(67.28972%)
      E/FrameMetrics: Janky frame detected on KtMainActivity with total duration: 15.47ms
      Layout/measure: 1.00ms, draw:2.05ms, gpuCommand:3.44ms others:8.98ms
      Janky frames: 73/108(67.59259%)
      E/FrameMetrics: Janky frame detected on KtMainActivity with total duration: 15.09ms
      Layout/measure: 1.30ms, draw:1.44ms, gpuCommand:2.91ms others:9.44ms
      Janky frames: 74/110(67.27273%)
      ****
      複製程式碼

5.Systrace:通過命令列生成html檔案,通過Chrome瀏覽器進行分析

  1. 首先電腦要安裝python,這裡有幾個坑:
    1. Python要安裝2.7x版本,不能安裝最新的3.x.
      • 比如自己電腦中systrace資料夾路徑是:C:\Users\你的使用者名稱\AppData\Local\Android\Sdk\platform-tools\systrace,如果我們安裝的是3.x版本,在這個路徑下執行python systrace.py *** 命令會報錯,提示你應該安裝2.7
    2. Python安裝時候,要記得勾選"Add python.exe to Path".
    3. 這時候直接執行python systrace.py ***命令還是會報錯:ImportError: No module named win32com
  2. 生成html及如何分析

相關文章