Android:为什么除非我重启或手动卸载应用程序,否则旧的 PendingIntents 不会被删除?
Android: why are old PendingIntents not deleted unless I reboot or manually uninstall the app?
一些背景
我有一个有持续通知的应用程序。我使用 NotificationCompat.Builder
创建通知。我在构建器上调用 setContentIntent()
以向通知添加 PendingIntent
,以便在我单击通知时运行 MyActivity。
当 MyActivity 为 运行 时,我单击正在进行的通知,我希望调用 MyActivity 的 onNewIntent()
。为了实现这一点,我将 MyActivity 的 launchMode 设置为 "singleTask",我传递给 setContentIntent()
的 PendingIntent 是这样创建的:
Intent notifyIntent = new Intent( this,
MyActivity.class );
PendingIntent pendingIntent = PendingIntent.getActivity( this,
REQUEST_CODE,
notifyIntent,
PendingIntent.FLAG_UPDATE_CURRENT );
我在 IntelliJ 中调试我的应用程序,我看到一切都按预期工作。到目前为止没有什么奇怪的。
问题
当我尝试将 PendingIntent 切换为使用 TaskStackBuilder
创建的一个时,它变得很奇怪。我知道这不会再导致 onNewIntent()
被调用,请耐心等待:
Intent notifyIntent = new Intent( this,
MyActivity.class );
TaskStackBuilder stackBuilder = TaskStackBuilder.create( this );
stackBuilder.addParentStack( MyActivity.class );
stackBuilder.addNextIntent( notifyIntent );
PendingIntent pendingIntent = stackBuilder.getPendingIntent( REQUEST_CODE,
PendingIntent.FLAG_UPDATE_CURRENT );
所以假设我已经更新了我的 PendingIntent 以这种方式创建。我按 ctrl+S 保存我的文件。我再次点击调试按钮,它会重建应用程序,替换我设备上的旧 apk 等,然后开始调试。当我单击正在进行的通知时,我希望现在看到的是现有的 运行 MyActivity 被销毁并创建一个新的,即 onCreate()
被调用。但是,我不这么认为。相反,我仍然看到 onNewIntent()
像以前一样被调用,就好像我没有保存我的代码并重建它一样。
嗯,但我确实保存并重建了它,因为同一文件中的其他更改(例如新的 Toast)正在显示。
让我重新启动我的设备并重试。哈,现在调用 onCreate()
而不是 onNewIntent()
。为什么要重启才能生效?
我已经在不同的设备和模拟器上试过了。我尝试了另一个 IDE。同样的事情。
我什至尝试从使用 TaskStackBuilder 返回到原来的 PendingIntent,但我仍然遇到意外行为。换句话说,我希望看到 onNewIntent()
被调用,因为我不再使用 TaskStackBuilder,但我看到 onCreate()
被调用。
所以,如果我重新启动我的设备,就会出现正确的预期行为。手动卸载应用程序也会产生正确的预期行为。
我是不是漏掉了一些显而易见的东西?为什么会这样?就好像 Android 正在记住上一个调试会话中的旧 PendingIntent。
重现
我根据此处的示例代码创建了一个玩具示例:http://developer.android.com/guide/topics/ui/notifiers/notifications.html
这是玩具示例:
MainActivity.java
package com.blah.pendingintent.example;
import android.app.Activity;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity
{
private static final int REQUEST_CODE = 1;
private static final int NOTIFICATION_ID = 2;
private static final String TAG = "MainActivity";
private NotificationCompat.Builder m_builder;
private NotificationManager m_notificationMgr;
@Override
protected void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_main );
m_builder = new NotificationCompat.Builder( this );
m_notificationMgr = (NotificationManager)getSystemService( Context.NOTIFICATION_SERVICE );
}
@Override
protected void onNewIntent( Intent intent )
{
super.onNewIntent( intent );
}
@Override
protected void onDestroy()
{
super.onDestroy();
}
public void onClick( View v )
{
Intent notifyIntent = new Intent( this,
MainActivity.class );
// comment out one of these lines and rerun
PendingIntent notifyPendingIntent = getPendingIntentWithActivity( notifyIntent );
// PendingIntent notifyPendingIntent = getPendingIntentWithBackStack( notifyIntent );
m_builder.setContentIntent( notifyPendingIntent );
m_builder.setContentTitle( "Picture Download" )
.setContentText( "Download in progress" )
.setSmallIcon( R.drawable.abc_ic_go );
new Thread( new Runnable()
{
@Override
public void run()
{
int incr;
for ( incr = 0; incr <= 100; incr += 5 )
{
m_builder.setProgress( 0,
0,
true );
m_notificationMgr.notify( NOTIFICATION_ID,
m_builder.build() );
try
{
Thread.sleep( 5 * 1000 );
}
catch ( InterruptedException e )
{
Log.d( TAG,
"sleep failure" );
}
}
m_builder.setContentText( "Download complete" )
.setProgress( 0,
0,
false );
m_notificationMgr.notify( NOTIFICATION_ID,
m_builder.build() );
}
}
).start();
}
private PendingIntent getPendingIntentWithBackStack( Intent notifyIntent )
{
Toast.makeText( this,
"Using TaskStackBuilder",
Toast.LENGTH_LONG )
.show();
TaskStackBuilder stackBuilder = TaskStackBuilder.create( this );
stackBuilder.addParentStack( MainActivity.class );
stackBuilder.addNextIntent( notifyIntent );
return stackBuilder.getPendingIntent( REQUEST_CODE,
PendingIntent.FLAG_UPDATE_CURRENT );
}
private PendingIntent getPendingIntentWithActivity( Intent notifyIntent )
{
Toast.makeText( this,
"Using PendingIntent",
Toast.LENGTH_LONG )
.show();
return PendingIntent.getActivity( this,
REQUEST_CODE,
notifyIntent,
PendingIntent.FLAG_UPDATE_CURRENT );
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${packageName}.${activityClass}">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="pres butan"
android:onClick="onClick"/>
</RelativeLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.blah.pendingintent.example" >
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.blah.pendingintent.example.MainActivity"
android:launchMode="singleTask"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
build.gradle(来自 IntelliJ 向导的默认文件)
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.9.+'
}
}
apply plugin: 'android'
repositories {
mavenCentral()
}
android {
compileSdkVersion 19
buildToolsVersion "19.1.0"
defaultConfig {
minSdkVersion 11
targetSdkVersion 18
versionCode 1
versionName "1.0"
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:19.+'
}
So, if I reboot my device, then the correct, expected behavior occurs.
Manually uninstalling the app also yields the correct, expected
behavior.
原因是,当你在不卸载的情况下调试应用程序时,之前创建的PendingIntent
仍然与AlarmManager
在一起。当您完全卸载该应用程序时,之前的 PendingIntent
将被销毁。重新启动 phone 也会破坏所有现有的 PendingIntent
。
It's as if Android is remembering old PendingIntent from the previous
debug session.
这就是正在发生的事情。因此,当您测试使用 PendingIntent
的应用程序时,您应该始终先卸载该应用程序,然后从 IDE 中重新 运行 它以产生正确的预期行为。另请注意,正如您所观察到的,当您重新启动 phone 时,现有的 PendingIntent
将被销毁。因此,您的应用程序必须以编程方式重新创建在重启时被破坏的 PendingIntent
s(这样,如果您的应用程序的用户重启他的 phone,他们会取回他们安排的警报)。这是通过拦截 BOOT_COMPLETE
事件来完成的。有关详细信息,请参阅 here。
编辑:
要以编程方式检查以前创建的 PendingIntent
是否存在,请使用:
boolean pendingIntentExists = (PendingIntent.getBroadcast(context, requestCode,
intent,
PendingIntent.FLAG_NO_CREATE) != null);
其中 requestCode
和 intent
必须与您之前创建此 PendingIntent
时相同。
我希望这能回答你的问题。
编辑 2:
我相信你的情况,检查 PendingIntent
是否存在的测试需要是:
// check PendingIntent for Activity
boolean pendingIntentExists = (PendingIntent.getActivity( this,
REQUEST_CODE,
notifyIntent,
PendingIntent.FLAG_NO_CREATE) != null);
// check PendingIntent for backstack
TaskStackBuilder stackBuilder = TaskStackBuilder.create( this );
stackBuilder.addParentStack( MainActivity.class );
stackBuilder.addNextIntent( notifyIntent );
boolean pendingIntentExists = (stackBuilder.getPendingIntent( REQUEST_CODE,
PendingIntent.FLAG_NO_CREATE ) != null);
请试试这个。
一些背景
我有一个有持续通知的应用程序。我使用 NotificationCompat.Builder
创建通知。我在构建器上调用 setContentIntent()
以向通知添加 PendingIntent
,以便在我单击通知时运行 MyActivity。
当 MyActivity 为 运行 时,我单击正在进行的通知,我希望调用 MyActivity 的 onNewIntent()
。为了实现这一点,我将 MyActivity 的 launchMode 设置为 "singleTask",我传递给 setContentIntent()
的 PendingIntent 是这样创建的:
Intent notifyIntent = new Intent( this,
MyActivity.class );
PendingIntent pendingIntent = PendingIntent.getActivity( this,
REQUEST_CODE,
notifyIntent,
PendingIntent.FLAG_UPDATE_CURRENT );
我在 IntelliJ 中调试我的应用程序,我看到一切都按预期工作。到目前为止没有什么奇怪的。
问题
当我尝试将 PendingIntent 切换为使用 TaskStackBuilder
创建的一个时,它变得很奇怪。我知道这不会再导致 onNewIntent()
被调用,请耐心等待:
Intent notifyIntent = new Intent( this,
MyActivity.class );
TaskStackBuilder stackBuilder = TaskStackBuilder.create( this );
stackBuilder.addParentStack( MyActivity.class );
stackBuilder.addNextIntent( notifyIntent );
PendingIntent pendingIntent = stackBuilder.getPendingIntent( REQUEST_CODE,
PendingIntent.FLAG_UPDATE_CURRENT );
所以假设我已经更新了我的 PendingIntent 以这种方式创建。我按 ctrl+S 保存我的文件。我再次点击调试按钮,它会重建应用程序,替换我设备上的旧 apk 等,然后开始调试。当我单击正在进行的通知时,我希望现在看到的是现有的 运行 MyActivity 被销毁并创建一个新的,即 onCreate()
被调用。但是,我不这么认为。相反,我仍然看到 onNewIntent()
像以前一样被调用,就好像我没有保存我的代码并重建它一样。
嗯,但我确实保存并重建了它,因为同一文件中的其他更改(例如新的 Toast)正在显示。
让我重新启动我的设备并重试。哈,现在调用 onCreate()
而不是 onNewIntent()
。为什么要重启才能生效?
我已经在不同的设备和模拟器上试过了。我尝试了另一个 IDE。同样的事情。
我什至尝试从使用 TaskStackBuilder 返回到原来的 PendingIntent,但我仍然遇到意外行为。换句话说,我希望看到 onNewIntent()
被调用,因为我不再使用 TaskStackBuilder,但我看到 onCreate()
被调用。
所以,如果我重新启动我的设备,就会出现正确的预期行为。手动卸载应用程序也会产生正确的预期行为。
我是不是漏掉了一些显而易见的东西?为什么会这样?就好像 Android 正在记住上一个调试会话中的旧 PendingIntent。
重现
我根据此处的示例代码创建了一个玩具示例:http://developer.android.com/guide/topics/ui/notifiers/notifications.html
这是玩具示例:
MainActivity.java
package com.blah.pendingintent.example;
import android.app.Activity;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
public class MainActivity extends Activity
{
private static final int REQUEST_CODE = 1;
private static final int NOTIFICATION_ID = 2;
private static final String TAG = "MainActivity";
private NotificationCompat.Builder m_builder;
private NotificationManager m_notificationMgr;
@Override
protected void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
setContentView( R.layout.activity_main );
m_builder = new NotificationCompat.Builder( this );
m_notificationMgr = (NotificationManager)getSystemService( Context.NOTIFICATION_SERVICE );
}
@Override
protected void onNewIntent( Intent intent )
{
super.onNewIntent( intent );
}
@Override
protected void onDestroy()
{
super.onDestroy();
}
public void onClick( View v )
{
Intent notifyIntent = new Intent( this,
MainActivity.class );
// comment out one of these lines and rerun
PendingIntent notifyPendingIntent = getPendingIntentWithActivity( notifyIntent );
// PendingIntent notifyPendingIntent = getPendingIntentWithBackStack( notifyIntent );
m_builder.setContentIntent( notifyPendingIntent );
m_builder.setContentTitle( "Picture Download" )
.setContentText( "Download in progress" )
.setSmallIcon( R.drawable.abc_ic_go );
new Thread( new Runnable()
{
@Override
public void run()
{
int incr;
for ( incr = 0; incr <= 100; incr += 5 )
{
m_builder.setProgress( 0,
0,
true );
m_notificationMgr.notify( NOTIFICATION_ID,
m_builder.build() );
try
{
Thread.sleep( 5 * 1000 );
}
catch ( InterruptedException e )
{
Log.d( TAG,
"sleep failure" );
}
}
m_builder.setContentText( "Download complete" )
.setProgress( 0,
0,
false );
m_notificationMgr.notify( NOTIFICATION_ID,
m_builder.build() );
}
}
).start();
}
private PendingIntent getPendingIntentWithBackStack( Intent notifyIntent )
{
Toast.makeText( this,
"Using TaskStackBuilder",
Toast.LENGTH_LONG )
.show();
TaskStackBuilder stackBuilder = TaskStackBuilder.create( this );
stackBuilder.addParentStack( MainActivity.class );
stackBuilder.addNextIntent( notifyIntent );
return stackBuilder.getPendingIntent( REQUEST_CODE,
PendingIntent.FLAG_UPDATE_CURRENT );
}
private PendingIntent getPendingIntentWithActivity( Intent notifyIntent )
{
Toast.makeText( this,
"Using PendingIntent",
Toast.LENGTH_LONG )
.show();
return PendingIntent.getActivity( this,
REQUEST_CODE,
notifyIntent,
PendingIntent.FLAG_UPDATE_CURRENT );
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="${packageName}.${activityClass}">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="pres butan"
android:onClick="onClick"/>
</RelativeLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.blah.pendingintent.example" >
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.blah.pendingintent.example.MainActivity"
android:launchMode="singleTask"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
build.gradle(来自 IntelliJ 向导的默认文件)
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.9.+'
}
}
apply plugin: 'android'
repositories {
mavenCentral()
}
android {
compileSdkVersion 19
buildToolsVersion "19.1.0"
defaultConfig {
minSdkVersion 11
targetSdkVersion 18
versionCode 1
versionName "1.0"
}
buildTypes {
release {
runProguard false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:19.+'
}
So, if I reboot my device, then the correct, expected behavior occurs. Manually uninstalling the app also yields the correct, expected behavior.
原因是,当你在不卸载的情况下调试应用程序时,之前创建的PendingIntent
仍然与AlarmManager
在一起。当您完全卸载该应用程序时,之前的 PendingIntent
将被销毁。重新启动 phone 也会破坏所有现有的 PendingIntent
。
It's as if Android is remembering old PendingIntent from the previous debug session.
这就是正在发生的事情。因此,当您测试使用 PendingIntent
的应用程序时,您应该始终先卸载该应用程序,然后从 IDE 中重新 运行 它以产生正确的预期行为。另请注意,正如您所观察到的,当您重新启动 phone 时,现有的 PendingIntent
将被销毁。因此,您的应用程序必须以编程方式重新创建在重启时被破坏的 PendingIntent
s(这样,如果您的应用程序的用户重启他的 phone,他们会取回他们安排的警报)。这是通过拦截 BOOT_COMPLETE
事件来完成的。有关详细信息,请参阅 here。
编辑:
要以编程方式检查以前创建的 PendingIntent
是否存在,请使用:
boolean pendingIntentExists = (PendingIntent.getBroadcast(context, requestCode,
intent,
PendingIntent.FLAG_NO_CREATE) != null);
其中 requestCode
和 intent
必须与您之前创建此 PendingIntent
时相同。
我希望这能回答你的问题。
编辑 2:
我相信你的情况,检查 PendingIntent
是否存在的测试需要是:
// check PendingIntent for Activity
boolean pendingIntentExists = (PendingIntent.getActivity( this,
REQUEST_CODE,
notifyIntent,
PendingIntent.FLAG_NO_CREATE) != null);
// check PendingIntent for backstack
TaskStackBuilder stackBuilder = TaskStackBuilder.create( this );
stackBuilder.addParentStack( MainActivity.class );
stackBuilder.addNextIntent( notifyIntent );
boolean pendingIntentExists = (stackBuilder.getPendingIntent( REQUEST_CODE,
PendingIntent.FLAG_NO_CREATE ) != null);
请试试这个。