android - 在 Android 应用程序组件之间共享数据的最佳方式

标签 android broadcastreceiver sharedpreferences android-context bootcompleted

我正在开发一个包含许多组件的应用程序。
该应用程序使用 AlarmManager 从服务器进行一些轮询。 还有显示数据的常规 Activity (存储在 SqliteSharedPreferences 上)

一切正常,直到我尝试添加一个功能,在设备完成启动时开始轮询(BOOT_COMPLETED),当我这样做时,我发现我无法访问SharedPreferencesContext 我从扩展 BroadcastReceiver 的类的 onReceive(Context context, Intent intent) 方法中得到>.

另一件事是,我使用 Singleton 来处理所有 SharedPreferencesDB 功能。此 Singleton 包含应用的第一个午餐 Activity (LoginActivity) 的Context。并在整个应用程序和 Polling BroadcastReciver 中使用它。

所以我理解(相信......)当设备完成启动时,我得到不同 Context(不是 LoginActivity 上下文我曾经得到),这就是问题的根源(是吗???)

在所有这些序言之后,我真正需要的是一个最佳实践方法来完成这样的任务 - 如何在 SharedPreferencesDB 上存储和获取数据 在整个应用程序中:

  1. 当用户运行时
  2. 当它通过 AlarmManager 执行后台任务时
  3. 当它通过BOOT_COMPLETED广播自动启动时

不会遇到此 Context 问题。一个例子会很棒。

编辑: 这是我的代码 fragment :

ConnectionManager.java - 此类包含 REST 请求实现并将内容存储到 SharedPreferences:

public class ConnectionManager {

    //There are many more variables here - irrelevant for the example

    private CookieStore _cookieStore;
    private static ConnectionManager _instance;
    private SharedPreferences _sharedPref;
    private Context _context;
    private DataPollingBroadcastReceiver _dataPoller;

    private ConnectionManager(Context caller) {
        _context = caller;
        _sharedPref = PreferenceManager.getDefaultSharedPreferences(_context);
    }

    public static ConnectionManager getInstance(Context caller) {
        if (_instance == null) {
            _instance = new ConnectionManager(caller);
        }
        return _instance;
    }

public void setPollingActive(boolean active) {
    if (active) {
        SharedPreferences.Editor editor = _sharedPref.edit();
        editor.putString("myapp.polling", "true");
        editor.commit();
        startRepeatingTimer();
    } else {
        SharedPreferences.Editor editor = _sharedPref.edit();
        editor.putString("myapp.polling", "false");
        editor.commit();
        cancelRepeatingTimer();
    }
}

private void startRepeatingTimer() {
    if (_dataPoller!= null) {
        _dataPoller.SetAlarm(_context);
    } else {
        Toast.makeText(_context, "_dataPoller object is null",
                Toast.LENGTH_SHORT).show();
    }
}

private void cancelRepeatingTimer() {
    if (_dataPoller!= null) {
        _dataPoller.CancelAlarm(_context);
    } else {
        Toast.makeText(_context, "_dataPoller object is null",
                Toast.LENGTH_SHORT).show();
    }
}

    //There are many more methods here - irrelevant for the example

} 

MainBootListener.java:这个类假设激活轮询机制 - 它不工作,因为 ConnectionManager 上有异常。

public class MainBootListener extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        Toast.makeText(context, "Activated by boot event",
                Toast.LENGTH_LONG).show();
        ConnectionManager cm = ConnectionManager.getInstance(context);
        cm.setPollingActive(true);
    }
}

DataPollingBroadcastReceiver.java : 这个类从服务器轮询数据

public class DataPollingBroadcastReceiver extends BroadcastReceiver {

    private ConnectionManager _mngr;

    @Override
    public void onReceive(Context context, Intent intent) {
        if (_mngr == null) {
            _mngr = ConnectionManager.getInstance(context);
        }
        PowerManager pm = (PowerManager) context
            .getSystemService(Context.POWER_SERVICE);
        PowerManager.WakeLock wl = pm.newWakeLock(
            PowerManager.PARTIAL_WAKE_LOCK, TAG);
        // Acquire the lock
        wl.acquire();
        // You can do the processing here update the widget/remote views.
        Bundle extras = intent.getExtras();
        StringBuilder msgStr = new StringBuilder();
        Format formatter = new SimpleDateFormat("hh:mm:ss");
        msgStr.append(formatter.format(new Date()));
        // /////
        _mngr.updateDataFromServer();
        msgStr.append(" [Updated AccessControlTable]");
        Log.i(TAG, msgStr.toString());
        Toast.makeText(context, msgStr, Toast.LENGTH_SHORT).show();
        // ////
        // Release the lock
        wl.release();
    }

    public void SetAlarm(Context context) {
        if (_mngr == null) {
            _mngr = ConnectionManager.getInstance(context);
        }
        AlarmManager am = (AlarmManager) context
            .getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent(context, DataPollingBroadcastReceiver.class);
        intent.putExtra(ONE_TIME, Boolean.TRUE);
        PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
        am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
            1000 * _mngr.getPollingIntervalInSeconds(), pi);
    }

    public void CancelAlarm(Context context) {
        if (_mngr == null) {
            _mngr = ConnectionManager.getInstance(context);
        }
        Intent intent = new Intent(context, DataPollingBroadcastReceiver.class);
        PendingIntent sender = PendingIntent
            .getBroadcast(context, 0, intent, 0);
        AlarmManager alarmManager = (AlarmManager) context
            .getSystemService(Context.ALARM_SERVICE);
        alarmManager.cancel(sender);
    }
}

当然还有更多的类(class) - 我尽量带上所需的最低限度。

编辑 我在 2 处收到的异常:如果应用程序的轮询机制处于 Activity 状态(单例将 LoginActivity 作为上下文)并且我从任务管理器关闭了应用程序,则轮询停止并显示这个异常(exception):

12-29 14:02:03.061: E/AndroidRuntime(9402): FATAL EXCEPTION: main
12-29 14:02:03.061: E/AndroidRuntime(9402): java.lang.RuntimeException: Unable to start receiver my.app.DataPollingBroadcastReceiver : java.lang.NullPointerException
12-29 14:02:03.061: E/AndroidRuntime(9402):     at android.app.ActivityThread.handleReceiver(ActivityThread.java:2277)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at android.app.ActivityThread.access$1500(ActivityThread.java:140)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at android.os.Handler.dispatchMessage(Handler.java:99)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at android.os.Looper.loop(Looper.java:137)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at android.app.ActivityThread.main(ActivityThread.java:4898)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at java.lang.reflect.Method.invokeNative(Native Method)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at java.lang.reflect.Method.invoke(Method.java:511)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1006)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:773)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at dalvik.system.NativeStart.main(Native Method)
12-29 14:02:03.061: E/AndroidRuntime(9402): Caused by: java.lang.NullPointerException
12-29 14:02:03.061: E/AndroidRuntime(9402):     at my.app.ConnectionManager.<init>(ConnectionManager.java:172)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at my.app.ConnectionManager.getInstance(ConnectionManager.java:196)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at my.app.DataPollingBroadcastReceiver .onReceive(DataPollingBroadcastReceiver .java:27)
12-29 14:02:03.061: E/AndroidRuntime(9402):     at android.app.ActivityThread.handleReceiver(ActivityThread.java:2270)
12-29 14:02:03.061: E/AndroidRuntime(9402):     ... 10 more

当应用程序未运行并且我从 adb 发送 BOOT_COMPLETED 时出现第二个异常,而不是尝试初始化单例。与 BroadcastReciver 上下文。这是个异常(exception):

12-26 11:54:58.556: E/AndroidRuntime(12373): FATAL EXCEPTION: main
12-26 11:54:58.556: E/AndroidRuntime(12373): java.lang.RuntimeException: Unable to start receiver my.app.MainBootListener : java.lang.NullPointerException
12-26 11:54:58.556: E/AndroidRuntime(12373):    at android.app.ActivityThread.handleReceiver(ActivityThread.java:2277)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at android.app.ActivityThread.access$1500(ActivityThread.java:140)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1303)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at android.os.Handler.dispatchMessage(Handler.java:99)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at android.os.Looper.loop(Looper.java:137)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at android.app.ActivityThread.main(ActivityThread.java:4898)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at java.lang.reflect.Method.invokeNative(Native Method)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at java.lang.reflect.Method.invoke(Method.java:511)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1006)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:773)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at dalvik.system.NativeStart.main(Native Method)
12-26 11:54:58.556: E/AndroidRuntime(12373): Caused by: java.lang.NullPointerException
12-26 11:54:58.556: E/AndroidRuntime(12373):    at my.app.ConnectionManager.<init>(ConnectionManager.java:172)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at my.app.ConnectionManager.getInstance(ConnectionManager.java:196)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at my.app.MainBootListener .onReceive(MainBootListener .java:14)
12-26 11:54:58.556: E/AndroidRuntime(12373):    at android.app.ActivityThread.handleReceiver(ActivityThread.java:2270)
12-26 11:54:58.556: E/AndroidRuntime(12373):    ... 10 more

最佳答案

您的问题应该与您在 BR 中收到的上下文是 ReceiverRestrictedContext 有关。 - 我打赌你会遇到异常 ReceiverCallNotAllowedException。如果你有一个异常,你总是必须发布一个异常 - 所以请发布它以便我们能够准确了解发生了什么!话虽这么说 - 你在接收器中做的太多了!

而且,拜托,拜托,拜托,简化你的代码。示例:

public void setPollingActive(boolean active) {
    _sharedPref.edit().putBoolean("myapp.polling", active).commit();
    (active) ? startRepeatingTimer() : cancelRepeatingTimer();
}

此外,如果您被闹钟管理器唤醒,则不需要接收器中的唤醒锁!警报管理器持有唤醒锁!如果你在你的接收器中做了很多事情,你确实需要一个 WakefulIntentService。
最后,如果你想要一个单例,那就做对吧 use an enum .您的实现是错误的 - 它不是线程安全的。

编辑:根据发布的异常,问题与上下文无关——这是一个 NPE,因为静态字段在某个时候变为空

关于android - 在 Android 应用程序组件之间共享数据的最佳方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20790720/

相关文章:

Android AWS CognitoCachingCredentialsProvider 使 API 17 中的应用程序崩溃 - JellyBean

Android - 获取对 Manifest 中定义的 BroadcastReceiver 的引用

android - 同时接收到 MEDIA_BUTTON 按钮事件 ACTION_DOWN 和 ACTION_UP

android - 尝试保存首选项(根据官方 android 引用)会导致错误

java - 我如何使用带有进度条的改造库?

android - 在布局中多次使用相同的 View

android - 如何在 Android 中获取前缀字符串值?

android - 无法使用 PendingIntent.FLAG_IMMUTABLE 获取所选第三方应用程序的包名称

java - 如何存储值并从共享首选项中检索它们

java - Android 共享首选项不起作用