【0176】Android 面試- Android異常與性能優化相關

1.anr異常

1.1 異常的認識

  產生的主要緣由是在主線程中作了耗時的操做;php

1.2 主線程有哪些

 

1.3 解決anr

【摘抄文章】html

1, 你碰到ANR了嗎
在App使用過程當中, 你可能遇到過這樣的狀況:

ANR
恭喜你, 這就是傳說中的ANR.

1.1 何爲ANR
ANR全名Application Not Responding, 也就是"應用無響應". 當操做在一段時間內系統沒法處理時, 系統層面會彈出上圖那樣的ANR對話框.

1.2 爲何會產生ANR
在Android裏, App的響應能力是由Activity Manager和Window Manager系統服務來監控的. 一般在以下兩種狀況下會彈出ANR對話框:

5s內沒法響應用戶輸入事件(例如鍵盤輸入, 觸摸屏幕等). BroadcastReceiver在10s內沒法結束.
形成以上兩種狀況的首要緣由就是在主線程(UI線程)裏面作了太多的阻塞耗時操做, 例如文件讀寫, 數據庫讀寫, 網絡查詢等等.
1.3 如何避免ANR 知道了ANR產生的緣由, 那麼想要避免ANR, 也就很簡單了, 就一條規則: 不要在主線程(UI線程)裏面作繁重的操做. 這裏面實際上涉及到兩個問題: 哪些地方是運行在主線程的? 不在主線程作, 在哪兒作? 稍後解答. 2, ANR分析 2.1 獲取ANR產生的trace文件 ANR產生時, 系統會生成一個traces.txt的文件放在/data/anr/. 能夠經過adb命令將其導出到本地: $adb pull data/anr/traces.txt .
2.2 分析traces.txt 2.2.1 普通阻塞致使的ANR 獲取到的tracs.txt文件通常以下: 以下以GithubApp代碼爲例, 強行sleep thread產生的一個ANR. ----- pid 2976 at 2016-09-08 23:02:47 ----- Cmd line: com.anly.githubapp // 最新的ANR發生的進程(包名) ... DALVIK THREADS (41): "main" prio=5 tid=1 Sleeping | group="main" sCount=1 dsCount=0 obj=0x73467fa8 self=0x7fbf66c95000 | sysTid=2976 nice=0 cgrp=default sched=0/0 handle=0x7fbf6a8953e0 | state=S schedstat=( 0 0 0 ) utm=60 stm=37 core=1 HZ=100 | stack=0x7ffff4ffd000-0x7ffff4fff000 stackSize=8MB | held mutexes= at java.lang.Thread.sleep!(Native method) - sleeping on <0x35fc9e33> (a java.lang.Object) at java.lang.Thread.sleep(Thread.java:1031) - locked <0x35fc9e33> (a java.lang.Object) at java.lang.Thread.sleep(Thread.java:985) // 主線程中sleep過長時間, 阻塞致使無響應. at com.tencent.bugly.crashreport.crash.c.l(BUGLY:258) - locked <@addr=0x12dadc70> (a com.tencent.bugly.crashreport.crash.c) at com.tencent.bugly.crashreport.CrashReport.testANRCrash(BUGLY:166) // 產生ANR的那個函數調用 - locked <@addr=0x12d1e840> (a java.lang.Class<com.tencent.bugly.crashreport.CrashReport>) at com.anly.githubapp.common.wrapper.CrashHelper.testAnr(CrashHelper.java:23) at com.anly.githubapp.ui.module.main.MineFragment.onClick(MineFragment.java:80) // ANR的起點 at com.anly.githubapp.ui.module.main.MineFragment_ViewBinding$2.doClick(MineFragment_ViewBinding.java:47) at butterknife.internal.DebouncingOnClickListener.onClick(DebouncingOnClickListener.java:22) at android.view.View.performClick(View.java:4780) at android.view.View$PerformClick.run(View.java:19866) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:135) at android.app.ActivityThread.main(ActivityThread.java:5254) at java.lang.reflect.Method.invoke!(Native method) at java.lang.reflect.Method.invoke(Method.java:372) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698) 拿到trace信息, 一切好說. 如上trace信息中的添加的中文註釋已基本說明了trace文件該怎麼分析: 文件最上的即爲最新產生的ANR的trace信息. 前面兩行代表ANR發生的進程pid, 時間, 以及進程名字(包名). 尋找咱們的代碼點, 而後往前推, 看方法調用棧, 追溯到問題產生的根源. 以上的ANR trace是屬於相對簡單, 還有可能你並無在主線程中作過於耗時的操做, 然而仍是ANR了. 這就有多是以下兩種狀況了: 2.2.2 CPU滿負荷 這個時候你看到的trace信息可能會包含這樣的信息: Process:com.anly.githubapp ... CPU usage from 3330ms to 814ms ago: 6% 178/system_server: 3.5% user + 1.4% kernel / faults: 86 minor 20 major 4.6% 2976/com.anly.githubapp: 0.7% user + 3.7% kernel /faults: 52 minor 19 major 0.9% 252/com.android.systemui: 0.9% user + 0% kernel ... 100%TOTAL: 5.9% user + 4.1% kernel + 89% iowait 最後一句代表了: 當是CPU佔用100%, 滿負荷了. 其中絕大數是被iowait即I/O操做佔用了. 此時分析方法調用棧, 通常來講會發現是方法中有頻繁的文件讀寫或是數據庫讀寫操做放在主線程來作了. 2.2.3 內存緣由 其實內存緣由有可能會致使ANR, 例如若是因爲內存泄露, App可以使用內存所剩無幾, 咱們點擊按鈕啓動一個大圖片做爲背景的activity, 就可能會產生ANR, 這時trace信息多是這樣的: // 如下trace信息來自網絡, 用來作個示例 Cmdline: android.process.acore DALVIK THREADS: "main"prio=5 tid=3 VMWAIT |group="main" sCount=1 dsCount=0 s=N obj=0x40026240self=0xbda8 | sysTid=1815 nice=0 sched=0/0 cgrp=unknownhandle=-1344001376 atdalvik.system.VMRuntime.trackExternalAllocation(NativeMethod) atandroid.graphics.Bitmap.nativeCreate(Native Method) atandroid.graphics.Bitmap.createBitmap(Bitmap.java:468) atandroid.view.View.buildDrawingCache(View.java:6324) atandroid.view.View.getDrawingCache(View.java:6178) ... MEMINFO in pid 1360 [android.process.acore] ** native dalvik other total size: 17036 23111 N/A 40147 allocated: 16484 20675 N/A 37159 free: 296 2436 N/A 2732 能夠看到free的內存已所剩無幾. 固然這種狀況可能更多的是會產生OOM的異常... 2.2 ANR的處理
針對三種不一樣的狀況, 通常的處理狀況以下 【1】主線程阻塞的:開闢單獨的子線程來處理耗時阻塞事務. 【2】CPU滿負荷, I
/O阻塞的 I/O阻塞通常來講就是文件讀寫或數據庫操做執行在主線程了, 也能夠經過開闢子線程的方式異步執行. 【3】內存不夠用的 增大VM內存, 使用largeHeap屬性, 排查內存泄露(這個在內存優化那篇細說吧)等. 3, 深刻一點 沒有人願意在出問題以後去解決問題. 高手和新手的區別是, 高手知道怎麼在一開始就避免問題的發生. 那麼針對ANR這個問題, 咱們須要作哪些層次的工做來避免其發生呢? 3.1 哪些地方是執行在主線程的
Activity的全部生命週期回調都是執行在主線程的. Service默認是執行在主線程的. BroadcastReceiver的onReceive回調是執行在主線程的. 沒有使用子線程的looper的Handler的handleMessage, post(Runnable)是執行在主線程的. AsyncTask的回調中除了doInBackground, 其餘都是執行在主線程的. View的post(Runnable)是執行在主線程的.
3.2 使用子線程的方式有哪些 上面咱們幾乎一直在說, 避免ANR的方法就是在子線程中執行耗時阻塞操做. 那麼在Android中有哪些方式可讓咱們實現這一點呢. 3.2.1 啓Thread方式 這個其實也是Java實現多線程的方式. 有兩種實現方法, 繼承Thread 或 實現Runnable接口: 繼承Thread class PrimeThread extends Thread { long minPrime; PrimeThread(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } } PrimeThread p = new PrimeThread(143); p.start(); 實現Runnable接口 class PrimeRun implements Runnable { long minPrime; PrimeRun(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime . . . } } PrimeRun p = new PrimeRun(143); new Thread(p).start(); 3.2.2 使用AsyncTask 這個是Android特有的方式, AsyncTask顧名思義, 就是異步任務的意思. private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> { // Do the long-running work in here // 執行在子線程 protected Long doInBackground(URL... urls) { int count = urls.length; long totalSize = 0; for (int i = 0; i < count; i++) { totalSize += Downloader.downloadFile(urls[i]); publishProgress((int) ((i / (float) count) * 100)); // Escape early if cancel() is called if (isCancelled()) break; } return totalSize; } // This is called each time you call publishProgress() // 執行在主線程 protected void onProgressUpdate(Integer... progress) { setProgressPercent(progress[0]); } // This is called when doInBackground() is finished // 執行在主線程 protected void onPostExecute(Long result) { showNotification("Downloaded " + result + " bytes"); } } // 啓動方式 new DownloadFilesTask().execute(url1, url2, url3); 3.2.3 HandlerThread Android中結合Handler和Thread的一種方式. 前面有云, 默認狀況下Handler的handleMessage是執行在主線程的, 可是若是我給這個Handler傳入了子線程的looper, handleMessage就會執行在這個子線程中的. HandlerThread正是這樣的一個結合體: // 啓動一個名爲new_thread的子線程 HandlerThread thread = new HandlerThread("new_thread"); thread.start(); // 取new_thread賦值給ServiceHandler private ServiceHandler mServiceHandler; mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { // 此時handleMessage是運行在new_thread這個子線程中了. } } 3.2.4 IntentService Service是運行在主線程的, 然而IntentService是運行在子線程的. 實際上IntentService就是實現了一個HandlerThread + ServiceHandler的模式. 以上HandlerThread的使用代碼示例也就來自於IntentService源碼. 3.2.5 Loader Android 3.0引入的數據加載器, 能夠在Activity/Fragment中使用. 支持異步加載數據, 並可監控數據源在數據發生變化時傳遞新結果. 經常使用的有CursorLoader, 用來加載數據庫數據. // Prepare the loader. Either re-connect with an existing one, // or start a new one. // 使用LoaderManager來初始化Loader getLoaderManager().initLoader(0, null, this); //若是 ID 指定的加載器已存在,則將重複使用上次建立的加載器。 //若是 ID 指定的加載器不存在,則 initLoader() 將觸發 LoaderManager.LoaderCallbacks 方法 //onCreateLoader()。在此方法中,您能夠實現代碼以實例化並返回新加載器 // 建立一個Loader public Loader<Cursor> onCreateLoader(int id, Bundle args) { // This is called when a new Loader needs to be created. This // sample only has one Loader, so we don't care about the ID. // First, pick the base URI to use depending on whether we are // currently filtering. Uri baseUri; if (mCurFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); } else { baseUri = Contacts.CONTENT_URI; } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); } // 加載完成 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data); } 具體請參看官網Loader介紹. 3.2.6 特別注意 使用Thread和HandlerThread時, 爲了使效果更好, 建議設置Thread的優先級偏低一點: Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND); 由於若是沒有作任何優先級設置的話, 你建立的Thread默認和UI Thread是具備一樣的優先級的, 你懂的. 一樣的優先級的Thread, CPU調度上仍是可能會阻塞掉你的UI Thread, 致使ANR的. 結語 對於ANR問題, 我的認爲仍是預防爲主, 認清代碼中的阻塞點, 善用線程. 同時造成良好的編程習慣, 要有MainThread和Worker Thread的概念的...(實際上人的工做狀態也是這樣的~~哈哈) 做者:anly_jun 連接:https://www.jianshu.com/p/6d855e984b99

2.oom異常

2.1 什麼是oom?

【什麼】Android系統會爲每一個app分配固定的內存空間java

 

2.2  概念的分辨

內存抖動】內存抖動是指在時間內有大量對象被建立或者被回收的現象,主要是循環中大量建立、回收對象。這種狀況應當儘可能避免。android

【內存泄露】程序經過new分配內存,在使用完畢後沒有釋放,形成內存佔用。這塊內存不受GC控制,沒法經過GC回收git

主要表如今:當一個對象已經再也不使用,本該被回收的,可是另一個正在使用的對象持有它的引用從而就致使對象不能被回收。這種對象存在堆內存中,就產生了內存泄漏。
危害?內存泄漏對於app沒有直接的危害,即便app有發生內存泄漏的狀況,也不必定會引發app崩潰,可是會增長app內存的佔用。內存得不到釋放,慢慢的會形成app內存溢出。解決內存泄漏目的就是防止app發生內存溢出。
【內存溢出】當前佔用的內存加上申請的內存資源超過了Dalvik虛擬機的最大的內存限制就會拋出oom異常;

 2.3 內存溢出的解決--與圖片相關

【圖片的請求】在顯示縮略圖的時候,不要調用網絡請求加載大圖;例如:在listView的滑動的時候不要調用網絡請求加載圖片,只有在不滑動的時候再加載圖片;程序員

【及時釋放內存】bitmap實際上的生成是java和c兩部分的內存同時生成的;github

【圖片的壓縮】在加載大圖以前須要bitmap的計算而後進行壓縮;web

 【isBitmap高級屬性使用】參考下面的文章面試

【捕獲outMemoryError屬性】算法

安卓編程:能否用try-catch捕獲Out Of Memory Error以免其發生?
現已知代碼A可能引起OOM。代碼B可替代代碼A但可維護性差。我但願能先嚐試執行代碼A,若是發生OOM,則退回來執行代碼B。
請問如下解決方案是否可行。
try {
    代碼A
} catch (OutOfMemoryError ignored) {
    代碼B
}

試驗了一下彷佛可行。但有同事認爲OOM發生在系統層級,上述代碼沒法得到我指望的效果。
=================
只有在一種狀況下,這樣作是可行的:在try語句中聲明瞭很大的對象,致使OOM,而且能夠確認OOM是由try語句中的對象聲明致使的,那麼在catch語句中,能夠釋放掉這些對象
解決OOM的問題,繼續執行剩餘語句。可是這一般不是合適的作法。Java中管理內存除了顯式地catch OOM以外還有更多有效的方法:好比SoftReference, WeakReference,
硬盤緩存等。在JVM用光內存以前,會屢次觸發GC,這些GC會下降程序運行的效率。若是OOM的緣由不是try語句中的對象(好比內存泄漏),那麼在catch語句中會繼續拋出OOM

2.4 其餘的方法

 

2.4 內存相關內容

【參考內容】

內存

JAVA是在JVM所虛擬出的內存環境中運行的,內存分爲三個區:堆、棧和方法區。
棧(stack):是簡單的數據結構,程序運行時系統 自動分配,使用完畢後 自動釋放。優勢:速度快。
堆(heap):用於存放 由new建立的對象和數組。在堆中分配的內存,一方面由java虛擬機自動垃圾回收器來管理,另外一方面還須要程序員提供修養,防止內存泄露問題。
方法區(method):又叫靜態區,跟堆同樣,被全部的 線程共享。方法區包含 全部的class和static變量

Java GC

GC能夠自動清理堆中不在使用(不在有對象持有該對象的引用)的對象。

在JAVA中對象若是再沒有引用指向該對象,那麼該對象就無從處理或調用該對象,這樣的對象稱爲不可到達(unreachable)。垃圾回收用於釋放不可到達的對象所佔據的內存。

對android來講,內存使用尤其吃緊,最開始的app進程最大分配才16M的內存,漸漸增長到32M、64M,可是和服務端相比仍是很眇小的。若是對象回收不及時,很容易出現OOM錯誤。

內存泄露

什麼是內存泄露?程序經過new分配內存,在使用完畢後沒有釋放,形成內存佔用。這塊內存不受GC控制,沒法經過GC回收。
主要表如今:當一個對象已經再也不使用,本該被回收的,可是另一個正在使用的對象持有它的引用從而就致使對象不能被回收。這種對象存在堆內存中,就產生了內存泄漏。危害?內存泄漏對於app沒有直接的危害,即便app有發生內存泄漏的狀況,也不必定會引發app崩潰,可是會增長app內存的佔用。內存得不到釋放,慢慢的會形成app內存溢出。解決內存泄漏目的就是防止app發生內存溢出。
內存泄露主要表現的當Activity在finish的時候,因爲對象持有對Activity的引用,形成Activity沒有被及時回收。
總結了下大體有5種狀況形成內存泄露,
(1)static變量、匿名類的使用
(2)線程執行處理
(3)各類監聽回調處置
(4)Bitmap等回收處置
(5)集合類只有增操做卻沒有減操做。
 
常見狀況
1)外部類持有Activity的靜態引用
  1. public class MainActivity extends AppCompatActivity {  
  2.     static Activity activity;  
  3.   
  4.     @Override  
  5.     protected void onCreate(Bundle savedInstanceState) {  
  6.         super.onCreate(savedInstanceState);  
  7.         setContentView(R.layout.activity_main);  
  8.         CommUtil commUtil = CommUtil.getInstance(this);  
  9.     }  
[java]  view plain  copy
 
  1. public class CommUtils {  
  2.     private static CommUtils instance;  
  3.     private Context context;  
  4.   
  5.     private CommUtils(Context context) {  
  6.         this.context = context;  
  7.     }  
  8.   
  9.     public static CommUtils getInstance(Context context) {  
  10.         if (instance == null) {  
  11.             instance = new CommUtils(context);  
  12.         }  
  13.         return instance;  
  14.     }  
  15. }  
2)異步執行耗時任務期間時,Thread、AsyncTask、TimeTask持有的Activty進行finish時,Activity實例不會被回收。
 
  1. protected void onCreate(Bundle savedInstanceState) {  
  2.         super.onCreate(savedInstanceState);  
  3.         setContentView(R.layout.activity_main);  
  4.         new AsyncTask<String, Void, String>() {  
  5.             @Override  
  6.             protected String doInBackground(String... params) {  
  7.                 for (int i = 0; i < 15; i++) {  
  8.                     try {  
  9.                         Log.e("MainActivity2", "dddd" + i + MainActivity2.this.getLocalClassName());  
  10.                         Thread.sleep(1000);  
  11.                     } catch (InterruptedException e) {  
  12.                         e.printStackTrace();  
  13.                     }  
  14.                 }  
  15.                 return null;  
  16.             }  
  17.   
  18.             @Override  
  19.             protected void onPostExecute(String s) {  
  20.                 super.onPostExecute(s);  
  21.             }  
  22.         }.execute();  
  23.     }  
3)Handler內部類形成內存泄露。
Handler爲非靜態內部類時會隱式持有當前activity引用。當Activity被 finish()時,若Handler有未處理完或延遲的消息(主要是Handler牽扯到線程問題),會形成activity不能被回收。
[java]  view plain  copy
 
  1. MyHandler myHandler = new MyHandler();  
  2.   
  3.    @Override  
  4.    protected void onCreate(@Nullable Bundle savedInstanceState) {  
  5.        super.onCreate(savedInstanceState);  
  6.        myHandler.postDelayed(new Runnable() {  
  7.            @Override  
  8.            public void run() {  
  9.   
  10.            }  
  11.        }, 50 * 1000);  
  12.    }  
  13.   
  14.    class MyHandler extends Handler {  
  15.   
  16.        @Override  
  17.        public void handleMessage(Message msg) {  
  18.            super.handleMessage(msg);  
  19.        }  
  20.    }  
解決辦法:在Activity生命週期結束前,確保Handler移除消息(mMyHanlder.removeCallbacksAndMessages(null);)或者使用靜態Handler內部類。
如:使用了弱引用替代強引用.
[java]  view plain  copy
 
  1. static MyHandler myHandler;  
  2.     @Override  
  3.     protected void onCreate(@Nullable Bundle savedInstanceState) {  
  4.         super.onCreate(savedInstanceState);  
  5.         myHandler = new MyHandler(this);  
  6.     }  
  7.     static class MyHandler extends Handler {  
  8.         WeakReference<Activity> mActivityReference;  
  9.   
  10.         MyHandler(Activity activity) {  
  11.             mActivityReference = new WeakReference<Activity>(activity);  
  12.         }  
  13.   
  14.         @Override  
  15.         public void handleMessage(Message msg) {  
  16.             final Activity activity = mActivityReference.get();  
  17.             if (activity != null) {  
  18.                 //....  
  19.             }  
  20.         }  
  21.     }  
建議熟悉下:強引用(StrongReference)、軟引用(SoftReference)、弱引用(WeakReference)、虛引用(PhantomReference)
 
4)匿名內部類的使用。
  1. public class DemoActivity extends AppCompatActivity {  
  2.       
  3.     Runnable runnable = new Runnable() {  
  4.         @Override  
  5.         public void run() {  
  6.   
  7.         }  
  8.     };  
runnable默認會持有DemoActivity的引用。若Activity被finish的時候,如線程在使用runnable,則會形成內存泄露。
 
5)構造Adapter時沒有使用緩存的 convertView
 
[java]  view plain  copy
 
  1. public View getView(int position, View convertView, ViewGroup parent) {  
  2.         View view = null;  
  3.         if (convertView == null)  
  4.             convertView = View.inflate(this, R.layout.item_layout, false);  
  5.         view = convertView;  
  6.         return view;  
  7.     }  
6) 當使用了BraodcastReceiver、Cursor、Bitmap等資源時,若沒有及時釋放,則會引發內存泄漏。
 
7)集合類的不當使用。

更多內存泄露能夠經過檢測工具發現。檢測工具主要有MAT、Memory Monitor 、Allocation Tracker 、Heap Viewer、LeakCanary

卡頓 是怎麼造成的 卡頓的解決方式 ANR

講下 GC 回收致使 畫面卡頓的問題:

這裏寫圖片描述

好比自定義view 中 
繪製第一個畫面 ,繪製完 之後 再繪製第二個畫面 把第一個頁面會的回收掉.時間16毫秒

通常手機 刷新頻率60hz, 1秒鐘 刷新60次 , 那麼 每隔16ms 就須要從新繪製一次 ,假如自定義view的計算超過16ms ,那麼就會造成卡頓的狀況 ,第二個畫面就沒有刷新上去. 就會造成卡頓.

例如 : 在一個長期運行循環的最內側建立對象, 那麼就會不斷增長,不少數據會污染內存堆, 致使GC 回收,因爲 這中額外的內存壓力致使 GC非正常回收,致使屢次回收.就造成卡頓.

須要內存優化.

工具1 momory monitor

做用 : 跟蹤APP內存變化的狀況. 
含義: 內存 監測 
視圖方式觀看 內存變化.

工具2 heap viewer

做用: 獲取內存堆 的快照. 
含義: 堆棧快照 
位置 : 在device monitor 中

問題 1 : 內存泄露, 觀察 momory monitor 出現,內存不斷增長 內存不斷增長狀況 而後下降.

工具 使用

經過 heap viewer 查看

說下內存泄露的狀況: 
內存泄漏是指你向系統申請分配內存進行使用(new),但是使用完了之後卻不歸還(delete),結果你申請到的那塊內存你本身也不能再訪問(也許你把它的地址給弄丟了),而系統也不能再次將它分配給須要的程序。一個盤子用盡各類方法只能裝4個果子,你裝了5個,結果掉倒地上不能吃了。這就是溢出!比方說棧,棧滿時再作進棧一定產生空間溢出,叫上溢,棧空時再作退棧也產生空間溢出,稱爲下溢。就是分配的內存不足以放下數據項序列,稱爲內存溢出.

http://blog.csdn.net/cyq1028/article/details/19980369 這篇文章講的不錯 不過他用的是Eclipse的查看 工具,我用的studio . studio 的這個比較好.

解決方式: 最簡單的方式. 能夠添加一個清理方法. 防止內存泄露 .

解決: 跟蹤內存分配

問題2 內存抖動: 經過momory monitor 發現 出現內存忽上忽下 造成針尖狀的狀況.

這裏寫圖片描述

工具 3 allocation tracking 使用

含義: 分配-跟蹤 
最重要的功能 : 
能夠跟蹤出現問題的源代碼, 上面兩個工具只能觀察現象.

http://www.th7.cn/Program/Android/201602/764859.shtml 所用工具的具體使用

工具4 Trace View

含義: 分配 視圖 
http://blog.jobbole.com/78995/ 鏈接

如今真實測試結果:  

1,爲了搞清楚每一個應用程序在Android系統中最多可分配多少內存空間,咱們使用了真機進行測試,測試機型爲魅族MX4 Pro,3G內存。

測試方法是直接申請一塊較大的內存空間,看應用程序在最多申請多大的內存空間時會崩潰。

  結果:(1)未設定屬性android:largeheap = "true"時,能夠申請到的最大內存空間爲221M。

     (2)設定屬性android:largeheap = "true"時, 能夠申請的最大內存空間爲478M,是原來的兩倍多一些。

  網上有網友提出可申請到的最大內存空間與手機配置有關,之後會加以驗證。

2.實測,不許確, 準確的說話是 google原生OS的默認值是16M,可是各個廠家的OS會對這個值進行修改。

好比本人小米2S爲例,這個值應該是96M。


Runtime rt=Runtime.getRuntime();
long maxMemory=rt.maxMemory();
log.i("maxMemory:",Long.toString(maxMemory/(1024*1024)));
這個能夠直接獲得app可以使用的最大memory size算出來是MB, 得到的是heapgrowthlimit


先看機器的內存限制,在/system/build.prop文件中:
heapgrowthlimit就是一個普通應用的內存限制,用ActivityManager.getLargeMemoryClass()得到的值就是這個。
而heapsize是在manifest中設置了largeHeap=true 以後,能夠使用的最大內存值
結論就是,設置largeHeap的確能夠增長內存的申請量。但不是系統有多少內存就能夠申請多少,而是由dalvik.vm.heapsize限制。
你能夠在app manifest.xml加 largetHeap=true
能夠申請較多的記憶體 ,但還是有機會爆掉.

<application
     .....
     android:label="XXXXXXXXXX"
     android:largeHeap="true">
    .......
</application>


cat /system/build.prop   //讀取這些值
getprop dalvik.vm.heapsize  //若是build.prop裏面沒有heapsize這些值,能夠用這個抓取默認值
setprop dalvik.vm.heapsize 256m  //設置

-----------------------    build.prop 部份內容 ---------------------

dalvik.vm.heapstartsize=8m
dalvik.vm.heapgrowthlimit=96m
dalvik.vm.heapsize=384m
dalvik.vm.heaputilization=0.25
dalvik.vm.heapidealfree=8388608
dalvik.vm.heapconcurrentstart=2097152
ro.setupwizard.mode=OPTIONAL
ro.com.google.gmsversion=4.1_r6
net.bt.name=Android
dalvik.vm.stack-trace-file=/data/anr/traces.txt


最先的說法:

1、APP默認分配內存大小

在Android裏,程序內存被分爲2部分:native和dalvik,dalvik就是咱們普通的java使用內存,也就是咱們上一篇文章分析堆棧的時候使用的內存。咱們建立的對象是在這裏面分配的,
對於內存的限制是 native
+dalvik 不能超過最大限制。android程序內存通常限制在16M,也有的是24M(早期的Android系統G1,就是隻有16M)。具體看定製系統的設置,
在Linux初始化代碼裏面Init.c,能夠查到到默認的內存大小。有興趣的朋友,能夠分析一下虛擬機啓動相關代碼。這塊比較深刻,目前我也沒時間去分析,後面有空會去鑽研一下。   gDvm.heapSizeStart
= 2 * 1024 * 1024; // heap初始化大小爲2M   gDvm.heapSizeMax = 16 * 1024 * 1024; // 最大的heap爲16M      2、Android的GC如何回收內存 Android的一個應用程序的內存泄露對別的應用程序影響不大。爲了可以使得Android應用程序安全且快速的運行,Android的每一個應用程序都會使用一個專有的Dalvik虛擬機實例來運行,
它是由Zygote服務進程孵化出來的,也就是說每一個應用程序都是在屬於本身的進程中運行的。Android爲不一樣類型的進程分配了不一樣的內存使用上限,若是程序在運行過程當中出現了內存泄漏的而形成應用進程使用的內存超過了這個上限,
則會被系統視爲內存泄漏,從而被kill掉,這使得僅僅本身的進程被kill掉,而不會影響其餘進程(若是是system_process等系統進程出問題的話,則會引發系統重啓)。 作應用開發的時候,你須要瞭解系統的GC(垃圾回收)機制是如何運行的,Android裏面使用有向圖做爲遍歷回收內存的機制。Java將引用關係考慮爲圖的有向邊,有向邊從引用者指向引用對象。線程對象能夠做爲有向圖的起始頂點,
該圖就是從起始頂點開始的一棵樹,根頂點能夠到達的對象都是有效對象,GC不會回收這些對象。若是某個對象 (連通子圖)與這個根頂點不可達(注意,該圖爲有向圖),那麼咱們認爲這個(這些)對象再也不被引用,能夠被GC回收。   所以對於咱們已經不須要使用的對象,咱們能夠把它設置爲null,這樣當GC運行的時候,就好遍歷到你這個對象已經沒有引用,會自動把該對象佔用的內存回收。咱們無法像C
++那樣立刻釋放不須要的內存,可是咱們能夠主動告訴系統,哪些內存能夠回收了。   3、查看應用內存使用狀況   下面咱們看看如何在開發過程當中查看咱們程序運行時內存使用狀況。咱們能夠經過ADB的一個命令查看:   //$package_name:應用包名   //$pid:應用進程ID,能夠用PS命令查看   adb shell dumpsys meminfo $package_name or $pid   上面是我使用包名查看Gallery例子的內存使用狀況圖,裏面信息不少,不過咱們主要關注的是native和Davilk的使用狀況。(Android2.X和Android4.X查看的信息排序是不同的,內容差很少,不過排布有差別,我上面是4.0的截圖) Android底層內核是基於Linux的,而Linux裏面相對Window來講,有一點很特別的是,會盡可能使用系統內存加載一些緩存數據或者進程間共享數據。Linux本着不用白不用的原則,會盡可能使用系統內存,加快咱們應用的運行速度。固然,若是咱們期待某個須要大內存的應用,
系統也能立刻釋放出必定的內存使用,這是系統內部調度實現。所以嚴格來講,咱們要準備計算Linux下某個進程內存大小比較困難。 由於有paging
out to disk(換頁),因此若是你把全部映射到進程的內存相加,它可能大於你的內存的實際物理大小。   dalvik:是指dalvik所使用的內存。   native:是被native堆使用的內存。應該指使用C\C++在堆上分配的內存。   other:是指除dalvik和native使用的內存。可是具體是指什麼呢?至少包括在C\C++分配的非堆內存,好比分配在棧上的內存。puzlle!   Pss:它是把共享內存根據必定比例分攤到共享它的各個進程來計算所獲得進程使用內存。網上又說是比例分配共享庫佔用的內存,也就是上面所說的進程共享問題。   PrivateDirty:它是指非共享的,又不能換頁出去(can not be paged to disk )的內存的大小。好比Linux爲了提升分配內存速度而緩衝的小對象,即便你的進程結束,該內存也不會釋放掉,它只是又從新回到緩衝中而已。   SharedDirty:參照PrivateDirty我認爲它應該是指共享的,又不能換頁出去(can not be paged to disk )的內存的大小。好比Linux爲了提升分配內存速度而緩衝的小對象,即便全部共享它的進程結束,該內存也不會釋放掉,它只是又從新回到緩衝中而已。   上面針對meminfo裏面的信息給出解析,這些不少我是參考了網上一些文章,因此若是有理解不到位的,歡迎各位指出。   4、程序中獲取內存信息   經過ActivityManager獲取相關信息,下面是一個例子代碼:   privatevoid displayBriefMemory()   {    final ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);    ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();    activityManager.getMemoryInfo(info);    Log.i(tag,"系統剩餘內存:"+(info.availMem >> 10)+"k");    Log.i(tag,"系統是否處於低內存運行:"+info.lowMemory);    Log.i(tag,"當系統剩餘內存低於"+info.threshold+"時就當作低內存運行");   }   另外經過Debug的getMemoryInfo(Debug.MemoryInfo memoryInfo)能夠獲得更加詳細的信息。跟咱們在ADB Shell看到的信息同樣比較詳細。   5、總結 今天主要是分析瞭如何獲取咱們應用的內存使用狀況信息,關於這方面的信息,其實還有其餘一些方法。另外還介紹APP應用的默認內存已經Android的GC回收,
不過上面只是很淺薄地分析了一下,讓你們有個印象。這些東西真要深刻分析得花很多精力。由於咱們的目的只是解決OOM問題,因此目前沒打算深刻分析,後面有時間進行Android系統分析的時候,咱們再深刻分析。下一次咱們用之前寫的Gallery例子講解如何避免OOM問題,以及內存優化方法。

2.5 圖片壓縮相關

【第一篇文章】

在網上調查了圖片壓縮的方法並實裝後,大體上能夠認爲有兩類壓縮:質量壓縮(不改變圖片的尺寸)和尺寸壓縮(至關因而像素上的壓縮);
質量壓縮通常可用於上傳大圖前的處理,這樣就能夠節省必定的流量,畢竟如今的手機拍照都能達到3M左右了,尺寸壓縮通常可用於生成縮略圖。
兩種方法都實裝在了個人項目中,結果卻發如今質量壓縮的模塊中,原本1.9M的圖片壓縮後反而變成3M多了,非常奇怪,再作了進一步調查終於知道緣由了。下面這個博客說的比較清晰:
android圖片壓縮總結

總結來看,圖片有三種存在形式:硬盤上時是file,網絡傳輸時是stream,內存中是stream或bitmap,所謂的質量壓縮,它其實只能實現對file的影響,你能夠把一個file轉成bitmap再轉成file,或者直接將一個bitmap轉成file時,這個最終的file是被壓縮過的,可是中間的bitmap並無被壓縮(或者說幾乎沒有被壓縮,我不肯定),由於bigmap在內存中的大小是按像素計算的,也就是width * height,對於質量壓縮,並不會改變圖片的像素,因此就算質量被壓縮了,可是bitmap在內存的佔有率仍是沒變小,但你作成file時,它確實變小了;

而尺寸壓縮因爲是減少了圖片的像素,因此它直接對bitmap產生了影響,固然最終的file也是相對的變小了;

最後把本身總結的工具類貼出來:
[java]  view plain  copy
 
  1. import java.io.ByteArrayInputStream;  
  2. import java.io.ByteArrayOutputStream;  
  3. import java.io.File;  
  4. import java.io.FileNotFoundException;  
  5. import java.io.FileOutputStream;  
  6. import java.io.IOException;  
  7.   
  8. import android.graphics.Bitmap;  
  9. import android.graphics.Bitmap.Config;  
  10. import android.graphics.BitmapFactory;  
  11.   
  12. /** 
  13.  * Image compress factory class 
  14.  *  
  15.  * @author  
  16.  * 
  17.  */  
  18. public class ImageFactory {  
  19.   
  20.     /** 
  21.      * Get bitmap from specified image path 
  22.      *  
  23.      * @param imgPath 
  24.      * @return 
  25.      */  
  26.     public Bitmap getBitmap(String imgPath) {  
  27.         // Get bitmap through image path  
  28.         BitmapFactory.Options newOpts = new BitmapFactory.Options();  
  29.         newOpts.inJustDecodeBounds = false;  
  30.         newOpts.inPurgeable = true;  
  31.         newOpts.inInputShareable = true;  
  32.         // Do not compress  
  33.         newOpts.inSampleSize = 1;  
  34.         newOpts.inPreferredConfig = Config.RGB_565;  
  35.         return BitmapFactory.decodeFile(imgPath, newOpts);  
  36.     }  
  37.       
  38.     /** 
  39.      * Store bitmap into specified image path 
  40.      *  
  41.      * @param bitmap 
  42.      * @param outPath 
  43.      * @throws FileNotFoundException  
  44.      */  
  45.     public void storeImage(Bitmap bitmap, String outPath) throws FileNotFoundException {  
  46.         FileOutputStream os = new FileOutputStream(outPath);  
  47.         bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);  
  48.     }  
  49.       
  50.     /** 
  51.      * Compress image by pixel, this will modify image width/height.  
  52.      * Used to get thumbnail 
  53.      *  
  54.      * @param imgPath image path 
  55.      * @param pixelW target pixel of width 
  56.      * @param pixelH target pixel of height 
  57.      * @return 
  58.      */  
  59.     public Bitmap ratio(String imgPath, float pixelW, float pixelH) {  
  60.         BitmapFactory.Options newOpts = new BitmapFactory.Options();    
  61.         // 開始讀入圖片,此時把options.inJustDecodeBounds 設回true,即只讀邊不讀內容  
  62.         newOpts.inJustDecodeBounds = true;  
  63.         newOpts.inPreferredConfig = Config.RGB_565;  
  64.         // Get bitmap info, but notice that bitmap is null now    
  65.         Bitmap bitmap = BitmapFactory.decodeFile(imgPath,newOpts);  
  66.             
  67.         newOpts.inJustDecodeBounds = false;    
  68.         int w = newOpts.outWidth;    
  69.         int h = newOpts.outHeight;    
  70.         // 想要縮放的目標尺寸  
  71.         float hh = pixelH;// 設置高度爲240f時,能夠明顯看到圖片縮小了  
  72.         float ww = pixelW;// 設置寬度爲120f,能夠明顯看到圖片縮小了  
  73.         // 縮放比。因爲是固定比例縮放,只用高或者寬其中一個數據進行計算便可    
  74.         int be = 1;//be=1表示不縮放    
  75.         if (w > h && w > ww) {//若是寬度大的話根據寬度固定大小縮放    
  76.             be = (int) (newOpts.outWidth / ww);    
  77.         } else if (w < h && h > hh) {//若是高度高的話根據寬度固定大小縮放    
  78.             be = (int) (newOpts.outHeight / hh);    
  79.         }    
  80.         if (be <= 0) be = 1;    
  81.         newOpts.inSampleSize = be;//設置縮放比例  
  82.         // 開始壓縮圖片,注意此時已經把options.inJustDecodeBounds 設回false了  
  83.         bitmap = BitmapFactory.decodeFile(imgPath, newOpts);  
  84.         // 壓縮比如例大小後再進行質量壓縮  
  85. //        return compress(bitmap, maxSize); // 這裏再進行質量壓縮的意義不大,反而耗資源,刪除  
  86.         return bitmap;  
  87.     }  
  88.       
  89.     /** 
  90.      * Compress image by size, this will modify image width/height.  
  91.      * Used to get thumbnail 
  92.      *  
  93.      * @param image 
  94.      * @param pixelW target pixel of width 
  95.      * @param pixelH target pixel of height 
  96.      * @return 
  97.      */  
  98.     public Bitmap ratio(Bitmap image, float pixelW, float pixelH) {  
  99.         ByteArrayOutputStream os = new ByteArrayOutputStream();  
  100.         image.compress(Bitmap.CompressFormat.JPEG, 100, os);  
  101.         if( os.toByteArray().length / 1024>1024) {//判斷若是圖片大於1M,進行壓縮避免在生成圖片(BitmapFactory.decodeStream)時溢出      
  102.             os.reset();//重置baos即清空baos    
  103.             image.compress(Bitmap.CompressFormat.JPEG, 50, os);//這裏壓縮50%,把壓縮後的數據存放到baos中    
  104.         }    
  105.         ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());    
  106.         BitmapFactory.Options newOpts = new BitmapFactory.Options();    
  107.         //開始讀入圖片,此時把options.inJustDecodeBounds 設回true了    
  108.         newOpts.inJustDecodeBounds = true;  
  109.         newOpts.inPreferredConfig = Config.RGB_565;  
  110.         Bitmap bitmap = BitmapFactory.decodeStream(is, null, newOpts);    
  111.         newOpts.inJustDecodeBounds = false;    
  112.         int w = newOpts.outWidth;    
  113.         int h = newOpts.outHeight;    
  114.         float hh = pixelH;// 設置高度爲240f時,能夠明顯看到圖片縮小了  
  115.         float ww = pixelW;// 設置寬度爲120f,能夠明顯看到圖片縮小了  
  116.         //縮放比。因爲是固定比例縮放,只用高或者寬其中一個數據進行計算便可    
  117.         int be = 1;//be=1表示不縮放    
  118.         if (w > h && w > ww) {//若是寬度大的話根據寬度固定大小縮放    
  119.             be = (int) (newOpts.outWidth / ww);    
  120.         } else if (w < h && h > hh) {//若是高度高的話根據寬度固定大小縮放    
  121.             be = (int) (newOpts.outHeight / hh);    
  122.         }    
  123.         if (be <= 0) be = 1;    
  124.         newOpts.inSampleSize = be;//設置縮放比例    
  125.         //從新讀入圖片,注意此時已經把options.inJustDecodeBounds 設回false了    
  126.         is = new ByteArrayInputStream(os.toByteArray());    
  127.         bitmap = BitmapFactory.decodeStream(is, null, newOpts);  
  128.         //壓縮比如例大小後再進行質量壓縮  
  129. //      return compress(bitmap, maxSize); // 這裏再進行質量壓縮的意義不大,反而耗資源,刪除  
  130.         return bitmap;  
  131.     }  
  132.       
  133.     /** 
  134.      * Compress by quality,  and generate image to the path specified 
  135.      *  
  136.      * @param image 
  137.      * @param outPath 
  138.      * @param maxSize target will be compressed to be smaller than this size.(kb) 
  139.      * @throws IOException  
  140.      */  
  141.     public void compressAndGenImage(Bitmap image, String outPath, int maxSize) throws IOException {  
  142.         ByteArrayOutputStream os = new ByteArrayOutputStream();  
  143.         // scale  
  144.         int options = 100;  
  145.         // Store the bitmap into output stream(no compress)  
  146.         image.compress(Bitmap.CompressFormat.JPEG, options, os);    
  147.         // Compress by loop  
  148.         while ( os.toByteArray().length / 1024 > maxSize) {  
  149.             // Clean up os  
  150.             os.reset();  
  151.             // interval 10  
  152.             options -= 10;  
  153.             image.compress(Bitmap.CompressFormat.JPEG, options, os);  
  154.         }  
  155.           
  156.         // Generate compressed image file  
  157.         FileOutputStream fos = new FileOutputStream(outPath);    
  158.         fos.write(os.toByteArray());    
  159.         fos.flush();    
  160.         fos.close();    
  161.     }  
  162.       
  163.     /** 
  164.      * Compress by quality,  and generate image to the path specified 
  165.      *  
  166.      * @param imgPath 
  167.      * @param outPath 
  168.      * @param maxSize target will be compressed to be smaller than this size.(kb) 
  169.      * @param needsDelete Whether delete original file after compress 
  170.      * @throws IOException  
  171.      */  
  172.     public void compressAndGenImage(String imgPath, String outPath, int maxSize, boolean needsDelete) throws IOException {  
  173.         compressAndGenImage(getBitmap(imgPath), outPath, maxSize);  
  174.           
  175.         // Delete original file  
  176.         if (needsDelete) {  
  177.             File file = new File (imgPath);  
  178.             if (file.exists()) {  
  179.                 file.delete();  
  180.             }  
  181.         }  
  182.     }  
  183.       
  184.     /** 
  185.      * Ratio and generate thumb to the path specified 
  186.      *  
  187.      * @param image 
  188.      * @param outPath 
  189.      * @param pixelW target pixel of width 
  190.      * @param pixelH target pixel of height 
  191.      * @throws FileNotFoundException 
  192.      */  
  193.     public void ratioAndGenThumb(Bitmap image, String outPath, float pixelW, float pixelH) throws FileNotFoundException {  
  194.         Bitmap bitmap = ratio(image, pixelW, pixelH);  
  195.         storeImage( bitmap, outPath);  
  196.     }  
  197.       
  198.     /** 
  199.      * Ratio and generate thumb to the path specified 
  200.      *  
  201.      * @param image 
  202.      * @param outPath 
  203.      * @param pixelW target pixel of width 
  204.      * @param pixelH target pixel of height 
  205.      * @param needsDelete Whether delete original file after compress 
  206.      * @throws FileNotFoundException 
  207.      */  
  208.     public void ratioAndGenThumb(String imgPath, String outPath, float pixelW, float pixelH, boolean needsDelete) throws FileNotFoundException {  
  209.         Bitmap bitmap = ratio(imgPath, pixelW, pixelH);  
  210.         storeImage( bitmap, outPath);  
  211.           
  212.         // Delete original file  
  213.                 if (needsDelete) {  
  214.                     File file = new File (imgPath);  
  215.                     if (file.exists()) {  
  216.                         file.delete();  
  217.                     }  
  218.                 }  
  219.     }  
  220.       
  221. }  
 
 
若是上面的工具類不知足你,那麼看看下面的方法。
 
1、圖片質量壓縮
[java]  view plain  copy
 
  1. /** 
  2.  * 質量壓縮方法 
  3.  * 
  4.  * @param image 
  5.  * @return 
  6.  */  
  7. public static Bitmap compressImage(Bitmap image) {  
  8.   
  9.     ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  10.     image.compress(Bitmap.CompressFormat.JPEG, 100, baos);// 質量壓縮方法,這裏100表示不壓縮,把壓縮後的數據存放到baos中  
  11.     int options = 90;  
  12.   
  13.     while (baos.toByteArray().length / 1024 > 100) { // 循環判斷若是壓縮後圖片是否大於100kb,大於繼續壓縮  
  14.         baos.reset(); // 重置baos即清空baos  
  15.         image.compress(Bitmap.CompressFormat.JPEG, options, baos);// 這裏壓縮options%,把壓縮後的數據存放到baos中  
  16.         options -= 10;// 每次都減小10  
  17.     }  
  18.     ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());// 把壓縮後的數據baos存放到ByteArrayInputStream中  
  19.     Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);// 把ByteArrayInputStream數據生成圖片  
  20.     return bitmap;  
  21. }  

2、按比例大小壓縮 (路徑獲取圖片)
 
[java]  view plain  copy
 
  1. /** 
  2.  * 圖片按比例大小壓縮方法 
  3.  * 
  4.  * @param srcPath (根據路徑獲取圖片並壓縮) 
  5.  * @return 
  6.  */  
  7. public static Bitmap getimage(String srcPath) {  
  8.   
  9.     BitmapFactory.Options newOpts = new BitmapFactory.Options();  
  10.     // 開始讀入圖片,此時把options.inJustDecodeBounds 設回true了  
  11.     newOpts.inJustDecodeBounds = true;  
  12.     Bitmap bitmap = BitmapFactory.decodeFile(srcPath, newOpts);// 此時返回bm爲空  
  13.   
  14.     newOpts.inJustDecodeBounds = false;  
  15.     int w = newOpts.outWidth;  
  16.     int h = newOpts.outHeight;  
  17.     // 如今主流手機比較可能是800*480分辨率,因此高和寬咱們設置爲  
  18.     float hh = 800f;// 這裏設置高度爲800f  
  19.     float ww = 480f;// 這裏設置寬度爲480f  
  20.     // 縮放比。因爲是固定比例縮放,只用高或者寬其中一個數據進行計算便可  
  21.     int be = 1;// be=1表示不縮放  
  22.     if (w > h && w > ww) {// 若是寬度大的話根據寬度固定大小縮放  
  23.         be = (int) (newOpts.outWidth / ww);  
  24.     } else if (w < h && h > hh) {// 若是高度高的話根據寬度固定大小縮放  
  25.         be = (int) (newOpts.outHeight / hh);  
  26.     }  
  27.     if (be <= 0)  
  28.         be = 1;  
  29.     newOpts.inSampleSize = be;// 設置縮放比例  
  30.     // 從新讀入圖片,注意此時已經把options.inJustDecodeBounds 設回false了  
  31.     bitmap = BitmapFactory.decodeFile(srcPath, newOpts);  
  32.     return compressImage(bitmap);// 壓縮比如例大小後再進行質量壓縮  
  33. }  
3、按比例大小壓縮 (Bitmap)
 
[java]  view plain  copy
 
  1. /** 
  2.  * 圖片按比例大小壓縮方法 
  3.  * 
  4.  * @param image (根據Bitmap圖片壓縮) 
  5.  * @return 
  6.  */  
  7. public static Bitmap compressScale(Bitmap image) {  
  8.   
  9.     ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  10.     image.compress(Bitmap.CompressFormat.JPEG, 100, baos);  
  11.   
  12.     // 判斷若是圖片大於1M,進行壓縮避免在生成圖片(BitmapFactory.decodeStream)時溢出  
  13.     if (baos.toByteArray().length / 1024 > 1024) {  
  14.         baos.reset();// 重置baos即清空baos  
  15.         image.compress(Bitmap.CompressFormat.JPEG, 80, baos);// 這裏壓縮50%,把壓縮後的數據存放到baos中  
  16.     }  
  17.     ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());  
  18.     BitmapFactory.Options newOpts = new BitmapFactory.Options();  
  19.     // 開始讀入圖片,此時把options.inJustDecodeBounds 設回true了  
  20.     newOpts.inJustDecodeBounds = true;  
  21.     Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);  
  22.     newOpts.inJustDecodeBounds = false;  
  23.     int w = newOpts.outWidth;  
  24.     int h = newOpts.outHeight;  
  25.     Log.i(TAG, w + "---------------" + h);  
  26.     // 如今主流手機比較可能是800*480分辨率,因此高和寬咱們設置爲  
  27.     // float hh = 800f;// 這裏設置高度爲800f  
  28.     // float ww = 480f;// 這裏設置寬度爲480f  
  29.     float hh = 512f;  
  30.     float ww = 512f;  
  31.     // 縮放比。因爲是固定比例縮放,只用高或者寬其中一個數據進行計算便可  
  32.     int be = 1;// be=1表示不縮放  
  33.     if (w > h && w > ww) {// 若是寬度大的話根據寬度固定大小縮放  
  34.         be = (int) (newOpts.outWidth / ww);  
  35.     } else if (w < h && h > hh) { // 若是高度高的話根據高度固定大小縮放  
  36.         be = (int) (newOpts.outHeight / hh);  
  37.     }  
  38.     if (be <= 0)  
  39.         be = 1;  
  40.     newOpts.inSampleSize = be; // 設置縮放比例  
  41.     // newOpts.inPreferredConfig = Config.RGB_565;//下降圖片從ARGB888到RGB565  
  42.   
  43.     // 從新讀入圖片,注意此時已經把options.inJustDecodeBounds 設回false了  
  44.     isBm = new ByteArrayInputStream(baos.toByteArray());  
  45.     bitmap = BitmapFactory.decodeStream(isBm, null, newOpts);  
  46.   
  47.     return compressImage(bitmap);// 壓縮比如例大小後再進行質量壓縮  
  48.   
  49.     //return bitmap;  
  50. }  
--------------------------------------------------------------------------------------------------------------------------------
 
分享個按照圖片尺寸壓縮:
 
[java]  view plain  copy
 
  1. public static void compressPicture(String srcPath, String desPath) {  
  2.         FileOutputStream fos = null;  
  3.         BitmapFactory.Options op = new BitmapFactory.Options();  
  4.   
  5.         // 開始讀入圖片,此時把options.inJustDecodeBounds 設回true了  
  6.         op.inJustDecodeBounds = true;  
  7.         Bitmap bitmap = BitmapFactory.decodeFile(srcPath, op);  
  8.         op.inJustDecodeBounds = false;  
  9.   
  10.         // 縮放圖片的尺寸  
  11.         float w = op.outWidth;  
  12.         float h = op.outHeight;  
  13.         float hh = 1024f;//  
  14.         float ww = 1024f;//  
  15.         // 最長寬度或高度1024  
  16.         float be = 1.0f;  
  17.         if (w > h && w > ww) {  
  18.             be = (float) (w / ww);  
  19.         } else if (w < h && h > hh) {  
  20.             be = (float) (h / hh);  
  21.         }  
  22.         if (be <= 0) {  
  23.             be = 1.0f;  
  24.         }  
  25.         op.inSampleSize = (int) be;// 設置縮放比例,這個數字越大,圖片大小越小.  
  26.         // 從新讀入圖片,注意此時已經把options.inJustDecodeBounds 設回false了  
  27.         bitmap = BitmapFactory.decodeFile(srcPath, op);  
  28.         int desWidth = (int) (w / be);  
  29.         int desHeight = (int) (h / be);  
  30.         bitmap = Bitmap.createScaledBitmap(bitmap, desWidth, desHeight, true);  
  31.         try {  
  32.             fos = new FileOutputStream(desPath);  
  33.             if (bitmap != null) {  
  34.                 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);  
  35.             }  
  36.         } catch (FileNotFoundException e) {  
  37.             e.printStackTrace();  
  38.         }  
  39.     }  


須要注意兩個問題:

 

1、調用getDrawingCache()前先要測量,不然的話獲得的bitmap爲null,這個我在OnCreate()、OnStart()、OnResume()方法裏都試驗過。

2、當調用bitmap.compress(CompressFormat.JPEG, 100, fos);保存爲圖片時發現圖片背景爲黑色,以下圖:

這時只須要改爲用png保存就能夠了,bitmap.compress(CompressFormat.PNG, 100, fos);,以下圖:

在實際開發中,有時候咱們需求將文件轉換爲字符串,而後做爲參數進行上傳。

必備工具類圖片bitmap轉成字符串string與String字符串轉換爲bitmap圖片格式

[java]  view plain  copy
 
  1. import android.graphics.Bitmap;  
  2. import android.graphics.BitmapFactory;  
  3. import android.util.Base64;  
  4.   
  5. import java.io.ByteArrayOutputStream;  
  6.   
  7. /** 
  8.  *  
  9.  *  
  10.  * 功能描述:Android開發之經常使用必備工具類圖片bitmap轉成字符串string與String字符串轉換爲bitmap圖片格式 
  11.  */  
  12. public class BitmapAndStringUtils {  
  13.     /** 
  14.      * 圖片轉成string 
  15.      * 
  16.      * @param bitmap 
  17.      * @return 
  18.      */  
  19.     public static String convertIconToString(Bitmap bitmap)  
  20.     {  
  21.         ByteArrayOutputStream baos = new ByteArrayOutputStream();// outputstream  
  22.         bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);  
  23.         byte[] appicon = baos.toByteArray();// 轉爲byte數組  
  24.         return Base64.encodeToString(appicon, Base64.DEFAULT);  
  25.   
  26.     }  
  27.   
  28.     /** 
  29.      * string轉成bitmap 
  30.      * 
  31.      * @param st 
  32.      */  
  33.     public static Bitmap convertStringToIcon(String st)  
  34.     {  
  35.         // OutputStream out;  
  36.         Bitmap bitmap = null;  
  37.         try  
  38.         {  
  39.             // out = new FileOutputStream("/sdcard/aa.jpg");  
  40.             byte[] bitmapArray;  
  41.             bitmapArray = Base64.decode(st, Base64.DEFAULT);  
  42.             bitmap =  
  43.                     BitmapFactory.decodeByteArray(bitmapArray, 0,  
  44.                             bitmapArray.length);  
  45.             // bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);  
  46.             return bitmap;  
  47.         }  
  48.         catch (Exception e)  
  49.         {  
  50.             return null;  
  51.         }  
  52.     }  
若是你的圖片是File文件,能夠用下面代碼:
[java]  view plain  copy
 
  1. /** 
  2.  * 圖片文件轉換爲指定編碼的字符串 
  3.  * 
  4.  * @param imgFile  圖片文件 
  5.  */  
  6. public static String file2String(File imgFile) {  
  7.     InputStream in = null;  
  8.     byte[] data = null;  
  9.     //讀取圖片字節數組  
  10.     try{  
  11.         in = new FileInputStream(imgFile);  
  12.         data = new byte[in.available()];  
  13.         in.read(data);  
  14.         in.close();  
  15.     } catch (IOException e){  
  16.         e.printStackTrace();  
  17.     }  
  18.     //對字節數組Base64編碼  
  19.     BASE64Encoder encoder = new BASE64Encoder();  
  20.     String result = encoder.encode(data);  
  21.     return result;//返回Base64編碼過的字節數組字符串  
  22. }  

【第二篇文章】Android壓縮圖片到100K如下並保持不失真的高效方法

在開發Android企業應用時,會常常上傳圖片到服務器,而咱們公司目前維護的一個項目即是如此。該項目是經過私有apn與服務器進行交互的,聯通的還好,但移動的速度實在太慢,客戶在使用軟件的過程當中,因爲上傳的信息中可能包含多張圖片,會常常出現上傳圖片失敗的問題,爲了解決這個問題,咱們決定把照片壓縮到100k如下,而且保證圖片不失真(目前圖片通過壓縮後,大約300k左右)。因而我就從新研究了一下Android的圖片壓縮技術。

Android端目錄結構以下圖所示:


使用的第三方庫jar包,以下圖所示:

其中ksoap2-android-xxx.jar是Android用來調用webservice的,gson-xx.jar是把JavaBean轉成Json數據格式的。
本篇博客主要講解圖片壓縮的,核心代碼以下:

[java]  view plain  copy
 
  1. //計算圖片的縮放值  
  2. public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) {  
  3.     final int height = options.outHeight;  
  4.     final int width = options.outWidth;  
  5.     int inSampleSize = 1;  
  6.   
  7.     if (height > reqHeight || width > reqWidth) {  
  8.              final int heightRatio = Math.round((float) height/ (float) reqHeight);  
  9.              final int widthRatio = Math.round((float) width / (float) reqWidth);  
  10.              inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;  
  11.     }  
  12.         return inSampleSize;  
  13. }  
[java]  view plain  copy
  1. // 根據路徑得到圖片並壓縮,返回bitmap用於顯示  
  2. public static Bitmap getSmallBitmap(String filePath) {  
  3.         final BitmapFactory.Options options = new BitmapFactory.Options();  
  4.         options.inJustDecodeBounds = true;  
  5.         BitmapFactory.decodeFile(filePath, options);  
  6.   
  7.         // Calculate inSampleSize  
  8.     options.inSampleSize = calculateInSampleSize(options, 480, 800);  
  9.   
  10.         // Decode bitmap with inSampleSize set  
  11.     options.inJustDecodeBounds = false;  
  12.   
  13.     return BitmapFactory.decodeFile(filePath, options);  
  14.     }  
[java]  view plain  copy
  1. //把bitmap轉換成String  
  2. public static String bitmapToString(String filePath) {  
  3.   
  4.         Bitmap bm = getSmallBitmap(filePath);  
  5.         ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  6.         bm.compress(Bitmap.CompressFormat.JPEG, 40, baos);  
  7.         byte[] b = baos.toByteArray();  
  8.         return Base64.encodeToString(b, Base64.DEFAULT);  
  9.     }  

查看所有源碼,請訪問:
https://github.com/feicien/StudyDemo/tree/master/FileUploadDemo

壓縮原理講解:壓縮一張圖片。咱們須要知道這張圖片的原始大小,而後根據咱們設定的壓縮比例進行壓縮。
這樣咱們就須要作3件事:
1.獲取原始圖片的長和寬

[java]  view plain  copy
 
  1. BitmapFactory.Options options = new BitmapFactory.Options();  
  2.        options.inJustDecodeBounds = true;  
  3.        BitmapFactory.decodeFile(filePath, options);  
  4.                int height = options.outHeight;  
  5.            int width = options.outWidth;  

以上代碼是對圖片進行解碼,inJustDecodeBounds設置爲true,能夠不把圖片讀到內存中,但依然能夠計算出圖片的大小,這正好能夠知足咱們第一步的須要。
2.計算壓縮比例

[java]  view plain  copy
  1. int height = options.outHeight;  
  2.     int width = options.outWidth;   
  3.     int inSampleSize = 1;  
  4.     int reqHeight=800;  
  5.     int reqWidth=480;  
  6.     if (height > reqHeight || width > reqWidth) {  
  7.    final int heightRatio = Math.round((float) height/ (float) reqHeight);  
  8.    final int widthRatio = Math.round((float) width / (float) reqWidth);              
  9.    inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;  
  10.     } 

通常手機的分辨率爲 480*800 ,因此咱們壓縮後圖片指望的寬帶定爲480,高度設爲800,這2個值只是指望的寬度與高度,實際上壓縮後的實際寬度也高度會比指望的要大。若是圖片的原始高度或者寬帶大約咱們指望的寬帶和高度,咱們須要計算出縮放比例的數值。不然就不縮放。heightRatio是圖片原始高度與壓縮後高度的倍數,widthRatio是圖片原始寬度與壓縮後寬度的倍數。inSampleSize爲heightRatio與widthRatio中最小的那個,inSampleSize就是縮放值。 inSampleSize爲1表示寬度和高度不縮放,爲2表示壓縮後的寬度與高度爲原來的1/2
3.縮放並壓縮圖片

[java]  view plain  copy
 
  1. //在內存中建立bitmap對象,這個對象按照縮放大小建立的  
  2.             options.inSampleSize = calculateInSampleSize(options, 480, 800);  
  3.        options.inJustDecodeBounds = false;  
  4.        Bitmap bitmap= BitmapFactory.decodeFile(filePath, options);  
  5.   
  6.        ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  7.        bm.compress(Bitmap.CompressFormat.JPEG, 60, baos);  
  8.        byte[] b = baos.toByteArray();  

前3行的代碼其實已經獲得了一個縮放的bitmap對象,若是你在應用中顯示圖片,就能夠使用這個bitmap對象了。因爲考慮到網絡流量的問題。咱們好須要犧牲圖片的質量來換取一部分空間,這裏調用bm.compress()方法進行壓縮,這個方法的第二個參數,若是是100,表示不壓縮,我這裏設置的是60,你也能夠更加你的須要進行設置,在實驗的過程當中我設置爲30,圖片都不會失真。

壓縮效果:本demo能夠把1.5M左右的圖片壓縮到100K左右,而且沒有失真。
效果圖以下:

更新:

[java]  view plain  copy
  1. /* 
  2. 壓縮圖片,處理某些手機拍照角度旋轉的問題 
  3. */  
  4. public static String compressImage(Context context,String filePath,String fileName,int q) throws FileNotFoundException {  
  5.   
  6.         Bitmap bm = getSmallBitmap(filePath);  
  7.   
  8.         int degree = readPictureDegree(filePath);  
  9.   
  10.         if(degree!=0){//旋轉照片角度  
  11.             bm=rotateBitmap(bm,degree);  
  12.         }  
  13.   
  14.         File imageDir = SDCardUtils.getImageDir(context);  
  15.   
  16.         File outputFile=new File(imageDir,fileName);  
  17.   
  18.         FileOutputStream out = new FileOutputStream(outputFile);  
  19.   
  20.         bm.compress(Bitmap.CompressFormat.JPEG, q, out);  
  21.   
  22.         return outputFile.getPath();  
  23.     }  

判斷照片角度

[java]  view plain  copy
 
  1. public static int readPictureDegree(String path) {  
  2.         int degree = 0;  
  3.         try {  
  4.             ExifInterface exifInterface = new ExifInterface(path);  
  5.             int orientation = exifInterface.getAttributeInt(  
  6.                     ExifInterface.TAG_ORIENTATION,  
  7.                     ExifInterface.ORIENTATION_NORMAL);  
  8.             switch (orientation) {  
  9.             case ExifInterface.ORIENTATION_ROTATE_90:  
  10.                 degree = 90;  
  11.                 break;  
  12.             case ExifInterface.ORIENTATION_ROTATE_180:  
  13.                 degree = 180;  
  14.                 break;  
  15.             case ExifInterface.ORIENTATION_ROTATE_270:  
  16.                 degree = 270;  
  17.                 break;  
  18.             }  
  19.         } catch (IOException e) {  
  20.             e.printStackTrace();  
  21.         }  
  22.         return degree;  
  23.     }  

旋轉照片

[java]  view plain  copy
 
    1. public static Bitmap rotateBitmap(Bitmap bitmap,int degress) {  
    2.         if (bitmap != null) {  
    3.             Matrix m = new Matrix();  
    4.             m.postRotate(degress);   
    5.             bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),  
    6.                     bitmap.getHeight(), m, true);  
    7.             return bitmap;  
    8.         }  
    9.         return bitmap;  
    10.     }  

 【第三篇文章】

1 分類

Android圖片壓縮結合多種壓縮方式,經常使用的有尺寸壓縮、質量壓縮、採樣率壓縮以及經過JNI調用libjpeg庫來進行壓縮。 
參考此方法:Android-BitherCompress

備註:對於資源圖片直接使用:tiny壓縮

2 質量壓縮

(1)原理:保持像素的前提下改變圖片的位深及透明度,(即:經過算法摳掉(同化)了圖片中的一些某個些點附近相近的像素),達到下降質量壓縮文件大小的目的。

注意:它其實只能實現對file的影響,對加載這個圖片出來的bitmap內存是沒法節省的,仍是那麼大。由於bitmap在內存中的大小是按照像素計算的,也就是width*height,對於質量壓縮,並不會改變圖片的真實的像素(像素大小不會變)。

(2)使用場景:將圖片壓縮後將圖片上傳到服務器,或者保存到本地。根據實際需求來。

(3)源碼示例

/** * 3.質量壓縮 * 設置bitmap options屬性,下降圖片的質量,像素不會減小 * 第一個參數爲須要壓縮的bitmap圖片對象,第二個參數爲壓縮後圖片保存的位置 * 設置options 屬性0-100,來實現壓縮 * * @param bmp * @param file */ public static void qualityCompress(Bitmap bmp, File file) { // 0-100 100爲不壓縮 int quality = 20; ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 把壓縮後的數據存放到baos中 bmp.compress(Bitmap.CompressFormat.JPEG, quality, baos); try { FileOutputStream fos = new FileOutputStream(file); fos.write(baos.toByteArray()); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } }

3 尺寸壓縮

(1)原理:經過減小單位尺寸的像素值,正真意義上的下降像素。1020*8880–

(2)使用場景:緩存縮略圖的時候(頭像處理)

(3)源碼示例

/** * 4.尺寸壓縮(經過縮放圖片像素來減小圖片佔用內存大小) * * @param bmp * @param file */ public static void sizeCompress(Bitmap bmp, File file) { // 尺寸壓縮倍數,值越大,圖片尺寸越小 int ratio = 8; // 壓縮Bitmap到對應尺寸 Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio, Config.ARGB_8888); Canvas canvas = new Canvas(result); Rect rect = new Rect(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio); canvas.drawBitmap(bmp, null, rect, null); ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 把壓縮後的數據存放到baos中 result.compress(Bitmap.CompressFormat.JPEG, 100, baos); try { FileOutputStream fos = new FileOutputStream(file); fos.write(baos.toByteArray()); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } }

4 採樣率壓縮

(1)原理:設置圖片的採樣率,下降圖片像素

(2) 好處:是不會先將大圖片讀入內存,大大減小了內存的使用,也沒必要考慮將大圖片讀入內存後的釋放事宜。

(3)問題:由於採樣率是整數,因此不能很好的保證圖片的質量。如咱們須要的是在2和3採樣率之間,用2的話圖片就大了一點,可是用3的話圖片質量就會有很明顯的降低,這樣也沒法徹底知足個人須要。

(4)源碼示例

/** * 5.採樣率壓縮(設置圖片的採樣率,下降圖片像素) * * @param filePath * @param file */ public static void samplingRateCompress(String filePath, File file) { // 數值越高,圖片像素越低 int inSampleSize = 8; BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = false; // options.inJustDecodeBounds = true;//爲true的時候不會真正加載圖片,而是獲得圖片的寬高信息。 //採樣率 options.inSampleSize = inSampleSize; Bitmap bitmap = BitmapFactory.decodeFile(filePath, options); ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 把壓縮後的數據存放到baos中 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos); try { if (file.exists()) { file.delete(); } else { file.createNewFile(); } FileOutputStream fos = new FileOutputStream(file); fos.write(baos.toByteArray()); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } }

5 JNI終極壓縮

5.1 Android圖像處理引擎的缺漏

爲何IOS拍照1M的圖片要比安卓拍照排出來的5M的圖片還要清晰。都是在同一個環境下,保存的都是JPEG?

(1)歷程 
95年 JPEG處理引擎,用於最初的在PC上面處理圖片的引擎。 
05年 skia開源的引擎, 開發了一套基於JPEG處理引擎的第二次開發。便於瀏覽器的使用。 
07年安卓用的skia引擎(閹割版),谷歌拿了skia,去掉一個編碼算法—哈夫曼算法。採用定長編碼算法。可是解碼仍是保留了哈夫曼算法,致使了圖片處理後文件變大了。 
(2)緣由 
當時因爲CPU和內存在手機上都很是吃緊 性能差,因爲哈夫曼算法很是吃CPU,被迫用了其餘的算法。 
(3)優化方案 
繞過安卓Bitmap API層,來本身編碼實現—-修復使用哈夫曼算法。

5.2 哈夫曼算法

哈夫曼樹詳解點擊—數據結構與算法之二叉樹+遍歷+哈夫曼樹

(1)ARGB:一個像素點包涵四個信息:alpha,red,green,blue 
(2)如何獲得每個字母出現的權重?須要去掃描整個信息(圖片信息–每個像素包括ARGB),要大量計算,很耗CPU,1280*800像素*4。 

5.3 JNI開發步驟(大概步驟,具體實現未定)

(1)準備工做

1)http://www.ijg.org/下載JPEG引擎使用的庫---libjpeg庫, 基於該引擎來作必定的開發----本身實現編碼,JNI開發。 (2)導入庫文件libjpegbither.so (3)導入頭文件 (4)寫mk文件——Android.mk、Applicatoin.mk (5)寫代碼——C++:XX.cpp、C:XX.c

(2)開發過程

1)將android的bitmap解碼,並轉換成RGB數據 一個圖片信息---像素點(argb),alpha去掉 (2)JPEG對象分配空間以及初始化 (3)指定壓縮數據源 (4)獲取文件信息 (5)爲壓縮設置參數,好比圖像大小、類型、顏色空間 boolean arith_code; /* TRUE=arithmetic coding, FALSE=Huffman */ (6)開始壓縮——jpeg_start_compress() (7)壓縮結束——jpeg_finish_compress() (8)釋放資源

5.4 源碼示例

/** * 1.JNI終極壓縮(經過JNI圖片壓縮把Bitmap保存到指定目錄) * * @param image bitmap對象 * @param filePath 要保存的指定目錄 * @Description: 經過JNI圖片壓縮把Bitmap保存到指定目錄 */ public static void jniUltimateCompress(Bitmap image, String filePath) { // 質量壓縮方法,這裏100表示不壓縮,把壓縮後的數據存放到baos中 int quality = 20; // JNI調用保存圖片到SD卡 這個關鍵 NativeUtil.saveBitmap(image, quality, filePath, true); } /** * 1.JNI基本壓縮(不保存Bitmap) * * @param bit bitmap對象 * @param fileName 指定保存目錄名 * @param optimize 是否採用哈弗曼表數據計算 品質相差5-10倍 * @Description: JNI基本壓縮 */ public static void jniBasicCompress(Bitmap bit, String fileName, boolean optimize) { saveBitmap(bit, DEFAULT_QUALITY, fileName, optimize); } /** * 調用native方法 * * @param bit * @param quality * @param fileName * @param optimize * @Description:函數描述 */ private static void saveBitmap(Bitmap bit, int quality, String fileName, boolean optimize) { compressBitmap(bit, bit.getWidth(), bit.getHeight(), quality, fileName.getBytes(), optimize); } /** * 調用底層 bitherlibjni.c中的方法 * * @param bit * @param w * @param h * @param quality * @param fileNameBytes * @param optimize * @return * @Description:函數描述 */ private static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes, boolean optimize); /** * 加載lib下兩個so文件 */ static { System.loadLibrary("jpegbither"); System.loadLibrary("bitherjni"); }

6 混合終極方法

(1)原理:三種方式結合使用實現指定圖片內存大小,清晰度達到最優。

(2)使用場景:大圖壓縮,同時對圖片質量要求較高。

(3)源碼示例

/** * 2.混合終極方法(尺寸、質量、JNI壓縮) * * @param image bitmap對象 * @param filePath 要保存的指定目錄 * @Description: 經過JNI圖片壓縮把Bitmap保存到指定目錄 */ public static void mixCompress(Bitmap image, String filePath) { // 最大圖片大小 1000KB int maxSize = 1000; // 獲取尺寸壓縮倍數 int ratio = NativeUtil.getRatioSize(image.getWidth(), image.getHeight()); // 壓縮Bitmap到對應尺寸 Bitmap result = Bitmap.createBitmap(image.getWidth() / ratio, image.getHeight() / ratio, Config.ARGB_8888); Canvas canvas = new Canvas(result); Rect rect = new Rect(0, 0, image.getWidth() / ratio, image.getHeight() / ratio); canvas.drawBitmap(image, null, rect, null); ByteArrayOutputStream baos = new ByteArrayOutputStream(); // 質量壓縮方法,這裏100表示不壓縮,把壓縮後的數據存放到baos中 int quality = 100; result.compress(Bitmap.CompressFormat.JPEG, quality, baos); // 循環判斷若是壓縮後圖片是否大於最大值,大於繼續壓縮 while (baos.toByteArray().length / 1024 > maxSize) { // 重置baos即清空baos baos.reset(); // 每次都減小10 quality -= 10; // 這裏壓縮options%,把壓縮後的數據存放到baos中 result.compress(Bitmap.CompressFormat.JPEG, quality, baos); } // JNI調用保存圖片到SD卡 這個關鍵 NativeUtil.saveBitmap(result, quality, filePath, true); // 釋放Bitmap if (result != null && !result.isRecycled()) { result.recycle(); result = null; } } /** * 計算縮放比 * * @param bitWidth 當前圖片寬度 * @param bitHeight 當前圖片高度 * @return * @Description:函數描述 */ public static int getRatioSize(int bitWidth, int bitHeight) { // 圖片最大分辨率 int imageHeight = 1920; int imageWidth = 1080; // 縮放比 int ratio = 1; // 縮放比,因爲是固定比例縮放,只用高或者寬其中一個數據進行計算便可 if (bitWidth > bitHeight && bitWidth > imageHeight) { // 若是圖片寬度比高度大,以寬度爲基準 ratio = bitWidth / imageHeight; } else if (bitWidth < bitHeight && bitHeight > imageHeight) { // 若是圖片高度比寬度大,以高度爲基準 ratio = bitHeight / imageHeight; } // 最小比率爲1 if (ratio <= 0) ratio = 1; return ratio; }

7 多種方法對比

7.1 當圖片內存大於1MB

(1)原圖內存是3.22MB 
(2)截圖以下: 
這裏寫圖片描述 
(3)效果: 
尺寸壓縮後圖片太模糊; 
混合終極方法壓縮效果更佳,與jni終極方法壓縮內存區別不大; 
質量壓縮後圖片與jni終極方法壓縮後圖片效果接近,略顯模糊; 
(4)總結:能夠考慮使用混合終極方法。

7.2 當圖片內存小於1MB

(1)原圖內存是109.94KB 
(2)截圖以下: 
這裏寫圖片描述 
(3)效果:

尺寸壓縮後圖片太模糊;
混合終極方法壓縮後內存反而增長了一半;
質量壓縮後圖片和jni終極方法壓縮後圖片效果接近,可是內存更大;
jni終極方法壓縮後圖片效果與原圖相差不大,內存也不大。

(4)總結:能夠考慮使用jni終極方法。

8 參考連接

Android圖片壓縮(質量壓縮和尺寸壓縮)&Bitmap轉成字符串上傳

 2.6 inBitmap屬性的使用

SDK版本

須要注意的是inBitmap只能在3.0之後使用。2.3上,bitmap的數據是存儲在native的內存區域,並非在Dalvik的內存堆上。

在android3.0開始,系統在BitmapFactory.Options裏引入了inBitmap機制來配合緩存機制。若是在載入圖片時傳入了inBitmap那麼載入的圖片就是inBitmap裏的值。

這樣能夠統一有緩存和無緩存的載入方式。

使用inBitmap,在4.4以前,只能重用相同大小的bitmap的內存區域,而4.4以後你能夠重用任何bitmap的內存區域,只要這塊內存比將要分配內存的bitmap大就能夠

例如給inBitmap賦值的圖片大小爲100-100,那麼新申請的bitmap必須也爲100-100纔可以被重用。從SDK 19開始,新申請的bitmap大小必須小於或者等於已經賦值過的bitmap大小。

解碼

新申請的bitmap與舊的bitmap必須有相同的解碼格式,例如你們都是8888的,若是前面的bitmap是8888,那麼就不能支持4444與565格式的bitmap了,不過能夠經過建立一個包含多種典型可重用bitmap的對象池,這樣後續的bitmap建立都可以找到合適的「模板」去進行重用。

DisplayingBitmaps

Managing Bitmap Memory 上的demo的DisplayingBitmaps.zip,代碼也有用到inBitmap,可是DisplayingBitmaps功能仍是很弱,由於遇到過不一樣的ImageView設置不一樣ScaleType,而後使用同一張圖片會形成相互影響,設置圖片圓角也是,因此這也是使用inBitmap要注意的地方。

使用

使用此方法須要inMutable=true,inSampleSize=1

測試

開發完APP最好用一些APP在線自動化測試工具進行一下測試:www.ineice.com

 +++++++++++++++++++++++++++++++++++++=

在Android3.0以前,Bitmap的內存分配分爲兩部分,一部分是分配在Dalvik的VM堆中,

而像素數據的內存是分配在Native堆中,而到了Android3.0以後,Bitmap的內存則已經所有分配在VM堆上,

這兩種分配方式的區別在於,Native堆的內存不受Dalvik虛擬機的管理,咱們想要釋放Bitmap的內存,

必須手動調用Recycle方法,而到了Android 3.0以後的平臺,咱們就能夠將Bitmap的內存徹底放心的交給虛擬機管理了,

咱們只須要保證Bitmap對象遵照虛擬機的GC Root Tracing的回收規則便可。

2.使用緩存,LruCache和DiskLruCache的結合 
關於LruCache和DiskLruCache,你們必定不會陌生(有疑問的朋友能夠去API官網搜一下LruCache,而DiskLrucCache能夠參考一下這篇不錯的文章:DiskLruCache使用介紹),出於對性能和app的考慮,咱們確定是想着第一次從網絡中加載到圖片以後,可以將圖片緩存在內存和sd卡中,這樣,咱們就不用頻繁的去網絡中加載圖片,爲了很好的控制內存問題,則會考慮使用LruCache做爲Bitmap在內存中的存放容器,在sd卡則使用DiskLruCache來統一管理磁盤上的圖片緩存。

3.SoftReference和inBitmap參數的結合 
在第二點中說起到,能夠採用LruCache做爲存放Bitmap的容器,而在LruCache中有一個方法值得留意,那就是entryRemoved,按照文檔給出的說法,在LruCache容器滿了須要淘汰存放其中的對象騰出空間的時候會調用此方法(注意,這裏只是對象被淘汰出LruCache容器,但並不意味着對象的內存會當即被Dalvik虛擬機回收掉),此時能夠在此方法中將Bitmap使用SoftReference包裹起來,並用事先準備好的一個HashSet容器來存放這些即將被回收的Bitmap,有人會問,這樣存放有什麼意義?之因此會這樣存放,還須要再說起到inBitmap參數(在Android3.0纔開始有的,詳情查閱API中的BitmapFactory.Options參數信息),這個參數主要是提供給咱們進行復用內存中的Bitmap,若是設置了此參數,且知足如下條件的時候:

  • Bitmap必定要是可變的,即inmutable設置必定爲ture;
  • Android4.4如下的平臺,須要保證inBitmap和即將要獲得decode的Bitmap的尺寸規格一致;
  • Android4.4及其以上的平臺,只須要知足inBitmap的尺寸大於要decode獲得的Bitmap的尺寸規格便可;

在知足以上條件的時候,系統對圖片進行decoder的時候會檢查內存中是否有可複用的Bitmap,避免咱們頻繁的去SD卡上加載圖片而形成系統性能的降低,畢竟從直接從內存中複用要比在SD卡上進行IO操做的效率要提升幾十倍。寫了太多文字,下面接着給出幾段Demo Code

Set<SoftReference<Bitmap>> mReusableBitmaps; private LruCache<String, BitmapDrawable> mMemoryCache; // 用來盛放被LruCache淘汰出列的Bitmap if (Utils.hasHoneycomb()) { mReusableBitmaps = Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>()); } mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) { // 當LruCache淘汰對象的時候被調用,用於在內存中重用Bitmap,提升加載圖片的性能 @Override protected void entryRemoved(boolean evicted, String key, BitmapDrawable oldValue, BitmapDrawable newValue) { if (RecyclingBitmapDrawable.class.isInstance(oldValue)) { ((RecyclingBitmapDrawable) oldValue).setIsCached(false); } else { if (Utils.hasHoneycomb()) { mReusableBitmaps.add (new SoftReference<Bitmap>(oldValue.getBitmap())); } } } .... } private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) { //將inMutable設置true,inBitmap生效的條件之一 options.inMutable = true; if (cache != null) { // 嘗試尋找能夠內存中課複用的的Bitmap Bitmap inBitmap = cache.getBitmapFromReusableSet(options); if (inBitmap != null) { options.inBitmap = inBitmap; } } } // 獲取當前能夠知足複用條件的Bitmap,存在則返回該Bitmap,不存在則返回null protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) { Bitmap bitmap = null; if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) { synchronized (mReusableBitmaps) { final Iterator<SoftReference<Bitmap>> iterator = mReusableBitmaps.iterator(); Bitmap item; while (iterator.hasNext()) { item = iterator.next().get(); if (null != item && item.isMutable()) { if (canUseForInBitmap(item, options)) { bitmap = item; iterator.remove(); break; } } else { iterator.remove(); } } } } return bitmap; } //判斷是否知足使用inBitmap的條件 static boolean canUseForInBitmap( Bitmap candidate, BitmapFactory.Options targetOptions) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // Android4.4開始,被複用的Bitmap尺寸規格大於等於須要的解碼規格便可知足複用條件 int width = targetOptions.outWidth / targetOptions.inSampleSize; int height = targetOptions.outHeight / targetOptions.inSampleSize; int byteCount = width * height * getBytesPerPixel(candidate.getConfig());return byteCount <= candidate.getAllocationByteCount();}// Android4.4以前,必須知足被複用的Bitmap和請求的Bitmap尺寸規格一致才能被複用return candidate.getWidth()== targetOptions.outWidth && candidate.getHeight()== targetOptions.outHeight && targetOptions.inSampleSize ==1;}

4.下降採樣率,inSampleSize的計算 
相信你們對inSampleSize是必定不會陌生的,因此此處再也不作過多的介紹,關於下降採樣率對inSampleSize的計算方法,我看到網上的算法有不少,下面的這段算法應該是最好的算法了,其中還考慮了那種寬高相差很懸殊的圖片(例如:全景圖)的處理。

public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } long totalPixels = width / inSampleSize * height / inSampleSize ; final long totalReqPixelsCap = reqWidth * reqHeight * 2; while (totalPixels > totalReqPixelsCap) { inSampleSize *= 2; totalPixels /= 2; } } return inSampleSize;

5.採用decodeFileDescriptor來編碼圖片(暫時不知道原理,歡迎高手指點迷津) 
關於採用decodeFileDescriptor去處理圖片能夠節省內存這方面,我在寫代碼的時候進行過嘗試,確實想比其餘的decode方法要節省內存,查詢了網上的解釋,不是很清楚,本身看了一些源代碼也弄不出個名堂,爲何使用這種方式就可以節省內存一些呢,若是有明白其中原理的高手,歡迎解答個人疑惑

 2.7 Bitmap

Bitmap在Android中指的是一張圖片,能夠是png,也能夠是jpg等其餘圖片格式。

1、Bitmap的基本加載

Bitmap的加載離不開BitmapFactory類,關於Bitmap官方介紹Creates Bitmap objects from various sources, including files, streams, and byte-arrays.查看api,發現和描述的同樣,BitmapFactory類提供了四類方法用來加載Bitmap:

  1. decodeFile 從文件系統加載
    a. 經過Intent打開本地圖片或照片
    b. 在onActivityResult中獲取圖片uri
    c. 根據uri獲取圖片的路徑
    d. 根據路徑解析bitmap:Bitmap bm = BitmapFactory.decodeFile(sd_path)
  2. decodeResource 以R.drawable.xxx的形式從本地資源中加載
    Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.aaa);
  3. decodeStream 從輸入流加載
    a.開啓異步線程去獲取網絡圖片
    b.網絡返回InputStream
    c.解析:Bitmap bm = BitmapFactory.decodeStream(stream),這是一個耗時操做,要在子線程中執行
  4. decodeByteArray 從字節數組中加載
    接3.a,3.b,
    c. 把InputStream轉換成byte[]
    d. 解析:Bitmap bm = BitmapFactory.decodeByteArray(myByte,0,myByte.length);

注意:decodeFile和decodeResource間接調用decodeStream方法。
關於圖片的基本加載既不是本文的重點,也不是什麼難點,因此這裏就不貼詳細代碼了,這裏只寫幾句關鍵代碼和僞代碼,【詳細代碼】能夠下載查看

2、高效的加載Bitmap

咱們在使用bitmap時,常常會遇到內存溢出等狀況,這是由於圖片太大或者android系統對單個應用施加的內存限制等緣由形成的,

好比上述方法1加載一張照片時就會報:06-28 10:43:30.777 26007-26036/com.peak.app W/OpenGLRenderer: Bitmap too large to be uploaded into a texture (3120x4160, max=4096x4096)

而方法2加載一個3+G的照片時會報Caused by: java.lang.OutOfMemoryError: Failed to allocate a 144764940 byte allocation with 16765264 free bytes and 109MB until OOM

因此,高效的使用bitmap就顯得尤其重要,對他效率的優化也是如此。

高效加載Bitmap的思想也很簡單,就是使用系統提供給咱們Options類來處理Bitmap。翻看Bitmap的源碼,發現上述四個加載bitmap的方法都是支持Options參數的。

經過BitmapFactory.Options按必定的採樣率來加載縮小後的圖片,而後在ImageView中使用縮小的圖片這樣就會下降內存佔用避免【OOM】,提升了Bitamp加載時的性能。

這其實就是咱們常說的圖片尺寸壓縮。尺寸壓縮是壓縮圖片的像素,一張圖片所佔內存的大小 圖片類型*寬*高,經過改變三個值減少圖片所佔的內存,防止OOM,固然這種方式可能會使圖片失真 。

android 色彩模式說明:

  • ALPHA_8:每一個像素佔用1byte內存。
  • ARGB_4444:每一個像素佔用2byte內存
  • ARGB_8888:每一個像素佔用4byte內存
  • RGB_565:每一個像素佔用2byte內存

Android默認的色彩模式爲ARGB_8888,這個色彩模式色彩最細膩,顯示質量最高。但一樣的,佔用的內存也最大。

BitmapFactory.Options的inPreferredConfig參數能夠 指定decode到內存中,手機中所採用的編碼,可選值定義在Bitmap.Config中。缺省值是ARGB_8888。

假設一張1024*1024,模式爲ARGB_8888的圖片,那麼它佔有的內存就是:1024*1024*4 = 4MB

一、採樣率inSampleSize
  • inSampleSize的值必須大於1時纔會有效果,且採樣率同時做用於寬和高;
  • 當inSampleSize=1時,採樣後的圖片爲圖片的原始大小
  • 當inSampleSize=2時,採樣後的圖片的寬高均爲原始圖片寬高的1/2,這時像素爲原始圖片的1/(22),佔用內存也爲原始圖片的1/(22);
  • inSampleSize的取值應該總爲2的整數倍,不然會向下取整,取一個最接近2的整數倍,好比inSampleSize=3時,系統會取inSampleSize=2

假設一張1024*1024,模式爲ARGB_8888的圖片,inSampleSize=2,原始佔用內存大小是4MB,採樣後的圖片佔用內存大小就是(1024/2) * (1024/2 )* 4 = 1MB

二、獲取採樣率遵循如下步驟
  1. 將BitmapFacpry.Options的inJustDecodeBounds參數設爲true並加載圖片當inJustDecodeBounds爲true時,執行decodeXXX方法時,BitmapFactory只會解析圖片的原始寬高信息,並不會真正的加載圖片
  2. 從BitmapFacpry.Options取出圖片的原始寬高(outWidth,outHeight)信息
  3. 選取合適的採樣率
  4. 將BitmapFacpry.Options的inSampleSize參數設爲false並從新加載圖片

通過上面過程加載出來的圖片就是採樣後的圖片,代碼以下:

public void decodeResource(View view) { Bitmap bm = decodeBitmapFromResource(); imageview.setImageBitmap(bm); } private Bitmap decodeBitmapFromResource(){ BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.drawable.bbbb, options); options.inSampleSize = calculateSampleSize(options,300,300); options.inJustDecodeBounds =false; return BitmapFactory.decodeResource(getResources(),R.drawable.bbbb,options); } // 計算合適的採樣率(固然這裏還能夠本身定義計算規則),reqWidth爲指望的圖片大小,單位是px private int calculateSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){ Log.i("========","calculateSampleSize reqWidth:"+reqWidth+",reqHeight:"+reqHeight); int width = options.outWidth; int height =options.outHeight; Log.i("========","calculateSampleSize width:"+width+",height:"+height); int inSampleSize = 1; int halfWidth = width/2; int halfHeight = height/2; while((halfWidth/inSampleSize)>=reqWidth&& (halfHeight/inSampleSize)>=reqHeight){ inSampleSize*=2; Log.i("========","calculateSampleSize inSampleSize:"+inSampleSize); } return inSampleSize; } 

3、使用Bitmap時的一些注意事項

一、不用的Bitmap及時釋放
if (!bmp.isRecycle()) {  bmp.recycle(); //回收圖片所佔的內存 bitmap = null; system.gc(); //提醒系統及時回收 } 

雖然調用recycle()並不能保證當即釋放佔用的內存,可是能夠加速Bitmap的內存的釋放。
釋放內存之後,就不能再使用該Bitmap對象了,若是再次使用,就會拋出異常。因此必定要保證再也不使用的時候釋放。好比,若是是在某個Activity中使用Bitmap,就能夠在Activity的onStop()或者onDestroy()方法中進行回收。

二、捕獲異常

由於Bitmap很是耗內存,了避免應用在分配Bitmap內存的時候出現OutOfMemory異常之後Crash掉,須要特別注意實例化Bitmap部分的代碼。

一般,在實例化Bitmap的代碼中,必定要對OutOfMemory異常進行捕獲。

不少開發者會習慣性的在代碼中直接捕獲Exception。

可是對於OutOfMemoryError來講,這樣作是捕獲不到的。由於OutOfMemoryError是一種Error,而不是Exception

Bitmap bitmap = null; try { // 實例化Bitmap bitmap = BitmapFactory.decodeFile(path); } catch (OutOfMemoryError e) { //捕獲不到 } if (bitmap == null) { return defaultBitmapMap; // 若是實例化失敗 返回默認的Bitmap對象 } 
三、【緩存通用的Bitmap對象】

有時候,可能須要在一個Activity裏屢次用到同一張圖片。好比一個Activity會展現一些用戶的頭像列表,而若是用戶沒有設置頭像的話,則會顯示一個默認頭像,

而這個頭像是位於應用程序自己的資源文件中的。

若是有相似上面的場景,就能夠對同一Bitmap進行緩存。

若是不進行緩存,儘管看到的是同一張圖片文件,可是使用BitmapFactory類的方法來實例化出來的Bitmap,是不一樣的Bitmap對象。

緩存能夠避免新建多個Bitmap對象,避免內存的浪費。

在Android應用開發過程當中所說的緩存有兩個級別,一個是硬盤緩存,一個是內存緩存。

四、圖片的質量壓縮

上述用inSampleSize壓縮是尺寸壓縮,Android中還有一種壓縮方式叫質量壓縮。

質量壓縮是在保持像素的前提下改變圖片的位深及透明度等,來達到壓縮圖片的目的,通過它壓縮的圖片文件大小(kb)會有改變,可是導入成bitmap後佔得內存是不變的,

寬高也不會改變。由於要保持像素不變,因此它就沒法無限壓縮,到達一個值以後就不會繼續變小了。

顯然這個方法並不適用與縮略圖,其實也不適用於想經過壓縮圖片減小內存的適用,僅僅適用於想在保證圖片質量的同時減小文件大小的狀況而已

private void compressImage(Bitmap image, int reqSize) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.JPEG, 100, baos);// 質量壓縮方法,這裏100表示不壓縮, int options = 100; while (baos.toByteArray().length / 1024 > reqSize) { // 循環判斷壓縮後的圖片是否大於reqSize,大於則繼續壓縮 baos.reset();//清空baos image.compress(Bitmap.CompressFormat.JPEG, options, baos);// 這裏壓縮options%,把壓縮後的數據放到baos中 options -= 10; } // 把壓縮後的baos放到ByteArrayInputStream中 ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray()); //decode圖片 Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null); } 
五、Android加載大量圖片內存溢出解決方案:
  1. 儘可能不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource來設置一張大圖,由於這些函數在完成decode後,最終都是經過java層的createBitmap來完成的,須要消耗更多內存,能夠經過BitmapFactory.decodeStream方法,建立出一個bitmap,再將其設爲ImageView的 source
  2. 使用BitmapFactory.Options對圖片進行壓縮(上述第二部分)
  3. 運用Java軟引用,進行圖片緩存,將須要常常加載的圖片放進緩存裏,避免反覆加載

4、Bitmap一些其餘用法

一、圖片旋轉指定角度
// 圖片旋轉指定角度 private Bitmap rotateImage(Bitmap image, final int degree) { int width = image.getWidth(); int height = image.getHeight(); if (width > height) { Matrix matrix = new Matrix(); matrix.postRotate(degree); if (image != null && !image.isRecycled()) { Bitmap resizedBitmap = Bitmap.createBitmap(image, 0, 0, width, height, matrix, true); return resizedBitmap; } else { return null; } } else { return image; } } 
二、圖片合成
private Bitmap createStarBitmap(float grade, int maxGrade) { Bitmap empty_star = BitmapFactory.decodeResource(getResources(), R.drawable.empty_star); // 空星 Bitmap normal_star = BitmapFactory.decodeResource(getResources(), R.drawable.normal_star); // 實星 Bitmap half_star = BitmapFactory.decodeResource(getResources(), R.drawable.half_star); ; // 半星 int star_width = empty_star.getWidth(); int star_height = empty_star.getHeight(); Bitmap newb = Bitmap.createBitmap(star_width * 5, star_height, Bitmap.Config.ARGB_8888);// 建立一個底層畫布 Canvas cv = new Canvas(newb); for (int i = 0; i < maxGrade; i++) { if (i < grade && i + 1 > grade) // 畫半星 { cv.drawBitmap(half_star, star_width * i, 0, null);// 畫圖片的位置 } else if (i < grade) // 畫實心 { cv.drawBitmap(normal_star, star_width * i, 0, null);// 畫圖片的位置 } else // 畫空心 { cv.drawBitmap(empty_star, star_width * i, 0, null);// 畫圖片的位置 } } // save all clip cv.save(Canvas.ALL_SAVE_FLAG);// 保存 // store cv.restore();// 存儲 return newb; } activity中調用 Bitmap bm = createStarBitmap(3.5f, 5); imageview.setImageBitmap(bm); 

上述代碼展現的是經過右圖的三張圖片動態合成評分組件:


 
QQ截圖20160628161121.png
三、圖片圓角
public Bitmap toRoundCorner(Bitmap bitmap, int pixels) { Bitmap roundCornerBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(roundCornerBitmap); int color = 0xff424242;// int color = 0xff424242; Paint paint = new Paint(); paint.setColor(color); // 防止鋸齒 paint.setAntiAlias(true); Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); RectF rectF = new RectF(rect); float roundPx = pixels; // 至關於清屏 canvas.drawARGB(0, 0, 0, 0); // 先畫了一個帶圓角的矩形 canvas.drawRoundRect(rectF, roundPx, roundPx, paint); paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); // 再把原來的bitmap畫到如今的bitmap!!!注意這個理解 canvas.drawBitmap(bitmap, rect, rect, paint); return roundCornerBitmap; } 
 
QQ截圖20160628161823.png
四、將Bitmap轉換成drawable

Drawable newBitmapDrawable = new BitmapDrawable(bitmap);
還能夠從BitmapDrawable中獲取Bitmap對象
Bitmap bitmap = new BitmapDrawable.getBitmap();

五、drawable轉換成Bitmap
public static Bitmap drawableToBitmap(Drawable drawable) { Bitmap bitmap = Bitmap.createBitmap( drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565 ); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); drawable.draw(canvas); return bitmap; } 
六、圖片的放大和縮小
public Bitmap scaleMatrixImage(Bitmap oldbitmap, float scaleWidth, float scaleHeight) { Matrix matrix = new Matrix(); matrix.postScale(scaleWidth,scaleHeight);// 放大縮小比例 Bitmap ScaleBitmap = Bitmap.createBitmap(oldbitmap, 0, 0, oldbitmap.getWidth(), oldbitmap.getHeight(), matrix, true); return ScaleBitmap; } 
七、圖片裁剪
public Bitmap cutImage(Bitmap bitmap, int reqWidth, int reqHeight) { Bitmap newBitmap = null; if (bitmap.getWidth() > reqWidth && bitmap.getHeight() > reqHeight) { bitmap = Bitmap.createBitmap(bitmap, 0, 0, reqWidth, reqHeight); } else { bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight()); } return bitmap; } 
八、圖片保存到sd
public void savePic(Bitmap bitmap,String path) { File file = new File(path); FileOutputStream fileOutputStream = null; try { file.createNewFile(); fileOutputStream = new FileOutputStream(file); bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream); fileOutputStream.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (fileOutputStream != null) { fileOutputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } }
5樓 

咱們項目要求聊天發送圖片要作圖片優化超過2M壓縮,,可是。。

咱們是集成環信聊天,我選擇作好了拍照壓縮,選擇相冊的圖片壓縮還沒作好,

不知道怎麼把已經的到的本地路徑變成Bitmap,而後傳我還得把bitmap轉換成路徑,

哎,這都不是重點,重點是我壓縮了傳到環信服務器不是一張高清大圖,那麼對方接收到的也是模模糊糊的,這樣好麼?我想知道你們的是怎樣

 環信的 Message,包含縮略圖和高清圖,默認加載的能夠是縮略圖,點擊看大圖加載原圖

decodeFile和decodeResource間接調用decodeStream方法。
這個好像是4.3版本以後去掉了,性能降低了,因此得本身封裝Stream性能好點

 

獲取採樣率遵循如下步驟中的第四條:應該是將inJustDecodeBounds參數設爲false並從新加載圖片

3.Bitmap

3.1 Recycle

【說明】

【1】Bitmap的內存的回收,分爲兩部分,須要對兩部分都要回收;

【2】bitmap官網:recycle能夠調用,調用以前確認圖片再也不使用,不然一旦清理,沒法恢復,會報異常;

3.2 LRU算法

【說明】存儲bitmap做爲三級緩存使用的,LRU:最近最少使用的對象會從隊列中清除;

什麼是LRU】內部使用了LinkedHashMap,裏面提供了get/post方法完成緩存的添加和獲取操做;當緩存慢的時候會提供一個trimToSize方法,將較早的緩存移除,而後添加新的緩存;

3.3 計算inSampleSize 

【說明】下面是官網提供的設置SampleSize的方法

3.4 縮略圖

【說明】必問:inJustDecodeBounds;

 

3.5 三級緩存

【說明】本地、內存、網絡三級緩存;減小用戶流量的使用和沒必要要的網絡數據的加載;

什麼是三級緩存?

  1. 內存緩存,優先加載,速度最快
  2. 本地緩存,次優先加載,速度快
  3. 網絡緩存,最後加載,速度慢,浪費流量

爲何要進行三級緩存

三級緩存策略,最實在的意義就是 減小沒必要要的流量消耗,增長加載速度

現在的 APP 網絡交互彷佛已經必不可少,經過網絡獲取圖片再正常不過了。可是,每次啓動應用都要從網絡獲取圖片,或者是想重複瀏覽一些圖片的時候,每次瀏覽都須要網絡獲取,消耗的流量就多了,在現在的流量資費來講,確定會容易影響用戶數量。

還有就是網絡加載圖片,有時候會加載很慢,影響了用戶體驗。

另外從開發角度來講,Bitmap 的建立很是消耗時間和內存,可能致使頻繁GC。而使用緩存策略,會更加高效地加載 Bitmap,減小卡頓,從而減小讀取時間。

而內存緩存的主要做用是防止應用重複將圖片數據讀取到內存當中,硬盤緩存則是防止應用重複從網絡或其餘地方重複下載和讀取數據。

三級緩存的原理

  1. 首次加載的時候經過網絡加載,獲取圖片,而後保存到內存和 SD 卡中。
  2. 以後運行 APP 時,優先訪問內存中的圖片緩存。
  3. 若是內存沒有,則加載本地 SD 卡中的圖片。

具體的緩存策略能夠是這樣的:內存做爲一級緩存,本地做爲二級緩存,網絡加載爲最後。

其中,內存使用 LruCache ,其內部經過 LinkedhashMap 來持有外界緩存對象的強引用;

對於本地緩存,使用 DiskLruCache。加載圖片的時候,首先使用 LRU 方式進行尋找,找不到指定內容,按照三級緩存的方式,進行本地搜索,尚未就網絡加載。

圖片緩存代碼實現

本身實現一個三級緩存的工具類並不困難。大概能夠這樣:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class BitmapUtil{
 
   //單例模式
  //···
  
  public void displayImage(ImageView img, String url){
    Bitmap bitmap;
    //內存緩存,url作惟一標識符
    bitmap = loadBitmapFromMemoryCache(url);
    if (bitmap != null ){
      img.setImageBitmap(bitmap);
      return ;
    }
    //本地緩存
    bitmap = loadBitmapFromDiskCache(url);
    if (bitmap != null ){
      img.setImageBitmap(bitmap);
      //而後將本地緩存保存到內存緩存中
      return ;
    }
    //網絡緩存
    bitmap = loadBitmapFromNet(url);
    if (bitmap != null ){
      img.setImageBitmap(bitmap);
      //同理將緩存保存到內存和本地中
      return ;
    }
  }
  
}

詳細不說了,網上有不少相似的文章能夠參考。

關於內存緩存的實現核心基本就是獲取APP最大內存,而後set的時候用 LruCache< url , bitmap> put 進去。他會按照最近最少使用的算法將內存控制在必定大小內,超出的時候自動回收。

還有一點注意的是,通常url做爲 key 的時候,會用MD5算法處理一下,最後是用其 MD5 值做爲key的,這多是爲了不一些特殊字符影響使用。

關於Glide的緩存

事實上,如今已經不多本身封裝一個三級緩存策略,在衆多的圖片框架中都加入緩存策略,實現起來更簡單。這裏以 Glide 爲例。

Glide 的使用基本就是一行代碼就解決了。像下面這樣

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 加載本地圖片
File file = new File(getExternalCacheDir() + "/image.jpg" );
Glide.with( this ).load(file).into(imageView);
 
// 加載應用資源
int resource = R.drawable.image;
Glide.with( this ).load(resource).into(imageView);
 
// 加載二進制流
byte [] image = getImageBytes();
Glide.with( this ).load(image).into(imageView);
 
// 加載Uri對象
Uri imageUri = getImageUri();
Glide.with( this ).load(imageUri).into(imageView);

固然應用到項目裏面最好二次封裝一下。這些不是此次文章的主題。咱們回到緩存上面來。

Glide 的內存緩存

Glide 是默認開啓了內存緩存的,只要你經過 Glide 加載一張圖片,他就會緩存到內存中,只要他還沒被從內存中清理以前,下次使用 Glide 都會從內存緩存中加載。大大提高了圖片加載的效率。

固然若是你有特殊要求,能夠添加一行代碼把默認開啓的內存緩存關閉掉。

 
1
2
3
4
Glide.with( this )
    .load(url)
    .skipMemoryCache( true ) //關閉內存緩存
    .into(imageView);

Glide 的內存緩存實際上和咱們上面說的差異不大,使用的也是LruCache算法,不過他還結合了一種弱引用機制,共同完成了內存緩存功能。

Glide 的硬盤緩存

關於 Glide 硬盤緩存使用也是十分簡單。

 
1
2
3
4
Glide.with( this )
    .load(url)
    .diskCacheStrategy(DiskCacheStrategy.RESULT)
    .into(imageView);

一個 diskCacheStrategy( ) 方法就能夠調整他的硬盤緩存策略。其中能夠傳入的參數有四種:

  1. DiskCacheStrategy.NONE: 表示不緩存任何內容。
  2. DiskCacheStrategy.SOURCE: 表示只緩存原始圖片。
  3. DiskCacheStrategy.RESULT: 表示只緩存轉換事後的圖片(默認選項)。
  4. DiskCacheStrategy.ALL : 表示既緩存原始圖片,也緩存轉換事後的圖片。

Glide 的硬盤緩存是默認將圖片壓縮轉換後再緩存到硬盤中,這種處理方式再避免OOM的時候會常常看見。

若是須要改變硬盤緩存策略只須要改變其傳入的參數便可。

面試題-bitmap使用的注意事項

Bitmap使用的時候主要從下面幾個方面優化
【1】三級緩存使用:內存緩存+文件緩存 這個應該不能算是內存方面的優化,應該算性能上面的優化。
【2】及時釋放Bitmap的內存:正如上面說到的Android 3.0以前有部份內存是分配在native上的,必須手動去釋放。
【3】複用內存:BitmapFactory.Options 參數inBitmap的使用。inMutable設置爲true,而且配合SoftReference軟引用使用(內存空間足夠,垃圾回收器就不會回收它;若是內存空間不足了,就會回收這些軟引用對象的內存)。
【4】有一點要注意Android4.4如下的平臺,須要保證inBitmap和即將要獲得decode的Bitmap的尺寸規格一致,Android4.4及其以上的平臺,只須要知足inBitmap的尺寸大於要decode獲得的Bitmap的尺寸規格便可。 【5】下降採樣率,必問:inJustDecodeBounds,BitmapFactory.Options 參數inSampleSize的使用,從而減小內存的使用。

 

 4.UI卡頓

 4.1 UI卡頓的原理

 

 

UI界面的渲染要在16ms內完成;

虛擬機在gc的時候,會暫停全部線程的運行,若是恰好界面在滑動的時候遇到了gc,則卡頓;

因此放置內存的抖動;

【出現卡頓緣由】主要緣由來自與Android的渲染時作了太多的耗時操做

【太多耗時的緣由】:多是UI佈局太複雜;多是UI佈局層疊了太多的佈局項;動畫執行次數過多; 

 【overDraw】屏幕上同一幀的數據被繪製了不少次,常常出如今多層次的UI結構中;

 解決UI卡頓的問題:能夠使用手機當中的開發者選項當中的GPU選項,有藍色、淡綠色、深紅色;減小紅色,儘可能出現藍色;

4.2 UI卡頓的緣由

【注意】background的設置和子view的設置必定要謹慎謹慎再謹慎;

 

4.3 UI卡頓的總結

【merge標籤】

每一個View都會經歷這三個步驟,若是這個View有子View的話,在onMeasure onlayout ondraw 這三個方法處理的方式 基本都是相同的,先是遍歷子View樹,而後測量、layout、draw。每一步測量都是耗時的,view樹越深,耗時越多,因此,在咱們在書寫xml佈局的時候,儘可能要減小多餘層級的嵌套。這時候,merge標籤就頗有用了。

咱們先看一個簡單的例子,由於可能不少人也都有寫過。<b>自定義組合View</b>

public class MyView extends RelativeLayout { public MyView(Context context) { this(context, null); } public MyView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initViews(); } private void initViews() { LayoutInflater.from(getContext()).inflate(R.layout.view_myview, this); } } 

而後佈局文件

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/first" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:text="我愛稀土掘金"/> <TextView android:layout_below="@+id/first" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="我愛稀土掘金"/> </RelativeLayout> 

運行結果

 
Paste_Image.png

也是so easy

OK,你可能會問了,這有什麼東西嗎?
咱們來看一下View的層級

 
Paste_Image.png

沒錯,多了一層,也就是MyView明明能夠很好的承載兩個TextView,爲何還要多一個ViewGroup承載呢?咱們做爲一名開發者,不能容忍任何性能的消耗,因此這種狀況,咱們可能須要使用merge標籤

<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/first" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:text="我愛稀土掘金"/> <TextView android:layout_below="@+id/first" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="我愛稀土掘金"/> </merge> 

OK在運行一下

 
Paste_Image.png
 
Paste_Image.png

成功消去那一層,勝利了。
那是否是什麼狀況均可以這麼搞呢?其實確定不是的,好比說

public class MyView extends LinearLayout { public MyView(Context context) { this(context, null); } public MyView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public MyView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initViews(); } private void initViews() { LayoutInflater.from(getContext()).inflate(R.layout.view_myview, this); } } 

我將相對佈局換成線性佈局了。而後看結果

 
Paste_Image.png

不同了哦,這是爲何?咱們只能從源碼入手,來解釋這個問題。因爲已經有大神寫的很是好了,因此我就不班門弄斧了。
我推薦一篇博客給你們<a href='http://blog.csdn.net/bboyfeiyu/article/details/45869393'>源碼解析</a>
關鍵源碼

final View view = createViewFromTag(parent, name, attrs); // 獲取merge標籤的parent final ViewGroup viewGroup = (ViewGroup) parent; // 獲取佈局參數 final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); // 遞歸解析每一個子元素 rInflate(parser, view, attrs, true); // 將子元素直接添加到merge標籤的parent view中 viewGroup.addView(view, params); 

緣由就是,其實咱們使用merge標籤後,Layoutinflater解析遇到merge標籤,直接解析成View,而後把解析出來的屬性經過generateLayoutParams方法,變成咱們熟悉的params,而後再添加到merge父容器裏面去。父容器,在咱們這裏對應着MyView,MyView也就是LinearLayout,因此纔會出現上面那樣的效果,雖然,咱們在merge標籤裏面寫了below,可是LinearLayout是沒法識別這個屬性,仍是按照默認的橫向走向佈局2個子View,致使出現了以上的狀況,因此,使用merge仍是有很大的侷限性的.

咱們要注意。要<b>自定義組合View會增長一層層級,因此,若是能夠的話,咱們要加上merge標籤。並且保證咱們在merge標籤裏面寫的一些屬性,要能夠被merge標籤的父容器識別才能夠。</b>

做者:suwanroy
連接:https://www.jianshu.com/p/fbcc6a17d11e
來源:簡書
著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。

 

ViewStub用法
在開發應用程序的時候,常常會遇到這樣的狀況,會在運行時動態根據條件來決定顯示哪一個View或某個佈局。那麼最一般的想法就是把可能用到的View都寫在上面,
先把它們的可見性都設爲View.GONE,而後在代碼中動態的更改它的可見性。這樣的作法的優勢是邏輯簡單並且控制起來比較靈活。
可是它的缺點就是,耗費資源。雖然把View的初始可見View.GONE可是在Inflate佈局的時候View仍然會被Inflate,也就是說仍然會建立對象,會被實例化,會被設置屬性。也就是說,會耗費內存等資源。 推薦的作法是使用android.view.ViewStub,ViewStub 是一個輕量級的View,它一個看不見的,不佔佈局位置,佔用資源很是小的控件。
能夠爲ViewStub指定一個佈局,在Inflate佈局的時候,只有 ViewStub會被初始化,而後當ViewStub被設置爲可見的時候,或是調用了ViewStub.inflate()的時候,V
iewStub所向 的佈局就會被Inflate和實例化,而後ViewStub的佈局屬性都會傳給它所指向的佈局。這樣,就能夠使用ViewStub來方便的在運行時,要還 是不要顯示某個佈局。 但ViewStub也不是萬能的,下面總結下ViewStub能作的事兒和何時該用ViewStub,何時該用可見性的控制。 首先來講說ViewStub的一些特色:
1. ViewStub只能Inflate一次,以後ViewStub對象會被置爲空。按句話說,某個被ViewStub指定的佈局被Inflate後,就不會夠再經過ViewStub來控制它了。 2. ViewStub只能用來Inflate一個佈局文件,而不是某個具體的View,固然也能夠把View寫在某個佈局文件中。 基於以上的特色,那麼能夠考慮使用ViewStub的狀況有: 1. 在程序的運行期間,某個佈局在Inflate後,就不會有變化,除非從新啓動。 由於ViewStub只能Inflate一次,以後會被置空,因此沒法期望後面接着使用ViewStub來控制佈局。因此當須要在運行時不止一次的顯示和 隱藏某個佈局,那麼ViewStub是作不到的。這時就只能使用View的可見性來控制了。 2. 想要控制顯示與隱藏的是一個佈局文件,而非某個View。 由於設置給ViewStub的只能是某個佈局文件的Id,因此沒法讓它來控制某個View。 因此,若是想要控制某個View(如Button或TextView)的顯示與隱藏,或者想要在運行時不斷的顯示與隱藏某個佈局或View,只能使用View的可見性來控制。 下面來看一個實例 在這個例子中,要顯示二種不一樣的佈局,一個是用TextView顯示一段文字,另外一個則是用ImageView顯示一個圖片。
這二個是在onCreate()時決定是顯示哪個,這裏就是應用ViewStub的最佳地點。 先來看看佈局,一個是主佈局,裏面只定義二個ViewStub,一個用來控制TextView一個用來控制ImageView,
另外就是一個是爲顯示文字的作的TextView佈局,一個是爲ImageView而作的佈局:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="center_horizontal"> <ViewStub --顯示文字 android:id="@+id/viewstub_demo_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="5dip" android:layout_marginRight="5dip" android:layout_marginTop="10dip" android:layout="@layout/viewstub_demo_text_layout"/> --分別引用了textView的佈局 <ViewStub --顯示圖片 android:id="@+id/viewstub_demo_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="5dip" android:layout_marginRight="5dip" android:layout="@layout/viewstub_demo_image_layout"/> --分別引用了image的佈局 </LinearLayout> 爲TextView的佈局: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:id="@+id/viewstub_demo_textview" android:layout_width="fill_parent" android:layout_height="wrap_content" android:background="#aa664411" android:textSize="16sp"/> </LinearLayout> 爲ImageView的佈局: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> <ImageView android:id="@+id/viewstub_demo_imageview" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </LinearLayout> 下面來看代碼,決定來顯示哪個,只須要找到相應的ViewStub而後調用其infalte()就能夠得到相應想要的佈局: package com.effective; import android.app.Activity; import android.os.Bundle; import android.view.ViewStub; import android.widget.ImageView; import android.widget.TextView; public class ViewStubDemoActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.viewstub_demo_activity); if ((((int) (Math.random() * 100)) & 0x01) == 0) { // to show text // all you have to do is inflate the ViewStub for textview ViewStub stub = (ViewStub) findViewById(R.id.viewstub_demo_text); stub.inflate(); TextView text = (TextView) findViewById(R.id.viewstub_demo_textview); text.setText("The tree of liberty must be refreshed from time to time" + " with the blood of patroits and tyrants! Freedom is nothing but " + "a chance to be better!"); } else { // to show image // all you have to do is inflate the ViewStub for imageview ViewStub stub = (ViewStub) findViewById(R.id.viewstub_demo_image); stub.inflate(); ImageView image = (ImageView) findViewById(R.id.viewstub_demo_imageview); image.setImageResource(R.drawable.happy_running_dog); } } } 運行結果:

使用的時候的注意事項:

1. 某些佈局屬性要加在ViewStub而不是實際的佈局上面,纔會起做用,好比上面用的android:layout_margin*系列屬性,若是加在 TextView上面,則不會起做用,須要放在它的ViewStub上面纔會起做用。而ViewStub的屬性在inflate()後會都傳給相應的布 局。

1.ViewStub之因此常稱之爲「延遲化加載」,是由於在較多數狀況下,程序無需顯示ViewStub所指向的佈局文件,只有在特定的某些較少條件下,此時ViewStub所指向的佈局文件才須要被inflate,且此佈局文件直 接將當前ViewStub替換掉,具體是經過viewStub.infalte()或 viewStub.setVisibility(View.VISIBLE)來完成;

2.正確把握住ViewStub的應用場景很是重要,正如如1中所描述需求場景下,使用ViewStub能夠優化佈局;

3.對ViewStub的inflate操做只能進行一次,由於inflate的 時候是將其指向的佈局文件解析inflate並替換掉當前ViewStub自己(由此體現出了ViewStub「佔位符」性質),一旦替換後,此時原來的 佈局文件中就沒有ViewStub控件了,
所以,若是屢次對ViewStub進行infalte,會出現錯誤信息:ViewStub must have a non-null ViewGroup viewParent。 4.3中所講到的ViewStub指向的佈局文件解析inflate並替換掉當前 ViewStub自己,並非徹底意義上的替換(與include標籤還不太同樣),替換時,佈局文件的layout params是以ViewStub爲準,其餘佈局屬性是以佈局文件自身爲準。 5.ViewStub自己是不可見的,對 ViewStub setVisibility(..)與其餘控件不同,ViewStub的setVisibility 成View.VISIBLE或INVISIBLE若是是首次使用,都會自動inflate其指向的佈局文件,並替換ViewStub自己,再次使用則是相 當於對其指向的佈局文件設置可見性。 viewStub中加載layout的代碼是 [html] view plain copy 在CODE上查看代碼片派生到個人代碼片 public View inflate() { final ViewParent viewParent = getParent();// 獲取當前view的父view,用於獲取須要加載的layout的index if (viewParent != null && viewParent instanceof ViewGroup) { if (mLayoutResource != 0) { final ViewGroup parent = (ViewGroup) viewParent; final LayoutInflater factory; if (mInflater != null) { factory = mInflater; } else { factory = LayoutInflater.from(mContext); } final View view = factory.<strong><span style="font-size:14px;color:#ff0000;">inflate</span></strong>(mLayoutResource, parent, false);// 獲取須要加載的layout if (mInflatedId != NO_ID) { view.setId(mInflatedId); } final int index = parent.indexOfChild(this); parent.removeViewInLayout(this);// 刪除以前加載的view final ViewGroup.LayoutParams layoutParams = getLayoutParams(); if (layoutParams != null) { parent.addView(view, index, layoutParams); } else { parent.<strong><span style="font-size:14px;color:#ff0000;">addView</span></strong>(view, index);// 添加view } mInflatedViewRef = new WeakReference<View>(view); if (mInflateListener != null) { mInflateListener.onInflate(this, view); } return view; } else { throw new IllegalArgumentException("ViewStub must have a valid layoutResource"); } } else { throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent"); } } 做者: 一點點征服 出處:http://www.cnblogs.com/ldq2016/

 

使用ViewStub來提升加載性能吧!
2016年12月08日 12:49:57【原文地址】http://www.javashuo.com/article/p-xucogvqq-ms.html
什麼是ViewStub?
ViewStub其實本質上也是一個View,其繼承關係如圖所示: 
ViewStub繼承關係

爲何ViewStub能夠提升加載性能?
ViewStub使用的是惰性加載的方式,即便將其放置於佈局文件中,若是沒有進行加載那就爲空,不像其它控件同樣只要佈局文件中聲明就會存在。 
那ViewStub適用於場景呢?一般用於網絡請求頁面失敗的顯示。通常狀況下若要實現一個網絡請求失敗的頁面,咱們是否是使用兩個View呢,一個隱藏,一個顯示。試想一下,
若是網絡情況良好,並不須要加載失敗頁面,可是此頁面確確實實已經加載完了,無非只是隱藏看不見而已。若是使用ViewStub,在須要的時候才進行加載,不就達到節約內存提升性能的目的了嗎?

ViewStub的加載原理 ViewStub只能加載一次,重複加載會致使異常,這是由於ViewStub只要加載過一次,其自身就會被移除,把並自身所包含的內容所有傳給父佈局。來張圖感覺一下 ViewStub的加載原理

ViewStub如何使用
父佈局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.example.administrator.myviewstub.MainActivity">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="inflate"
        android:text="inflate" />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="setData"
        android:text="setdata"/>
    <ViewStub
        android:id="@+id/vs"
        android:layout="@layout/viewstub"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
能夠看到ViewStub又引用了另一個佈局。

ViewStub佈局
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <TextView
        android:id="@+id/hello_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="DATA EMPTY!"/>
</FrameLayout>
MainActivity
public class MainActivity extends AppCompatActivity {
    private ViewStub viewStub;
    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        viewStub = (ViewStub) findViewById(R.id.vs);
        //textView  = (TextView) findViewById(R.id.hello_tv);空指針
    }
    public  void inflate(View view){
        viewStub.inflate();
        //textView  = (TextView) viewStub.findViewById(R.id.hello_tv);空指針
        textView  = (TextView) findViewById(R.id.hello_tv);
    }
    public void setData(View view){
        textView.setText("DATA!!");

    }

注意:這裏有幾個坑,前面咱們說了ViewStub只有在初始化以後纔會存在,因此第一個註釋中的空指針是由於ViewStub還未初始化。那第二個註釋中的空指針是怎麼回事呢?
前面咱們也說了,ViewStub在加載完成以後會移除自身,並把自身的內容轉給父佈局,因此此時viewStub中的內容已經不存在了,textView已是父佈局的東西了,因此不能使用viewStub來findViewById。另外,前面咱們說了ViewStub只能加載一次,若調用兩次inflate()的話會致使異常。 加載異常
爲了驗證所得出的結論是否是正確的,我截了兩張ViewStub加載先後的圖。

加載前: 
加載前
從圖中能夠看到ViewStub還沒加載,是灰色的。
加載後: 
加載後
當點擊了INFLATE以後,能夠看到,ViewStub消失了,取而代之的是一個FrameLayout,其中包含了一個AppCompatTextView(ps.其實就是TextView,只是Google在5.0以後提供了向前兼容,就比如AppCompatActivity和Activity同樣)。咳,這個FrameLayout是否是很眼熟,沒錯!就是咱們以前寫的ViewStub的佈局,忘記的童鞋翻回去看看。

順便驗證一下TextView是否是能用,點擊SETDATA: 
這裏寫圖片描述
沒有問題。

關於這個圖是怎麼來的,童鞋們只要點擊Android Montior中的Layout Inspector就行啦,就是介個: 
Layout Inspector 
感興趣的童鞋能夠本身去試試。

源碼分析
ViewStub是如何實現的呢,接下來咱們來一探究竟:

 public View inflate() {
        final ViewParent viewParent = getParent();//獲取父View

        if (viewParent != null && viewParent instanceof ViewGroup) {
        //若父不是ViewGroup就會拋出異常("ViewStub must have a non-null ViewGroup viewParent")
            if (mLayoutResource != 0) {
            //這個就是ViewStub只能加載一次的緣由,第二次加載則拋出異常(throw new IllegalArgumentException("ViewStub must have a valid layoutResource"))
                final ViewGroup parent = (ViewGroup) viewParent;
                final LayoutInflater factory;
                if (mInflater != null) {
                    factory = mInflater;
                } else {
                    factory = LayoutInflater.from(mContext);
                }
                final View view = factory.inflate(mLayoutResource, parent,
                        false);
                //建立一個View,這個View爲ViewStub的內容,mLayoutResource爲ViewStub自身的Layout資源文件id
                if (mInflatedId != NO_ID) {
                    view.setId(mInflatedId);
                    //若mInflatedId存在,則將id從新賦值給新的View
                }

                final int index = parent.indexOfChild(this);
                parent.removeViewInLayout(this);
                //經過父佈局將ViewStub移除
                final ViewGroup.LayoutParams layoutParams = getLayoutParams();
                if (layoutParams != null) {
                    parent.addView(view, index, layoutParams);
                } else {
                    parent.addView(view, index);
                }
                //將ViewStub中的內容添加到父容器中
                mInflatedViewRef = new WeakReference<View>(view);
                //設置爲弱引用,當VIewStub設置爲空時將當即執行GC
                if (mInflateListener != null) {
                    mInflateListener.onInflate(this, view);
                }

                return view;
                //最後將View返回出去
            } else {
                throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
            }
        } else {
            throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
        }
    }
ViewStub中還有一個setVisibility(int visibility)值得咱們注意:

public void setVisibility(int visibility) {
        if (mInflatedViewRef != null) {
            View view = mInflatedViewRef.get();
            //拿到關聯的Layout
            if (view != null) {
                view.setVisibility(visibility);
            } else {
                throw new IllegalStateException("setVisibility called on un-referenced view");
            }
        } else {
            super.setVisibility(visibility);
            if (visibility == VISIBLE || visibility == INVISIBLE) {
                inflate();
            }
        }
    }

能夠看到,ViewStub若以前沒有進行inflate,setVisibility(View.VISIBLE)或setVisibility(View.INVISIBLE)會自動先進行加載,
而加載以後能夠設置顯示和隱藏。而且ViewStub設置的不是本身,而是拿到關聯的那個Layout設置visible。ViewStub此時並未銷燬,因此建議初始化後將其設置爲空。

 

5.內存泄露

【內存泄露】生成的實例一致被持有,使用結束後沒法被gc;

5.1 java的內存分配策略

Java 程序運行時的內存分配策略有三種,分別是靜態分配,棧式分配,和堆式分配,對應的,三種存儲策略使用的內存空間主要分別是靜態存儲區(也稱方法區)、棧區和堆區。

  • 靜態存儲區(方法區):主要存放靜態數據、全局 static 數據和常量。這塊內存在程序編譯時就已經分配好,而且在程序整個運行期間都存在。

  • 棧區 :當方法被執行時,方法體內的局部變量(其中包括基礎數據類型、對象的引用)都在棧上建立,並在方法執行結束時這些局部變量所持有的內存將會自動被釋放。由於棧內存分配運算內置於處理器的指令集中,效率很高,可是分配的內存容量有限。

  • 堆區 : 又稱動態內存分配,一般就是指在程序運行時直接 new 出來的內存,也就是對象的實例。這部份內存在不使用時將會由 Java 垃圾回收器來負責回收。

棧與堆的區別:

在方法體內定義的(局部變量)一些基本類型的變量和對象的引用變量都是在方法的棧內存中分配的。

當在一段方法塊中定義一個變量時,Java 就會在棧中爲該變量分配內存空間,當超過該變量的做用域後,該變量也就無效了,分配給它的內存空間也將被釋放掉,該內存空間能夠被從新使用。

堆內存用來存放全部由 new 建立的對象(包括該對象其中的全部成員變量)和數組。在堆中分配的內存,將由 Java 垃圾回收器來自動管理。在堆中產生了一個數組或者對象後,還能夠在棧中定義一個特殊的變量,這個變量的取值等於數組或者對象在堆內存中的首地址,這個特殊的變量就是咱們上面說的引用變量。咱們能夠經過這個引用變量來訪問堆中的對象或者數組。

舉個例子:

public class Sample {
    int s1 = 0;
    Sample mSample1 = new Sample();

    public void method() {
        int s2 = 1;
        Sample mSample2 = new Sample();
    }
}

Sample mSample3 = new Sample();

Sample 類的局部變量 s2 和引用變量 mSample2 都是存在於棧中,但 mSample2 指向的對象是存在於堆上的。

mSample3 指向的對象實體存放在堆上,包括這個對象的全部成員變量 s1 和 mSample1,而它本身存在於棧中。

結論:

局部變量的基本數據類型和引用存儲於棧中,引用的對象實體存儲於堆中。—— 由於它們屬於方法中的變量,生命週期隨方法而結束。

成員變量所有存儲與堆中(包括基本數據類型,引用和引用的對象實體)—— 由於它們屬於類,類對象終究是要被new出來使用的。

瞭解了 Java 的內存分配以後,咱們再來看看 Java 是怎麼管理內存的。

Java是如何管理內存

Java的內存管理就是對象的分配和釋放問題。在 Java 中,程序員須要經過關鍵字 new 爲每一個對象申請內存空間 (基本類型除外),全部的對象都在堆 (Heap)中分配空間。另外,對象的釋放是由 GC 決定和執行的。在 Java 中,內存的分配是由程序完成的,而內存的釋放是由 GC 完成的,這種收支兩條線的方法確實簡化了程序員的工做。但同時,它也加劇了JVM的工做。這也是 Java 程序運行速度較慢的緣由之一。由於,GC 爲了可以正確釋放對象,GC 必須監控每個對象的運行狀態,包括對象的申請、引用、被引用、賦值等,GC 都須要進行監控。

監視對象狀態是爲了更加準確地、及時地釋放對象,而釋放對象的根本原則就是該對象再也不被引用。

爲了更好理解 GC 的工做原理,咱們能夠將對象考慮爲有向圖的頂點,將引用關係考慮爲圖的有向邊,有向邊從引用者指向被引對象。

另外,每一個線程對象能夠做爲一個圖的起始頂點,例如大多程序從 main 進程開始執行,那麼該圖就是以 main 進程頂點開始的一棵根樹。

在這個有向圖中,根頂點可達的對象都是有效對象,GC將不回收這些對象。若是某個對象 (連通子圖)與這個根頂點不可達(注意,該圖爲有向圖),

那麼咱們認爲這個(這些)對象再也不被引用,能夠被 GC 回收。如下,咱們舉一個例子說明如何用有向圖表示內存管理。

對於程序的每個時刻,咱們都有一個有向圖表示JVM的內存分配狀況。如下右圖,就是左邊程序運行到第6行的示意圖。

Java使用有向圖的方式進行內存管理,能夠消除引用循環的問題,例若有三個對象,相互引用,只要它們和根進程不可達的,

那麼GC也是能夠回收它們的。這種方式的優勢是管理內存的精度很高,可是效率較低。另一種經常使用的內存管理技術是使用計數器,

例如COM模型採用計數器方式管理構件,它與有向圖相比,精度行低(很難處理循環引用的問題),但執行效率很高。

 

什麼是Java中的內存泄露

在Java中,內存泄漏就是存在一些被分配的對象,這些對象有下面兩個特色,

首先,這些對象是可達的,即在有向圖中,存在通路能夠與其相連;

其次,這些對象是無用的,即程序之後不會再使用這些對象。

若是對象知足這兩個條件,這些對象就能夠斷定爲Java中的內存泄漏,這些對象不會被GC所回收,然而它卻佔用內存。

在C++中,內存泄漏的範圍更大一些。有些對象被分配了內存空間,而後卻不可達,因爲C++中沒有GC,這些內存將永遠收不回來。

在Java中,這些不可達的對象都由GC負責回收,所以程序員不須要考慮這部分的內存泄露。

經過分析,咱們得知,對於C++,程序員須要本身管理邊和頂點,而對於Java程序員只須要管理邊就能夠了(不須要管理頂點的釋放)。

經過這種方式,Java提升了編程的效率。

所以,經過以上分析,咱們知道在Java中也有內存泄漏,但範圍比C++要小一些。

由於Java從語言上保證,任何對象都是可達的,全部的不可達對象都由GC管理。

對於程序員來講,GC基本是透明的,不可見的。雖然,咱們只有幾個函數能夠訪問GC,例如運行GC的函數System.gc(),可是根據Java語言規範定義, 該函數不保證JVM的垃圾收集器必定會執行。由於,不一樣的JVM實現者可能使用不一樣的算法管理GC。一般,GC的線程的優先級別較低。JVM調用GC的策略也有不少種,有的是內存使用到達必定程度時,GC纔開始工做,也有定時執行的,有的是平緩執行GC,有的是中斷式執行GC。但一般來講,咱們不須要關心這些。除非在一些特定的場合,GC的執行影響應用程序的性能,例如對於基於Web的實時系統,如網絡遊戲等,用戶不但願GC忽然中斷應用程序執行而進行垃圾回收,那麼咱們須要調整GC的參數,讓GC可以經過平緩的方式釋放內存,例如將垃圾回收分解爲一系列的小步驟執行,Sun提供的HotSpot JVM就支持這一特性。

一樣給出一個 Java 內存泄漏的典型例子,

Vector v = new Vector(10);
for (int i = 1; i < 100; i++) {
    Object o = new Object();
    v.add(o);
    o = null;   
}

在這個例子中,咱們循環申請Object對象,並將所申請的對象放入一個 Vector 中,若是咱們僅僅釋放引用自己,那麼 Vector 仍然引用該對象,因此這個對象對 GC 來講是不可回收的。所以,若是對象加入到Vector 後,還必須從 Vector 中刪除,最簡單的方法就是將 Vector 對象設置爲 null。

 

5.3 內存泄露

【單例引發的內存泄露】

 

 【匿名內部類引發的內存泄露】正確的寫法:將內部類寫爲static的靜態內部類;這樣,靜態內部類就不會持有外部類的實例,不會致使外部實例沒法正常的回收;

 

【handler的內存泄露】正確寫法以下

【儘可能避免使用static成員變量】

【資源的內存泄露】socket的關閉;io流的關閉;數據庫course的關閉等等;

【AsyncTask內存泄露】在onDestory中增長移除消息的方法;

 

【bitmap】使用結束以後須要調動recycle方法,回收c端的內存實例;

【listView的內存泄露】

 6.android內存管理

 6.1 內存管理機制概述

 【說明】操做系統會爲每一個應用合理的分配內存資源,

 【內存的分配機制】操做系統會爲每一個應用分配合理的內存大小,從而保證每一個應用可以正常的運行;

 不至於內存不夠使用或者每一個應用進程使用太多的內存;

【內存的回收機制】在內存不足的時候會存在內存的合理回收和內存資源再回收的機制;

 從而保證新的進程能夠正常的運行,殺死正在佔有內存的進程;

操做系統須要提供一個合理的殺死進程的機制,從而保證反作用下降最低;

6.2 android中的內存管理機制

 【內存分配機制】android爲每一個應用分配內存時候使用了彈性機制,  開始不會爲app分配太多的內存;當app運行起來以後,

再根據狀況分配額外的內存;注意:並不是隨意分配內存的大小;

是爲了讓更多的進程存活在內存中, 當下次app啓動的時候就須要再次從新啓動進程,只要回覆已有的進程便可;減小了應用啓動的時間,提升了用戶的體驗;

【android的回收機制】android的內存的使用是盡最大的使用內存資源;

在內存不足的時候會存在內存的合理回收和內存資源再回收的機制;

 從而保證新的進程能夠正常的運行,殺死正在佔有內存的進程;

內存的分配具備優先級的概念,主要分爲5個級別:前臺進程、可見進程、服務進程(消息的推送、開啓定位等等)、後臺進程(後臺進程計算)、空進程;

前3個進程在系統通常是不會被殺死的;

後臺進程會存放在緩存列表LRU中,先殺死的進程處於列的尾部,

【回收效率】系統在殺死進程以前,會判斷殺死進程帶來的回收效率;系統傾向於殺死一個可以回收更多內存資源的進程;

但願可以殺死的進程越少,可以保留的數據越多,app啓動加載的效率越高;

 6.3 內存管理機制的特色

 【說明】在app開發的過程當中,會給app定內存目標:

6.4 內存優化的方法

 

 

 內存泄露第三方的框架;eclipse MAT內存泄露分析工具 分析程序運行時的內存的印象文件,查找到內存泄露的代碼;

 6.5 參考文章:Android工具:LeakCanary—內存泄露檢測神器

1、LeakCanary簡介
LeakCanary是Square公司開源的一個檢測內存的泄露的函數庫,能夠方便地和你的項目進行集成,在Debug版本中監控Activity、Fragment等的內存泄露;
LeakCanary集成到項目中以後,在檢測到內存泄露時,會發送消息到系統通知欄。點擊後打開名稱DisplayLeakActivity的頁面,並顯示泄露的跟蹤信息,Logcat上面也會有對應的日誌輸出。同時若是跟蹤信息不足以定位時,DisplayLeakActivity還爲開發者默認保存了最近7個dump文件到App的目錄中,能夠使用MAT等工具對dump文件進行進一步的分析;
2、內存泄漏簡介
在瞭解了LeakCanary的接入方式後,咱們確定着急想見識見識LeakCanary的威力。在跟你們演示LeakCanary檢測和處理構成以前,你們應該明應該對內存泄露有基本的瞭解和認識;
1.爲何會產生內存泄漏?
當一個對象不須要使用本該回收時,有另一個正在使用的對象持有它的引用,從而致使它不能回收停留在堆內存中,這就產生了內存泄漏;
2.內存泄露對程序產生的影響?
內存泄漏是形成應用程序OOM的主要緣由之一。Android系統爲每一個應用程序分配有限的內存,當應用中內存泄漏較多時,就不免會致使應用所須要的內存超出系統分配限額,從而致使OOM應用Crash;
3.Android常見的內存泄露?
相信內存泄露對你們都早有耳聞,可是它不像一些Java異常狀況,會當即形成程序的Crash,卻有讓你們比較「陌生」。下面咱們就列舉出平常開發中常見的內存泄露類型,讓你們對內存泄露的認識不只僅停留在「有所耳聞 」的層面;
 單例形成:因爲單例靜態特性使得單例的生命週期和應用的生命週期同樣長,若是一個對象(如Context)已經不使用了,而單例對象還持有對象的引用形成這個對象不能正常被回收;
 非靜態內部類建立靜態實例形成:在Acitivity內存建立一個非靜態內部類單例,避免每次啓動資源從新建立。可是由於非靜態內部類默認持有外部類(Activity)的引用,而且使用該類建立靜態實例。形成該實例和應用生命週期同樣長,致使靜態實例持有引用的Activity和資源不能正常回收;
 Handler形成:子線程執行網絡任務,使用Handler處理子線程發送消息。因爲handler對象是非靜態匿名內部類的對象,持有外部類(Activity)的引用。在Handler-Message中Looper線程不斷輪詢處理消息,當Activity退出還有未處理或者正在處理的消息時,消息隊列中的消息持有handler對象引用,handler又持有Activity,致使Activity的內存和資源不能及時回收;
 線程形成:匿名內部類Runnalbe和AsyncTask對象執行異步任務,對當前Activity隱式引用。當Activity銷燬以前,任務尚未執行完,將致使Activity的內存和資源不能及時回收;
 資源未關閉形成的內存泄露:對於使用了BroadcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源的使用,應該在Activity銷燬時及時關閉或者註銷,不然這些資源將不會被回收,形成內存泄露;
3、LeakCanary接入
下面咱們仍是以QProject項目進行演示如何在項目中接入LeakCanary,項目目錄以下:

1.添加LeakCanary依賴
在主項目main模塊的build.gradle文件中添加LeakCanary相關依賴;
/main/build.gradle文件

[plain]  view plain  copy
 
  1. apply plugin: 'com.android.application'  
  2. android {  
  3.     ...  ...  
  4. }  
  5. dependencies {  
  6.     ... ...  
  7.     //添加leakcanary相關的依賴  
  8.     //在release和test版本中,使用的是LeakCanary的no-op版本,也就是沒有實際代碼和操做的Wrapper版本,只包含LeakCanary和RefWatcher類的空實現,這樣不會對生成的APK包體積和應用性能形成影響  
  9.     debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'  
  10.     releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'  
  11.     testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'  
  12.     ... ...  
  13.     compile project(':test')  
  14. }  

2.初始化LeakCanary
在主項目main模塊的QApplication的onCreate()方法中初始化LeakCanary;
/main/src/main/java/com/qproject/main/QApplication.java文件

[java]  view plain  copy
 
  1. public class QAplication extends Application{  
  2.     @Override  
  3.     public void onCreate() {  
  4.         super.onCreate();  
  5.         ... ...  
  6.         //初始化LeakCanary  
  7.         if (LeakCanary.isInAnalyzerProcess(this)) {  
  8.             return;  
  9.         }  
  10.         LeakCanary.install(this);  
  11.     }  
  12. }  

OK,到這裏咱們就完成了一個項目的LeankCanary的簡單接入;
提示1:集成LeakCanary後,構建和安裝apk的時候報錯以下:
Error:Error converting bytecode to dex:
Cause: com.android.dex.DexException: Multiple dex files define Lcom/squareup/leakcanary/LeakCanary;
Error:Execution failed for task ':main:transformClassesWithDexForDebug'.
> com.android.build.api.transform.TransformException: com.android.ide.common.process.ProcessException: java.util.concurrent.ExecutionException: java.lang.UnsupportedOperationException
處理1:添加依賴debugCompile 'com.squareup.haha:haha:2.0.3',修改依賴的版本爲1.4-beta2;
4、LeakCanary檢測
經過對一些常見的內存泄露的學習,咱們已經對內存泄露有所見聞了。那麼下面咱們經過LeakCanary工具,讓你們感覺下它在你的平常開發中的真實存在。咱們以常見內存—單例形成的內存泄露爲例進行實踐;
1.單例內存泄露模擬
test/src/main/com/qproject/test/TestManager.java

[java]  view plain  copy
 
  1. public class TestManager {  
  2.     //單例靜態特性使得單例的生命週期和應用的生命週期同樣長  
  3.     private static TestManager instance;  
  4.     private Context context;  
  5.   
  6.     /** 
  7.      * 傳入的Context的生命週期很重要: 
  8.      *   若是傳入的是Application的Context,則生命週期和單例生命週期同樣長; 
  9.      *   若是傳入的是Activity的Context,因爲該Context和Activity的生命週期同樣長,當Activity退出的時候它的內存不會被回收,由於單例對象持有它的引用; 
  10.      */  
  11.     private TestManager(Context context) {  
  12.         this.context = context;  
  13.     }  
  14.   
  15.     public static TestManager getInstance(Context context) {  
  16.         if (instance == null) {  
  17.             instance = new TestManager(context);  
  18.         }  
  19.         return instance;  
  20.     }  
  21. }  

test/src/main/com/qproject/test/leakcanary/LeakCanaryActivity.java

[java]  view plain  copy
 
  1. public class LeakCanaryActivity extends AppCompatActivity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_leakcanary);  
  7.         //獲取單例對象,退出Activity便可模擬出內存泄露  
  8.         TestManager testManager = TestManager.getInstance(this);  
  9.     }  
  10. }  

2.檢測消息通知
運行App到LeakCanaryActivit頁面並退出,在檢測到內存泄露的時候,會發送消息到系統通知欄;
 
3.查看檢測詳情
點擊通知消息,打開名爲DisplayLeakActivity的頁面,並顯示泄漏的跟蹤信息;

4.查看LogCat日誌
除了以上泄漏信息的顯示,Logcat上面也會有對應的日誌輸出;

[plain]  view plain  copy
 
  1. //內存泄露對象com.qproject.test.leakcanary.LeakCanaryActivity  
  2. 12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * com.qproject.test.leakcanary.LeakCanaryActivity has leaked:  
  3. //static com.qproject.test.TestManager.instance的com.qproject.test.TestManager.context引用了回收的內存對象  
  4. 12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * GC ROOT static com.qproject.test.TestManager.instance  
  5. 12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * references com.qproject.test.TestManager.context  
  6. //內存泄露對象大小,Reference Key,Device和Android Version等信息  
  7. 12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * leaks com.qproject.test.leakcanary.LeakCanaryActivity instance  
  8. 12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Retaining: 46 KB.  
  9. 12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Reference Key: 3d74d294-70dc-4447-a9a2-64e656ea86b8  
  10. 12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Device: Genymotion Android PREVIEW - Google Nexus 5X - 7.0.0 - API 24 - 1080x1920 vbox86p  
  11. 12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Android Version: 7.0 API: 24 LeakCanary: 1.5 00f37f5  
  12. 12-25 07:50:51.710 4941-5795/com.qproject.main D/LeakCanary: * Durations: watch=5038ms, gc=137ms, heap dump=2390ms, analysis=27325ms  
  13. //內存泄露對象詳細信息  
  14. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: * Details:  
  15. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: * Class com.qproject.test.TestManager  
  16. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   static instance = com.qproject.test.TestManager@316184816 (0x12d898f0)  
  17. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   static $classOverhead = byte[308]@316175745 (0x12d87581)  
  18. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: * Instance of com.qproject.test.TestManager  
  19. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   static instance = com.qproject.test.TestManager@316184816 (0x12d898f0)  
  20. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   static $classOverhead = byte[308]@316175745 (0x12d87581)  
  21. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   context = com.qproject.test.leakcanary.LeakCanaryActivity@315059712 (0x12c76e00)  
  22. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   shadow$_klass_ = com.qproject.test.TestManager  
  23. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   shadow$_monitor_ = 0  
  24. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: * Instance of com.qproject.test.leakcanary.LeakCanaryActivity  
  25. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   static $classOverhead = byte[2228]@316203009 (0x12d8e001)  
  26. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mDelegate = android.support.v7.app.AppCompatDelegateImplN@315842128 (0x12d35e50)  
  27. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mEatKeyUpEvent = false  
  28. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mResources = null  
  29. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mThemeId = 2131230884  
  30. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mCreated = true  
  31. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mFragments = android.support.v4.app.FragmentController@316183584 (0x12d89420)  
  32. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mHandler = android.support.v4.app.FragmentActivity$1@316163360 (0x12d84520)  
  33. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mNextCandidateRequestIndex = 0  
  34. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mOptionsMenuInvalidated = false  
  35. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mPendingFragmentActivityResults = android.support.v4.util.SparseArrayCompat@316172368 (0x12d86850)  
  36. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mReallyStopped = true  
  37. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mRequestedPermissionsFromFragment = false  
  38. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mResumed = false  
  39. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mRetaining = false  
  40. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mStopped = true  
  41. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mStartedActivityFromFragment = false  
  42. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mStartedIntentSenderFromFragment = false  
  43. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mExtraDataMap = android.support.v4.util.SimpleArrayMap@316171864 (0x12d86658)  
  44. 12-25 07:50:51.711 4941-5795/com.qproject.main D/LeakCanary: |   mActionBar = null  
  45. 12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: |   mActionModeTypeStarting = 0  
  46. 12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: |   mActivityInfo = android.content.pm.ActivityInfo@315841984 (0x12d35dc0)  
  47. 12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: |   mActivityTransitionState = android.app.ActivityTransitionState@316207336 (0x12d8f0e8)  
  48. 12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: |   mApplication = com.qproject.main.QAplication@314916416 (0x12c53e40)  
  49. 12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: |   mCalled = true  
  50. 12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: |   mChangeCanvasToTranslucent = false  
  51. 12-25 07:50:51.712 4941-5795/com.qproject.main D/LeakCanary: |   mChangingConfigurations = false  
  52. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mComponent = android.content.ComponentName@315998320 (0x12d5c070)  
  53. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mConfigChangeFlags = 0  
  54. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mCurrentConfig = android.content.res.Configuration@316178888 (0x12d881c8)  
  55. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mDecor = null  
  56. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mDefaultKeyMode = 0  
  57. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mDefaultKeySsb = null  
  58. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mDestroyed = true  
  59. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mDoReportFullyDrawn = false  
  60. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mEatKeyUpEvent = false  
  61. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mEmbeddedID = null  
  62. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mEnableDefaultActionBarUp = false  
  63. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mEnterTransitionListener = android.app.SharedElementCallback$1@1887062680 (0x707a4a98)  
  64. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mExitTransitionListener = android.app.SharedElementCallback$1@1887062680 (0x707a4a98)  
  65. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mFinished = true  
  66. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mFragments = android.app.FragmentController@316183536 (0x12d893f0)  
  67. 12-25 07:50:51.713 4941-5795/com.qproject.main D/LeakCanary: |   mHandler = android.os.Handler@316163296 (0x12d844e0)  
  68. 12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: |   mHasCurrentPermissionsRequest = false  
  69. 12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: |   mIdent = 20356640  
  70. 12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: |   mInstanceTracker = android.os.StrictMode$InstanceTracker@316183552 (0x12d89400)  
  71. 12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: |   mInstrumentation = android.app.Instrumentation@314950632 (0x12c5c3e8)  
  72. 12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: |   mIntent = android.content.Intent@316215416 (0x12d91078)  
  73. 12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: |   mLastNonConfigurationInstances = null  
  74. 12-25 07:50:51.714 4941-5795/com.qproject.main D/LeakCanary: |   mMainThread = android.app.ActivityThread@314966272 (0x12c60100)  
  75. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mManagedCursors = java.util.ArrayList@316171816 (0x12d86628)  
  76. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mManagedDialogs = null  
  77. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mMenuInflater = null  
  78. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mParent = null  
  79. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mReferrer = java.lang.String@316215864 (0x12d91238)  
  80. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mResultCode = 0  
  81. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mResultData = null  
  82. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mResumed = false  
  83. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mSearchEvent = null  
  84. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mSearchManager = null  
  85. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mStartedActivity = false  
  86. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mStopped = true  
  87. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mTaskDescription = android.app.ActivityManager$TaskDescription@316163328 (0x12d84500)  
  88. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mTemporaryPause = false  
  89. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mTitle = java.lang.String@315129824 (0x12c87fe0)  
  90. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mTitleColor = 0  
  91. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mTitleReady = true  
  92. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mToken = android.os.BinderProxy@315867328 (0x12d3c0c0)  
  93. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mTranslucentCallback = null  
  94. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mUiThread = java.lang.Thread@1959751680 (0x74cf7000)  
  95. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mVisibleBehind = false  
  96. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mVisibleFromClient = true  
  97. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mVisibleFromServer = true  
  98. 12-25 07:50:51.715 4941-5795/com.qproject.main D/LeakCanary: |   mVoiceInteractor = null  
  99. 12-25 07:50:51.716 4941-5795/com.qproject.main D/LeakCanary: |   mWindow = com.android.internal.policy.PhoneWindow@315116864 (0x12c84d40)  
  100. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |   mWindowAdded = true  
  101. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |   mWindowManager = android.view.WindowManagerImpl@316172152 (0x12d86778)  
  102. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |   mInflater = com.android.internal.policy.PhoneLayoutInflater@316010352 (0x12d5ef70)  
  103. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |   mOverrideConfiguration = null  
  104. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |   mResources = android.content.res.Resources@316235992 (0x12d960d8)  
  105. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |   mTheme = android.content.res.Resources$Theme@316183728 (0x12d894b0)  
  106. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |   mThemeResource = 2131230884  
  107. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |   mBase = android.app.ContextImpl@316155392 (0x12d82600)  
  108. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |   shadow$_klass_ = com.qproject.test.leakcanary.LeakCanaryActivity  
  109. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: |   shadow$_monitor_ = 1316364430  
  110. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: * Excluded Refs:  
  111. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Field: android.view.Choreographer$FrameDisplayEventReceiver.mMessageQueue (always)  
  112. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Thread:FinalizerWatchdogDaemon (always)  
  113. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Thread:main (always)  
  114. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Thread:LeakCanary-Heap-Dump (always)  
  115. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.WeakReference (always)  
  116. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.SoftReference (always)  
  117. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.PhantomReference (always)  
  118. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.Finalizer (always)  
  119. 12-25 07:50:51.717 4941-5795/com.qproject.main D/LeakCanary: | Class:java.lang.ref.FinalizerReference (always)  

5.獲取dump日誌文件
若是以爲跟蹤信息不足以定位時,DisplayLeakActivity還爲開發者默認保存了最近7個dump文件到APP的目錄中,能夠使用MAT等工具對dump文件進行進一步的分析;

[plain]  view plain  copy
 
  1. vbox86p:/data/data/com.qproject.main/files/leakcanary # ls  
  2. 2016-12-25_07-50-51_718.hprof 2016-12-25_07-50-51_718.hprof.result  
  3. D:\>adb pull ./data/data/com.qproject.main/files/leakcanary/2016-12-25_07-50-51_ 718.hprof  
  4. [100%] ./data/data/com.qproject.main/f...akcanary/2016-12-25_07-50-51_718.hprof  

Android Studio->View->Tool Windows->Captures,打開Captures窗口,將pull獲取的hprof文件剪切到Capture中文件的目錄下,雙擊打開便可;

具體的分析過程,這裏就不重點講述,你們去查詢MAT相關的資料;
5、檢測其餘對象
若是想監聽其餘的對象(例如Fragment ),能夠經過RefWatcher的實例來實現;
 
6、LeakCanary原理
1.RefWatcher.watch()函數會爲被監控的對象建立一個KeyedWeakReference弱引用對象,是WeakReference對的子類,增長了鍵值對信息,後面會根據指定的鍵key找到弱引用對象;
2.在後臺線程AndroidWatchExecutor中,檢查KeyedWeakReference弱引用是否已經被清楚。若是還存在,則觸發一次垃圾回收以後。垃圾回收以後,若是弱引用對象依然存在,說明發生了內存泄露;

7.冷啓動優化 

 7.1 冷啓動的相關概念

冷啓動:屬於你第一次打開APP,系統給你開一個進程,啓動應用前,系統中沒有任何該應用的任何進程信息的啓動方式就稱爲冷啓動;

熱啓動:當啓動應用時,後臺已有該應用的進程(例:按back鍵、home鍵,應用雖然會退出,可是該應用的進程是依然會保留在後臺,可進入任務列表查看),因此在已有進程的狀況下,這種啓動會從已有的進程中來啓動應用,這個方式叫熱啓動。

 7.2  冷啓動和熱啓動區別:

從定義和特色兩方面來講:

  一、冷啓動:冷啓動由於系統會從新建立一個新的進程分配給它,因此會先建立和初始化Application類,再建立和初始化MainActivity類(包括一系列的測量、佈局、繪製),最後顯示在界面上。

  二、熱啓動:熱啓動由於會從已有的進程中來啓動,因此熱啓動就不會走Application這步了,而是直接走MainActivity(包括一系列的測量、佈局、繪製),因此熱啓動的過程只須要建立和初始化一個MainActivity就好了,而沒必要建立和初始化Application

  由於一個應用重新進程的建立到進程的銷燬,Application只會初始化一次。

【冷啓動白屏】冷啓動啓動的時候具備短暫的白屏時間;

 

7.3 冷啓動的時間計算

  • API19 以後,系統會出打印日誌輸出啓動的時間;
  • 冷啓動時間 = 應用啓動(建立進程) —> 完成視圖的第一次繪製(Activity內容對用戶可見);

7.4 冷啓動的流程

  • Zygote進程中fork建立出一個新的進程;
  • 建立和初始化Application類、建立MainActivity;
  • 建立MainActivity類結束以後,開始inflate佈局、當onCreate/onStart/onResume方法都走完;
  • 而後將:contentView的measure/layout/draw顯示在界面上;

總結: 
Application構造方法 –> attachBaseContext() –>Application的onCreate() –> Activity構造方法 –> Activity的onCreate() –> 配置主題中背景等屬性 –> onStart() –> onResume() –> 測量佈局繪製顯示在界面上

 7.5 冷啓動的優化

  • 減小在Application和第一個Activity的onCreate()方法的工做量;
  • 不要讓Application參與業務的操做;
  • 不要在Application進行耗時操做,好比圖片的加載等等;
  • 不要以靜態變量的方式在Application中保存數據;
  • 減小布局的複雜性和深度,①能夠使用viewStub類,須要的時候再加載就能夠;②在MainThread減小初始化的操做,能夠經過懶加載,延遲全部的初始化;

 7.6 參考文章

【Android冷啓動實現APP秒開】連接地址:https://blog.csdn.net/xx326664162/article/details/65630815

在冷啓動的時間段內發生了什麼?

首先咱們要知道當打開一個Activity的時候發生了什麼,在一個Activity打開時,若是該Activity所屬的Application尚未啓動,那麼系統會爲這個Activity建立一個進程(每建立一個進程都會調用一次Application,因此Application的onCreate()方法可能會被調用屢次),在進程的建立和初始化中,勢必會消耗一些時間,在這個時間裏,WindowManager會先加載APP裏的主題樣式裏的窗口背景(windowBackground)做爲預覽元素,而後纔去真正的加載佈局,若是這個時間過長,而默認的背景又是黑色或者白色,這樣會給用戶形成一種錯覺,這個APP很卡,很不流暢,天然也影響了用戶體驗。

熱啓動

熱啓動:當啓動應用時,後臺存在該應用的進程(back鍵,home鍵,應用退出,可是沒有銷燬),從已有的進程中啓動

熱啓動的特色:從已有的進程中啓動,不須要建立和初始化Application ,直接建立和初始化它的Launch Activity

先來看下,未優化和優化後的對比圖:

未優化,冷啓動app會出現短暫的白屏

這裏寫圖片描述

優化方案一:

使用背景圖

這裏寫圖片描述

優化方案二:

使用透明背景

這裏寫圖片描述

消除啓動時的白屏/黑屏

在用戶點擊手機桌面APP的時候,看到的黑屏或者白屏實際上是界面渲染前的第一幀,若是你看懂了文章頭的那2個問題,那麼解決這個問題就很是輕鬆了,無非就是將Theme裏的windowBackground設置成咱們想要讓用戶看到的畫面就能夠了,這裏有2種作法:

一、將背景圖設置成咱們APP的Logo圖,做爲APP啓動的引導,如今市面上大部分的APP也是這麼作的。

1 <style name="AppWelcome" parent="AppTheme">
2         <item name="android:windowBackground">@mipmap/bg_welcome_start</item>
3 </style>

二、將背景顏色設置爲透明色,這樣當用戶點擊桌面APP圖片的時候,並不會」當即」進入APP,並且在桌面上停留一會,其實這時候APP已是啓動的了,只是咱們心機的把Theme裏的windowBackground的顏色設置成透明的,強行把鍋甩給了手機應用廠商(手機反應太慢了啦,哈哈),其實如今微信也是這樣作的,不信你能夠試試。

1 <item name="android:windowIsTranslucent">true</item>
2 
3 <item name="android:windowFullscreen">true</item>

或者使用同名主題

1 <style name="Appwelcome" parent="android:Theme.Translucent.NoTitleBar.Fullscreen"/>

透明化這種作法須要注意的一點,若是直接把Theme引入Activity,在運行的時候可能會出現以下異常:

java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.

這個是由於使用了不兼容的Theme,例如我這裏的Activity繼承了AppCompatActivity,解決方案很簡單:
一、讓其Activity集成Activity而不要集成兼容性的AppCompatActivity
二、在onCreate()方法裏的super.onCreate(savedInstanceState)以前設置咱們原來APP的Theme

1 public class MainActivity extends AppCompatActivity {
2     @Override
3     protected void onCreate(Bundle savedInstanceState) {
4             setTheme(R.style.AppTheme);
5             super.onCreate(savedInstanceState);
6     }
7 }

上面的2種作法,咱們都須要將Theme引入對應的Activity

1         <activity
2             android:name=".app.main.MainActivity"
3             android:theme="@style/AppWelcome"
4             android:screenOrientation="portrait">
5             <intent-filter>
6                 <action android:name="android.intent.action.MAIN" />
7                 <category android:name="android.intent.category.LAUNCHER" />
8             </intent-filter>
9         </activity>

2、關於啓動優化

上面的作法其實能夠達到」秒開」APP的效果,不過確不是真實的速度,在Activity建立過程當中實際上是會通過一系列framework層的操做,在平常開發中,咱們都會去重寫Application類,而後在Application裏進行一些初始化操做,好比存放用戶標識的靜態化TOKEN,第三方SDK的初始化等。 
這裏給出幾點建議: 
一、不要讓Application參與業務的操做 
二、不要在APPlication進行耗時操做,好比有些開發者會在本身的APP裏一系列文件夾或文件(好比我本身),這些I/O操做應該放到」確實該使用的時候再去建立」亦或者是數據庫的一些操做。 
三、不要以靜態變量的方式在Application中保存數據等。 
四、減小LaunchActivity的View層級,減小View測量繪製時間

固然這是絕對的理想主義,把上面的」不要」2字以前添上」儘可能」2字吧,畢竟在實際開發中,這樣作確實會讓咱們方便許多。

對了,補充一點,佈局也是很重要的,儘可能的去減小布局的複雜性,佈局深度,由於在View繪製的過程當中,測量也是很耗費性能的。

3、如何測量應用啓動時間

android是有命令能夠計算啓動時間的

1 adb shell am start -W [packageName]/[packageName.launchActivity]

那就拿本身的項目來給你們看看上面兩種啓動的時間差異

冷啓動:

這裏寫圖片描述

熱啓動

這裏寫圖片描述

參考: 
Android冷啓動實現APP秒開 
探究Android的冷啓動優化 
Android Application啓動流程分析 
[Android從頭再來] App啓動過程 
Android冷啓動時間優化

 【參考文章】Android Application啓動流程分析 

1, App基礎理論

要想優化App啓動時間, 第一步就是了解App啓動進程的工做原理. 有幾個基礎理論:

Android Application與其餘移動平臺有兩個重大不一樣點:

  1. 每一個Android App都在一個獨立空間裏, 意味着其運行在一個單獨的進程中, 擁有本身的VM, 被系統分配一個惟一的user ID.
  2. Android App由不少不一樣組件組成, 這些組件還能夠啓動其餘App的組件. 所以, Android App並無一個相似程序入口的main()方法.

Android Application組件包括:

  • Activities: 前臺界面, 直接面向User, 提供UI和操做.
  • Services: 後臺任務.
  • Broadcast Receivers: 廣播接收者.
  • Contexnt Providers: 數據提供者.

Android進程與Linux進程同樣. 默認狀況下, 每一個apk運行在本身的Linux進程中. 另外, 默認一個進程裏面只有一個線程---主線程. 這個主線程中有一個Looper實例, 經過調用Looper.loop()從Message隊列裏面取出Message來作相應的處理.

那麼, 這個進程什麼時候啓動的呢?
簡單的說, 進程在其須要的時候被啓動. 任意時候, 當用戶或者其餘組件調取你的apk中的任意組件時, 若是你的apk沒有運行, 系統會爲其建立一個新的進程並啓動. 一般, 這個進程會持續運行直到被系統殺死. 關鍵是: 進程是在被須要的時候才建立的.

舉個例子, 若是你點擊email中的超連接, 會在瀏覽器裏面打開一個網頁. Email App和瀏覽器App是兩個不一樣的App, 運行在不一樣的進程中. 此次點擊事件促使Android系統去建立了一個新的進程來實例化瀏覽器的組件.

首先, 讓咱們快速看下Android啓動流程. 與衆多基於Linux內核的系統相似, 啓動系統時, bootloader啓動內核和init進程. init進程分裂出更多名爲"daemons(守護進程)"的底層的Linux進程, 諸如android debug deamon, USB deamon等. 這些守護進程處理底層硬件相關的接口.

隨後, init進程會啓動一個很是有意思的進程---"Zygote". 顧名思義, 這是一個Android平臺的很是基礎的進程. 這個進程初始化了第一個VM, 而且預加載了framework和衆多App所須要的通用資源. 而後它開啓一個Socket接口來監聽請求, 根據請求孵化出新的VM來管理新的App進程. 一旦收到新的請求, Zygote會基於自身預先加載的VM來孵化出一個新的VM建立一個新的進程.

啓動Zygote以後, init進程會啓動runtime進程. Zygote會孵化出一個超級管理進程---System Server. SystemServer會啓動全部系統核心服務, 例如Activity Manager Service, 硬件相關的Service等. 到此, 系統準備好啓動它的第一個App進程---Home進程了.

2, 啓動App流程

用戶點擊Home上的一個App圖標, 啓動一個應用時:

 
 

Click事件會調用startActivity(Intent), 會經過Binder IPC機制, 最終調用到ActivityManagerService. 該Service會執行以下操做:

  • 第一步經過PackageManager的resolveIntent()收集這個intent對象的指向信息.
  • 指向信息被存儲在一個intent對象中.
  • 下面重要的一步是經過grantUriPermissionLocked()方法來驗證用戶是否有足夠的權限去調用該intent對象指向的Activity.
  • 若是有權限, ActivityManagerService會檢查並在新的task中啓動目標activity.
  • 如今, 是時候檢查這個進程的ProcessRecord是否存在了.

若是ProcessRecord是null, ActivityManagerService會建立新的進程來實例化目標activity.

2.1 建立進程

ActivityManagerService調用startProcessLocked()方法來建立新的進程, 該方法會經過前面講到的socket通道傳遞參數給Zygote進程. Zygote孵化自身, 並調用ZygoteInit.main()方法來實例化ActivityThread對象並最終返回新進程的pid.

ActivityThread隨後依次調用Looper.prepareLoop()和Looper.loop()來開啓消息循環.

流程圖以下:

2.2 綁定Application

接下來要作的就是將進程和指定的Application綁定起來. 這個是經過上節的ActivityThread對象中調用bindApplication()方法完成的. 該方法發送一個BIND_APPLICATION的消息到消息隊列中, 最終經過handleBindApplication()方法處理該消息. 而後調用makeApplication()方法來加載App的classes到內存中.

流程以下:

 

2.3 啓動Activity

通過前兩個步驟以後, 系統已經擁有了該application的進程. 後面的調用順序就是普通的從一個已經存在的進程中啓動一個新進程的activity了.

實際調用方法是realStartActivity(), 它會調用application線程對象中的sheduleLaunchActivity()發送一個LAUNCH_ACTIVITY消息到消息隊列中, 經過 handleLaunchActivity()來處理該消息.

假設點擊的是一個視頻瀏覽的App, 其流程以下:

 

8.其餘優化的問題

 8.1 儘可能不使用靜態變量保存數據

本文講解的其實並非一個技術方面,而是一個Android產品研發過程當中的技巧:儘可能不使用靜態變量保存核心數據。

這是爲何呢?這是由於Android系統中的應用進程並非安全的,包括application對象、靜態變量在內的進程級別變量並不會一直呆着內存裏面,它會被kill掉,它真的有可能會被kill掉,真的真的,重要的事情說三遍。

與你們廣泛的見解不一樣之處在於,當進程被幹掉以後,實際上app不會從新開始啓動。Android系統會建立一個新的Application 對象,而後啓動上次用戶離開時的activity以形成這個app歷來沒有被kill掉得假象。

而這時候靜態變量等數據因爲進程已經被殺死而被初始化,因此就有了咱們的不推薦在靜態變量(包括Application中保存全局數據靜態數據)的觀點。

總結: 
在實際的Android開發過程當中咱們不建議使用靜態變量傳遞數據,這樣會由於進程被殺死而使靜態變量初始化,咱們能夠使用其餘數據傳輸方式:

    • 直接將數據經過intent傳遞給 Activity 。

    • 使用官方推薦的幾種方式將數據持久化到磁盤上。

    • 在使用數據的時候老是要對變量的值進行非空檢查。

最近咱們的產品友友用車就遇到了這樣的問題,下面咱們詳細的說明一下這個問題以及解決方案。在友友用車下單的過程當中有一個當前行程的頁面,該頁面就是用戶開車過程當中須要展現的頁面,主要用於展現當前用戶的用車費用,行駛里程,用車時長,還車網點等信息。具體以下圖: 
這裏寫圖片描述

而在當前行程頁面中App端有一個輪訓請求,大概每隔一分鐘會請求一次服務器,用於更新用戶的用車費用,行駛里程,用車時長還車網點等信息。用戶能夠在當前行程頁面停留很長的時間。

須要注意的是這裏還有一個更換還車網點的按鈕,點擊這個按鈕會跳轉到更換還車網點的頁面,從中會選擇用戶的經常使用網點,而這時候須要傳遞一個參數:用戶的訂單ID,此時用戶的訂單ID保存在了系統的靜態變量中。

在前幾天服務器端報了一個bug,說的是在當前行程頁面點擊更改換車網點請求經常使用網點時沒有上傳訂單ID,而App端的代碼是判斷此時的訂單ID是否爲空,若不爲空的話則上傳.

/** * 判斷訂單ID是否爲空,若不爲空的話則上傳 */ if (!TextUtils.isEmpty(orderId)) { builder.setOrderId(orderId); }

能夠看到,若是這時候咱們的orderId爲空,那麼咱們就不會上傳orderId,而服務器端說咱們沒有上傳orderId,可能的緣由就是咱們的orderId這時候爲空了,那麼這裏的orderId是一個靜態變量,並且咱們沒有手動的置空,爲何orderId會變爲空了呢?

好吧,這時候就須要看一下服務器端的用戶請求日誌。

下面的這一段日誌是用戶這段時間全部的網絡請求日誌,固然也包括更改還車地點的請求服務器的日誌:

[log@iZ28tw9apzsZ UUAccess-App]$ cat CmdAccess4Stat.log.2016-06-08 | grep '2119489256' 2016-06-08 11:22:52,778 1100004 229BAEC4B95E4C6ABB83F26FB7A48C88 2119489256 117.136.0.134 1465356172778 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 11:22:58,409 1070002 BABEDA5E46494FB4B00A2E50FD2F8F4D 2119489256 117.136.0.134 1465356178409 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 11:22:58,409 1130001 5F9D626DB30D442880A67D1C3A2A1C67 2119489256 117.136.0.134 1465356178409 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 11:22:58,862 1070002 68767819033342E5A31D03956C3FA84C 2119489256 117.136.0.134 1465356178862 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 11:22:58,862 1110001 6EB7CFC572E7404782512BAA26FE9D10 2119489256 117.136.0.134 1465356178862 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 11:22:59,029 1110002 C1039D50827D40098E3654FAD1260404 2119489256 117.136.0.134 1465356179029 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 11:22:59,498 1050007 DD0198099023454C889522A148F82955 2119489256 117.136.0.134 1465356179498 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 11:23:00,168 1130002 5E2A776EBF8743869202FB86A419580A 2119489256 117.136.0.134 1465356180168 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 11:23:10,038 1010003 0311997A14544D85BAA0F942650EFA84 2119489256 117.136.0.134 1465356190038 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 11:23:39,098 1070002 4B495330AC804EC3932BEBB6B6586595 2119489256 117.136.0.134 1465356219098 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 14:01:32,322 1070003 5F3F2146CDF54662A4CD5E4849D07422 2119489256 123.118.169.6 1465365692322 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 14:01:32,953 1050007 29A5FF713B11481CAABBD9084145AAA1 2119489256 123.118.169.6 1465365692953 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 14:01:33,177 1070002 AFB545D58AF648B69328D1417E73DAE6 2119489256 123.118.169.6 1465365693177 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 14:01:37,157 1110001 5CD82483782E4AF98A5FD34F2C8FB96A 2119489256 123.118.169.6 1465365697157 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 14:02:52,246 1050007 AF177A8CDCEC4B97B7C0EAA92B0DD7F5 2119489256 123.118.169.6 1465365772246 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 14:03:52,277 1050007 778CA2EC92094513A16E6EDCBB0664F0 2119489256 123.118.169.6 1465365832277 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:48:13,395 1010003 2DFA5673D9104412B6C5621B9E9FE19A 2119489256 123.118.169.6 1465372093395 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:48:15,019 1010003 D95851D7A08F4227A91A445E12105FEE 2119489256 123.118.169.6 1465372095019 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:48:16,984 1100004 CB208E17087C422DB56DB681E774B7C9 2119489256 123.118.169.6 1465372096984 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:48:17,382 1110001 CBABF14385374911BDCAEA427C1C0780 2119489256 123.118.169.6 1465372097382 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:48:18,011 1110002 F1D79FF8481D43F0B8861B986B458EFD 2119489256 123.118.169.6 1465372098011 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:48:18,716 1050007 58BDBF796411497CBD1A7CCD2F3E21AA 2119489256 123.118.169.6 1465372098716 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:48:18,716 1110002 8CF8D68D0A4A420C8D83A78818DBD4B7 2119489256 123.118.169.6 1465372098716 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:51:01,855 1050007 284E391D94EA48C6877F81B94E8E51DD 2119489256 123.118.169.6 1465372261855 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:51:04,784 1010002 6085D946E4AA46C884E775DE9B40987E 2119489256 123.118.169.6 1465372264784 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:51:09,249 1050007 4147044DC8014FC4B64488FFC1A0D83C 2119489256 123.118.169.6 1465372269249 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509

-------------------------------------------------------------------------------------------------------------------------------------------------

[log@iZ28foila6mZ UUAccess-App]$ cat CmdAccess4Stat.log.2016-06-08 | grep '2119489256' 2016-06-08 14:01:32,273 1100004 C6CC832BE038415CB6590D9174025FE9 2119489256 123.118.169.6 1465365692273 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 14:01:32,577 1070002 A23766F7F63C405080C3E7FEA4C6D1E1 2119489256 123.118.169.6 1465365692577 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 14:01:37,248 1110002 4C9A9F7521954E439C25B41CB4EB1E1A 2119489256 123.118.169.6 1465365697248 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 14:01:51,142 1010003 8EBB280697D349669A9C01925D2965E8 2119489256 123.118.169.6 1465365711142 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 14:01:51,955 1010002 0F35494F31ED475D93AC79F08D50CAF5 2119489256 123.118.169.6 1465365711955 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 14:06:44,272 1110001 2AF0F04090714B6FAAC15AD4E5F7924A 2119489256 123.118.169.6 1465366004272 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 14:06:44,365 1110002 728B5A82C759443A9DBBC91D3812C14F 2119489256 123.118.169.6 1465366004365 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:48:13,447 1100004 40AC6AFA4A5D4E75867A6459CAFA50AD 2119489256 123.118.169.6 1465372093447 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:48:17,025 1070003 3A2DAC87041E4B4BAD4A73596D5A1785 2119489256 123.118.169.6 1465372097025 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:48:17,758 1070002 BA4F5C07FB504882826DB2B99FA8CB5D 2119489256 123.118.169.6 1465372097758 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:48:18,015 1110001 CC2F250F823044298B85C23344D36B2A 2119489256 123.118.169.6 1465372098015 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:51:02,089 1070002 BF580DD1CA2F4F29800EE631C7940747 2119489256 123.118.169.6 1465372262089 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:51:03,518 1010003 9047E26C47C94F4C90A4FA9D460C2F54 2119489256 123.118.169.6 1465372263518 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:57:26,699 1050007 A283E7F6F70E4902946716474231F01A 2119489256 123.118.169.6 1465372646699 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 15:59:04,659 1050007 D6AF179627014BBF885C41292B59F26E 2119489256 123.118.169.6 1465372744659 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:00:45,601 1050007 D8029E9094B44737ADB47C94803D2753 2119489256 123.118.169.6 1465372845601 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:00:45,890 1070002 0475136678324CAABD0D18637ED97D3C 2119489256 123.118.169.6 1465372845890 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:01:12,120 1110001 6E784DF19F3C479183A8D06FD84DBF1B 2119489256 123.118.169.6 1465372872120 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:01:48,705 1050007 AA87FE1B960B40889A9F0511187D2F3D 2119489256 123.118.169.6 1465372908705 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:02:03,148 1010003 6801B16D60364BC8826C7AB9E7D29051 2119489256 123.118.169.6 1465372923148 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:02:03,501 1010002 CCA10A3EE4D9415384A761E623FE9959 2119489256 123.118.169.6 1465372923501 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:02:05,096 1050007 837721D2E671453888E170C95849E926 2119489256 123.118.169.6 1465372925096 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:02:09,446 1070002 D161AA4123354BA4A653582B65C8077E 2119489256 123.118.169.6 1465372929446 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:04:04,784 1050007 3B8B45A4066747AD8E43DD197B47C223 2119489256 123.118.169.6 1465373044784 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:05:04,778 1050007 18AA8CE0E8C446928B230D54F068D2E6 2119489256 123.118.169.6 1465373104778 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:56:20,860 1070003 BC4E84F58F654CB49FD76D8BA4854D85 2119489256 117.136.0.154 1465376180860 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:56:20,883 1100004 184A5E0784F94A64AE5D5D7DD6E6FB86 2119489256 117.136.0.154 1465376180883 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:56:21,291 1070002 25738C17D2314B4382089F600EF3BEB9 2119489256 117.136.0.154 1465376181291 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:56:22,233 1070002 FDACF66E84DC4FF696E2FC61C368DAB0 2119489256 117.136.0.154 1465376182233 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:56:25,967 1110002 B5B3CC24758B4AF7AB6F3FD6185D6213 2119489256 117.136.0.154 1465376185967 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:56:29,674 1050003 3EB8C2993802494998603A6FBCC4243A 2119489256 117.136.0.154 1465376189674 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:56:35,833 1070002 655CDEEDF98E4718A17431AB9B90060C 2119489256 117.136.0.154 1465376195833 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:56:57,419 1050004 FD6EEDA543744A3998FB7B1E4CCFABD2 2119489256 117.136.0.154 1465376217419 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509 2016-06-08 16:56:57,837 1070002 A4BE993338094CFDBB28C1C7C72BF0C6 2119489256 117.136.0.154 1465376217837 MI 4LTE Android xiaomi 2.2.0 c671b0b2b6de7aacdc2eabfa93ed4601 865372024757509

這就是出現問題的用戶的網絡請求日誌,須要說明的是後臺的日誌分爲兩個節點,因此用戶的請求日誌信息被隨機的分到了兩個節點中,能夠按照時間將這兩段日誌信息當作是一段日誌信息。

能夠發如今2016-06-08 14:00:00的時候用戶每一個一分鐘執行一次請求1050007請求,而這裏的1050007就是當前行程頁面的輪訓請求,大概每隔一分鐘執行一次,而後在2016-06-08 14:03的的時候就不開始執行了,這應該是用戶鎖屏以後小米手機限制後臺的網絡請求,而這時候直接到了15:48分鐘,這時候用戶開始請求1010003接口,該接口就是用戶請求更換網點時請求的接口,可是這時候咱們發現用戶同時調用了:1100004接口,而這個接口只有在Application的onCreate方法中調用了,因此這說明這時候應用進程已經從新啓動了,可是這時候用戶仍是在當前行程頁面,而且用戶點擊了更換還車網點按鈕,而這時候因爲點擊更換還車網點須要靜態變量orderId,而這時候上傳的orderId爲空,因此這時候的進程靜態變量已經被初始化了。

因此綜上能夠還原一下bug的出現流程:

  • 用戶在14:00的時候停留在當前造成頁面;

  • 用戶鎖屏,系統限制後臺網絡請求,輪訓操做被限制;

  • 系統內存吃緊,用戶應用進程被殺死,進程的靜態變量被初始化,activity界面被保留;

  • 用戶打開屏幕,點擊更換還車網點,這時候因爲進程已經被殺死,靜態變量被初始化,因此上報給服務器的orderId爲空

  • 在用戶打開屏幕的同時,系統重啓應用進程,形成應用進程沒有被殺死的假象;

好吧,既然已經知道了bug出現的緣由,那麼就好解決了,既然在內存中保存數據可能被系統殺死,那麼咱們能夠有針對性的:

  • 直接將數據經過intent傳遞給 Activity 。

  • 使用官方推薦的幾種方式將數據持久化到磁盤上。

  • 在使用數據的時候老是要對變量的值進行非空檢查。

最後咱們決定將該數據持久化,具體而言使用SharedPreferences來保存數據,相對來講這種方式比較簡單。下面是使用sharedpreferences保存數據的靜態工具方法:

/** * 獲取SP中保存的訂單ID * * @param context * @return */ public static String getOrderId(Context context) { if (context == null) { L.i("從sp中獲取orderId失敗,context對象爲空..."); return ""; } SharedPreferences sp = context.getSharedPreferences("morder", Context.MODE_PRIVATE); return sp.getString("morderid", ""); } /** * 向Sp中保存orderId信息 * @param context * @param orderId */ public static void setOrderId(Context context, String orderId) { if (context == null) { L.i("向sp中設置orderId失敗,context對象爲空..."); return; } // 若當前orderId爲null,則設置orderId爲"" if (orderId == null) { orderId = ""; } SharedPreferences sp = context.getSharedPreferences("morder", Context.MODE_PRIVATE); boolean isCommitSuccess = sp.edit().putString("morderid", orderId).commit(); if (!isCommitSuccess) { sp.edit().putString("morderid", orderId).commit(); } } /** * 清空sp中的orderId * @param context */ public static void clearOrderId(Context context) { if (context == null) { L.i("向sp中設置orderId失敗,context對象爲空..."); return; } SharedPreferences sp = context.getSharedPreferences("morder", Context.MODE_PRIVATE); sp.edit().putString("morderid", "").commit(); }

這樣咱們在使用order數據的時候能夠經過sharedpreferences方法來獲取。這樣靜態變量數據被銷燬的狀況就不會出現了。

可是須要注意的是,當數據持久化的時候必定要在app啓動的時候清空。

9. sharePrefrence使用問題

小結

總價一下,sp是一種輕量級的存儲方式,使用方便,可是也有它適用的場景。要優雅滴使用sp,要注意如下幾點:

  1. 不要存放大的key和value!我就不重複三遍了,會引發界面卡、頻繁GC、佔用內存等等,好自爲之!
  2. 絕不相關的配置項就不要丟在一塊兒了!文件越大讀取越慢,不知不覺就被豬隊友給坑了;藍後,放進defalut的那個簡直就是愚蠢行爲!
  3. 讀取頻繁的key和不易變更的key儘可能不要放在一塊兒,影響速度。(若是整個文件很小,那麼忽略吧,爲了這點性能添加維護成本得不償失)
  4. 不要亂edit和apply,儘可能批量修改一次提交!
  5. 儘可能不要存放JSON和HTML,這種場景請直接使用json!
  6. 不要期望用這貨進行跨進程通訊!!!

9.1不能用來跨進程

【說明】每一個進程都會維護本身的sp副本,在其運行的過程當中,其餘的進程是沒法獲取此進程建立的sp副本的,只有在應用結束以後纔會將數據持久化的寫到副本當中;

 還有童鞋發現sp有一個貌似能夠提供「跨進程」功能的FLAG——MODE_MULTI_PROCESS,咱們看看這個FLAG的文檔:

@deprecated MODE_MULTI_PROCESS does not work reliably in
some versions of Android, and furthermore does not provide any mechanism for reconciling concurrent modifications across processes. Applications should not attempt to use it. Instead, they should use an explicit cross-process data management approach such as {@link android.content.ContentProvider ContentProvider}.

文檔也說了,這玩意在某些Android版本上不可靠,而且將來也不會提供任何支持,要是用跨進程數據傳輸須要使用相似ContentProvider的東西。並且,SharedPreference的文檔也特別說明:

Note: This class does not support use across multiple processes.

那麼咱們姑且看一看,設置了這個Flag到底幹了啥;在SharedPreferenceImpl裏面,沒有發現任何對這個Flag的使用;而後咱們去ContextImpl類裏面找找getSharedPreference的時候作了什麼:

@Override public SharedPreferences getSharedPreferences(File file, int mode) { checkMode(mode); SharedPreferencesImpl sp; synchronized (ContextImpl.class) { final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked(); sp = cache.get(file); if (sp == null) { sp = new SharedPreferencesImpl(file, mode); cache.put(file, sp); return sp; } } if ((mode & Context.MODE_MULTI_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB) { // If somebody else (some other process) changed the prefs // file behind our back, we reload it. This has been the // historical (if undocumented) behavior. sp.startReloadIfChangedUnexpectedly(); } return sp; } 

這個flag保證了啥?保證了在API 11之前的系統上,若是sp已經被讀取進內存,再次獲取這個sp的時候,若是有這個flag,會

從新讀一遍文件,僅此而已!因此,若是仰仗這個Flag作跨進程存取,簡直就是丟人現眼。

9.2 sp存儲文件會存在文件過大的問題

【android5大存儲】網絡、數據庫、sp、文件、contentProvide;

 【說明】文件過大致使的問題

【1】app獲取sp數據時候會阻塞主線程;致使時間過長致使了UI卡頓;

【2】解析sp大文件會出現大量的臨時對象,形成大量的gc操做,致使UI卡頓;致使內存抖動;內存泄露等,有可能出現oom;

 【3】sp存儲是以key-value形式存儲在內存中,特別消耗內存資源;

下面是默認的sp實現SharedPreferenceImpl這個類的getString函數:

public String getString(String key, @Nullable String defValue) { synchronized (this) { awaitLoadedLocked(); String v = (String)mMap.get(key); return v != null ? v : defValue; } } 

繼續看看這個awaitLoadedLocked:

private void awaitLoadedLocked() { while (!mLoaded) { try { wait(); } catch (InterruptedException unused) { } } } 

一把鎖就是掛在那裏!!這意味着,若是你直接調用getString,主線程會等待加載sp的那麼線程加載完畢!這不就把主線程卡住了麼?

另外,有一個叫訣竅能夠節省一下等待的時間:既然getString之類的操做會等待sp加載完成,而加載是在另一個線程執行的,咱們可讓sp先去加載,作一堆事情,而後再getString!以下:

// 先讓sp去另一個線程加載 SharedPreferences sp = getSharedPreferences("test", MODE_PRIVATE); // 作一堆別的事情 setContentView(testSpJson); // ... // OK,這時候估計已經加載完了吧,就算沒完,咱們在本來應該等待的時間也作了一些事! String testValue = sp.getString("testKey", null); 

更爲嚴重的是,被加載進來的這些大對象,會永遠存在於內存之中,不會被釋放。咱們看看ContextImpl這個類,在getSharedPreference的時候會把全部的sp放到一個靜態變量裏面緩存起來:

private ArrayMap<File, SharedPreferencesImpl> getSharedPreferencesCacheLocked() { if (sSharedPrefsCache == null) { sSharedPrefsCache = new ArrayMap<>(); } final String packageName = getPackageName(); ArrayMap<File, SharedPreferencesImpl> packagePrefs = sSharedPrefsCache.get(packageName); if (packagePrefs == null) { packagePrefs = new ArrayMap<>(); sSharedPrefsCache.put(packageName, packagePrefs); } return packagePrefs; } 

注意這個static的sSharedPrefsCache,它保存了你全部使用的sp,而後sp裏面有一個成員mMap保存了全部的鍵值對;

這樣,你程序中使用到的那些個sp永遠就呆在內存中。

9.3 存儲JSON等特殊符號不少的value

還有一些童鞋,他在sp裏面存json或者HTML;這麼作不是不能夠,可是,若是這個json相對較大,那麼也會引發sp讀取速度的急劇降低。

JSON或者HTML格式存放在sp裏面的時候,須要轉義,這樣會帶來不少 & 這種特殊符號,sp在解析碰到這個特殊符號的時候會進行特殊的處理,引起額外的字符串拼接以及函數調用開銷。

而JSON原本就是能夠用來作配置文件的,你幹嗎又把它放在sp裏面呢?畫蛇添足。下面我寫個demo驗證一下。

下面這個sp是某個app的換膚配置:

咱們先用sp進行讀取,而後用直接把它丟json文件,直接讀取而且解析;json使用的代碼以下:

public int getValueByJson(Context context, String key) { File jsonFile = new File(context.getFilesDir().getParent() + File.separator + SP_DIR_NAME, "skin_beta2.json"); FileInputStream fis = null; ByteArrayOutputStream bao = new ByteArrayOutputStream(); try { fis = new FileInputStream(jsonFile); FileChannel channel = fis.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(1 << 13); // 8K int i1; while ((i1 = channel.read(buffer)) != -1) { buffer.flip(); bao.write(buffer.array(), 0, i1); buffer.clear(); } String content = bao.toString(); JSONObject jsonObject = new JSONObject(content); return jsonObject.getInt(key); } catch (IOException e) { e.printStackTrace(); } catch (JSONException e) { throw new RuntimeException("not a json file"); } finally { close(fis); close(bao); } return 0; } 

而後個人測試結果是:直接解析JSON比在xml裏面要快一倍!在小米1S上結果以下:

時間jsonspMi 1S8038Nexus5X6.53.5

這個JSON的讀取尚未作任何的優化,提高潛力巨大!所以,若是你須要用JSON作配置,請不要把它存放在sp裏面!!

9.4 屢次edit屢次apply

我見過這樣的使用代碼:

SharedPreferences sp = getSharedPreferences("test", MODE_PRIVATE);
sp.edit().putString("test1", "sss").apply();
sp.edit().putString("test2", "sss").apply();
sp.edit().putString("test3", "sss").apply();
sp.edit().putString("test4", "sss").apply();

每次edit都會建立一個Editor對象,額外佔用內存;固然多建立幾個對象也影響不了多少;可是,屢次apply也會卡界面你造嗎?

有童鞋會說,apply不是在別的線程些磁盤的嗎,怎麼可能卡界面?我帶你仔細看一下源碼。

public void apply() { final MemoryCommitResult mcr = commitToMemory(); final Runnable awaitCommit = new Runnable() { public void run() { try { mcr.writtenToDiskLatch.await(); } catch (InterruptedException ignored) { } } }; QueuedWork.add(awaitCommit); Runnable postWriteRunnable = new Runnable() { public void run() { awaitCommit.run(); QueuedWork.remove(awaitCommit); } }; SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); notifyListeners(mcr); } 

注意兩點,第一,把一個帶有await的runnable添加進了QueueWork類的一個隊列;

第二,把這個寫入任務經過enqueueDiskWrite丟給了一個只有單個線程的線程池執行。

到這裏一切都OK,在子線程裏面寫入不會卡UI。可是,你去ActivityThread類的handleStopActivity裏看一看:

private void handleStopActivity(IBinder token, boolean show, int configChanges, int seq) {

    // 省略無關。。
    // Make sure any pending writes are now committed.
    if (!r.isPreHoneycomb()) {
        QueuedWork.waitToFinish();
    }

    // 省略無關。。
}

waitToFinish?? 又要等?源碼以下:

public static void waitToFinish() { Runnable toFinish; while ((toFinish = sPendingWorkFinishers.poll()) != null) { toFinish.run(); } } 

還記得這個toFinish的Runnable是啥嗎?就是上面那個awaitCommit它裏面就一句話,等待寫入線程!!

若是在Activity Stop的時候,已經寫入完畢了,那麼萬事大吉,不會有任何等待,這個函數會立馬返回。

可是,若是你使用了太屢次的apply,那麼意味着寫入隊列會有不少寫入任務,而那裏就只有一個線程在寫。

當App規模很大的時候,這種狀況簡直就太常見了!

所以,雖然apply是在子線程執行的,可是請不要無節制地apply;

commit我就很少說了吧?直接在當前線程寫入,若是你在主線程幹這個,當心捱揍。

10. 內存對象的序列化

 

序列化 (Serialization)將對象的狀態信息轉換爲能夠存儲或傳輸的形式的過程。在序列化期間,對象將其當前狀態寫入到臨時或持久性存儲區。之後,能夠經過從存儲區中讀取或反序列化對象的狀態,從新建立該對象。

爲何須要序列化:

知道前面的序列化定義,內存對象什麼須要實現序列化呢?

  • 永久性保存對象,保存對象的字節序列到本地文件

  • 經過序列化對象在網絡中傳遞對象。

  • 經過序列化對象在進程間傳遞對象。

實現序列化的兩種方式:

那麼咱們如何實現序列化的操做呢?在Android開發中咱們實現序列化有兩種方式:

  • 實現Serializable接口

  • 實現parcelable接口

兩種序列化方式的區別:

都知道在Android studio中序列化有兩種方式:serializable與parcelable。那麼這兩種實現序列化的方式有什麼區別呢?下面是這兩種實現序列化方式的區別:

  1. Serializeble是java的序列化方式,Parcelable是Android特有的序列化方式;

  2. 使用內存的時候,Parcelable比Serializable性能高,因此推薦使用Parcelable。

  3. Serializable在序列化的時候會產生大量的臨時變量,從而引發頻繁的GC。

  4. Parcelable不能使用在要將數據存儲在磁盤上的狀況,由於Parcelable不能很好的保證數據的持續性在外界有變化的狀況下。  儘管Serializable效率低點, 也不提倡用,但在這種狀況下,仍是建議你用Serializable。

最後還有一點就是Serializeble序列化的方式比較簡單,直接集成一個接口就行了,而parcelable方式比較複雜,不只須要集成Parcelable接口還須要重寫裏面的方法。

對象實現序列化的實例:

經過實現Serializable接口實現序列化:

上面介紹了那麼多概念上的知識,下面咱們就具體看一下如何經過這兩種方式實現序列化,咱們首先看一下如何經過實現Serializable接口實現序列化,

經過實現Serializable接口實現序列化,只須要簡單的實現Serialiizable接口便可。經過實現Serializable接口就至關於標記類型爲序列化了,不須要作其餘的操做了。

 1 /**
 2  * Created by aaron on 16/6/29.
 3  */
 4 public class Person implements Serializable{
 5 
 6     public static final long serialVersionUID = 1;
 7 
 8     private int age;
 9     private String name;
10     private String address;
11 
12     public int getAge() {
13         return age;
14     }
15 
16     public void setAge(int age) {
17         this.age = age;
18     }
19 
20     public String getName() {
21         return name;
22     }
23 
24     public void setName(String name) {
25         this.name = name;
26     }
27 
28     public String getAddress() {
29         return address;
30     }
31 
32     public void setAddress(String address) {
33         this.address = address;
34     }
35 }

 

能夠發現咱們定義了一個普通的實體Person類,並設置了三個成員屬性以及各自的set,get方法,而後咱們就只是簡單的實現了Serializable接口就至關於將該類序列化了,

當咱們在程序中傳輸該類型的對象的時候就沒有問題了。

而且咱們在Person類中定義了一個屬性爲serialVersionUID的成員變量,這個成員變量是作什麼的呢? 
在Java中,軟件的兼容性是一個大問題,尤爲在使用到對象串行性的時候,那麼在某一個對象已經被串行化了,

但是這個對象又被修改後從新部署了,那麼在這種狀況下, 用老軟件來讀取新文件格式雖然不是什麼難事,可是有可能丟失一些信息。 
serialVersionUID來解決這些問題,新增的serialVersionUID必須定義成下面這種形式:

public static final long serialVersionUID=1;

其中數字後面加上的L表示這是一個long值。 經過這種方式來解決不一樣的版本之間的串行化的問題。

簡單來講就是用serialVersionUID標識class類的版本,當序列化的class源文件發生變化時,反序列化的一端因爲該標識不一致會反序列化失敗,進而保證了兩端源文件的一致性。

經過實現Parcelable接口實現序列化:

而後咱們在看一下經過實現Parcelable接口來實現序列化的方式,經過實現Parcelable接口實現序列化至關於實現Serialiable接口稍微複雜一些,由於其須要實現一些特定的方法,下面咱們仍是以咱們定義的Person類爲例子,看一下若是是實現Parcelable接口具體是如何實現的:

 1 /**
 2  * Created by aaron on 16/6/29.
 3  */
 4 public class Person implements Parcelable{
 5 
 6     private int age;
 7     private String name;
 8     private String address;
 9 
10     public int getAge() {
11         return age;
12     }
13 
14     public void setAge(int age) {
15         this.age = age;
16     }
17 
18     public String getName() {
19         return name;
20     }
21 
22     public void setName(String name) {
23         this.name = name;
24     }
25 
26     public String getAddress() {
27         return address;
28     }
29 
30     public void setAddress(String address) {
31         this.address = address;
32     }
33 
34     @Override
35     public int describeContents() {
36         return 0;
37     }
38 
39     @Override
40     public void writeToParcel(Parcel dest, int flags) {
41         dest.writeInt(this.age);
42         dest.writeString(this.name);
43         dest.writeString(this.address);
44     }
45 
46     public Person() {
47     }
48 
49     protected Person(Parcel in) {
50         this.age = in.readInt();
51         this.name = in.readString();
52         this.address = in.readString();
53     }
54 
55     public static final Creator<Person> CREATOR = new Creator<Person>() {
56         @Override
57         public Person createFromParcel(Parcel source) {
58             return new Person(source);
59         }
60 
61         @Override
62         public Person[] newArray(int size) {
63             return new Person[size];
64         }
65     };
66 }

 

能夠發現當咱們經過實現Parcelable接口實現序列化還須要重寫裏面的成員方法,而且這些成員方法的寫法都比較固定。

實現Parcelable序列化的Android studio插件:

順便說一下最近發現一個比較不錯的Parcelable序列化插件。下面就來看一下如何安裝使用該插件。

  • 打開Android studio –> settings –> Plugins –> 搜索Parcelable –> Install –> Restart,這樣安裝好了Parcelable插件;

這裏寫圖片描述

  • 而後在源文件中右鍵 –> Generate… –> Parcelable

這裏寫圖片描述

  • 點擊Parcelable以後能夠看到,源文件中已經實現了Parcelable接口,並重寫了相應的方法:

這裏寫圖片描述

這樣咱們就安裝好Parcelable插件了,而後當咱們執行Parcelable操做的時候就重寫了Parcelable接口的相應序列化方法了。

總結:

  • 能夠經過實現Serializable和Parcelable接口的方式實現序列化

  • 實現Serializable接口是java中實現序列化的方式,而實現Parcelable是Android中特有的實現序列化的方式,更適合Android環境

  • 實現Serializable接口只須要實現該接口便可無需其餘操做,而實現Parcelable接口須要重寫相應的方法

  • Android studio中有實現Parcelable接口的相應插件,可安裝該插件很方便的實現Parcelable接口,實現序列化

11.UI卡頓的工具

【1】StrackMode   UI卡頓追蹤工具;沒有找到;

 Android 性能優化:使用 TraceView 找到卡頓的元兇

https://blog.csdn.net/u011240877/article/details/54347396

【2】Android UI性能優化 檢測應用中的UI卡頓

 https://blog.csdn.net/lmj623565791/article/details/58626355

相關文章
相關標籤/搜索