如何解决Binder泄漏问题
作者:王小二C? 2019/09/06
前言
[011]一個看似是系統問題的應用問題的解決過程[1]中我們解決了一個注冊過多的BroadcastReceiver導致的某一次發送廣播失敗的問題。我這邊遇到了一個類似的問題,但是我用了一個可能網絡上從來沒有提出過的方法,解決了這個問題,寫下這個文章記錄一下,如果三年前的我肯定想不出這種解決手段。
問題
簡單看了一下log,發現和[011]一個看似是系統問題的應用問題的解決過程[2]的root cause是一樣的,還是在這次發廣播的Binder通信中無法申請足夠的buffer。
1143 1297 W BroadcastQueue: Can't deliver broadcast to com.android.systemui (pid 2107). Crashing it. 1143 1297 W BroadcastQueue: Failure sending broadcast Intent { act=android.intent.action.BATTERY_CHANGED flg=0x60000010 (has extras) } 1143 1297 W BroadcastQueue: android.os.DeadObjectException: Transaction failed on small parcel; remote process probably died 1143 1297 W BroadcastQueue: at android.os.BinderProxy.transactNative(Native Method) 1143 1297 W BroadcastQueue: at android.os.BinderProxy.transact(Binder.java:1129) 1143 1297 W BroadcastQueue: at android.app.IApplicationThread$Stub$Proxy.scheduleRegisteredReceiver(IApplicationThread.java:1237) 1143 1297 W BroadcastQueue: at com.android.server.am.BroadcastQueue.performReceiveLocked(BroadcastQueue.java:496) 1143 1297 W BroadcastQueue: at com.android.server.am.BroadcastQueue.deliverToRegisteredReceiverLocked(BroadcastQueue.java:715) 1143 1297 W BroadcastQueue: at com.android.server.am.BroadcastQueue.processNextBroadcastLocked(BroadcastQueue.java:875) 1143 1297 W BroadcastQueue: at com.android.server.am.BroadcastQueue.processNextBroadcast(BroadcastQueue.java:834) 1143 1297 W BroadcastQueue: at com.android.server.am.BroadcastQueue$BroadcastHandler.handleMessage(BroadcastQueue.java:172) 1143 1297 W BroadcastQueue: at android.os.Handler.dispatchMessage(Handler.java:106) 1143 1297 W BroadcastQueue: at android.os.Looper.loop(Looper.java:193) 1143 1297 W BroadcastQueue: at android.os.HandlerThread.run(HandlerThread.java:65) 1143 1297 W BroadcastQueue: at com.android.server.ServiceThread.run(ServiceThread.java:44) 1143 1297 W BroadcastQueue: Can't deliver broadcast to com.android.systemui (pid 2107). Crashing it.初步分析
首先我按照之前解決問題的思路,看systemui是否注冊了過多的BATTERY_CHANGED的廣播,但是排查了好多遍代碼,好像systemui并沒有注冊過多的廣播,這條路走不通。
查看內存信息
通過反復的測試,我發現systemui中存在大量的Local Binder,這個代表systemui創建了2207個Binder的Server端,這明顯是不正常的。
** MEMINFO in pid 2558 [com.android.systemui] ** Objects Views: 4976 ViewRootImpl: 5 AppContexts: 201 Activities: 1 Assets: 21 AssetManagers: 0 Local Binders: 2207 Proxy Binders: 267 Parcel memory: 91 Parcel count: 434 Death Recipients: 196 OpenSSL Sockets: 0 WebViews: 0如果找到Binder對象莫名增長的原因?
方案1:抓Hprof的文件
通過抓Hprof的文件,想查看Binder這個類的引用名,發現都是臨時變量,并沒有引用名,這條路走不通。
方案2:純看代碼
由于這個模塊不是我負責的,我也不是特別熟悉,這條路也走不通
重要發現
正當我一籌莫展的時候,同事發現反復進行某個操作的時候,會導致Binder增加,這個給了我一些線索,這個時候其實如果去反復看這個操作的代碼,我相信肯定可以找到原因,但是這個也只是把大海撈針變成了游泳池撈針,還是挺費時間的,對于代碼不熟悉的我來說,這個難度有點大。
#這樣的Binder對象對系統有威脅嗎?假如我按照以下的代碼,創建多個Binder對象,其實對系統沒有威脅,因為這樣子的Binder對象并不會在Binder驅動中創建Binder Node,說白了就是一個普通類,其他進程并不會持有這個Binder的BinderProxy對象。
while(true) { Binder binder = new Binder(); }怎么的Binder對象對系統有威脅?
首先我們可以確認,systemui創建的Binder對象肯定是匿名的Binder對象,匿名的Binder對象只有通過Binder的接口傳遞的時候才會創建Binder Node,這樣子才有威脅。既然要通過Binder的接口,必定要走以下代碼,所以我在下面加了這個Debug Log,我前面所說的關鍵方法,這個Debug Log。
Parcel.java public final void writeStrongBinder(IBinder val) { if(Binder.getCallingUid() == 10049) {//10049是systemui的uid android.util.Log.v("kobewang", "writeStrongBinder", new Exception("kobewang")); } nativeWriteStrongBinder(mNativePtr, val); }發現了異常的log
從下面的異常堆棧,發現一個問題不管是addCallback,還是removeCallback,都會調用registerSoftApCallback,這不是明顯的錯誤了嗎,不應該是removeCallback調用unregisterSoftApCallback才對嘛。
kobewang: writeStrongBinder kobewang: java.lang.Exception: kobewang kobewang: at android.os.Parcel.writeStrongBinder(Parcel.java:738) kobewang: at android.net.wifi.IWifiManager$Stub$Proxy.registerSoftApCallback(IWifiManager.java:2341) kobewang: at android.net.wifi.WifiManager.registerSoftApCallback(WifiManager.java:2664) kobewang: at com.android.systemui.statusbar.policy.HotspotControllerImpl.updateWifiStateListeners(HotspotControllerImpl.java:128) kobewang: at com.android.systemui.statusbar.policy.HotspotControllerImpl.addCallback(HotspotControllerImpl.java:94) kobewang: at com.android.systemui.statusbar.policy.HotspotControllerImpl.addCallback(HotspotControllerImpl.java:35) kobewang: at com.android.systemui.qs.tiles.HotspotTile.handleSetListening(HotspotTile.java:84) kobewang: at com.android.systemui.qs.tileimpl.QSTileImpl.handleSetListeningInternal(QSTileImpl.java:401) kobewang: at com.android.systemui.qs.tileimpl.QSTileImpl.access$700(QSTileImpl.java:70) kobewang: at com.android.systemui.qs.tileimpl.QSTileImpl$H.handleMessage(QSTileImpl.java:544) kobewang: at android.os.Handler.dispatchMessage(Handler.java:106) kobewang: at android.os.Looper.loop(Looper.java:193) kobewang: at android.os.HandlerThread.run(HandlerThread.java:65) kobewang: writeStrongBinder kobewang: java.lang.Exception: kobewang kobewang: at android.os.Parcel.writeStrongBinder(Parcel.java:738) kobewang: at android.net.wifi.IWifiManager$Stub$Proxy.registerSoftApCallback(IWifiManager.java:2341) kobewang: at android.net.wifi.WifiManager.registerSoftApCallback(WifiManager.java:2664) kobewang: at com.android.systemui.statusbar.policy.HotspotControllerImpl.updateWifiStateListeners(HotspotControllerImpl.java:128) kobewang: at com.android.systemui.statusbar.policy.HotspotControllerImpl.removeCallback(HotspotControllerImpl.java:105) kobewang: at com.android.systemui.statusbar.policy.HotspotControllerImpl.removeCallback(HotspotControllerImpl.java:35) kobewang: at com.android.systemui.qs.tiles.HotspotTile.handleSetListening(HotspotTile.java:88) kobewang: at com.android.systemui.qs.tileimpl.QSTileImpl.handleSetListeningInternal(QSTileImpl.java:407) kobewang: at com.android.systemui.qs.tileimpl.QSTileImpl.access$700(QSTileImpl.java:70) kobewang: at com.android.systemui.qs.tileimpl.QSTileImpl$H.handleMessage(QSTileImpl.java:544) kobewang: at android.os.Handler.dispatchMessage(Handler.java:106) kobewang: at android.os.Looper.loop(Looper.java:193) kobewang: at android.os.HandlerThread.run(HandlerThread.java:65)代碼分析
從代碼中可以發現removeCallback方法中updateWifiStateListeners(!mCallbacks.isEmpty());是有問題的。他這樣子寫代碼會導致removeCallback,最后走的也是registerSoftApCallback。
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java @Override public void addCallback(Callback callback) { synchronized (mCallbacks) { if (callback == null || mCallbacks.contains(callback)) return; if (DEBUG) Log.d(TAG, "addCallback " + callback); mCallbacks.add(callback); updateWifiStateListeners(!mCallbacks.isEmpty()); } } @Override public void removeCallback(Callback callback) { if (callback == null) return; if (DEBUG) Log.d(TAG, "removeCallback " + callback); synchronized (mCallbacks) { mCallbacks.remove(callback); //問題點:如果mCallbacks永遠存在一個callback, //那么!mCallbacks.isEmpty()就永遠是true。 updateWifiStateListeners(!mCallbacks.isEmpty()); } } private void updateWifiStateListeners(boolean shouldListen) { mWifiStateReceiver.setListening(shouldListen); if (shouldListen) { //永遠只會走registerSoftApCallback mWifiManager.registerSoftApCallback( this, Dependency.get(Dependency.MAIN_HANDLER)); } else { mWifiManager.unregisterSoftApCallback(this); } }查看了registerSoftApCallback的代碼,發現這個接口,會創建不止一個Binder對象,而是兩個Binder對象,一個Binder,一個SoftApCallbackProxy。
public void registerSoftApCallback(@NonNull SoftApCallback callback, @Nullable Handler handler) { if (callback == null) throw new IllegalArgumentException("callback cannot be null"); Log.v(TAG, "registerSoftApCallback: callback=" + callback + ", handler=" + handler); Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper(); Binder binder = new Binder();//一個binder對象 try { //SoftApCallbackProxy也是一個Binder對象 mService.registerSoftApCallback(binder, new SoftApCallbackProxy(looper, callback), callback.hashCode()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }總結
其實這是一個android 9.0的原生bug,所以有時候谷歌工程師也會犯錯誤,那如果來解決這個問題,其實這個問題已經在android 10上被谷歌工程師修復了,修復的方式,由于保密協議,我無法貼出android 10的代碼,等代碼正式釋放了,你們可以看看如何修復這個問題,當然你們自己也可以想想如何解決這個bug,其實也不是特別難。
PS
經過解決了兩個Binder申請buffer失敗的問題,我覺得最近幾年持續不斷的研究Binder驅動是非常值得的,換做2年前的我,可能就會和測試扯皮了,讓他monitor這些問題,然后然后最后無法復現或者低概率,這個bug就被close掉了。當然我現在還會遇到一些低概率input ANR難以解決的問題,以我現在的水平,還是無法解決這類問題,我相信在我不斷的學習之下,肯定最后會被我攻克的。
應用開發的建議
1.register和unregister一定要成對出現
2.對于注冊callback到system_server進程,一定要注意,因為一般這種callback就是一個binder對象,所以最好注冊一次,如果多處代碼需要注冊這個callback,請在通過你的應用層注冊一個callbackmanager到system_server,然后其他callback,都注冊到你的callbackmanager,這樣子system_server和你的應用跨進程通信就只需要一次。
References
[1]?[011]一個看似是系統問題的應用問題的解決過程:?/p/160290f38ee9
掃碼或長按關注
總結
以上是生活随笔為你收集整理的如何解决Binder泄漏问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何把一个float存到一个长度为4的c
- 下一篇: 一款强大的反编译工具luyten