1

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

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

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

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

在所有这些序言之后,我真正需要的是针对此类任务的最佳实践方法 - 如何在整个应用程序上存储SharedPreferences和获取数据:DB

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

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

编辑: 这是我的代码片段:

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

当然还有更多的课程——我尽量带上最低要求。

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

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 上下文。这是个例外:

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
4

3 回答 3

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 绝对是方式去,还有应用程序缓存元素,你总是可以创建一个从应用程序扩展的类,在你的清单中声明它并在那里缓存对象,注意这个类只要你的应用程序存在,遵循单例设计模式,你可以依赖该对象在您的应用程序创建后立即实例化并在应用程序被终止时被销毁,这可以被认为是内存中可用的最大范围之一,当然它在重新启动设备后不会持续存在,所以对于这个特定情况下,数据库可能会更好。如果您想了解更多有关其他方法的信息,请参阅此邮政

希望这可以帮助。

问候!

于 2013-12-26T20:32:40.853 回答
1

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

并且,请,请,简化您的代码。例子 :

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

如果您被警报管理器唤醒,您也不需要接收器中的唤醒锁!警报管理器持有唤醒锁!如果您在接收器中做了很多事情,那么您确实需要 WakefulIntentService 。
最后,如果您想要一个单例,请正确并使用 enum。您的实现是错误的 - 一开始就不是线程安全的。

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

于 2013-12-29T15:53:24.940 回答
1

由于许多原因,将活动上下文保持在单例中是一个坏主意,通常它可能会导致您泄漏活动。

在你的单例中拥有一个应用程序上下文根本不是一个坏主意,但是那些编写 Android 框架的人可能不同意我的观点,因为他们建议将每个活动都变成一个迷你应用程序。但是,假设您同意我的观点。

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

在您的单例更改中:

 _context = caller;

 _context = caller.getApplicationContext();

或者你可以使用静态方法

Context.getApplicationContext()

还有第三种方法,允许您扩展Application类。在这里阅读。

于 2013-12-29T16:05:26.597 回答