Pendingintent getbroadcast 丢失可打包数据

Posted

技术标签:

【中文标题】Pendingintent getbroadcast 丢失可打包数据【英文标题】:Pendingintent getbroadcast lost parcelable data 【发布时间】:2021-01-27 04:50:09 【问题描述】:

这就是问题所在。我的程序在 android 6.0 中运行完美。将设备更新到 android 7.0 后。 Pendingintent 不能将 Parcelable 数据传递给 boradcast reveiver。这是代码。

报警

public static void setAlarm(@NonNull Context context, @NonNull Todo todo) 
    AlarmManager alarmManager = (AlarmManager) context.getSystemService(context.ALARM_SERVICE);
    Intent intent = new Intent(context, AlarmReceiver.class);
    intent.putExtra("KEY_TODO", todo);
    PendingIntent alarmIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    alarmManager.set(AlarmManager.RTC_WAKEUP, todo.remindDate.getTime(), alarmIntent);

Todo 是一个 Parcelable 类,而 todo 是我在通知中需要的实例。

在 Broadcastreceiver 中,我无法获取 Parcelable 数据。

public void onReceive(Context context, Intent intent) 

    Todo todo = intent.getParcelableExtra("KEY_TODO");


这是我调试时的意图结果

我不知道为什么意图只包含一个我从未放入过的整数。 Parcelable 待办事项在哪里。 此代码在android 6.0下没有问题,但在7.0下无法运行

【问题讨论】:

在将Todo 对象添加到“附加”之前,您是否尝试过将其包装在Bundle 中?这通常在将自定义 Parcelable 对象传递给 AlarmManager 时有效(但现在可能在 Android 7 中被破坏)。我会对你的发现感兴趣。 添加额外内容:Bundle bundle = new Bundle; bundle.putParcelable("todo", todo); intent.putExtra("KEY_TODO", bundle);。提取额外:Bundle bundle = intent.getBundleExtra("KEY_TODO"); if (bundle != null) Todo todo = bundle.getParcelableExtra("todo"); 【参考方案1】:

引用myself:

自定义 Parcelable 类 — 对您的应用来说是独一无二的,而不是一部分 Android 框架的 - 有过间歇性的问题 用作Intent 临时演员的年份。基本上,如果一个核心操作系统进程 需要修改Intent extras,这个过程最终会尝试 重新创建您的 Parcelable 对象作为设置的一部分 额外Bundle 进行修改。那个过程没有你的 类,所以它得到一个运行时异常。

可能发生这种情况的一个区域是AlarmManager。使用的代码 带有AlarmManager 的自定义Parcelable 对象可能有效 在旧版本的 Android 上will not work on Android N。

我所知道的最有效的解决方法是手动将Parceable 转换为byte[] 并将其放入Intent 额外,然后根据需要手动将其转换回Parcelable。 This Stack Overflow answer 展示了该技术,this sample project 提供了完整的工作示例。

关键位是Parcelablebyte[]之间的转换:

/***
 Copyright (c) 2016 CommonsWare, LLC
 Licensed under the Apache License, Version 2.0 (the "License"); you may not
 use this file except in compliance with the License. You may obtain a copy
 of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
 by applicable law or agreed to in writing, software distributed under the
 License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
 OF ANY KIND, either express or implied. See the License for the specific
 language governing permissions and limitations under the License.

 From _The Busy Coder's Guide to Android Development_
 https://commonsware.com/Android
 */

package com.commonsware.android.parcelable.marshall;

import android.os.Parcel;
import android.os.Parcelable;

// inspired by https://***.com/a/18000094/115145

public class Parcelables 
  public static byte[] toByteArray(Parcelable parcelable) 
    Parcel parcel=Parcel.obtain();

    parcelable.writeToParcel(parcel, 0);

    byte[] result=parcel.marshall();

    parcel.recycle();

    return(result);
  

  public static <T> T toParcelable(byte[] bytes,
                                   Parcelable.Creator<T> creator) 
    Parcel parcel=Parcel.obtain();

    parcel.unmarshall(bytes, 0, bytes.length);
    parcel.setDataPosition(0);

    T result=creator.createFromParcel(parcel);

    parcel.recycle();

    return(result);
  

【讨论】:

你拯救了我的一天!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!是Android N系统无法通过AlarmManager传递parcelable数据。 感谢 CommonsWare 告诉我们这个限制。你的回答是一个很大的帮助。通过了解这个限制,我们打算只传递 DB Id。触发警报时,根据收到的 DB Id 执行 SQLite 查询(Using Room)是否安全? @CheokYanCheng:我不确定你所说的“安全”是什么意思。您受到所有标准限制:不要在主应用程序线程上进行 I/O,后台服务只能在 Android 8.0+ 上运行一分钟,等等。 我明白你的意思,我理解自 Android O 以来启动服务的限制。我想知道,如果我让Executors.newSingleThreadExecutor 在 BroadcastReceiver 的 onReceive 中执行 DB 操作(向表中写入几行),是这样做可以吗?我已经测试过,它到目前为止有效。但是,我不确定是否有任何边缘情况。 @CheokYanCheng:“这样做可以吗?” -- 在没有服务或其他东西保持您的进程的情况下,一旦onReceive() 返回,您的进程可以随时终止。这可能是在您的线程完成其工作之前。 goAsync() 会有所帮助,或者将工作委托给服务。【参考方案2】:

按照@David Wasser 在问题下方 cmets 中的建议,我能够像这样解决同样的问题:

    public static void setAlarm(@NonNull Context context, @NonNull Todo todo) 

      ...

      Intent intent = new Intent(context, AlarmReceiver.class);

      Bundle todoBundle = new Bundle();
      todoBundle.putParcelable("KEY_TODO", todo);

      intent.putExtra("KEY_TODO", todoBundle); // i just reuse the same key for convenience

      ...

    

然后在广播接收器中像这样提取捆绑包:

    public void onReceive(Context context, Intent intent) 

        Bundle todoBundle = intent.getBundleExtra("KEY_TODO");

        Todo todo;

        if (todoBundle != null ) 
            todo = todoBundle.getParcelable("KEY_TODO");
        

    

【讨论】:

谢谢休伯特。这个解决方案很棒。我浪费了 1 天时间来解决这个问题。

以上是关于Pendingintent getbroadcast 丢失可打包数据的主要内容,如果未能解决你的问题,请参考以下文章

取消 PendingIntent

PendingIntent

解决PendingIntent传递参数为空的问题

AlarmManager与PendingIntent

Android PendingIntent小结

彻底认识 PendingIntent