定期更新我的应用 UI

Periodically Updating my app's UI

我希望我的 Android 应用程序根据 REST 服务的响应定期更新其 UI。我不能在主线程上执行此操作,因为在主线程上访问网络是不允许的/不好的做法。 SO 和 Internet 上的一般智慧是结合使用 BroadcastReceiver 和 AlarmManager。例如,这是建议 。我尝试了两种设计,但都无法实现:

  1. 定义一个 class 扩展 BroadcastReceiver 作为我的 MainActivity 的内部 class。
  2. 将相同的 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 的 BroadcastReceivers 必须是 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 都会有一个接收者将收到通知。