GITHUB
說明
這篇文章是將好久以來看過的文章,包括本身寫的一些測試代碼的總結.屬於筆記的性質,沒有面面俱到,一些本身相對熟悉的點可能會略過.<br> 最開始看到的性能優化的文章,就是胡凱的優化典範系列,後來又陸續看過一些人寫的,我的以爲anly_jun和胡凱的質量最好.<br> 文章大的框架也是先把優化典範過一遍,記錄我的認爲重要的點,而後是anly_jun的系列,將以前未覆蓋的補充進去,也包括HenCoder的一些課程相關內容.<br> 固然除了上面幾位,還有不少其餘大神的文章,時間久了也記不太清,在此一併謝過.javascript
筆記內容引用來源
1.Android性能優化之渲染篇
1.VSYNC
- 幀率:GPU在1秒內繪製操做的幀數.如60fps.
- 咱們一般都會提到60fps與16ms,這是由於人眼與大腦之間的協做沒法感知超過60fps的畫面更新.
- 開發app的性能目標就是保持60fps,這意味着每一幀只有16ms=1000/60的時間來處理全部的任務
- 刷新率:屏幕在1秒內刷新屏幕的次數.如60Hz,每16ms刷新1次屏幕.
- GPU獲取圖形數據進行渲染,而後屏幕將渲染後的內容展現在屏幕上.
- 大多數手機屏幕的刷新率是60Hz,若是GPU渲染1幀的時間低於1000/60=16ms,那麼在屏幕刷新時候都有最新幀可顯示.若是GPU渲染某1幀 f 的時間超過16ms,在屏幕刷新時候,f並無被GPU渲染完成則沒法展現,屏幕只能繼續展現f的上1幀的內容.這就是掉幀,形成了UI界面的卡頓.
<br> 下面展現了幀率正常和幀率低於刷新率(掉幀)的情形html
2.GPU渲染:GPU渲染依賴2個組件:CPU和GPU
- CPU負責Measure,Layout,Record,Execute操做.
- GPU負責Rasterization(柵格化)操做.
- Resterization柵格化是繪製那些Button,Shape,Path,String,Bitmap等組件最基礎的操做.它把組件拆分到不一樣的像素上進行顯示.這是一個很費時的操做.
- CPU負責把UI組件計算成Polygons(多邊形),Texture(紋理),而後交給GPU進行柵格化渲染.
- 爲了App流暢,咱們須要確保在16ms內完成全部CPU和GPU的工做.
3.過分繪製
Overdraw過分繪製是指屏幕上的某個像素在同一幀的時間內被繪製了屢次.過分繪製會大量浪費CPU及GPU資源/佔用CPU和GPU的處理時間java
- 過分繪製的緣由
- UI佈局存在大量重疊
- 非必須的背景重疊.
- 如Activity有背景,Layout又有背景,子View又有背景.僅僅移除非必要背景就能夠顯著提高性能.
- 子View在onDraw中存在重疊部分繪製的狀況,好比Bitmap重疊繪製
4.如何提高渲染性能
- 移除XML佈局文件中非必要的Background
- 保持佈局扁平化,儘可能避免佈局嵌套
- 在任什麼時候候都避免調用requestLayout(),調用requestLayout會致使該layout的全部父節點都發生從新layout的操做
- 在自定義View的onDraw中避免過分繪製. <br>代碼實例:
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(); } }
<br>效果圖:python
2.Android性能優化以內存篇
1.Android虛擬機的 分代堆內存/Generational Heap Memory模型
- 和JVM不一樣:Android的堆內存多了1個永久代/Permanent Generation.
- 和JVM相似:
- 新建立的對象存儲在新生代/Young Generation
- GC所佔用的時間和它是哪個Generation有關,Young Generation的每次GC操做時間是最短的,Old Generation其次,Permanent Generation最長
- 不管哪一代,觸發GC後,全部非垃圾回收線程暫停,GC結束後全部線程恢復執行
- 若是短期內進行過多GC,屢次暫停線程進行垃圾回收的累積時間就會增大.佔用過多的幀間隔時間/16ms,致使CPU和GPU用於計算渲染的時間不足,致使卡頓/掉幀.
2.內存泄漏和內存溢出
內存泄漏就是無用對象佔據的內存空間沒有及時釋放,致使內存空間浪費的狀況.memory leak. <br> 內存溢出是App爲1個對象申請內存空間,內存空間不足的狀況.out of memory. <br> 內存泄漏數量足夠大,就會引發內存溢出.或者說內存泄漏是內存溢出的緣由之一.android
3.Android性能優化典範-第2季
1.提高動畫性能
- Bitmap的縮放,旋轉,裁剪比較耗性能.例如在一個圓形的鐘表圖上,咱們把時鐘的指針摳出來當作單獨的圖片進行旋轉會比旋轉一張完整的圓形圖性能好.
- 儘可能減小每次重繪的元素能夠極大提高性能.能夠把複雜的View拆分會更小的View進行組合,在須要刷新界面時候僅對指定View進行重繪.
- 假如鐘錶界面上有不少組件,能夠把這些組件作拆分,背景圖片單獨拎出來設置爲一個獨立的View,經過setLayerType()方法使得這個View強制用Hardware來進行渲染.至於界面上哪些元素須要作拆分,他們各自的更新頻率是多少,須要有針對性的單獨討論
2.對象池
- 短期內大量對象被建立而後很快被銷燬,會屢次觸發Android虛擬機在Young generation進行GC,使用AS查看內存曲線,會看到內存曲線劇烈起伏,稱爲"內存抖動".
- GC會暫停其餘線程,短期屢次GC/內存抖動會引發CPU和GPU在16ms內沒法完成當前幀的渲染,引發界面卡頓.
- 避免內存抖動,可使用對象池
- 實例
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
- 不要用for index去遍歷鏈表,由於LinkedList在get任何一個位置的數據的時候,都會把前面的數據走一遍.應該使用Iterator去遍歷
- get(0),直接拿到0位的Node0的地址,拿到Node0裏面的數據
- get(1),直接拿到0位的Node0的地址,從0位的Node0中找到下一個1位的Node1的地址,找到Node1,拿到Node1裏面的數據
- get(2),直接拿到0位的Node0的地址,從0位的Node0中找到下一個1位的Node1的地址,找到Node1,從1位的Node1中找到下一個2位的Node2的地址,找到Node2,拿到Node2裏面的數據
- Vector和ArrayList,使用for index遍歷效率較高
4.Merge:經過Merge減小1個View層級
- 能夠將merge當作1個ViewGroup v,若是v的類型和v的父控件的類型一致,那麼v其實不必存在,由於白白增長了佈局的深度.因此merge使用時必須保證merge中子控件所應該在的ViewGroup類型和merge所在的父控件類型一致.
- Merge的使用場景有2個:
- Activity的佈局文件的根佈局是FrameLayout,則將FrameLayout替換爲merge
- 由於setContentView本質就是將佈局文件inflate後加載到了id爲android.id.content的FrameLayout上.
- merge做爲根佈局的佈局文件經過include標籤被引入其餘佈局文件中.這時候include所在的父控件,必須和merge所在的佈局文件"本來根佈局"一致.
- Activity的佈局文件的根佈局是FrameLayout,則將FrameLayout替換爲merge
- 代碼示例<br> 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>
<br> merge做爲根佈局的佈局文件,被include標籤引入其餘佈局文件中:git
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
- 對於不存在過分繪製的View,如沒有背景的TextView,就能夠直接設置文字顏色;ImageView設置圖片透明度setImageAlpha;自定義View設置繪製時的paint的透明度
- 若是是自定義View,肯定不存在過分繪製,能夠重寫hasOverlappingRendering返回false便可.這樣設置alpha時android會自動優化,避免使用offscreen buffer.
@Override public boolean hasOverlappingRendering() { return false; }
- 若是不是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個對象,相比較Integer和String常量,枚舉的內存開銷至少是其2倍.
- 過多枚舉會增長dex大小及其中的方法數量,增長App佔用的空間及引起65536概率
- 如何替代枚舉:使用註解
- android.support.annotation中的@IntDef,@StringDef來包裝Integer和String常量.
- 3個步驟
- 首先定義常量
- 而後自定義註解,設置取值範圍就是剛剛定義的常量,並設置自定義註解的保留範圍爲源碼時/SOURCE
- 位指定的屬性及方法添加自定義註解.
- 代碼實例
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.減少對象的內存佔用
- 避免使用枚舉,用註解替代
- 減少建立的Bitmap的內存,使用合適的縮放比例及解碼格式
- inSampleSize:縮放比例
- decode format:解碼格式
- 如今不少圖片資源的URL均可以添加圖片尺寸做爲參數.在經過網絡獲取圖片時選擇合適的尺寸,減少網絡流量消耗,並減少生成的Bitmap的大小.
2.內存對象的重複利用
- 對象池技術:減小頻繁建立和銷燬對象帶來的成本,實現對象的緩存和複用
- 儘可能使用Android系統內置資源,可下降APK大小,在必定程度下降內存開銷
- ConvertView的複用
- LRU的機制實現Bitmap的緩存(圖片加載框架的必備機制)
- 在for循環中,用StringBuilder代替String實現字符串拼接
3.避免內存泄漏
- 在App中使用leakcanary檢測內存泄漏:leakcanary
- Activity的內存泄漏
- Handler引發Activity內存泄漏
- 緣由:Handler做爲Activity的1個非靜態內部類實例,持有Activity實例的引用.若Activity退出後Handler依然有待接收的Message,這時候發生GC,Message-Handler-Activity的引用鏈致使Activity沒法被回收.
- 2種解決方法
-
在onDestroy調用Handler.removeCallbacksAndMessages(null)移除該Handler關聯的全部Message及Runnable.再發生GC,Message已經不存在,就能夠順利的回收Handler及Activitygithub
@Override protected void onDestroy() { super.onDestroy(); m.removeCallbacksAndMessages(null); }
-
自定義靜態內部類繼承Handler,靜態內部類實例不持有外部Activity的引用.在自定義Handler中定義外部Activity的弱引用,只有弱引用關聯的外部Activity實例未被回收的狀況下才繼續執行handleMessage.自定義Handler持有外部Activity的弱引用,發生GC時不耽誤Activity被回收.web
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); }
-
在避免內存泄漏的前提下,若是要求Activity退出就不執行後續動做,用方法1.若是要求後續動做在GC發生前繼續執行,使用方法2算法
-
- Handler引發Activity內存泄漏
- Context:儘可能使用Application Context而不是Activity Context,避免不經意的內存泄漏
- 資源對象要及時關閉
4.內存使用策略優化
- 圖片選擇合適的文件夾進行存放
- hdpi/xhdpi/xxhdpi等等不一樣dpi的文件夾下的圖片在不一樣的設備上會通過scale的處理。例如咱們只在hdpi的目錄下放置了一張100100的圖片,那麼根據換算關係,xxhdpi的手機去引用那張圖片就會被拉伸到200200。須要注意到在這種狀況下,內存佔用是會顯著提升的。對於不但願被拉伸的圖片,須要放到assets或者nodpi的目錄下
- 謹慎使用依賴注入框架.依賴注入框架會掃描代碼,須要大量的內存空間映射代碼.
- 混淆能夠減小沒必要要的代碼,類,方法等.下降映射代碼所需的內存空間
- onLowMemory()與onTrimMemory():沒想到應該怎麼用
- onLowMemory
- 當全部的background應用都被kill掉的時候,forground應用會收到onLowMemory()的回調.在這種狀況下,須要儘快釋放當前應用的非必須的內存資源,從而確保系統可以繼續穩定運行
- onTrimMemory(int level)
- 當系統內存達到某些條件的時候,全部正在運行的應用都會收到這個回調,同時在這個回調裏面會傳遞如下的參數,表明不一樣的內存使用狀況,收到onTrimMemory()回調的時候,須要根據傳遞的參數類型進行判斷,合理的選擇釋放自身的一些內存佔用,一方面能夠提升系統的總體運行流暢度,另外也能夠避免本身被系統判斷爲優先須要殺掉的應用
- onLowMemory
6.Android開發最佳實踐
1.注意對隱式Intent的運行時檢查保護
- 相似打開相機等隱式Intent,不必定可以在全部的Android設備上都正常運行.
- 例如系統相機應用被關閉或者不存在相機應用,或者某些權限被關閉均可能致使拋出ActivityNotFoundException的異常.
- 預防這個問題的最佳解決方案是在發出這個隱式Intent以前調用resolveActivity作檢查
- 代碼實例
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 <br> MVP架構實現的Github客戶端(4-加入網絡緩存)json
2.代碼混淆
2.1.AS中生成keystore.jks應用於APK打包 <br>
- 1:生成keystore.jks
<br>
- 2:查看.jks文件的SHA1安全碼
<br> 在AS的Terminal中輸入: <br> keytool -list -v -keystore C:\Users\Administrator\Desktop\key.jks <br> keytool -list -v -keystore .jks文件詳細路徑 <br> 回車後,輸入密鑰庫口令/就是.jks的密碼,輸入過程不可見,輸入完畢回車便可! <br>
2.2.proguard-rules關鍵字及部分通配符含義
<table align="center"> <tr> <td>關鍵字</td> <td>描述</td> </tr> <tr> <td>keep</td> <td>保留類和類中的成員,防止它們被混淆或移除</td> </tr> <tr> <td>keepnames</td> <td>保留類和類中的成員,防止它們被混淆,但當成員沒有被引用時會被移除</td> </tr> <tr> <td>keepclasseswithmembers</td> <td>保留類和類中的成員,防止它們被混淆或移除,前提是指名的類中的成員必須存在,若是不存在則仍是會混淆</td> </tr> <tr> <td>keepclasseswithmembernames</td> <td>保留類和類中的成員,防止它們被混淆,但當成員沒有被引用時會被移除,前提是指名的類中的成員必須存在,若是不存在則仍是會混淆</td> </tr> <tr> <td>keepclassmembers</td> <td>只保留類中的成員,防止它們被混淆或移除</td> </tr> <tr> <td>keepclassmembernames</td> <td>只保留類中的成員,防止它們被混淆,但當成員沒有被引用時會被移除</td> </tr> </table> <br> <table align="center"> <tr> <td>通配符</td> <td>描述</td> </tr> <tr> <td>< field ></td> <td>匹配類中的全部字段</td> </tr> <tr> <td>< method ></td> <td>匹配類中的全部方法</td> </tr> <tr> <td>< init ></td> <td>匹配類中的全部構造函數</td> </tr> <tr> <td>*</td> <td> 1.*和字符串聯合使用,*表明任意長度的不包含包名分隔符(.)的字符串:<br> a.b.c.MainActivity: a.*.*.MainActivity能夠匹配;a.*就匹配不上;<br> 2.*單獨使用,就能夠匹配全部東西 </td> </tr> <tr> <td>**</td> <td> 匹配任意長度字符串,包含包名分隔符(.)<br> a.b.**能夠匹配a.b包下全部內容,包括子包 </td> </tr> <tr> <td>***</td> <td> 匹配任意參數類型.好比:<br> void set*(***)就能匹配任意傳入的參數類型;<br> *** get*()就能匹配任意返回值的類型 </td> </tr> <tr> <td>…</td> <td> 匹配任意長度的任意類型參數.好比:<br> void test(…)就能匹配任意void test(String a)或者是void test(int a, String b) </td> </tr> </table> <br>
- keep 完整類名{*;}, 能夠對指定類進行徹底保留,不混淆類名,變量名,方法名.<br>
-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包 <br> 郭霖大神博客有介紹,本身沒試過
2.5.幾條實用的Proguard rules <br> 在上面提供的通用模板上繼續添加下面幾行:
-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(...); }
- repackageclasses:除了keep的類,會把咱們本身寫的全部類以及所使用到的各類第三方庫代碼通通移動到咱們指定的單個包下.
- 好比一些比較敏感的被keep的類在包a.b.min下,咱們可使用 -repackageclasses a.b.min,這樣就有成千上萬的被混淆的類和未被混淆的敏感的類在a.b.min下面,正常人根本就找不到關鍵類.尤爲是keep的類也只是保留關鍵方法,名字也被混淆過.
- -obfuscationdictionary,-classobfuscationdictionary和-packageobfuscationdictionary分別指定變量/方法名,類名,包名混淆後的字符串集.
- 默認咱們的代碼命名會被混淆成字母組合,使用這些配置能夠用亂碼或中文內容進行命名.中文命名能夠破壞部分反編譯軟件的正常工做,亂碼則極大加大了查看代碼的難度.
- dict.txt:須要放到和app模塊的proguard-rules.pro同級目錄.dict.txt具體內容能夠本身寫,參考開源項目:一種生成閱讀極其困難的proguard字典的算法
- -assumenosideeffects class android.util.Log是在編譯成 APK 以前把日誌代碼所有刪掉.
- Androidstudio 混淆去掉日誌 assumenosideeffects 不起做用
- 由於默認狀況下,使用的是proguard-android.txt的混淆規則.proguard-android.txt中包含-dontoptimize.-dontoptimize致使日誌語句不會被優化掉.因此咱們提供的模板不包含-dontoptimize這一句.
- Androidstudio 混淆去掉日誌 assumenosideeffects 不起做用
2.6.字符串硬編碼
- 對於反編譯者來講,最簡單的入手點就是字符串搜索.硬編碼留在代碼裏的字符串值都會在反編譯過程當中被原樣恢復,不要使用硬編碼.
- 若是必定要使用硬編碼
- 新建1個存儲硬編碼的常量類,靜態存放字符串常量,即便找到了常量類,反編譯者很難搜索到哪裏用了這些字符串.
- 常量類中的靜態常量字符串,用名稱做爲真正內容,而值用難以理解的編碼表示.
//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資源混淆及多渠道打包 <br> 簡單講,使用騰訊的2個gradle插件來實現res資源混淆及多渠道打包. <br> res資源混淆:AndResGuard <br> 多渠道打包:VasDolly <br> 多渠道打包原理+VasDolly和其餘多渠道打包方案對比 <br><br> 具體流程: <br> AndResGuard使用了chaychan的方法,單首創建gradle文件 <br>
- 項目根目錄下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' } }
- 在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" } }
- 模塊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' }
- 首先使用AndResGuard實現資源混淆,再使用VasDolly實現多渠道打包
-
在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文件已經被混淆.
-
將app-release_aligned_signed.apk放到app模塊下,在Gradle界面中,找到app模塊下channel的task,執行reBuildChannel指令.
- 雙擊執行reBuildChannel指令,幾秒鐘就生成了20個經過app-release_aligned_signed.apk的多渠道apk.
- 經過helper類庫中的ChannelReaderUtil類讀取渠道信息
String channel = ChannelReaderUtil.getChannel(getApplicationContext());
- 雙擊執行reBuildChannel指令,幾秒鐘就生成了20個經過app-release_aligned_signed.apk的多渠道apk.
-
3.APK瘦身
4.更高效的數據序列化:只是看看從沒用過,Protocal Buffers,Nano-Proto-Buffers,FlatBuffers
5.數據呈現的順序以及結構會對序列化以後的空間產生不小的影響
- gzip
- gzip概念:HTTP協議上的GZIP編碼是一種用來改進WEB應用程序性能的技術
- 通常對純文本內容可壓縮到原大小的40%
- 減小文件大小有兩個明顯的好處,一是能夠減小存儲空間,二是經過網絡傳輸文件時,能夠減小傳輸的時間
- okHttp對gzip的支持
- okHttp支持gzip自動解壓縮,不須要設置Accept-Encoding爲gzip
- 開發者手動設置Accept-Encoding,okHttp不負責解壓縮
- 開發者沒有設置Accept-Encoding時,則自動添加Accept-Encoding: gzip,自動添加的request,response支持自動解壓
- 自動解壓時移除Content-Length,因此上層Java代碼想要contentLength時爲-1
- 自動解壓時移除 Content-Encoding
- 自動解壓時,若是是分塊傳輸編碼,Transfer-Encoding: chunked不受影響
- 咱們在向服務器提交大量數據的時候,但願對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();
- okHttp支持gzip自動解壓縮,不須要設置Accept-Encoding爲gzip
- gzip概念:HTTP協議上的GZIP編碼是一種用來改進WEB應用程序性能的技術
- 改變數據結構,就是將原始集合按照集合中對象的屬性進行拆分,變成多個屬性集合的形式.
- 改變數據結構後JSON字符串長度有明顯下降
- 使用GZIP對JSON字符串進行壓縮,在原始集合中元素數據重複率逐漸變大的狀況下,GZIP壓縮後的原始JSON字符串長度/GZIP壓縮後的改變數據結構的JSON字符串會明顯大於1.
- 代碼及測試結果以下,結果僅供參考(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.線程
-
線程就是代碼線性執行,執行完畢就結束的一條線.UI線程不會結束是由於其初始化完畢後會執行死循環,因此永遠不會執行完畢.
-
如何簡單建立新線程:
//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種方式
- Executors.newCachedThreadPool():通常狀況下使用newCachedThreadPool便可.
- 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();
- 《阿里巴巴Java開發手冊》規定:
- 線程池不容許使用 Executors 去建立,而是經過 ThreadPoolExecutor 的方式.這樣 的處理方式讓寫的同窗更加明確線程池的運行規則,規避資源耗盡的風險
- 看Android中Executors源碼.Executors.newCachedThreadPool/newScheduledThreadPool容許的建立線程數量爲 Integer.MAX_VALUE,可能會建立大量的線程,從而致使 OOM.而newFixedThreadPool,newSingleThreadExecutor不會存在這種風險.
- 如何正確建立ThreadPoolExecutor:有點麻煩,晚點詳述
- ExecutorService的shutdown和shutdownNow
- shutdown:在調用shutdown以前ExecutorService中已經啓動的線程,在調用shutdown後,線程若是執行未結束會繼續執行完畢並結束,但不會再啓動新的線程執行新任務.
- shutdownNow:首先中止啓動新的線程執行新任務;並嘗試結束全部正在執行的線程,正在執行的線程可能被終止也可能會繼續執行完成.
-
如何正確建立ThreadPoolExecutor<br> 3.1:ThreadPoolExecutor構造參數
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
- int corePoolSize:該線程池中核心線程最大數量.默認狀況下,即便核心線程處於空閒狀態也不會被銷燬.除非經過allowCoreThreadTimeOut(true),則核心線程在空閒時間達到keepAliveTime時會被銷燬<br>
- int maximumPoolSize:該線程池中線程最大數量<br>
- long keepAliveTime:該線程池中非核心線程被銷燬前最大空閒時間,時間單位由unit決定.默認狀況下核心線程即便空閒也不會被銷燬,在調用allowCoreThreadTimeOut(true)後,該銷燬時間設置也適用於核心線程<br>
- TimeUnit unit:keepAliveTime/被銷燬前最大空閒時間的單位<br>
- BlockingQueue<Runnable> workQueue:該線程池中的任務隊列.維護着等待被執行的Runnable對象.BlockingQueue有幾種類型,下面會詳述<br>
- ThreadFactory threadFactory:建立新線程的工廠.通常狀況使用Executors.defaultThreadFactory()便可.固然也能夠自定義.<br>
- RejectedExecutionHandler handler:拒絕策略.當須要建立的線程數量達到maximumPoolSize而且等待執行的Runnable數量超過了任務隊列的容量,該如何處理.<br>
3.2:當1個任務被放進線程池,ThreadPoolExecutor具體執行策略以下:<br>
- 若是線程數量沒有達到corePoolSize,有核心線程空閒則核心線程直接執行,沒有空閒則直接新建核心線程執行任務;
- 若是線程數量已經達到corePoolSize,且核心線程無空閒,則將任務添加到等待隊列;
- 若是等待隊列已滿,則新建非核心線程執行該任務;
- 若是等待隊列已滿且總線程數量已達到maximumPoolSize,則會交由RejectedExecutionHandler handler處理.<br>
3.3:阻塞隊列/BlockingQueue<Runnable> workQueue<br>
- BlockingQueue有以下幾種:SynchronousQueue/LinkedBlockingQueue/LinkedTransferQueue/ArrayBlockingQueue/PriorityBlockingQueue/DelayQueue.
- SynchronousQueue:SynchronousQueue的容量是0,不存儲任何Runnable實例.新任務到來會直接嘗試交給線程執行,如全部線程都在忙就建立新線程執行該任務.
- LinkedBlockingQueue:默認狀況下沒有容量限制的隊列.
- ArrayBlockingQueue:一個有容量限制的隊列.
- DelayQueue:一個沒有容量限制的隊列.隊列中的元素必須實現了Delayed接口.元素在隊列中的排序按照當前時間的延遲值,延遲最小/最先要被執行的任務排在隊列頭部,依次排序.延遲時間到達後執行指定任務.
- PriorityBlockingQueue:一個沒有容量限制的隊列.隊列中元素必須實現了Comparable接口.隊列中元素排序依賴元素的天然排序/compareTo的比較結果.
- 各類BlockingQueue的問題<br> 1.SynchronousQueue缺點:由於不具有存儲元素的能力,於是當任務很頻繁時候,爲了防止線程數量超標,咱們每每設置maximumPoolSize是Integer.MAX_VALUE,建立過多線程會致使OOM.《阿里巴巴Java開發手冊》中強調不能使用Executors直接建立線程池,就是對應Android源碼中newCachedThreadPool和newScheduledThreadPool,本質上就是建立了maximumPoolSize爲Integer.MAX_VALUE的ThreadPoolExecutor.<br> 2.LinkedBlockingQueue由於沒有容量限制,因此咱們使用LinkedBlockingQueue建立ThreadPoolExecutor,設置maximumPoolSize是無心義的,若是線程數量已經達到corePoolSize,且核心線程都在忙,那麼新來的任務會一直被添加到隊列中.只要核心線程無空閒則一直得不到被執行機會.<br> 3.DelayQueue和PriorityBlockingQueue也具備一樣的問題.因此corePoolSize必須設置合理,不然會致使超出核心線程數量的任務一直得不到機會被執行.這兩類隊列分別適用於定時及優先級明確的任務.<br>
3.4:RejectedExecutionHandler handler/拒絕策略有4種<br> 1.hreadPoolExecutor.AbortPolicy:丟棄任務,並拋出RejectedExecutionException異常.ThreadPoolExecutor默認就是使用AbortPolicy.<br> 2.ThreadPoolExecutor.DiscardPolicy:丟棄任務,但不會拋出異常.<br> 3.ThreadPoolExecutor.DiscardOldestPolicy:丟棄排在隊列頭部的任務,不拋出異常,並嘗試從新執行任務.<br> 4.ThreadPoolExecutor.CallerRunsPolicy:丟棄任務,但不拋出異常,並將該任務交給調用此ThreadPoolExecutor的線程執行.
-
synchronized 的本質
- 保證synchronized方法或者代碼塊內部資源/數據的互斥訪問
- 即同一時間,由同一個Monitor監視的代碼,最多隻有1個線程在訪問
- 保證線程之間對監視資源的數據同步.
- 任何線程在獲取Monitor後,會第一時間將共享內存中的數據複製到本身的緩存中;
- 任何線程在釋放Monitor後,會第一時間將緩存中的數據複製到共享內存中
- 保證synchronized方法或者代碼塊內部資源/數據的互斥訪問
-
volatile
- 保證被volatile修飾的成員的操做具備原子性和同步性.至關於簡化版的synchronized
- 原子性就是線程間互斥訪問
- 同步性就是線程之間對監視資源的數據同步
- volatile生效範圍:基本類型的直接複製賦值 + 引用類型的直接賦值
//引用類型的直接賦值操做有效 private volatile User u = U1; //修改引用類型的屬性,則不是原子性的,volatile無效 U1.name = "吊炸天" //對引用類型的直接賦值是原子性的 u = U2; private volatile int a = 0; private int b = 100; //volatile沒法實現++/--的原子性 a++;
- volatile型變量自增操做的隱患
- volatile類型變量每次在讀取的時候,會越過線程的工做內存,直接從主存中讀取,也就不會產生髒讀
- ++自增操做,在Java對應的彙編指令有三條
- 從主存讀取變量值到cpu寄存器
- 寄存器裏的值+1
- 寄存器的值寫回主存
- 若是N個線程同時執行到了第1步,那麼最終變量會損失(N-1).第二步第三步只有一個線程是執行成功.
- 對變量的寫操做不依賴於當前值,才能用volatile修飾.
- volatile型變量自增操做的隱患
- 保證被volatile修飾的成員的操做具備原子性和同步性.至關於簡化版的synchronized
-
針對num++這類複合類的操做,可使用java併發包中的原子操做類原子操做類:AtomicInteger AtomicBoolean等來保證其原子性.
public static AtomicInteger num = new AtomicInteger(0); num.incrementAndGet();//原子性的num++,經過循環CAS方式
2.線程間交互
- 一個線程終結另外一個線程
- 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(); }
- Thread.interrupt:僅僅設置當前線程爲被中斷狀態.在運行的線程依然會繼續運行.
- Thread.isInterrupted:獲取當前線程是否被中斷
- Thread.interrupted():若是線程A調用了Thread.interrupted()
- 若是A以前已經被中斷,調用Thread.interrupted()返回false,A已經不是被中斷狀態
- 若是A以前不是被中斷狀態,調用Thread.interrupted()返回true,A變成被中斷狀態.
- 單純調用A.interrupt是無效果的,interrupt須要和isInterrupted聯合使用
- 用於咱們但願線程處於被中斷狀態時結束運行的場景.
- interrupt和stop比較的優勢:stop後,線程直接結束,咱們徹底沒法控制當前執行到哪裏;<br> 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
- InterruptedException:
- 若是線程A在sleep過程當中被其餘線程調用A.interrupt(),會觸發InterruptedException.
- 若是調用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
- Thread.stop不要用:
- 線程等待:wait,notifyAll,notify
- wait,notifyAll,notify是屬於Object的方法.用於線程等待的場景,需用Monitor進行調用
- wait:
- 當1個線程A持有Monitor M.
- 此時調用M.wait,A會釋放M並處於等待狀態.並記錄A在當前代碼執行的位置Position.
- notify:
- 當調用M.notify(),就會喚醒1個由於調用M.wait()而處於等待狀態的線程
- 若是有A,B,C--多個線程都是由於調用M.wait()而處於等待狀態,不必定哪一個會被喚醒並嘗試獲取M
- notifyAll:
- 當調用M.notifyAll(),全部由於調用M.wait()而處於等待狀態的線程都被喚醒,一塊兒競爭嘗試獲取M
- 調用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 如何選擇
- HandlerThead就不要用,HandlerThead設計目的就是爲了主界面死循環刷新界面,無其餘應用場景.
- 能用線程池就用線程池,由於最簡單.
- 涉及後臺線程推送任務到UI線程,可使用Handler或AsyncTask
- Service:就是爲了作後臺任務,不要UI界面,須要持續存活.有複雜的須要長期存活/等待的場景使用Service.
- IntentService:屬於Service.當咱們須要使用Service,且須要後臺代碼執行完畢後該Service自動被銷燬,使用IntentService.
4.AsyncTask的內存泄漏
- GC Roots:由堆外指向堆內的引用,包括:
- Java方法棧幀中的局部變量
- 已加載類的靜態變量
- native代碼的引用
- 運行中的Java線程
- AsyncTask內存泄漏本質:正在運行的線程/AsyncTask 在虛擬機中屬於GC ROOTS,AsyncTask持有外部Activity的引用.被GC ROOTS引用的對象不能被回收.
- 因此AsyncTask和其餘線程工具同樣,只要是使用線程,都有可能發生內存泄漏,都要及時關閉,AsyncTask並不比其餘工具更差.
- 如何避免AsyncTask內存泄漏:使用弱引用解決AsyncTask在Activity銷燬後依然持有Activity引用的問題
5.RxJava.
講的太多了這裏推薦1個專題RxJava2.x<br> 下面記錄一下本身不太熟的幾點<br>
- RxJava總體結構:
- 鏈的最上游:生產者Observable
- 鏈的最下游:觀察者Observer
- 鏈的中間多個節點:雙重角色.便是上一節點的觀察者Observer,也是下一節點的生產者Observable.
- Scheduler切換線程的原理:源碼跟蹤下去,實質是經過Excutor實現了線程切換.
6.Android M對Profile GPU Rendering工具的更新
- Swap Buffers:CPU等待GPU處理的時間
- Command Issur:OpenGL渲染Display List所須要的時間
- Sync&Upload:一般表示的是準備當前界面上有待繪製的圖片所耗費的時間,爲了減小該段區域的執行時間,咱們能夠減小屏幕上的圖片數量或者是縮小圖片自己的大小
- Draw:測量繪製Display List的時間
- Measure & Layout:這裏表示的是佈局的onMeasure與onLayout所花費的時間.一旦時間過長,就須要仔細檢查本身的佈局是否是存在嚴重的性能問題
- Animation:表示的是計算執行動畫所須要花費的時間.包含的動畫有ObjectAnimator,ViewPropertyAnimator,Transition等等.一旦這裏的執行時間過長,就須要檢查是否是使用了非官方的動畫工具或者是檢查動畫執行的過程當中是否是觸發了讀寫操做等等
- Input Handling:表示的是系統處理輸入事件所耗費的時間,粗略等於對於的事件處理方法所執行的時間.一旦執行時間過長,意味着在處理用戶的輸入事件的地方執行了複雜的操做
- 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.啓動閃屏
- 當點擊桌面圖標啓動APP的時候,App會出現短暫的白屏,一直到第一個Activity的頁面的渲染加載完畢
- 爲了消除白屏,咱們能夠爲App入口Activity單獨設置theme.
- 在單獨設置的theme中設置android:background屬性爲App的品牌宣傳圖片背景.
- 在代碼執行到入口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張圖片,放在mipmap-nodpi,或mipmap-xxxhdpi下
- 全部的大背景圖片,統一放在mipmap-nodpi目錄,用一套1080P素材能夠解決大部分手機適配問題,不用每一個資源目錄下放一套素材
- 通過試驗,不論ImageView寬高是不是wrap_content,只要圖片所在文件夾和當前設備分辨率不匹配,都會涉及到放大或壓縮,佔用的內存都會相應的變化.尤爲對於大圖,放在低分辨率文件夾下直接OOM. <br>具體緣由:<br> 郭霖: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.儘可能複用已經存在的圖片.<br>
好比一張圖片O已經存在,若是有View的背景就是O旋轉事後的樣子,能夠直接用O建立RotateDrawable.而後將設置給View使用.<br> 注意:RotateDrawable已經重寫了其onLevelChange方法,因此必定要設置level纔會生效<br>
@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.下降網絡請求數量:獲取一樣的數據,屢次網絡請求會增長電量消耗,且屢次請求整體上將消耗服務端更多的時間及資源<br>
- 接口Api設計要合理.能夠將多個接口合併,屢次請求顯示1個界面,改造後1個接口便可提供完整數據.
- 根據具體場景實時性需求,在App中加入網絡緩存,在實時性有效區間避免重複請求:主要包括網絡框架和圖片加載框架的緩存.
2.下降單次請求的數據量
- 網絡接口Api在設計時候,去除多餘的請求參數及響應數據.
- 網絡請求及響應數據的傳輸開啓GZIP壓縮,下降傳輸數據量.
- okHttp對gzip的支持前面已記錄
- Protocal Buffers,Nano-Proto-Buffers,FlatBuffers代替GSON執行序列化.
- Protocal Buffers網上有使用的方法,相對GSON有點繁瑣.若是對網絡傳輸量很敏感,能夠考慮使用.其餘幾種方案的文章很少.
- 網絡請求圖片,添加圖片寬高參數,避免下載過大圖片增長流量消耗.
3.弱網環境優化這塊沒有經驗,直接看anly_jun的文章
App優化之網絡優化<br> 文章中提到:用戶點贊操做, 能夠直接給出界面的點同意功的反饋, 使用JobScheduler在網絡狀況較好的時候打包請求.
11.電量優化
12.JobScheduler,AlarmManager和WakeLock
JobScheduler在網絡優化中出現過,WakeLock涉及電量優化,AlarmManager和WakeLock有類似,但側重點不一樣.
- WakeLock:好比一段關鍵邏輯T已經在執行,執行未完成Android系統就進入休眠,會致使T執行中斷.WakeLock目的就在於阻止Android系統進入休眠狀態,保證T得以繼續執行.
- 休眠過程當中自定義的Timer、Handler、Thread、Service等都會暫停
- AlarmManager:Android系統自帶的定時器,能夠將處於休眠狀態的Android系統喚醒
- 保證Android系統在休眠狀態下被及時喚醒,執行 定時/延時/輪詢任務
- JobScheduler:JobScheduler目的在於將當下不緊急的任務延遲到後面更合適的某個時間來執行.咱們能夠控制這些任務在什麼條件下被執行.
- JobScheduler能夠節約Android設備當下網絡,電量,CPU等資源.在指定資源充裕狀況下再執行"不緊要"的任務.
JobScheduler:<br> Android Jobscheduler使用<br> Android開發筆記(一百四十三)任務調度JobScheduler<br> WakeLock:<br> Android WakeLock詳解<br> Android PowerManager.WakeLock使用小結<br> Android的PowerManager和PowerManager.WakeLock用法簡析<br> AlarmManager和WakeLock使用:<br> 後臺任務 - 保持設備喚醒狀態
13.性能檢測工具
1.Android Studio 3.2以後,Android Device Monitor已經被移除.Android Device Monitor原先包含的工具由新的方案替代.Android Device Monitor
- DDMS:由Android Profiler代替.能夠進行CPU,內存,網絡分析.
- TraceView:能夠經過Debug類在代碼中調用Debug.startMethodTracing(String tracePath)和Debug.stopMethodTracing()來記錄二者之間全部線程及線程中方法的耗時,生成.trace文件.經過abd命令能夠將trace文件導出到電腦,經過CPU profiler分析.
- Systrace:能夠經過命令行生成html文件,經過Chrome瀏覽器進行分析.
- 生成html文件已實現.但文件怎麼分析暫未掌握,看了網上一些文章說實話仍是沒搞懂
- Hierarchy Viewer:由Layout Inspector代替.但當前版本的Layout Inspector不能查看每一個View具體的onMeasure,onLayout,onDraw耗時,功能是不足的.咱們可以使用系統提供的Window.OnFrameMetricsAvailableListener來計算指定View的onLayout及onDraw耗時.
- Network Traffic tool:由Network Profiler代替.
2.其中Android Profiler如何使用,直接看官網便可.Profile your app performance.
3.TraceView
- TraceView能夠直接經過CPU profiler中點擊Record按鈕後,任意時間後點擊Stop按鈕.便可生成trace文件.並可將.trace文件導出.
- TraceView也能夠經過Debug類在代碼中精確控制要統計哪一個區間代碼/線程的CPU耗時. <br>這種用法是 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<br> 在JetApp.trace上點擊右鍵->Copy Path,將trace文件路徑複製下來.<br> Windows下cmd打開命令行,執行 adb pull 路徑,便可trace文件導出到電腦. - 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) { **** } }
- 在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
- 在Application中使用OnFrameMetricsAvailableListener,則能夠統一設置,不須要每一個Activity單獨設置,推薦使用開源項目ActivityFrameMetrics
- 在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()); } }
- 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%) ****
- 在Application的onCreate中設置單幀渲染總時間超過W毫秒和E毫秒,會在Logcat中打印警告和錯誤的Log信息.
5.Systrace:經過命令行生成html文件,經過Chrome瀏覽器進行分析
- 首先電腦要安裝python,這裏有幾個坑:<br>
- Python要安裝2.7x版本,不能安裝最新的3.x.
- 好比本身電腦中systrace文件夾路徑是:C:\Users\你的用戶名\AppData\Local\Android\Sdk\platform-tools\systrace,若是咱們安裝的是3.x版本,在這個路徑下執行python systrace.py *** 命令會報錯,提示你應該安裝2.7
- Python安裝時候,要記得勾選"Add python.exe to Path".
- 這時候直接執行python systrace.py ***命令仍是會報錯:ImportError: No module named win32com
- Python要安裝2.7x版本,不能安裝最新的3.x.
- 生成html及如何分析