前段時間開發了一個小屏的功能(效果圖如下:),果了一段時間,測試的人員說我們的手機有時開機不了:一直停止在開機的界面.剛開始都不知道什麼原因.查了各種代碼都沒有發現問題.到後來感覺問題越來越嚴重了.
到後來感覺問題越來越嚴重了.重要的是這個時低概率事件,很難復現一次,所以有點難.好不容易出現,通過data/anr/traces.txt找到問題,下面來看看關鍵信息:
"WindowManag4 MONITOR
| group="main" sCount=1 dsCount=0 obj=0x4275d4d8 self=0x57929cb0
| sysTid=732 nice=-4 sched=0/0 cgrp=apps handle=1469227632
| schedstat=( 1563495873 1380427864 20236 )
- waiting to lock <0x427e72d8> (a Ljava/lang/Object; ) held by tid=1 (main)
| [0]:<0006> Landroid/hardware/display/DisplayManagerGlobal;.getDisplayInfo(I)Landroid/view/DisplayInfo; (<span style="color:#FF0000;">DisplayManagerGlobal.java:108</span>)
| [1]:<0008> Landroid/view/Display;.updateDisplayInfoLocked()V (Display.java:657)
| [2]:<0002> Landroid/view/Display;.getMetrics(Landroid/util/DisplayMetrics;)V (Display.java:585)
| [3]:<0104> Lcom/android/server/display/xunhuOverlayDisplayAdapter;.<init>(Lcom/android/server/display/DisplayManagerService$SyncRoot;Lan (<span style="color:#FF0000;">xunhuOverlayDisplayAdapter.java:104</span>)
| [4]:<0030> Lcom/android/server/display/DisplayManagerService;.register_xunhuOverlayDisplayAdapterLocked()V (DisplayManagerService.java:994)
"main" prio=5 tid=1 MONITOR
| group="main" sCount=1 dsCount=0 obj=0x422c6cf8 self=0x4182e500
| sysTid=715 nice=-2 sched=0/0 cgrp=apps handle=1074631044
| schedstat=( 7254851604 2756007452 25767 )
- waiting to lock <0x4277acc8> (a Lcom/android/server/display/DisplayManagerService$SyncRoot; ) held by tid=14 (WindowManager)
| [0]:<0020> Lcom/android/server/display/DisplayManagerService;.getDisplayInfo(I)Landroid/view/DisplayInfo; (<span style="color:#FF0000;">DisplayManagerService.java:449</span>)
| [1]:<0012> Landroid/hardware/display/DisplayManagerGlobal;.getDisplayInfo(I)Landroid/view/DisplayInfo; (<span style="color:#FF0000;">DisplayManagerGlobal.java:117</span>)
| [2]:<0000> Landroid/hardware/display/DisplayManagerGlobal;.getCompatibleDisplay(ILandroid/view/DisplayAdjustments;)Landroid/view/Display; (DisplayManagerGlobal.java:176)
上面兩段信息的關鍵是:waiting to lock 看來貌似是進入一個死鎖.兩個鎖等待都在這個文件出現:DisplayManagerGlobal.java,所以看看DisplayManagerGlobal就會發現問題:
public DisplayInfo getDisplayInfo(int displayId) {
try {
synchronized (mLock) {<span style="color:#FF0000;">//108</span>
DisplayInfo info;
if (USE_CACHE) {
info = mDisplayInfoCache.get(displayId);
if (info != null) {
return info;
}
}
info = mDm.getDisplayInfo(displayId);<span style="color:#FF0000;">//117</span>
if (info == null) {
return null;
}
首先是這裏mLock鎖競爭.在這個鎖裏調用mDm.getDisplayInfo(displayId)的時候會回調到DisplayManagerService裏面,如下代碼:
public DisplayInfo getDisplayInfo(int displayId) {
final int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
synchronized (mSyncRoot) {<span style="color:#FF0000;">//449</span>
/// M:[SmartBook]Ignore extra display devicee info
if (FeatureOption.MTK_SMARTBOOK_SUPPORT) {
if (displayId == mExtraDisplayId) {
return null;
}
}
很顯然,在獲取了 DisplayManagerGlobal裏面的鎖:mLock,還需要DisplayManagerService裏面的鎖mSyncRoot.
而我開發的小屏功能在DisplayManagerService裏面register小屏的DisplayAdapter的時候已經獲取了mSyncRoot鎖,所以如果在這同時(register小屏的DisplayAdapter的時候)調用下面的代碼:
WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
wm.getDefaultDisplay().getMetrics(metric);
就容易出現死鎖. 因爲在調用getDefaultDisplay()的時候最後會調用DisplayManagerGlobal的getDisplayInfo這個方法.而在開機的時候有大量其他地方調用DisplayManagerGlobal的getDisplayInfo這個方法.