最近有個APP中使用了微信授權登錄功能,項目中我們采用leakcanary來檢測內存泄漏,發(fā)現(xiàn)微信登錄有內存泄漏的問題?,F(xiàn)將解決過程記錄如下,不確定與微信SDK版本有沒關系,歡迎討論指正。
一般我們是這樣使用微信登錄的,包括微信給出的demo也是如此,代碼片段如下:
private IWXAPI mIWXAPI;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mIWXAPI = WXAPIFactory.createWXAPI(this, WX_APP_ID);
mIWXAPI.registerApp(WX_APP_ID);
findViewById(R.id.btn_wx_login).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final SendAuth.Req req = new SendAuth.Req();
req.scope = "snsapi_userinfo";
mIWXAPI.sendReq(req);
}
});
}
代碼邏輯很簡單,就是創(chuàng)建一個IWXAPI對象,然后發(fā)送一個授權請求。leakcanary檢測到的內存泄漏截圖如下所示:

從圖中可以看到WXApiImplV10持有了一個名為ActivityLifecycleCb的引用,ActivityLifecycleCb又持有了MainActivity的引用,這樣導致MainActivity內存得不到釋放,如果多次重復進入該界面,則會引起嚴重的內存泄漏。
查看微信SDK代碼我們發(fā)現(xiàn):
public final boolean registerApp(String var1, long var2) {
if(this.detached) {
throw new IllegalStateException("registerApp fail, WXMsgImpl has been detached");
} else if(!WXApiImplComm.validateAppSignatureForPackage(this.context, "com.tencent.mm", this.checkSignature)) {
Log.e("MicroMsg.SDK.WXApiImplV10", "register app failed for wechat app signature check failed");
return false;
} else {
Log.d("MicroMsg.SDK.WXApiImplV10", "registerApp, appId = " + var1);
if(var1 != null) {
this.appId = var1;
}
if(activityCb == null && VERSION.SDK_INT >= 14) {
if(this.context instanceof Activity) {
this.initMta(this.context, var1);
activityCb = new WXApiImplV10.ActivityLifecycleCb(this.context);
((Activity)this.context).getApplication().registerActivityLifecycleCallbacks(activityCb);
} else if(this.context instanceof Service) {
this.initMta(this.context, var1);
activityCb = new WXApiImplV10.ActivityLifecycleCb(this.context);
((Service)this.context).getApplication().registerActivityLifecycleCallbacks(activityCb);
} else {
Log.w("MicroMsg.SDK.WXApiImplV10", "context is not instanceof Activity or Service, disable WXStat");
}
}
Log.d("MicroMsg.SDK.WXApiImplV10", "registerApp, appId = " + var1);
if(var1 != null) {
this.appId = var1;
}
Log.d("MicroMsg.SDK.WXApiImplV10", "register app " + this.context.getPackageName());
a var4;
(var4 = new a()).W = "com.tencent.mm";
var4.X = "com.tencent.mm.plugin.openapi.Intent.ACTION_HANDLE_APP_REGISTER";
var4.content = "weixin://registerapp?appid=" + this.appId;
var4.Y = var2;
return com.tencent.mm.opensdk.channel.a.a.a(this.context, var4);
}
}
在調用registerApp()方法時,里面有句代碼 registerActivityLifecycleCallbacks(activityCb),該方法注冊了一個Activity的生命周期回調方法,activityCb持有了我們對應的Activity的引用,Activity在退出時并沒有解綁,所以內存泄漏的罪魁禍首應該就是這個。通常情況下,有注冊的方法必然會有解綁的方法,果不其然找到了下面這個方法:
public final void detach() {
Log.d("MicroMsg.SDK.WXApiImplV10", "detach");
this.detached = true;
if(activityCb != null && VERSION.SDK_INT >= 14) {
if(this.context instanceof Activity) {
((Activity)this.context).getApplication().unregisterActivityLifecycleCallbacks(activityCb);
} else if(this.context instanceof Service) {
((Service)this.context).getApplication().unregisterActivityLifecycleCallbacks(activityCb);
}
activityCb.detach();
}
this.context = null;
}
是不是很坑,微信的demo里并沒有提及這個,我們在開發(fā)時通常都是對著demo來一遍,一不小心就采坑了。接下來修改代碼如下,在Activity的onDestroy()方法里進行解綁操作:
@Override
protected void onDestroy() {
super.onDestroy();
mIWXAPI.detach();
}
我已經(jīng)迫不及待地再次測試了,很遺憾的是這個問題解決了,確又蹦出來另外一個問題:

剛才的喜悅之情瞬間蕩然無存,怎么還是有內存泄漏。從leakcanary上能看到,是一個com.tencent.a.a.a.a.g.V最終持有了MainActivity的引用,從包名上可以看到這也是微信SDK里的一個類。由于這是個被混淆的類,實在是不知道這個地方怎么會有內存泄漏(有興趣的同學可以去仔細分析下),既然內存泄漏是因為MainActivity被一直引用,那如果我們手動切斷這種引用關系,是不是就可以解決這個問題呢。那動手來試驗一下,通過反射來將這種引用關系置空。
@Override
protected void onDestroy() {
super.onDestroy();
mIWXAPI.detach();
cleanWXLeak();
}
/**
* 清除微信memory leak
*/
public static void cleanWXLeak() {
try {
Class clazz = com.tencent.a.a.a.a.g.class;
Field field = clazz.getDeclaredField("V");
field.setAccessible(true);
Object obj = field.get(clazz);
if (obj != null) {
com.tencent.a.a.a.a.g g = (com.tencent.a.a.a.a.g) obj;
Field mapField = clazz.getDeclaredField("U");
mapField.setAccessible(true);
Map map = (Map) mapField.get(g);
map.clear();
}
field.set(clazz, null);
} catch (Exception e) {
e.printStackTrace();
}
}
經(jīng)過反復測試,內存泄漏的問題終于解決了。