2019年5月30號:
更新內存泄漏相關內容,新增使用系統服務引起的內存泄漏相關內容。
更新內存泄漏未關閉資源對象內存泄露,新增WebView擴展,介紹WebView的內存分配並提出解決方案。
2019年5月29號:
更新內存優化相關內容,新增內存管理介紹、內存抖動。
2019年5月28號:
用戶zhangkai2811指出Fresco拼寫錯誤,現已修改完畢。java
View的繪製流程有3個步驟,分別是measure、layout和draw,它們主要運行在系統的應用框架層,而真正將數據渲染到屏幕上的則是系統Native層的SurfaceFlinger服務來完成的。android
繪製過程主要由CPU來進行Measure、Layout、Record、Execute的數據計算工做,GPU負責柵格化、渲染。CPU和GPU是經過圖形驅動層來進行鏈接的,圖形驅動層維護了一個隊列,CPU將display list添加到該隊列中,這樣GPU就能夠從這個隊列中取出數據進行繪製。git
Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,若是每次渲染都成功,這樣就可以達到流暢的畫面所須要的60fps,VSYNC是Vertical Synchronization(垂直同步)的縮寫,是一種定時中斷,一旦收到VSYNC信號,CPU就開始處理各幀數據。若是某個操做要花費30ms,這樣系統在獲得VSYNC信號時沒法進行正常的渲染,會發生丟幀。github
產生卡頓緣由有不少,主要有如下幾點:shell
一、Profile GPU Rendering數據庫
Profile GPU Rendering是Android 4.1系統提供的開發輔助功能,能夠在開發者選項中打開這一功能,以下圖:編程
單擊Profile GPU Rendering選項並開啓Profile GPU Rendering功能,以下圖:後端
上面的彩色的圖的橫軸表明時間,縱軸表示某一幀的耗時。綠色的橫線爲警惕線,超過這條線則意味着時長超過了16m,儘可能要保證垂直的彩色柱狀圖保持在綠線下面。這些垂直的彩色柱狀圖表明着一幀,不一樣顏色的彩色柱狀圖表明不一樣的含義:數組
在Android 6.0中,有更多的顏色被加了進來,以下圖所示:緩存
下面來分別介紹它們的含義:
Profile GPU Rendering能夠找到渲染有問題的界面,可是想要修復的話,只依賴Profile GPU Rendering是不夠的,能夠用另外一個工具Hierarchy Viewer來查看佈局層次和每一個View所花的時間。
二、Systrace
Systrace是Android4.1中新增的性能數據採樣和分析工具。它可幫助開發者收集Android關鍵子系統(SurfaceFlinger、WindowManagerService等Framework部分關鍵模塊、服務,View體系系統等)的運行信息。Systrace的功能包括跟蹤系統的I/O操做、內核工做隊列、CPU負載以及Android各個子系統的運行情況等。對於UI顯示性能,好比動畫播放不流暢、渲染卡頓等問題提供了分析數據。在android-sdk/tools/目錄的命令行中輸入‘monitor’,會打開Android Device Monitor。
三、Traceview
TraceView是Android SDK中自帶的數據採集和分析工具。通常來講,經過TraceView咱們能夠獲得如下兩種數據:
在android-sdk/tools/目錄的命令行中輸入‘monitor’,會打開Android Device Monitor,選擇相應的進程,並單擊Start Method Profiling按鈕,對應用中須要監控的點進行操做,單擊Stop Method Profiling按鈕,會自動跳到TraceView視圖。
也能夠代碼中添加TraceView監控語句,代碼以下所示。
Debug.startMethodTracing();
...
Debug.stopMethodTracing();
複製代碼
在開始監控的地方調用startMethodTracing方法,在須要結束監控的地方調用stopMethodTracing方法。系統會在SD卡中生成trace文件,將trace文件導出並用SDK中的Traceview打開便可。固然不要忘了在manifest中加入
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>權限。
複製代碼
四、GPU過分繪製
Android手機上面的開發者選項提供了工具來檢測過分繪製,能夠按以下步驟來打開:
開發者選項->調試GPU過分繪製->顯示過分繪製區域
以下圖所示:
能夠看到,界面上出現了一堆紅綠藍的區域,咱們來看下這些區域表明什麼意思:
須要注意的是,有些過分繪製是沒法避免的。所以在優化界面時,應該儘可能讓大部分的界面顯示爲真彩色(即無過分繪製)或者爲藍色(僅有 1 次過分繪製)。儘可能避免出現粉色或者紅色。
知識掃盲
OOM:
系統分配給app的堆內存是有上限的,不是系統空閒多少內存app就能夠用多少,getMemoryClass()能夠獲取到這個值。 能夠在manifest文件中設置largeHeap爲true,這樣會增大堆內存上限,getLargeMemoryClass()能夠獲取到這個值。 超出虛擬機堆內存上限會形成OOM。
Low Memory Killer:
android內存管理使用了分頁(paging)和內存映射(memory-mapping)技術,可是沒有使用swap,而是使用Low Memory Killer策略來提高物理內存的利用率 ,致使除了gc和殺死進程回收物理內存以外沒有其餘方式來利用已經被佔用的內存。 當前臺應用切換到後臺後,系統並不結束它的進程,而是把它緩存起來,供下次啓動。當系統內存不足時,按最近最少使用+優先釋放內存使用密集的策略釋放緩存進程。
GC:
內存使用的多也會形成GC速度變慢,形成卡頓。 內存佔用太高,在建立對象時內存不足,很容易形成觸發GC影響APP性能。
目前 Android 端支持的圖片格式有JPEG、GIF、PNG、BMP、WebP,可是在 Android中可以使用編解碼使用的只有其中的三種:JPEG、PNG、WebP。
Android中Bitmap所佔內存大小計算方式:圖片長度 x 圖片寬度 x 一個像素點佔用的字節數
影響Bitmap佔用內存的因素:
一、Bitmap的Compress方法(質量壓縮):
public boolean compress(CompressFormat format, int quality, OutputStream stream) 複製代碼
參數format:表示圖像的壓縮格式,目前有CompressFormat.JPEG、CompressFormat.PNG、CompressFormat.WEBP。
參數quality: 圖像壓縮率,0-100。 0 壓縮100%,100意味着不壓縮。
參數stream: 寫入壓縮數據的輸出流。
經常使用的用法:
public static Bitmap compress(Bitmap bitmap){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, baos);
byte[] bytes = baos.toByteArray();
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
複製代碼
上面方法中經過bitmap的compress方法對bitmap進行質量壓縮,10%壓縮,90%不壓縮。
圖片的大小是沒有變的,由於質量壓縮不會減小圖片的像素,它是在保持像素的前提下改變圖片的位深及透明度等,來達到壓縮圖片的目的,這也是爲何該方法叫質量壓縮方法。圖片的長,寬,像素都不變,那麼bitmap所佔內存大小是不會變的。
quality值越小壓縮後的baos越小(使用場景:在微信分享時,須要對圖片的字節數組大小進行限制,這時可使用bitmap的compress方法對圖片進行質量壓縮)。
二、BitmapFactory.Options的inJustDecodeBounds和inSampleSize參數(採樣率壓縮):
inJustDecodeBounds:當inJustDecodeBounds設置爲true的時候,BitmapFactory經過decodeXXXX解碼圖片時,將會返回空(null)的Bitmap對象,這樣能夠避免Bitmap的內存分配,可是它能夠返回Bitmap的寬度、高度以及MimeType。
inSampleSize: 當它小於1的時候,將會被當作1處理,若是大於1,那麼就會按照比例(1 / inSampleSize)縮小bitmap的寬和高、下降分辨率,大於1時這個值將會被處置爲2的倍數。例如,width=100,height=100,inSampleSize=2,那麼就會將bitmap處理爲,width=50,height=50,寬高降爲1 / 2,像素數降爲1 / 4。
經常使用用法:
public static Bitmap inSampleSize(byte[] data,int reqWidth,int reqHeight){
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeByteArray(data, 0, data.length, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int picheight = options.outHeight;
final int picwidth = options.outWidth;
int targetheight = picheight;
int targetwidth = picwidth;
int inSampleSize = 1;
if (targetheight > reqHeight || targetwidth > reqWidth) {
while (targetheight >= reqHeight
&& targetwidth >= reqWidth) {
inSampleSize += 1;
targetheight = picheight / inSampleSize;
targetwidth = picwidth / inSampleSize;
}
}
return inSampleSize;
}
}
複製代碼
inSampleSize方法中先將inJustDecodeBounds設置爲false,在經過BitmapFactory的decodeXXXX方法解碼圖片,返回空(null)的Bitmap對象,同時獲取了bitmap的寬高,再經過calculateInSampleSize方法根據原bitmap的 寬高和目標寬高計算出合適的inSampleSize,最後將inJustDecodeBounds設置爲true,經過BitmapFactory的decodeXXXX方法解碼圖片(使用場景:好比讀取本地圖片時,防止Bitmap過大致使內存溢出)。
三、經過Matrix壓縮圖片
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
bm = Bitmap.createBitmap(bit, 0, 0, bit.getWidth(),bit.getHeight(), matrix, true);
}
複製代碼
使用場景:自定義View時,對圖片進行縮放、旋轉、位移以及傾斜等操做,常見的就是對圖片進行縮放處理,以及圓角圖片等。
inBitmap: 若是設置,在加載Bitmap的時候會嘗試去重用這塊內存(內存複用),不能重用的時候會返回null,不然返回bitmap。
複用內存:BitmapFactory.Options 參數inBitmap的使用。inMutable設置爲true,而且配合SoftReference軟引用使用(內存空間足夠,垃圾回收器就不會回收它;若是內存空間不足了,就會回收這些軟引用對象的內存)。有一點要注意Android4.4如下的平臺,須要保證inBitmap和即將要獲得decode的Bitmap的尺寸規格一致,Android4.4及其以上的平臺,只須要知足inBitmap的尺寸大於要decode獲得的Bitmap的尺寸規格便可。
常見的圖片加載緩存庫有 Picasso、Glide、Fresco。
根據 App 對圖片顯示和緩存的需求從低到高的選擇順序:Picasso < Glide < Fresco
一、使用完畢後釋放圖片資源
Android編程中,每每最容易出現OOM的地方就是在圖片處理的時候,咱們先上個數據:一個像素的顯示須要4字節(R、G、B、A各佔一個字節),因此一個1080x720像素的手機一個滿屏幕畫面就須要近3M內存,而開發一個輕量應用的安裝包大小也差很少就3M左右,因此說圖片很佔內存。在Android中,圖片的資源文件叫作Drawable,存儲在硬盤上,不耗內存,但咱們並沒有法對其進行處理,最多隻能進行展現。而若是想對該圖片資源進行處理,咱們須要把這個Drawable解析爲Bitmap形式裝載入內存中。其中Android的不一樣版本對Bitmap的存儲方式還有所不一樣。下面是Android官方文檔中對此描述的一段話
On Android 2.3.3 (API level 10) and lower, the backing pixel data for a bitmap is stored in native memory. It is separate from the bitmap itself, which is stored in the Dalvik heap. The pixel data in native memory is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash. As of Android 3.0 (API level 11), the pixel data is stored on the Dalvik heap along with the associated bitmap.
bitmap分紅兩個部分,一部分爲bitmap對象,用以存儲此圖片的長、寬、透明度等信息;另外一部分爲bitmap數據,用以存儲bitmap的(A)RGB字節數據。在2.3.3及之前版本中bitmap對象和bitmap數據是存儲在不一樣的內存空間上的,bitmap數據部分存儲在native內存中,GC沒法涉及。因此以前咱們須要調用bitmap的recycle方法來顯示的告訴系統此處內存可回收,而在3.0版本開始,bitmap的的這兩部分都存儲在了Dalvik堆中,能夠被GC機制統一處理,也就無需用recycle了。 關於bitmap優化,不一樣版本方法也不相同,2.3.3版本及之前,就要作到及時調用recycle來回收不在使用的bitmap,而3.0開始可使用BitmapFactory.Options.inBitmap這個選項,設置一個可複用的bitmap,這樣之後新的bitmap且大小相同的就能夠直接使用這塊內存,而無需重複申請內存。4.4以後解決了對大小的限制,不一樣大小也能夠複用該塊空間。
注:若調用了Bitmap.recycle()後,再繪製Bitmap,則會出現Canvas: trying to use a recycled bitmap錯誤
二、根據分辨率適配 & 縮放圖片
若 Bitmap 與 當前設備的分辨率不匹配,則會拉伸Bitmap,而Bitmap分辨率增長後,所佔用的內存也會相應增長
由於Bitmap 的內存佔用 根據 x、y的大小來增長的
三、按需 選擇合適的解碼方式
不一樣的圖片解碼方式 對應的 內存佔用大小 相差很大,具體以下
使用參數:BitmapFactory.inPreferredConfig 設置 默認使用解碼方式:ARGB_8888
四、設置 圖片緩存
重複加載圖片資源耗費太多資源(CPU、內存 & 流量)
內存泄露,即Memory Leak,指程序中再也不使用到的對象因某種緣由從而沒法被GC正常回收。發生內存泄露,會致使一些再也不使用到的對象沒有及時釋放,這些對象佔用了寶貴的內存空間,很容易致使後續須要分配內存的時候,內存空間不足而出現OOM(內存溢出)。
一、靜態變量致使的內存泄露
靜態變量的生命週期與應用的生命週期一致,該對象會一直被引用直到應用結束。
例子1:
public class MainActivity extends Activity {
public static Context context;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context=this;
}
}
複製代碼
上述代碼在MainActivity中context爲靜態變量,並持有Context,當Activity退出後,因爲Activity被context一直引用着,致使Activity沒法被回收,所以形成了內存泄漏。上述代碼比較明顯,通常不會犯這種錯誤。
例子2:
public class MainActivity extends Activity {
public static Out mOut;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mOut = new Out(this);
}
}
//外部Out類
public class Out {
Out(Context context) {
}
}
複製代碼
上述代碼與例子1相似,mOut爲靜態變量,生命週期與應用一致,傳入的MainActivity也被一直引用,致使Activity沒法被回收,形成內存泄漏。
解決方案:
一、在不使用靜態變量時,置空。
二、可使用Application的Context。
三、經過弱引用和軟引用來引用Activity。
例子3:
單例模式在Android開發中會常常用到,可是若是使用不當就會致使內存泄露。由於單例的靜態特性使得它的生命週期同應用的生命週期同樣長,若是一個對象已經沒有用處了,可是單例還持有它的引用,那麼在整個應用程序的生命週期它都不能正常被回收,從而致使內存泄露。
public class Singleton {
private static Singleton singleton = null;
private Context mContext;
public Singleton(Context mContext) {
this.mContext = mContext;
}
public static Singleton getSingleton(Context context){
if (null == singleton){
singleton = new Singleton(context);
}
return singleton;
}
}
複製代碼
像上面代碼中這樣的單例,若是咱們在調用getInstance(Context context)方法的時候傳入的context參數是Activity、Service等上下文,就會致使內存泄露。
當咱們退出Activity時,該Activity就沒有用了,可是由於singleton做爲靜態單例(在應用程序的整個生命週期中存在)會繼續持有這個Activity的引用,致使這個Activity對象沒法被回收釋放,這就形成了內存泄露。
二、非靜態內部類致使內存泄露
非靜態內部類(包括匿名內部類)默認就會持有外部類的引用,當非靜態內部類對象的生命週期比外部類對象的生命週期長時,就會致使內存泄露。
非靜態內部類致使的內存泄露在Android開發中有一種典型的場景就是使用Handler,不少開發者在使用Handler是這樣寫的:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
// 作相應邏輯
}
}
};
}
複製代碼
當Activity退出後,msg可能仍然存在於消息對列MessageQueue中未處理或者正在處理,那麼這樣就會致使Activity沒法被回收,以至發生Activity的內存泄露。 一般在Android開發中若是要使用內部類,但又要規避內存泄露,通常都會採用靜態內部類+弱引用的方式。
public class MainActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler = new MyHandler(this);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private static class MyHandler extends Handler {
private WeakReference<MainActivity> activityWeakReference;
public MyHandler(MainActivity activity) {
activityWeakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = activityWeakReference.get();
if (activity != null) {
if (msg.what == 1) {
// 作相應邏輯
}
}
}
}
}
複製代碼
mHandler經過弱引用的方式持有Activity,當GC執行垃圾回收時,遇到Activity就會回收並釋放所佔據的內存單元。這樣就不會發生內存泄露了。 上面的作法確實避免了Activity致使的內存泄露,發送的msg再也不已經沒有持有Activity的引用了,可是msg仍是有可能存在消息隊列MessageQueue中,因此更好的是在Activity銷燬時就將mHandler的回調和發送的消息給移除掉。
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
複製代碼
非靜態內部類形成內存泄露還有一種狀況就是使用Thread或者AsyncTask。 好比在Activity中直接new一個子線程Thread:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
// 模擬相應耗時邏輯
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
複製代碼
或者直接新建AsyncTask異步任務:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
// 模擬相應耗時邏輯
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}.execute();
}
}
複製代碼
這種方式新建的子線程Thread和AsyncTask都是匿名內部類對象,默認就隱式的持有外部Activity的引用,致使Activity內存泄露。要避免內存泄露的話仍是須要像上面Handler同樣使用靜態內部類+弱應用的方式(代碼就不列了,參考上面Hanlder的正確寫法)。
三、集合類內存泄露
集合類添加元素後,將會持有元素對象的引用,致使該元素對象不能被垃圾回收,從而發生內存泄漏。
四、未關閉資源對象內存泄露
WebView擴展:
WebView 解析網頁時會申請Native堆內存用於保存頁面元素,當頁面較複雜時會有很大的內存佔用。若是頁面包含圖片,內存佔用會更嚴重。而且打開新頁面時,爲了能快速回退,以前頁面佔用的內存也不會釋放。有時瀏覽十幾個網頁,都會佔用幾百兆的內存。這樣加載網頁較多時,會致使系統不堪重負,最終強制關閉應用,也就是出現應用閃退或重啓。
因爲佔用的都是Native 堆內存,因此實際佔用的內存大小不會顯示在經常使用的 DDMS Heap 工具中( DMS Heap 工具看到的只是Java虛擬機分配的內存,即便Native堆內存已經佔用了幾百兆,這裏顯示的還只是幾兆或十幾兆)。只有使用 adb shell 中的一些命令好比 dumpsys meminfo 包名,或者在程序中使用 Debug.getNativeHeapSize()才能看到 Native 堆內存信息。
五、使用系統服務引起的內存泄漏
爲了方便咱們使用一些常見的系統服務,Activity 作了一些封裝。好比說,能夠經過 getPackageManager在 Activtiy 中獲取 PackageManagerService,可是,裏面實際上調用了 Activity 對應的 ContextImpl 中的 getPackageManager 方法
ContextWrapper#getPackageManager
@Override
public PackageManager getPackageManager() {
return mBase.getPackageManager();
}
ContextImpl#getPackageManager
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));//建立 ApplicationPackageManager
}
return null;
}
複製代碼
ApplicationPackageManager#ApplicationPackageManager
ApplicationPackageManager(ContextImpl context,
IPackageManager pm) {
mContext = context;//保存 ContextImpl 的強引用
mPM = pm;
}
private UserManagerService(Context context, PackageManagerService pm,
Object packagesLock, File dataDir) {
mContext = context;//持有外部 Context 引用
mPm = pm;
//代碼省略
}
PackageManagerService#PackageManagerService
public class PackageManagerService extends IPackageManager.Stub {
static UserManagerService sUserManager;//持有 UMS 靜態引用
public PackageManagerService(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
sUserManager = new UserManagerService(context, this, mPackages);//初始化 UMS
}
}
複製代碼
遇到的內存泄漏問題是由於在 Activity 中調用了 getPackageManger 方法獲取 PMS ,該方法調用的是 ContextImpl,此時若是ContextImpl 中 PackageManager 爲 null,就會建立一個 PackageManger(ContextImpl 會將本身傳遞進去,而 ContextImpl 的 mOuterContext 爲 Activity),建立 PackageManager 實際上會建立 PackageManagerService(簡稱 PMS),而 PMS 的構造方法中會建立一個 UserManger(UserManger 初始化以後會持有 ContextImpl 的強引用)。 只要 PMS 的 class 未被銷燬,那麼就會一直引用着 UserManger ,進而致使其關聯到的資源沒法正常釋放。
解決辦法:
將getPackageManager()改成 getApplication()#getPackageManager() 。這樣引用的就是 Application Context,而非 Activity 了。
一、leakcanary
leakcanary是square開源的一個庫,可以自動檢測發現內存泄露,其使用也很簡單: 在build.gradle中添加依賴:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
//可選項,若是使用了support包中的fragments
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'
}
複製代碼
根目錄下的build.gradle添加mavenCentral()便可,以下:
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
複製代碼
而後在自定義的Application中調用如下代碼就能夠了。
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this);
//正常初始化代碼
}
}
複製代碼
若是檢測到有內存泄漏,通知欄會有提示,以下圖;若是沒有內存泄漏,則沒有提示。
二、Memory Profiler
Memory Profiler 是 Android Profiler 中的一個組件,能夠幫助你分析應用卡頓,崩潰和內存泄露等等問題。
打開 Memory Profiler後便可看到一個相似下圖的視圖。
上面的紅色數字含義以下:
1.用於強制執行垃圾回收事件的按鈕。
2.用於捕獲堆轉儲的按鈕。
3.用於記錄內存分配狀況的按鈕。 此按鈕僅在鏈接至運行 Android 7.1 或更低版本的設備時纔會顯示。
4.用於放大/縮小/還原時間線的按鈕。
5.用於跳轉至實時內存數據的按鈕。
6.Event 時間線,其顯示 Activity 狀態、用戶輸入 Event 和屏幕旋轉 Event。
7.內存使用量時間線,其包含如下內容:
一個顯示每一個內存類別使用多少內存的堆疊圖表,如左側的 y 軸以及頂部的彩色鍵所示。
虛線表示分配的對象數,如右側的 y 軸所示。
用於表示每一個垃圾回收事件的圖標。
複製代碼
如何Memory Profiler分析內存泄露,按如下步驟來便可:
1.使用Memory Profiler監聽要分析的應用進程
2.旋轉幾回要分析的Activity。(這是由於旋轉Activity後會從新建立)
3.點擊捕獲堆轉儲按鈕去捕獲堆轉儲
4.在捕獲結果中搜索要分析的類。(這裏是MainActivity)
5.點擊要分析的類,右邊會顯示這個類建立對象的數量。
複製代碼
以下圖:
內存抖動的緣由:
內存抖動通常是瞬間建立了大量對象,會在短期內觸發屢次GC,產生卡頓。
內存抖動的在分析工具上的表現:
解決方案:
最簡單的作法就是把以前的主線程操做放到子線程去,雖然內存抖動依然存在,可是卡頓問題能夠大大緩解。
對於內存抖動自己:
儘可能避免在循環體內建立對象,應該把對象建立移到循環體外。 須要大量使用Bitmap和其餘大型對象時,儘可能嘗試複用以前建立的對象。
客戶端請求流程以下:
分析網絡狀況的方式能夠經過Wireshark, Fiddler, Charlesr等抓包工具,也能夠經過Android Studio的Network Profiler
窗口頂部顯示的是 Event 時間線以及 1 無線裝置功耗狀態(低/高)與 WLAN 的對比。 在時間線上,您能夠 2點擊並拖動選擇時間線的一部分來檢查網絡流量。
下方的3窗口會顯示在時間線的選定片斷內收發的文件,包括文件名稱、大小、類型、狀態和時間。 您能夠點擊任意列標題爲此列表排序。
同時,您還能夠查看時間線選定片斷的明細數據,顯示每一個文件的發送或接收時間。
點擊網絡鏈接的名稱便可查看 4 有關所發送或接收的選定文件的詳細信息。 點擊各個標籤可查看響應數據、標題信息或調用堆棧。
注: 必須啓用高級分析才能從時間線中選擇要檢查的片斷,查看發送和接收的文件列表,或查看有關所發送或接收的選定文件的詳細信息。 要啓用高級分析,請參閱啓用高級分析。
啓用高級分析須要點擊Run Configuration:
打開Run/Debug Configurations,左側選擇你的應用,右側在Profiling中勾選Enable advanced profiling。
經過以上這些工具能夠查看某個時間段內網絡請求的具體狀況,從而進行網絡優化的相關工做。
一、後端API設計
後端設計API時須要考慮網絡請求的頻次、資源狀態,在某些狀況下能夠合併多個接口以知足客戶端業務需求。
二、Gzip壓縮
使用Gzip來壓縮request和response, 減小傳輸數據量, 從而減小流量消耗。同時能夠考慮使用Protocol Buffer代替JSON,protobuf會比JSON數據量小不少.
三、圖片大小優化
一、Batterystats & bugreport
Android 5.0及以上的設備, 容許咱們經過adb命令dump出電量使用統計信息.
由於電量統計數據是持續的, 會很是大, 統計咱們的待測試App以前先reset下, 連上設備,命令行執行:
$ adb shell dumpsys batterystats --reset
Battery stats reset.
複製代碼
斷開測試設備, 操做咱們的待測試App,從新鏈接設備, 使用adb命令導出相關統計數據:
// 此命令持續記錄輸出, 想要中止記錄時按Ctrl+C退出.
$ adb bugreport > bugreport.txt
複製代碼
導出的統計數據存儲到bugreport.txt, 此時咱們能夠藉助以下工具來圖形化展現電池的消耗狀況。
二、Battery Historian
Google提供了一個開源的電池歷史數據分析工具
建議: 根據具體業務需求,嚴格限制應用位於後臺時是否禁用某些數據傳輸,儘可能可以避免無效的數據傳輸。 數據傳輸的頻度問題,如網絡請求能夠壓縮合並,如本地數據上傳,能夠選擇恰當的時機上傳。
經過不停的喚醒CPU(經過後天常駐的Service)來達到一些功能的使用,這樣會形成電量資源的消耗,好比後臺日誌的上報,按期更新數據等等,在Android 5.0提供了一個JobScheduler組件,經過設置一系列的預置條件,當條件知足時,才執行對應的操做,這樣既能省電,有保證了功能的完整性。
JobScheduler的適用場景:
JobScheduler的使用
private Context mContext;
private JobScheduler mJobScheduler;
public JobSchedulerManager(Context context){
this.mContext=context;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
this.mJobScheduler= (JobScheduler) mContext.getSystemService(Context.JOB_SCHEDULER_SERVICE);
}
}
複製代碼
經過getSystemService()方法獲取一個JobSchedule的對象。
public boolean addTask(int taskId) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
JobInfo.Builder builder = new JobInfo.Builder(taskId,
new ComponentName("com.apk.administrator.loadapk",
JobScheduleService.class.getName()));
switch (taskId) {
case 1:
//每隔1秒執行一次
builder.setPeriodic(1000);
break;
case 2:
//設備重啓後,再也不執行該任務
builder.setPersisted(false);
break;
default:
break;
}
if (null != mJobScheduler) {
return mJobScheduler.schedule(builder.build()) > 0;
} else {
return false;
}
} else {
return true;
}
}
複製代碼
建立一個JobInfo對象時傳入兩個參數,第一個參數是任務ID,能夠對不一樣的任務ID作不一樣的觸發條件,執行任務時根據任務ID執行具體的任務;第二個參數是JobScheduler任務的服務,參數爲進程名和服務類名。
JobInfo支持如下幾種觸發條件:
public class JobScheduleService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}
複製代碼
JobService運行在主線程,若是是耗時任務,使用ThreadHandler或者一個異步任務來運行耗時的任務,防止阻塞主線程。
JobScheduleService繼承JobService,實現兩個方法onStartJob和onStopJob。 任務開始時,執行onStartJob方法,當任務執行完畢後,須要調用jobFinished方法來通知系統;任務執行完成後,調用jobFinished方法通知JobScheduler;當系統接受到一個取消請求時,調用onStopJob方法取消正在等待執行的任務。若是系統在接受到一個取消請求時,實際任務隊列中已經沒有正在運行的任務,onStopJob不會被調用。 最後在AndroidManifest中配置下:
<service android:name=".JobScheduleService" android:permission="android.permission.BIND_JOB_SERVICE" />
複製代碼
一、從圖片入手:.9圖、壓縮或採用Webp。
二、使用Lint刪除無用資源
三、經過Gradle配置,過濾無用資源和.so文件
四、第三方庫慎重使用,能夠只提取使用到的代碼
五、資源混淆:方案有:美團和微信,前者是經過修改AAPT在處理資源文件相關的源碼達到資源名的替換,後者經過直接修改resources.arsc文件來達到資源文件名的混淆。
六、插件化
複製代碼
整合網上相關資料,不按期更新此文。