定期更新我的应用 UI
Periodically Updating my app's UI
我希望我的 Android 应用程序根据 REST 服务的响应定期更新其 UI。我不能在主线程上执行此操作,因为在主线程上访问网络是不允许的/不好的做法。 SO 和 Internet 上的一般智慧是结合使用 BroadcastReceiver 和 AlarmManager。例如,这是建议 。我尝试了两种设计,但都无法实现:
- 定义一个 class 扩展 BroadcastReceiver 作为我的 MainActivity 的内部 class。
- 将相同的 class 定义为外部 class。
使用 (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
对于 (2),问题是我不知道如何访问我想在 MainActivity 中修改的视图。
这是 (1) 的示例实现:
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);
}
}
我也把这个添加到我的AndroidManifest.xml,考虑到我得到一个例外,它似乎注册成功了:
<receiver android:name="com.dbs.alarm.MainActivity$AlarmReceiver">
</receiver>
以最简单的方式,您必须 运行 在后台线程中像 AsyncTask
doInBackground
REST 请求并将请求结果发送到 UI-thread onPostExecute
。你可以通过不同的方式做到这一点,但对我来说最方便的是使用 Bus
'es,例如 Otto.
由于您需要直接访问您的文本视图,因此为您的接收器选择内部 class 是正确的做法。但是,声明为内部 classes 的 BroadcastReceiver
s 必须是 static
才能在清单中声明,这违背了在第一个中使其成为内部 class 的目的放置(至少在您的场景中)。因此,我建议 registering/unregistering 在 onStart()
和 onStop()
生命周期方法中动态地 BroadcastReceiver
:
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
);
}
}
您还会注意到,在为警报创建 Intent 时,我没有传入接收器 class,而是将其更改为使用可用于定义的字符串 ("YOUR_ACTION_NAME"
)您的 BroadcastReceiver
将用来收听广播的意图过滤器。
至于 运行 在 UI 线程上更新更新的问题,您总是可以从一个视图调用 post()
到 运行 在 UI 线程,或使用 activity 的 runOnUiThread
,就像您在 BroadcastReceiver
中尝试做的那样。我让 "update" 方法属于 activity 而不是广播接收器,因为在我看来这样更有意义。
编辑:在写这个答案时,我更专注于解决您在实施解决方案时遇到的问题,而不是实际尝试帮助解决执行周期性的更大问题UI 更新。 @Ashikee AbHi 建议为此使用 Handler
而不是警报,这绝对是您应该考虑的事情。当您必须在不同的进程中通知某些内容时,alarm/broadcast 接收器非常有用,但如果所有内容都包含在单个 activity 中,那么使用 Handler.postDelayed
.[=23 会更简洁=]
您可以使用 Handler 并递归调用它来定期执行以下操作 operations.Check
Repeat a task with a time delay?
如果更新视图,您需要使用 new Handler(Looper.getMainLooper())
对其进行初始化
好的,根据您的要求,我会说您正在获取一些数据,并且您想让您的应用知道已获取新数据,以便应用可以进行必要的 UI 更改。我建议使用 Local Broadcast Manager ,它允许您在应用程序内发送广播。
实现很容易找到,您可以查看 this。
基本上这个想法是您从 REST API 获取数据,向您的应用程序广播数据已被获取,并且每个需要响应此事件的 activity 都会有一个接收者将收到通知。
我希望我的 Android 应用程序根据 REST 服务的响应定期更新其 UI。我不能在主线程上执行此操作,因为在主线程上访问网络是不允许的/不好的做法。 SO 和 Internet 上的一般智慧是结合使用 BroadcastReceiver 和 AlarmManager。例如,这是建议
- 定义一个 class 扩展 BroadcastReceiver 作为我的 MainActivity 的内部 class。
- 将相同的 class 定义为外部 class。
使用 (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
对于 (2),问题是我不知道如何访问我想在 MainActivity 中修改的视图。
这是 (1) 的示例实现:
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);
}
}
我也把这个添加到我的AndroidManifest.xml,考虑到我得到一个例外,它似乎注册成功了:
<receiver android:name="com.dbs.alarm.MainActivity$AlarmReceiver">
</receiver>
以最简单的方式,您必须 运行 在后台线程中像 AsyncTask
doInBackground
REST 请求并将请求结果发送到 UI-thread onPostExecute
。你可以通过不同的方式做到这一点,但对我来说最方便的是使用 Bus
'es,例如 Otto.
由于您需要直接访问您的文本视图,因此为您的接收器选择内部 class 是正确的做法。但是,声明为内部 classes 的 BroadcastReceiver
s 必须是 static
才能在清单中声明,这违背了在第一个中使其成为内部 class 的目的放置(至少在您的场景中)。因此,我建议 registering/unregistering 在 onStart()
和 onStop()
生命周期方法中动态地 BroadcastReceiver
:
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
);
}
}
您还会注意到,在为警报创建 Intent 时,我没有传入接收器 class,而是将其更改为使用可用于定义的字符串 ("YOUR_ACTION_NAME"
)您的 BroadcastReceiver
将用来收听广播的意图过滤器。
至于 运行 在 UI 线程上更新更新的问题,您总是可以从一个视图调用 post()
到 运行 在 UI 线程,或使用 activity 的 runOnUiThread
,就像您在 BroadcastReceiver
中尝试做的那样。我让 "update" 方法属于 activity 而不是广播接收器,因为在我看来这样更有意义。
编辑:在写这个答案时,我更专注于解决您在实施解决方案时遇到的问题,而不是实际尝试帮助解决执行周期性的更大问题UI 更新。 @Ashikee AbHi 建议为此使用 Handler
而不是警报,这绝对是您应该考虑的事情。当您必须在不同的进程中通知某些内容时,alarm/broadcast 接收器非常有用,但如果所有内容都包含在单个 activity 中,那么使用 Handler.postDelayed
.[=23 会更简洁=]
您可以使用 Handler 并递归调用它来定期执行以下操作 operations.Check Repeat a task with a time delay?
如果更新视图,您需要使用 new Handler(Looper.getMainLooper())
对其进行初始化好的,根据您的要求,我会说您正在获取一些数据,并且您想让您的应用知道已获取新数据,以便应用可以进行必要的 UI 更改。我建议使用 Local Broadcast Manager ,它允许您在应用程序内发送广播。
实现很容易找到,您可以查看 this。 基本上这个想法是您从 REST API 获取数据,向您的应用程序广播数据已被获取,并且每个需要响应此事件的 activity 都会有一个接收者将收到通知。