我正在开发一个由许多组件组成的应用程序
该应用正在使用AlarmManager
从服务器进行一些轮询。
还有定期活动显示数据(存储在Sqlite
和SharedPreferences
)
一切都很好,直到我尝试添加一个功能,当设备完成启动时启动轮询(BOOT_COMPLETED
),当我这样做时,我发现我无法访问{{1使用SharedPreferences
我从扩展Context
的类的onReceive(Context context, Intent intent)
方法获得。
另一件事是我使用Singleton来处理所有BroadcastReceiver
和SharedPreferences
功能。这个Singleton持有应用程序(DB
)的第一个lunched活动的Context
。并在整个应用和投票LoginActivity
上使用它。
所以我理解(相信......)当设备完成启动时,我得到不同 BroadcastReciver
(不是我曾经获得的Context
上下文)是问题的根源(是???)
在所有这个序言之后我真正需要的是一个 BEST-PRACTICE 方法,用于完成这样的任务 - 如何在LoginActivity
和SharedPreferences
上存储和获取数据应用程序:
DB
AlarmManager
广播没有遇到这个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
答案 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。