編輯:關於android開發
本文內容來源於最近一次內部分享的總結,沒來得及詳細整理,見諒。
本次分享主要對內存洩露和線程安全這兩個問題進行一些說明,內部代碼掃描發現的BUG大致分為四類:1)空指針;2)除0;3)內存、資源洩露;4)線程安全。第一、二個問題屬於編碼考慮不周,第三、四個問題則需要更深入的分析。
怎樣才能讓app報OOM呢?最簡單的辦法如下:
Bitmap bt1 = BitmapFactory.decodeResource(this.getResources(), R.drawable.image); Bitmap bt2 = BitmapFactory.decodeResource(this.getResources(), R.drawable.image); Bitmap btn = ...
首先,每個app有最大內存限制。
ActivityManager activityManager = (ActivityManager) context.getSystemServiceContext.ACTIVITY_SERVICE); activityManager.getMemoryClass();
getMemoryClass()取到的是最大內存資源。Android中的堆內存分為Native Heap和Dalvik Heap。C/C++申請的內存空間在Native Heap中,Java申請的內存空間則在Dalvik Heap中。對於head堆的大小限制,可以查看/system/build.prop文件:
dalvik.vm.heapstartsize=8m dalvik.vm.heapgrowthlimit=96m dalvik.vm.heapsize=256m
注意:
heapsize參數表示單個進程heap可用的最大內存,但如果存在以下參數”dalvik.vm.headgrowthlimit =96m”表示單個進程heap內存被限定在96m,即程序運行過程實際只能使用96m內存。
如果申請的內存資源超過上述限制,系統就會拋出OOM錯誤。
以下主要從四個方面總結常見的措施:1)減小對象的內存占用;2)內存對象的重復利用;3)避免對象的內存洩露;4)內存使用策略優化。
4.1 減小對象的內存占用
4.2 內存對象的重復利用
4.3 避免對象的內存洩露
4.1和4.2都是比較常規的措施,4.3需要重點關注。
1)Activity洩露
導致Activity洩露的原因較多,下面列舉一些比較常見的。從原理上主要分為兩類:i)靜態對象;ii)this$0。
private static Map<ComponentName, ExceptionHandler> configMap =
new HashMap<ComponentName, ExceptionHandler>();
public static void setActivity(final Activity activity, boolean send2Server) {
Log.d(TAG, "bind exception handler : " + activity.getComponentName().getClassName());
//上下文初始化
SDKContext.init(activity.getApplication());
init(activity.getApplication());
ExceptionHandler exceptionHandler = new ExceptionHandler(
activity, send2Server, Thread.getDefaultUncaughtExceptionHandler());
configMap.put(activity.getComponentName(), exceptionHandler);
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
}下面是通過MAT分析一個Activity洩露的截圖:
2)考慮使用Application Context而不是Activity Context
對於大部分非必須使用Activity Context的情況(Dialog的Context就必須是Activity Context),我們都可以考慮使用Application Context而不是Activity的Context,這樣可以避免不經意的Activity洩露。
3)注意臨時Bitmap對象的及時回收
雖然在大多數情況下,我們會對Bitmap增加緩存機制,但是在某些時候,部分Bitmap是需要及時回收的。例如臨時創建的某個相對比較大的bitmap對象,在經過變換得到新的bitmap對象之後,應該盡快回收原始的bitmap,這樣能夠更快釋放原始bitmap所占用的空間。
4)內存占用監控
通過Runtime獲取maxMemory,而maxMemory-totalMemory即為剩余可使用的dalvik內存。定期檢查這個值,達到80%就去釋放各種cache資源(bitmap的cache)。
/** * Returns the maximum number of bytes the heap can expand to. See {@link #totalMemory} for the * current number of bytes taken by the heap, and {@link #freeMemory} for the current number of * those bytes actually used by live objects. */ int maxMemory = Runtime.getRuntime().maxMemory()); // 應用程序最大可用內存 /** * Returns the number of bytes taken by the heap at its current size. The heap may expand or * contract over time, as the number of live objects increases or decreases. See * {@link #maxMemory} for the maximum heap size, and {@link #freeMemory} for an idea of how much * the heap could currently contract. */ long totalMemory = Runtime.getRuntime().totalMemory()); // 應用程序已獲得內存 /** * Returns the number of bytes currently available on the heap without expanding the heap. See * {@link #totalMemory} for the heap's current size. When these bytes are exhausted, the heap * may expand. See {@link #maxMemory} for that limit. */ long freeMemory = Runtime.getRuntime().freeMemory()); // 應用程序已獲得內存中未使用內存
5)注意Cursor對象是否及時關閉
在程序中我們經常會進行查詢數據庫的操作,但時常會存在不小心使用Cursor之後沒有及時關閉的情況。這些Cursor的洩露,反復多次出現的話會對內存管理產生很大的負面影響,我們需要謹記對Cursor對象的及時關閉。
4.4 內存使用策略優化
class MyCounter { private static int counter = 0; public static int getCount() { return counter++; } }
怎樣使上述方法線程安全?
怎樣保持在多線程環境下的數據一致性,Java提供了多種方法實現:
2.1 synchronized
JVM保證被synchronized關鍵字修飾的代碼段在同一時間只能被一個線程訪問,內部通過對對象或類加鎖來實現的。當方法被synchronized修飾時,鎖加在對象上;當方法同時為static時,鎖加在類上。從性能的角度來講,一般不建議直接將鎖加在類上,這樣會使得類的所有對象的該方法均為synchronized的。
從之前掃描的問題來看,在編寫synchronized程序時主要有兩點需要注意:
1) 鎖加在哪裡?
List<ResultPoint> currentPossible = possibleResultPoints;
List<ResultPoint> currentLast = lastPossibleResultPoints;
int frameLeft = frame.left;
int frameTop = frame.top;
if (currentPossible.isEmpty()) {
lastPossibleResultPoints = null;
} else {
possibleResultPoints = new ArrayList<>(5);
lastPossibleResultPoints = currentPossible;
paint.setAlpha(CURRENT_POINT_OPACITY);
paint.setColor(resultPointColor);
synchronized (currentPossible) {
for (ResultPoint point : currentPossible) {
canvas.drawCircle(frameLeft
+ (int) (point.getX() * scaleX), frameTop
+ (int) (point.getY() * scaleY), POINT_SIZE,
paint);
}
}
}
上述方法中,possibleResultPoints的創建沒有采用同步措施,需要使用Collections.synchronizedXxx。
List<MyType> list = Collections.synchronizedList(new ArrayList(<MyType>));
...
synchronized(list){
for(MyType m : list){
foo(m);
m.doSomething();
}
}
一般比較推薦創建一個虛擬的對象專門用於獲取鎖。
//dummy object variable for synchronization private Object mutex=new Object(); ... //using synchronized block to read, increment and update count value synchronously synchronized (mutex) { count++; }
PS:直接在方法上加synchronized可能DoS攻擊喔,舉個栗子:
public class MyObject {
// Locks on the object's monitor
public synchronized void doSomething() {
// ...
}
}
// 黑客的代碼
MyObject myObject = new MyObject();
synchronized (myObject) {
while (true) {
// Indefinitely delay myObject
Thread.sleep(Integer.MAX_VALUE);
}
}
黑客的代碼獲取了MyObject對象的鎖,導致doSomething死鎖,從而引發Denial of Service。
public class MyObject {
//locks on the class object's monitor
public static synchronized void doSomething() {
// ...
}
}
// 黑客的代碼
synchronized (MyObject.class) {
while (true) {
Thread.sleep(Integer.MAX_VALUE); // Indefinitely delay MyObject
}
}
2) 死鎖。
public class ThreadDeadlock {
public static void main(String[] args) throws InterruptedException {
Object obj1 = new Object();
Object obj2 = new Object();
Object obj3 = new Object();
Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");
t1.start();
Thread.sleep(5000);
t2.start();
Thread.sleep(5000);
t3.start();
}
}
class SyncThread implements Runnable{
private Object obj1;
private Object obj2;
public SyncThread(Object o1, Object o2){
this.obj1=o1;
this.obj2=o2;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + " acquiring lock on "+obj1);
synchronized (obj1) {
System.out.println(name + " acquired lock on "+obj1);
work();
System.out.println(name + " acquiring lock on "+obj2);
synchronized (obj2) {
System.out.println(name + " acquired lock on "+obj2);
work();
}
System.out.println(name + " released lock on "+obj2);
}
System.out.println(name + " released lock on "+obj1);
System.out.println(name + " finished execution.");
}
private void work() {
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上述代碼會輸出什麼呢?
Android-socket服務端斷重啟後,android客戶端自動重連,androidsocket服務端
Android-socket服務端斷重啟後,android客戶端自動重連,androidsocket服務端今天研究這個問題搞了整整一天啊!終於出來了,不過我沒有多大的成就
Android Studio多渠道打包,androidstudio打包
Android Studio多渠道打包,androidstudio打包本文所講述的多渠道打包是基於友盟統計實施的。 多渠道打包的步驟: 1、在AndroidManifes
Android Develop:構建系統解析
Android Develop:構建系統解析 Android構建系統是你用來構建、測試、運行和打包你的app的工具集。這個構建系統能作為Android Studio菜單
自己實現android側滑菜單
自己實現android側滑菜單 當今的android應用設計中,一種主流的設計方式就是會擁有一個側滑菜單,以圖為證: 實現這樣的側滑效果,在5.0以前