Pushwoosh 泄露了我的 activity
Pushwoosh leaking my activity
我在我的应用程序中使用 Pushwoosh 来接收推送通知。我正在使用最新版本的 Pushwoosh 库 3.1.14。
我有这样的屏幕结构。
Login Activity -> Main Activity with multiple tabs.
所以我在 MainActivity 中实现我的 pushwoosh 相关逻辑。我想在注销时取消注册推送,然后返回登录 Activity。
我的代码如下。我已经过滤掉了与 Pushwoosh 无关的所有其他部分。坦率地说,这段代码与 Pushwoosh 文档 here 中的代码完全相似。唯一的区别在于 onLogout() 方法,我尝试从 pushwoosh 中注销并返回 LoginActivity.
TabbarActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Pushwoosh Registration
registerReceivers();
PushManager pushManager = PushManager.getInstance(this);
pushManager.setNotificationFactory(new PushNotificationFactory());
try {
pushManager.onStartup(this);
} catch(Exception e) {}
//Register for push!
pushManager.registerForPushNotifications();
checkMessage(getIntent());
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
checkMessage(intent);
}
@Override
protected void onResume() {
super.onResume();
registerReceivers();
}
@Override
protected void onPause() {
super.onPause();
unregisterReceivers();
}
BroadcastReceiver mBroadcastReceiver = new BaseRegistrationReceiver() {
@Override
public void onRegisterActionReceive(Context context, Intent intent) {
checkMessage(intent);
}
};
private BroadcastReceiver mReceiver = new BasePushMessageReceiver() {
@Override protected void onMessageReceive(Intent intent) {
//JSON_DATA_KEY contains JSON payload of push notification.
}
};
public void registerReceivers() {
IntentFilter intentFilter = new IntentFilter(
getPackageName() + ".action.PUSH_MESSAGE_RECEIVE");
registerReceiver(mReceiver, intentFilter,
getPackageName() +".permission.C2D_MESSAGE", null);
registerReceiver(mBroadcastReceiver, new IntentFilter(
getPackageName() + "." + PushManager.REGISTER_BROAD_CAST_ACTION));
}
public void unregisterReceivers() {
try {
unregisterReceiver(mReceiver);
} catch (Exception e) {
e.printStackTrace();
}
try {
unregisterReceiver(mBroadcastReceiver);
} catch (Exception e) {
e.printStackTrace();
}
}
private void checkMessage(Intent intent) {
if (null != intent) {
if (intent.hasExtra(PushManager.REGISTER_EVENT)) {
uploadPushTokenToServer(PushManager.getPushToken(this));
}
resetIntentValues();
}
}
private void resetIntentValues() {
Intent mainAppIntent = getIntent();
if (mainAppIntent.hasExtra(PushManager.PUSH_RECEIVE_EVENT)) {
mainAppIntent.removeExtra(PushManager.PUSH_RECEIVE_EVENT);
} else if (mainAppIntent.hasExtra(PushManager.REGISTER_EVENT)) {
mainAppIntent.removeExtra(PushManager.REGISTER_EVENT);
} else if (mainAppIntent.hasExtra(PushManager.UNREGISTER_EVENT)) {
mainAppIntent.removeExtra(PushManager.UNREGISTER_EVENT);
} else if (mainAppIntent.hasExtra(PushManager.REGISTER_ERROR_EVENT)) {
mainAppIntent.removeExtra(PushManager.REGISTER_ERROR_EVENT);
} else if (mainAppIntent.hasExtra(PushManager.UNREGISTER_ERROR_EVENT)) {
mainAppIntent.removeExtra(PushManager.UNREGISTER_ERROR_EVENT);
}
setIntent(mainAppIntent);
}
//Finally on logout
private void onLogout() {
//other cleanup
//pushwoosh
PushManager.getInstance(this).unregisterForPushNotifications();
//goback to login activity
}
我从服务器收到推送没有任何问题。我面临的唯一问题是在我注销并返回 LoginActivity 后,TabbarActivity 仍保留在内存中,这反过来又保留了许多其他片段和视图。我尝试使用 MAT 进行调试,结果显示如下。
Class Name | Ref. Objects | Shallow Heap | Ref. Shallow Heap | Retained Heap
--------------------------------------------------------------------------------------------------------------------------------------------------
com.pushwoosh.internal.request.RequestManager @ 0x12f89ce0 Thread-1737 Thread| 1 | 88 | 360 | 536
'- val$context in.myproject.activities.TabbarActivity @ 0x12d8ac40 | 1 | 360 | 360 | 18,520
--------------------------------------------------------------------------------------------------------------------------------------------------
我也用 LeakCanary 工具进行了相同的交叉检查,这也表明 Pushwoosh 正在保留我的 activity。
所以我的问题是,如何清理 pushwoosh 以避免我的 activity 被泄露?
您所引用的文档,通过简单地查看,给出了一个示例,这些示例并不总是将 api 实现为具有其他活动的全功能应用程序的最佳方式。我知道通过使用 getInstance,您正在尝试使用单例,但怀疑这没有得到很好的管理。
我会控制在您的应用运行期间使用的 PushManager 实例。
问题可能是从范围创建 PushManager 的多个实例以及在 class 内创建 PushManager 的多个实例,并且可能在程序的生命周期内。这会导致泄漏。
我会让 pushManager 成为一个 class 变量,而不是使用 PushManager.getInstance 两次,并考虑创建一个 PushManager 的静态实例以在应用程序运行期间使用,就像使用整个应用程序中的单个数据库实例。
class 水平:
PushManager pushManager;
并在 oncreate 中初始化
pushManager = PushManager.getInstance(this);
//Finally on logout
private void onLogout() {
//other cleanup
//pushwoosh
// Here the first instance is left dangling.
// PushManager.getInstance(this).unregisterForPushNotifications();
pushManager..unregisterForPushNotifications();
//goback to login activity
}
这样您就为 pushmanager 的一个实例清理了资源。
要使用应用范围内的静态 PushManager:
static PushManager pushManager;
初始化为:
pushManager = new PushManager(this.getApplicationContext());
@CommonsWare 的评论很到位。查看 Pushwoosh SDK 的(反编译)源代码,PushManager.onStartUp()
将提供的上下文直接转发到它的 RequestManager
,后者又将它交给一个 Thread
,它基本上保持 运行无限。这意味着它会在您的 activity 实例失效后很长时间内挂起。
请注意这正是 MAT 试图告诉您的(LeakCanary 也很可能)。
换句话说,在应用程序的整个生命周期内,内部都会对您传递给 onStartUp()
的任何内容进行强引用。因此,请确保您提供的上下文具有适当范围的生命周期。换句话说:只有这里的正确选项是应用上下文。
您可能想向 Pushwoosh 提交错误报告并告知他们问题是:
public void onStartup(Context context) throws Exception {
Context applicationContext = context.getApplicationContext();
this.pushRegistrar.checkDevice(applicationContext);
sendAppOpen(context); // <--- ISSUE
...
}
我只能猜测有人在办公室度过了糟糕的一天,忘记将有问题的行更改为 sendAppOpen(applicationContext)
。
在此更改后,我无法保证所有泄漏都将成为历史(我没有深入挖掘源代码),但它至少应该解决眼前的问题。
此外,这一点再怎么强调也不为过,一般来说,如果您不知道(或控制)组件的生命周期,请使用应用程序上下文。如果 activity 确实 需要,则方法签名 will/should 表明如此。如果只是询问上下文,请谨慎行事。 (是的,当然这个经验法则有很多例外,但通常更容易追踪到问题的结果而不是内存泄漏)。
嗯,我收到了 Pushwoosh 的回复,说他们已经解决了这个问题。我下载了他们最新的 SDK,瞧,泄漏消失了。似乎用户@MH 发现了罪魁祸首代码。
此为新SDK反编译源码,
public void onStartup(Context context) throws Exception {
Context applicationContext = context.getApplicationContext();
this.pushRegistrar.checkDevice(applicationContext);
sendAppOpen(applicationContext); // <--- NO ISSUE
...
}
我在我的应用程序中使用 Pushwoosh 来接收推送通知。我正在使用最新版本的 Pushwoosh 库 3.1.14。
我有这样的屏幕结构。
Login Activity -> Main Activity with multiple tabs.
所以我在 MainActivity 中实现我的 pushwoosh 相关逻辑。我想在注销时取消注册推送,然后返回登录 Activity。
我的代码如下。我已经过滤掉了与 Pushwoosh 无关的所有其他部分。坦率地说,这段代码与 Pushwoosh 文档 here 中的代码完全相似。唯一的区别在于 onLogout() 方法,我尝试从 pushwoosh 中注销并返回 LoginActivity.
TabbarActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Pushwoosh Registration
registerReceivers();
PushManager pushManager = PushManager.getInstance(this);
pushManager.setNotificationFactory(new PushNotificationFactory());
try {
pushManager.onStartup(this);
} catch(Exception e) {}
//Register for push!
pushManager.registerForPushNotifications();
checkMessage(getIntent());
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
checkMessage(intent);
}
@Override
protected void onResume() {
super.onResume();
registerReceivers();
}
@Override
protected void onPause() {
super.onPause();
unregisterReceivers();
}
BroadcastReceiver mBroadcastReceiver = new BaseRegistrationReceiver() {
@Override
public void onRegisterActionReceive(Context context, Intent intent) {
checkMessage(intent);
}
};
private BroadcastReceiver mReceiver = new BasePushMessageReceiver() {
@Override protected void onMessageReceive(Intent intent) {
//JSON_DATA_KEY contains JSON payload of push notification.
}
};
public void registerReceivers() {
IntentFilter intentFilter = new IntentFilter(
getPackageName() + ".action.PUSH_MESSAGE_RECEIVE");
registerReceiver(mReceiver, intentFilter,
getPackageName() +".permission.C2D_MESSAGE", null);
registerReceiver(mBroadcastReceiver, new IntentFilter(
getPackageName() + "." + PushManager.REGISTER_BROAD_CAST_ACTION));
}
public void unregisterReceivers() {
try {
unregisterReceiver(mReceiver);
} catch (Exception e) {
e.printStackTrace();
}
try {
unregisterReceiver(mBroadcastReceiver);
} catch (Exception e) {
e.printStackTrace();
}
}
private void checkMessage(Intent intent) {
if (null != intent) {
if (intent.hasExtra(PushManager.REGISTER_EVENT)) {
uploadPushTokenToServer(PushManager.getPushToken(this));
}
resetIntentValues();
}
}
private void resetIntentValues() {
Intent mainAppIntent = getIntent();
if (mainAppIntent.hasExtra(PushManager.PUSH_RECEIVE_EVENT)) {
mainAppIntent.removeExtra(PushManager.PUSH_RECEIVE_EVENT);
} else if (mainAppIntent.hasExtra(PushManager.REGISTER_EVENT)) {
mainAppIntent.removeExtra(PushManager.REGISTER_EVENT);
} else if (mainAppIntent.hasExtra(PushManager.UNREGISTER_EVENT)) {
mainAppIntent.removeExtra(PushManager.UNREGISTER_EVENT);
} else if (mainAppIntent.hasExtra(PushManager.REGISTER_ERROR_EVENT)) {
mainAppIntent.removeExtra(PushManager.REGISTER_ERROR_EVENT);
} else if (mainAppIntent.hasExtra(PushManager.UNREGISTER_ERROR_EVENT)) {
mainAppIntent.removeExtra(PushManager.UNREGISTER_ERROR_EVENT);
}
setIntent(mainAppIntent);
}
//Finally on logout
private void onLogout() {
//other cleanup
//pushwoosh
PushManager.getInstance(this).unregisterForPushNotifications();
//goback to login activity
}
我从服务器收到推送没有任何问题。我面临的唯一问题是在我注销并返回 LoginActivity 后,TabbarActivity 仍保留在内存中,这反过来又保留了许多其他片段和视图。我尝试使用 MAT 进行调试,结果显示如下。
Class Name | Ref. Objects | Shallow Heap | Ref. Shallow Heap | Retained Heap
--------------------------------------------------------------------------------------------------------------------------------------------------
com.pushwoosh.internal.request.RequestManager @ 0x12f89ce0 Thread-1737 Thread| 1 | 88 | 360 | 536
'- val$context in.myproject.activities.TabbarActivity @ 0x12d8ac40 | 1 | 360 | 360 | 18,520
--------------------------------------------------------------------------------------------------------------------------------------------------
我也用 LeakCanary 工具进行了相同的交叉检查,这也表明 Pushwoosh 正在保留我的 activity。
所以我的问题是,如何清理 pushwoosh 以避免我的 activity 被泄露?
您所引用的文档,通过简单地查看,给出了一个示例,这些示例并不总是将 api 实现为具有其他活动的全功能应用程序的最佳方式。我知道通过使用 getInstance,您正在尝试使用单例,但怀疑这没有得到很好的管理。
我会控制在您的应用运行期间使用的 PushManager 实例。
问题可能是从范围创建 PushManager 的多个实例以及在 class 内创建 PushManager 的多个实例,并且可能在程序的生命周期内。这会导致泄漏。
我会让 pushManager 成为一个 class 变量,而不是使用 PushManager.getInstance 两次,并考虑创建一个 PushManager 的静态实例以在应用程序运行期间使用,就像使用整个应用程序中的单个数据库实例。
class 水平:
PushManager pushManager;
并在 oncreate 中初始化
pushManager = PushManager.getInstance(this);
//Finally on logout
private void onLogout() {
//other cleanup
//pushwoosh
// Here the first instance is left dangling.
// PushManager.getInstance(this).unregisterForPushNotifications();
pushManager..unregisterForPushNotifications();
//goback to login activity
}
这样您就为 pushmanager 的一个实例清理了资源。
要使用应用范围内的静态 PushManager:
static PushManager pushManager;
初始化为:
pushManager = new PushManager(this.getApplicationContext());
@CommonsWare 的评论很到位。查看 Pushwoosh SDK 的(反编译)源代码,PushManager.onStartUp()
将提供的上下文直接转发到它的 RequestManager
,后者又将它交给一个 Thread
,它基本上保持 运行无限。这意味着它会在您的 activity 实例失效后很长时间内挂起。
请注意这正是 MAT 试图告诉您的(LeakCanary 也很可能)。
换句话说,在应用程序的整个生命周期内,内部都会对您传递给 onStartUp()
的任何内容进行强引用。因此,请确保您提供的上下文具有适当范围的生命周期。换句话说:只有这里的正确选项是应用上下文。
您可能想向 Pushwoosh 提交错误报告并告知他们问题是:
public void onStartup(Context context) throws Exception {
Context applicationContext = context.getApplicationContext();
this.pushRegistrar.checkDevice(applicationContext);
sendAppOpen(context); // <--- ISSUE
...
}
我只能猜测有人在办公室度过了糟糕的一天,忘记将有问题的行更改为 sendAppOpen(applicationContext)
。
在此更改后,我无法保证所有泄漏都将成为历史(我没有深入挖掘源代码),但它至少应该解决眼前的问题。
此外,这一点再怎么强调也不为过,一般来说,如果您不知道(或控制)组件的生命周期,请使用应用程序上下文。如果 activity 确实 需要,则方法签名 will/should 表明如此。如果只是询问上下文,请谨慎行事。 (是的,当然这个经验法则有很多例外,但通常更容易追踪到问题的结果而不是内存泄漏)。
嗯,我收到了 Pushwoosh 的回复,说他们已经解决了这个问题。我下载了他们最新的 SDK,瞧,泄漏消失了。似乎用户@MH 发现了罪魁祸首代码。
此为新SDK反编译源码,
public void onStartup(Context context) throws Exception {
Context applicationContext = context.getApplicationContext();
this.pushRegistrar.checkDevice(applicationContext);
sendAppOpen(applicationContext); // <--- NO ISSUE
...
}