由于Android應用的沙箱機制,每個應用所分配的內存大小是有限度的,因此內存會顯得非常珍貴,如果我們的內存占用超過了一定的水平就會出現OutOfMemory錯誤
內存概述
RAM(random access memory)隨機存取存儲器.(通俗的說就是內存)
- Java的內存分配策略:
Java內存分配時會涉及到以下區域:
棧(Stack):一些基本類型的變量和對象的引用都是在棧內存中分配,當超過變量的作用域后,java會自動釋放該變量分配的內存(對象本身不存放在棧中,而是存放在堆中)
堆(Heap): 通常用來存放new出來的對象和數組,由java垃圾回收器回收.
靜態存儲區(static field): 編譯時就分配好,在程序整個運行期間都存在.它主要存放靜態數據和常量
還一個CPU存儲區:
寄存器(Registers): 速度最快的存儲場所,因為寄存器位于處理器內部,我們在程序中無法控制
- 堆棧的特點:
棧:
定義一個變量時,Java在棧中為這個變量分配內存空間,當該變量退出該作用域后,Java會自動釋放為該變量所分配的內存空間.
棧的存取速度比堆要快,僅次于寄存器.但是存在棧中的數據大小與生存期必須是確定的,缺乏靈活性
棧中的數據可以共享,它是由編譯器完成的,有利于節省空間
例如:需要定義兩個變量int a = 3;int b = 3;
編譯器先處理int a = 3;首先它會在棧中創建一個變量為a的引用,然后查找棧中是否有3這個值,如果沒有,就將3存放進來再將a指向3.
接著處理int b = 3,創建完b的引用變量后在棧中已經有3這個值,便將b直接指向3.這樣,就出現了a與b同時均指向3的情況.
這時,如果再讓a=4,那么編譯器會重新搜索棧中是否有4值,如果沒有,則將4存放進來,并讓a指向4.
如果已經有了,則直接將a指向這個地址.因此a值的改變不會影響到b的值。
堆:
當堆中通過new產生數組和對象超出其作用域后,它們不會被釋放,只有在沒有引用變量指向它們的時候才變成垃圾,不能再被使用,并且只有等被垃圾回收器回收才回釋放內存.這也是Java比較占內存的原因.
堆是一個運行時數據區,可以動態地分配內存大小,因此存取速度較慢.
如上例子,棧中a的修改并不會影響到b,而在堆中一個對象引用變量修改了這個對象的內部狀態,會影響到另一個對象引用變量
- APP內存占用信息查詢
float max = Runtime.getRuntime().maxMemory() * 1.0f / (1024 * 1024);
float total = Runtime.getRuntime().totalMemory() * 1.0f / (1024 * 1024);
float free = Runtime.getRuntime().freeMemory() * 1.0f / (1024 * 1024);
查看系統設置單個進程的內存上限
C:\Users\Administrator>adb shell
sagit:/ $ getprop|grep heapgrowthlimit
[dalvik.vm.heapgrowthlimit]: [256m]
- java中四種引用類型:
強引用(StrongReference):強引用是使用最普遍的引用(如:Object object=new Object(),object就是一個強引用了),
如果一個對象具有強引用內存不足時,寧拋異常OOM導致程序終止也不回收,也就是JVM停止時才終止
軟引用(SoftReference):如果內存空間不足時,才會被回收(當內存達到一個閥值,GC就會去回收它)
弱引用(WeakReference):不管當前內存空間是否足夠,在GC 時都會回收
虛引用(PhantomReference):顧名思義,就是形同虛設,任何時候都可能被GC回收(已經不用)
image
軟引用實例:
// 例子1:
private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
public void add(String path) {
Bitmap bitmap = BitmapFactory.decodeFile(path); // 這里的bitmap屬于強引用
SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap); // 軟引用的Bitmap對象
imageCache.put(path, softBitmap);
}
public Bitmap get(String path) {
SoftReference<Bitmap> softBitmap = imageCache.get(path);
if (softBitmap == null) {
return null;
}
return softBitmap.get(); // 取出軟引用的Bitmap,如果內存不足被回收,獲取為NUll
}
public static Bitmap readBitmap(Context context, int resId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is, null, opt);
}
// 例子2:
static class MyHandler extends Handler {
private SoftReference<Activity> reference;
public MyHandler(Activity activity) {
// 持有 Activity 的軟引用
reference = new SoftReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = reference.get();
if (activity != null && !activity.isFinishing()) {
switch (msg.what) {
// 處理消息
}
}
}
}
- 垃圾回收機制:
垃圾回收是指清理在內存中不再需要的數據對象,以便大塊內存可以重新分配給新的對
象。一般來說,一旦某個對象在 App 中沒有一個活動的引用,就可以作為垃圾被回收了。
垃圾回收器會先從根部的對象開始(它知道這些對象是活動的并且正被進程所使用),并
且沿著每個引用去查找它們的關聯。如果一個對象不在這個有效引用的列表中,那么它肯
定不會再被使用,就可以被回收了。此時,分配給這個對象的內存空間也可以回收了
內存優化
-
內存泄漏
內存泄漏是內存優化中最重要的部分
-
IntentService的使用
IntentService是一種特殊的Service,繼承自Service;用于在后臺執行耗時的異步任務,當任務完成后會自動停止
為什么使用IntentService?
我們通常Service用法如下,也是標準用法:
public class MyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread() {
@Override
public void run() {
// 處理耗時邏輯
stopSelf(); // 如需實現處理完自動停止功能,可這樣做
}
}.start();
return super.onStartCommand(intent, flags, startId);
}
}
如上寫法并沒有什么錯誤,但是需要寫如上額外代碼,同時 當業務邏輯復雜后會有Service停止失敗導致內存泄漏的風險,Android官方推薦的最佳解決方案就是使用IntentService
public class MyIntentService extends IntentService {
public MyIntentService(String name) {
super(name);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
// 處理耗時邏輯,處理完自動停止
}
}
內部通過HandlerThread和Handler實現異步操作,創建IntentService時,只需實現onHandleIntent和構造方法.onHandleIntent為異步方法,可執行耗時操作.
-
Bitmap優化
Bitmap是內存消耗大戶,是導致OMM最常見的原因之一
圖片顯示:
我們可以根據場景需求去加載圖片的大小,例如列表中的小圖我們可以只加載縮略圖(thumbnails)
等比例壓縮圖片:
直接使用圖片(bitmap)會占用較多資源,特別是圖片較大的時候,可能導致崩潰,這時,我們可以使用BitmapFactory.Options設置inSampleSize.inSampleSize表示縮略圖大小為原始圖片大小的幾分之一,即如果這個值為2,則獲取圖片的寬和高都是原始圖片的1/2,圖片大小就為原始大小的1/4.
BitmapFactory.Options options = new BitmapFactory.Options();
// 該值設為true后將不返回實際的bitmap,也不給其分配內存空間.但允許我們查詢圖片的信息,計算出原始圖片的長和寬
options.inJustDecodeBounds = true;
//縮放的倍數,圖片寬高都為原來的二分之一,即圖片為原來的四分之一,SDK中建議該值為2,值越大會導致圖片不清晰
options.inSampleSize = 2;
options.inJustDecodeBounds = false;
Bitmap bmp = BitmapFactory.decodeFile(sourceBitmap, options);
圖片像素:
Android中圖片有四種屬性,分別是:
ALPHA_8:每個像素占用1byte內存
ARGB_4444:每個像素占用2byte內存
ARGB_8888:每個像素占用4byte內存 (默認)
RGB_565:每個像素占用2byte內存
Android默認的顏色模式為ARGB_8888,這個顏色模式色彩最細膩,顯示質量最高.同時占用的內存也最大 所以對圖片效果不是特別高的情況下可以使用RGB_565(565沒有透明度屬性)
public static Bitmap readBitmap(Context context, int resId) {
BitmapFactory.Options opt = new BitmapFactory.Options();
opt.inPreferredConfig = Bitmap.Config.RGB_565;
opt.inPurgeable = true;
opt.inInputShareable = true;
InputStream is = context.getResources().openRawResource(resId);
return BitmapFactory.decodeStream(is, null, opt);
}
圖片回收:
使用Bitmap過后及時的調用Bitmap.recycle()方法來釋放內存,不要等Android系統來進行釋放
if (bitmap != null && !bitmap.isRecycled()) {
// 回收并且置為null
bitmap.recycle();
bitmap = null;
}
System.gc();
對圖片采用軟引用
SoftReference<Bitmap> bitmap = new SoftReference<Bitmap>(pBitmap);
捕獲異常:
最壞的情況下不能導致程序崩潰,捕獲OOM異常
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeFile(path);
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
if (bitmap == null) {
return defaultBitmap;
}
相關鏈接直達:
Android APP性能優化之 ---- 優化監測工具(四)