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

时间:2013-12-26 20:14:37

标签: android broadcastreceiver sharedpreferences android-context bootcompleted

我正在开发一个由许多组件组成的应用程序 该应用正在使用AlarmManager从服务器进行一些轮询。 还有定期活动显示数据(存储在SqliteSharedPreferences

一切都很好,直到我尝试添加一个功能,当设备完成启动时启动轮询(BOOT_COMPLETED),当我这样做时,我发现我无法访问{{1使用SharedPreferences我从扩展Context的类的onReceive(Context context, Intent intent)方法获得。

另一件事是我使用Singleton来处理所有BroadcastReceiverSharedPreferences功能。这个Singleton持有应用程序DB)的第一个lunched活动的Context。并在整个应用和投票LoginActivity上使用它。

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

在所有这个序言之后我真正需要的是一个 BEST-PRACTICE 方法,用于完成这样的任务 - 如何在LoginActivitySharedPreferences上存储和获取数据应用程序:

  1. 当用户运行它时
  2. 通过DB
  3. 执行背景任务时
  4. 当它通过AlarmManager广播
  5. 自动启动时

    没有遇到这个BOOT_COMPLETED问题。一个例子很棒。

    修改 这是我的代码片段:

    Context - 此类保存REST请求实现并将内容存储到SharedPreferences:

    ConnectionManager.java

    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 } :此类假设激活轮询机制 - 由于ConnectionManager上存在异常,因此无法正常工作。

    MainBootListener.java

    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

    当然还有更多的课程 - 我试图带来最低限度的要求。

    编辑我收到的异常2:如果轮询机制在应用程序中处于活动状态(单例将LoginActivity保存为Context)并且我从任务管理器关闭了应用程序,则轮询停止并显示这个例外:

    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);
        }
    }
    

    当应用程序未运行并且我从adb发送了BOOT_COMPLETED时,secont异常被执行,而单步尝试是init。使用BroadcastReciver Context。这是例外:

    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
    

3 个答案:

答案 0 :(得分:1)

首先,您需要知道“上下文”实际上是应用程序上下文,并且用于复合该应用程序的所有不同构建块。 如果您无法使用该上下文访问共享首选项,那可能是因为您正在使用“活动首选项”,这是一个狭窄的范围首选项,通常按如下方式使用:

SharedPreferences preferences = getPreferences(MODE_PRIVATE);
int storedPreference = preferences.getInt("storedInt", 0);

为了在整个应用程序上下文中共享首选项,您需要使用以下首选项:

SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);

在这种情况下,只要您从应用程序中的任何组件获得任何上下文,您就可以访问键/值对元素列表,以便在此共享首选项中读取/写入所有内容要这样做:

//Write
Editor editor = preferences.edit();
editor.putString("yourkey", "value");
editor.commit();//Do not forget to commit..

//Read
String value = preferences.getString(key, defValue);

现在,回到最重要的方式是在应用程序组件之间共享数据,这一切都取决于,有不同的机制,SharedPreference可能对于简单对象可能是原始的,但是对于大量数据,DB是肯定是要走的路,而且还有Application Cached元素,你总是可以创建一个从Application扩展的类,在你的清单中声明它并在那里缓存对象,注意这个类只要你的应用程序存在,就遵循单例设计模式,您可以依赖该对象在应用程序创建后立即实例化,并在应用程序被杀死时被销毁,这可以被认为是内存中可用的最大范围之一,当然它在重启设备后不会持续存在,所以对于这种特定情况,数据库可能会更好。如果您想了解更多其他方法,请参阅此post

希望这有助于。

问候!

答案 1 :(得分:1)

你的问题应该与你在无线电通信局收到的上下文是ReceiverRestrictedContext这一事实有关 - 我打赌你会得到例外ReceiverCallNotAllowedException。如果你有一个例外,你总是必须发布一个例外 - 所以请发布它以便我们能够理解究竟发生了什么!话虽如此 - 你在接收器中做得太多了!

而且,请,,简化您的代码。示例:

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

如果您被警报管理员唤醒,您不需要接收器中的唤醒锁!警报管理器持有唤醒锁!如果你在接收器中做了很多东西,你确实需要一个WakefulIntentService 最后,如果你想要一个单身人士做对了use an enum。你的实现是错误的 - 开始时它不是线程安全的。

编辑:根据发布的异常,问题与上下文无关 - 由于静态字段在某些时候变为空,它是NPE

答案 2 :(得分:1)

在单身人士中保持活动上下文是一个坏主意,原因很多,通常它可能导致你泄漏活动。

在你的单身人士中有一个应用程序上下文根本不是一个坏主意,但是那些编写Android框架的人可能不同意我,因为他们建议将每个活动变成一个迷你应用程序。但是,让我们说你赞同我的意见。

你有两种方法可以做到这一点:

在你的单身人士改变中:

 _context = caller;

 _context = caller.getApplicationContext();

或者您可以使用static method

Context.getApplicationContext()

还有第三种方法,允许你扩展Application类。阅读它here