產生的主要緣由是在主線程中作了耗時的操做;php
【摘抄文章】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
【什麼】Android系統會爲每一個app分配固定的內存空間java
【內存抖動】內存抖動是指在短時間內有大量的對象被建立或者被回收的現象,主要是循環中大量建立、回收對象。這種狀況應當儘可能避免。android
【內存泄露】程序經過new分配內存,在使用完畢後沒有釋放,形成內存佔用。這塊內存不受GC控制,沒法經過GC回收。git
【圖片的請求】在顯示縮略圖的時候,不要調用網絡請求加載大圖;例如:在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
【參考內容】
GC能夠自動清理堆中不在使用(不在有對象持有該對象的引用)的對象。
在JAVA中對象若是再沒有引用指向該對象,那麼該對象就無從處理或調用該對象,這樣的對象稱爲不可到達(unreachable)。垃圾回收用於釋放不可到達的對象所佔據的內存。
對android來講,內存使用尤其吃緊,最開始的app進程最大分配才16M的內存,漸漸增長到32M、64M,可是和服務端相比仍是很眇小的。若是對象回收不及時,很容易出現OOM錯誤。
更多內存泄露能夠經過檢測工具發現。檢測工具主要有MAT、Memory Monitor 、Allocation Tracker 、Heap Viewer、LeakCanary
講下 GC 回收致使 畫面卡頓的問題:
好比自定義view 中
繪製第一個畫面 ,繪製完 之後 再繪製第二個畫面 把第一個頁面會的回收掉.時間16毫秒
通常手機 刷新頻率60hz, 1秒鐘 刷新60次 , 那麼 每隔16ms 就須要從新繪製一次 ,假如自定義view的計算超過16ms ,那麼就會造成卡頓的狀況 ,第二個畫面就沒有刷新上去. 就會造成卡頓.
例如 : 在一個長期運行循環的最內側建立對象, 那麼就會不斷增長,不少數據會污染內存堆, 致使GC 回收,因爲 這中額外的內存壓力致使 GC非正常回收,致使屢次回收.就造成卡頓.
須要內存優化.
做用 : 跟蹤APP內存變化的狀況.
含義: 內存 監測
視圖方式觀看 內存變化.
做用: 獲取內存堆 的快照.
含義: 堆棧快照
位置 : 在device monitor 中
經過 heap viewer 查看
說下內存泄露的狀況:
內存泄漏是指你向系統申請分配內存進行使用(new),但是使用完了之後卻不歸還(delete),結果你申請到的那塊內存你本身也不能再訪問(也許你把它的地址給弄丟了),而系統也不能再次將它分配給須要的程序。一個盤子用盡各類方法只能裝4個果子,你裝了5個,結果掉倒地上不能吃了。這就是溢出!比方說棧,棧滿時再作進棧一定產生空間溢出,叫上溢,棧空時再作退棧也產生空間溢出,稱爲下溢。就是分配的內存不足以放下數據項序列,稱爲內存溢出.
http://blog.csdn.net/cyq1028/article/details/19980369 這篇文章講的不錯 不過他用的是Eclipse的查看 工具,我用的studio . studio 的這個比較好.
解決方式: 最簡單的方式. 能夠添加一個清理方法. 防止內存泄露 .
解決: 跟蹤內存分配
含義: 分配-跟蹤
最重要的功能 :
能夠跟蹤出現問題的源代碼, 上面兩個工具只能觀察現象.
http://www.th7.cn/Program/Android/201602/764859.shtml 所用工具的具體使用
含義: 分配 視圖
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問題,以及內存優化方法。
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圖片格式
在開發Android企業應用時,會常常上傳圖片到服務器,而咱們公司目前維護的一個項目即是如此。該項目是經過私有apn與服務器進行交互的,聯通的還好,但移動的速度實在太慢,客戶在使用軟件的過程當中,因爲上傳的信息中可能包含多張圖片,會常常出現上傳圖片失敗的問題,爲了解決這個問題,咱們決定把照片壓縮到100k如下,而且保證圖片不失真(目前圖片通過壓縮後,大約300k左右)。因而我就從新研究了一下Android的圖片壓縮技術。
Android端目錄結構以下圖所示:
使用的第三方庫jar包,以下圖所示:
其中ksoap2-android-xxx.jar是Android用來調用webservice的,gson-xx.jar是把JavaBean轉成Json數據格式的。
本篇博客主要講解圖片壓縮的,核心代碼以下:
查看所有源碼,請訪問:
https://github.com/feicien/StudyDemo/tree/master/FileUploadDemo
壓縮原理講解:壓縮一張圖片。咱們須要知道這張圖片的原始大小,而後根據咱們設定的壓縮比例進行壓縮。
這樣咱們就須要作3件事:
1.獲取原始圖片的長和寬
以上代碼是對圖片進行解碼,inJustDecodeBounds設置爲true,能夠不把圖片讀到內存中,但依然能夠計算出圖片的大小,這正好能夠知足咱們第一步的須要。
2.計算壓縮比例
通常手機的分辨率爲 480*800 ,因此咱們壓縮後圖片指望的寬帶定爲480,高度設爲800,這2個值只是指望的寬度與高度,實際上壓縮後的實際寬度也高度會比指望的要大。若是圖片的原始高度或者寬帶大約咱們指望的寬帶和高度,咱們須要計算出縮放比例的數值。不然就不縮放。heightRatio是圖片原始高度與壓縮後高度的倍數,widthRatio是圖片原始寬度與壓縮後寬度的倍數。inSampleSize爲heightRatio與widthRatio中最小的那個,inSampleSize就是縮放值。 inSampleSize爲1表示寬度和高度不縮放,爲2表示壓縮後的寬度與高度爲原來的1/2
3.縮放並壓縮圖片
前3行的代碼其實已經獲得了一個縮放的bitmap對象,若是你在應用中顯示圖片,就能夠使用這個bitmap對象了。因爲考慮到網絡流量的問題。咱們好須要犧牲圖片的質量來換取一部分空間,這裏調用bm.compress()方法進行壓縮,這個方法的第二個參數,若是是100,表示不壓縮,我這裏設置的是60,你也能夠更加你的須要進行設置,在實驗的過程當中我設置爲30,圖片都不會失真。
壓縮效果:本demo能夠把1.5M左右的圖片壓縮到100K左右,而且沒有失真。
效果圖以下:
更新:
判斷照片角度
旋轉照片
Android圖片壓縮結合多種壓縮方式,經常使用的有尺寸壓縮、質量壓縮、採樣率壓縮以及經過JNI調用libjpeg庫來進行壓縮。
參考此方法:Android-BitherCompress
(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(); } }
(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(); } }
(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(); } }
爲何IOS拍照1M的圖片要比安卓拍照排出來的5M的圖片還要清晰。都是在同一個環境下,保存的都是JPEG?
(1)歷程
95年 JPEG處理引擎,用於最初的在PC上面處理圖片的引擎。
05年 skia開源的引擎, 開發了一套基於JPEG處理引擎的第二次開發。便於瀏覽器的使用。
07年安卓用的skia引擎(閹割版),谷歌拿了skia,去掉一個編碼算法—哈夫曼算法。採用定長編碼算法。可是解碼仍是保留了哈夫曼算法,致使了圖片處理後文件變大了。
(2)緣由
當時因爲CPU和內存在手機上都很是吃緊 性能差,因爲哈夫曼算法很是吃CPU,被迫用了其餘的算法。
(3)優化方案
繞過安卓Bitmap API層,來本身編碼實現—-修復使用哈夫曼算法。
(1)ARGB:一個像素點包涵四個信息:alpha,red,green,blue
(2)如何獲得每個字母出現的權重?須要去掃描整個信息(圖片信息–每個像素包括ARGB),要大量計算,很耗CPU,1280*800像素*4。
(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)釋放資源
/** * 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"); }
(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; }
(1)原圖內存是3.22MB
(2)截圖以下:
(3)效果:
尺寸壓縮後圖片太模糊;
混合終極方法壓縮效果更佳,與jni終極方法壓縮內存區別不大;
質量壓縮後圖片與jni終極方法壓縮後圖片效果接近,略顯模糊;
(4)總結:能夠考慮使用混合終極方法。
(1)原圖內存是109.94KB
(2)截圖以下:
(3)效果:
尺寸壓縮後圖片太模糊; 混合終極方法壓縮後內存反而增長了一半; 質量壓縮後圖片和jni終極方法壓縮後圖片效果接近,可是內存更大; jni終極方法壓縮後圖片效果與原圖相差不大,內存也不大。
(4)總結:能夠考慮使用jni終極方法。
Android圖片壓縮(質量壓縮和尺寸壓縮)&Bitmap轉成字符串上傳
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,若是設置了此參數,且知足如下條件的時候:
在知足以上條件的時候,系統對圖片進行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)