我希望我的Android应用根据REST服务的响应定期更新其UI。我无法在主线程上执行此操作,因为在主线程上访问网络不允许/不良做法。 SO和互联网的一般智慧是使用BroadcastReceiver和AlarmManager的组合。例如,这是建议here。我尝试了两种设计,我都无法工作:
使用(1)我得到这个运行时错误:
<div class="wrapper">
<label class="container">
<input type="checkbox" checked="checked">
<span class="checkmark"></span>
</label>
<label class="container">
<input type="checkbox">
<span class="checkmark"></span>
</label>
<label class="container">
<input type="checkbox">
<span class="checkmark"></span>
</label>
<label class="container">
<input type="checkbox">
<span class="checkmark"></span>
</label>
<label class="container">
<input type="checkbox">
<span class="checkmark"></span>
</label>
</div>
问题是我无法弄清楚如何在MainActivity中访问我想要修改的视图。
以下是(1)的示例实现:
java.lang.RuntimeException: Unable to instantiate receiver com.dbs.alarm.MainActivity$AlarmReceiver: java.lang.InstantiationException: java.lang.Class<com.dbs.alarm.MainActivity$AlarmReceiver> has no zero argument constructor
我还将此添加到我的AndroidManifest.xml中,考虑到我得到一个异常,它似乎已成功注册:
package com.dbs.alarm;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// I tried making this its own class, but then findViewById isn't accessible.
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// I tried wrapping this in runOnUiThread() but it made no difference.
TextView myTextView = findViewById(R.id.my_text);
CharSequence myCharSequence = "Set from UpdateReceiver.onReceive()";
myTextView.setText(myCharSequence);
}
}
private void setRecurringAlarm(Context context) {
Intent intent = new Intent(context, AlarmReceiver.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
context, 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
alarmManager.setInexactRepeating(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + 1000,
1000, // Set so short for demo purposes only.
pendingIntent
);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setRecurringAlarm(this);
}
}
答案 0 :(得分:3)
由于您需要直接访问文本视图,因此选择接收器的内部类是正确的做法。但是,声明为内部类的BroadcastReceiver
必须在清单中声明static
,这样才能使其成为内部类的目的(至少在您的场景中) 。因此,我建议您在BroadcastReceiver
和onStart()
生命周期方法中动态注册/取消注册onStop()
:
public class MainActivity extends AppCompatActivity {
private BroadcastReceiver alarmReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// the "MainActivity.this" tells it to use the method from the parent class
MainActivity.this.updateMyTextView();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setRecurringAlarm(this);
}
@Override
protected void onStart() {
super.onStart();
final IntentFilter filter = new IntentFilter();
filter.addAction("YOUR_ACTION_NAME");
registerReceiver(alarmReceiver, filter);
}
@Override
protected void onStop() {
unregisterReceiver(alarmReceiver);
super.onStop();
}
private void updateMyTextView(){
final TextView myTextView = findViewById(R.id.my_text);
if (myTextView != null){
CharSequence myCharSequence = "Set from UpdateReceiver.onReceive()";
myTextView.post(()-> myTextView.setText(myCharSequence));
}
}
private void setRecurringAlarm(Context context) {
Intent intent = new Intent("YOUR_ACTION_NAME");
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
context, 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
alarmManager.setInexactRepeating(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() + 1000,
1000, // Set so short for demo purposes only.
pendingIntent
);
}
}
您还会注意到,在创建警报意图时,不是传入接收器类,而是将其更改为使用可用于定义意图过滤器的字符串("YOUR_ACTION_NAME"
){{{ 1}}将用于收听广播。
至于在UI线程上运行更新的问题,您始终可以从视图中调用BroadcastReceiver
以在UI线程上运行某些内容,或者像您尝试的那样使用活动post()
在runOnUiThread
内。我使“更新”方法属于活动而不是广播接收器,因为它似乎在我的头脑中更有意义。
编辑:在撰写此答案时,我更专注于解决您在实施解决方案时遇到的问题,而不是实际尝试帮助解决执行定期UI更新的更大问题。 @Ashikee AbHi建议使用BroadcastReceiver
而不是警报,这绝对是你应该考虑的事情。当您必须在不同的过程中通知某些内容时,警报/广播接收器非常棒,但如果所有内容都包含在单个活动中,则使用Handler
会更加清晰。
答案 1 :(得分:1)
您可以使用Handler并递归调用它来执行定期操作。请检查以下内容 Repeat a task with a time delay?
如果要更新视图,则需要使用新的Handler(Looper.getMainLooper())初始化它
答案 2 :(得分:0)
以最简单的方式,您必须在后台线程中运行REST请求,如AsyncTask
doInBackground
,并将请求的结果发送到onPostExecute
中的UI线程。你可以通过不同的方式做到这一点,但对我来说最方便的是使用Bus
&#39; es,例如Otto。
答案 3 :(得分:0)
好的,看看你的要求,我会说你正在提取一些数据,并且你想让你的应用知道已经提取了新数据,这样应用就可以进行必要的UI更改。我建议使用Local Broadcast Manager,它允许您在应用中发送广播。
可以很容易地找到实现,您可以检查this。 基本上,您的想法是从REST API获取数据,向您的应用广播已获取数据,并且需要响应此事件的每个活动都将有一个接收器,将会收到通知。