【问题标题】:How to update UI in a BroadcastReceiver如何在 BroadcastReceiver 中更新 UI
【发布时间】:2013-01-16 14:14:31
【问题描述】:

我创建了一个应用程序,在该应用程序中我在主类 (Main Activity) 中注册了一个广播接收器,每当我在 BroadcastReceiver 中收到某些内容时,我都想更新 UI,例如我想显示一个警告框或设置我的MainActivity 的一些文本视图。我收到接收器中的所有值但无法设置它们,有人可以帮助我,以便我可以在BroadcastReceiver 中更新我的 UI。

我的 BroadcastReceiver 类是 MainActivity 的内部类,如下所示:-

public class MainActivity extends Activity {

   ..........

public static class NissanTabBroadcast extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            SharedPreferences shrd = context.getSharedPreferences("NissanGallery", context.MODE_WORLD_READABLE);
            type = shrd.getString("type", "null");
            badges = shrd.getString("badge_count", "null");

            //badge_tips_text.setText(badges);
            /*Editor edit =  shrd.edit();
            edit.remove("type");*/

            Toast.makeText(context, "" + type + "\n" + badge_tips_text.getText().toString(), Toast.LENGTH_LONG).show();
        }
    }
}

任何帮助将不胜感激

谢谢

【问题讨论】:

标签: android broadcastreceiver


【解决方案1】:

使用runOnUiThread:

 MainActivity.this.runOnUiThread(new Runnable() {

        @Override
        public void run() {
            // show alert

        }
    });

【讨论】:

  • 正确,所有 UI 更新都必须在主线程上执行。
  • 它给了我一个错误“无法从类型 Activity 对非静态方法 runOnUiThread(Runnable) 进行静态引用”
  • 我的内部广播接收器类是静态的
  • 它给出了一个新错误,“在范围内没有可以访问 MainActivity 类型的封闭实例”
  • 检查了,仍然不允许我添加警报框,甚至不允许在广播接收器中设置文本视图的值。
【解决方案2】:

我为此使用完全不同的方法。通过将处理程序实例放入意图中,将其传递给广播接收器。比在广播接收器中使用该处理程序向您的活动类发送消息,该活动类在您的 onHandleMessage 方法中更新 UI您的自定义处理程序类。

create handler class like

   public class MyHandler extends Handler{

onHandlerMessage(Message msg){//do whatever you want here after broadcast get fired}
}

现在使用Myhandler handler=new MyHandler(); 在活动的全局范围内创建此处理程序对象。

现在通过putExtra将此处理程序放入您的意图中,然后通过sendBraodcast(intent)发送此意图。

在广播接收器类中,通过 getExtras 获取这个处理程序对象并像下面这样使用它 在onReceive() 方法中

 handler.sendEmptyMessage();

【讨论】:

  • 我没听懂你,你能分享一些代码sn-ps,以便我能理解你想说的话。
【解决方案3】:

我建议你使用 Handler。

  1. 在Activity中初始化一个Handler,例如:handler = new Handler()
  2. 在构造函数中为 BroadcastReceiver 提供处理程序,就像我在上面为 NissanTabBroadcast 所做的那样
  3. onReceive()方法中使用你的Handler实例的post()方法来提交更新UI的Runnable

这是我能想象到的最干净的解决方案。

public class MainActivity extends Activity {

    private MyReceiver receiver;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        receiver = new MyReceiver(new Handler()); // Create the receiver
        registerReceiver(receiver, new IntentFilter("some.action")); // Register receiver

        sendBroadcast(new Intent("some.action")); // Send an example Intent
    }

    public static class MyReceiver extends BroadcastReceiver {

        private final Handler handler; // Handler used to execute code on the UI thread

        public MyReceiver(Handler handler) {
            this.handler = handler;
        }

        @Override
        public void onReceive(final Context context, Intent intent) {
            // Post the UI updating code to our Handler
            handler.post(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(context, "Toast from broadcast receiver", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }
}

【讨论】:

  • 我试过了,但第一次它给了我一个错误“没有空的构造函数存在”,然后我做了一个空的构造函数,但现在它给了我一个空指针异常,我正在使用handle.post 方法.....我现在该怎么办?
  • 更新了帖子中的代码。如果您获得 NullPointerExc,堆栈跟踪将显示在 LogCat 中。尝试分析它以了解在执行期间哪个变量或表达式是null。提示您的问题:由于您尚未在构造函数中设置handler 字段,因此您尝试使用它时为null
  • 你试过这个解决方案了吗?我从一个工作应用程序中复制了代码,所以它没有错误。您能解释一下为什么取消接受吗?
  • 这太过分了,因为BroadcastReceivers 已经在主线程上运行,这与“GUI 线程”相同。因此,您基本上是在使用 Handler 与同一个线程交谈。直接从活动中的本地广播接收器更新 GUI 绝对没问题。更多信息:stackoverflow.com/q/5674518/813951
  • @MisterSmith 来自文档:unless you explicitly asked for it to be scheduled on a different thread。我想说,这是一种非常强大的方法,并且可以非常轻松地进行测试。
【解决方案4】:

我使用 Intent 让 Broadcast Receiver 知道主 Activity 线程的 Handler 实例,并使用 Message 将消息传递给 Main Activity

我已经使用这种机制来检查广播接收器是否已经注册。有时,当您动态注册广播接收器并且不想重复两次或在广播接收器正在运行时向用户展示时需要它。

主要活动:

public class Example extends Activity {

private BroadCastReceiver_example br_exemple;

final Messenger mMessenger = new Messenger(new IncomingHandler());

private boolean running = false;

static class IncomingHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        running = false;    
        switch (msg.what) {
        case BroadCastReceiver_example.ALIVE:
    running = true;
            ....
            break;
        default:

            super.handleMessage(msg);
        }

    }
    }

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    IntentFilter filter = new IntentFilter();
        filter.addAction("pl.example.CHECK_RECEIVER");

        br_exemple = new BroadCastReceiver_example();
        getApplicationContext().registerReceiver(br_exemple , filter); //register the Receiver
    }

// call it whenever you want to check if Broadcast Receiver is running.

private void check_broadcastRunning() {    
        /**
        * checkBroadcastHandler - the handler will start runnable which will check if Broadcast Receiver is running
        */
        Handler checkBroadcastHandler = null;

        /**
        * checkBroadcastRunnable - the runnable which will check if Broadcast Receiver is running
        */
        Runnable checkBroadcastRunnable = null;

        Intent checkBroadCastState = new Intent();
        checkBroadCastState .setAction("pl.example.CHECK_RECEIVER");
        checkBroadCastState .putExtra("mainView", mMessenger);
        this.sendBroadcast(checkBroadCastState );
        Log.d(TAG,"check if broadcast is running");

        checkBroadcastHandler = new Handler();
        checkBroadcastRunnable = new Runnable(){    

            public void run(){
                if (running == true) {
                    Log.d(TAG,"broadcast is running");
                }
                else {
                    Log.d(TAG,"broadcast is not running");
                }
            }
        };
        checkBroadcastHandler.postDelayed(checkBroadcastRunnable,100);
        return;
    }

.............
}

广播接收器:

public class BroadCastReceiver_example extends BroadcastReceiver {


public static final int ALIVE = 1;
@Override
public void onReceive(Context context, Intent intent) {
    // TODO Auto-generated method stub
    Bundle extras = intent.getExtras();
    String action = intent.getAction();
    if (action.equals("pl.example.CHECK_RECEIVER")) {
        Log.d(TAG, "Received broadcast live checker");
        Messenger mainAppMessanger = (Messenger) extras.get("mainView");
        try {
            mainAppMessanger.send(Message.obtain(null, ALIVE));
        } catch (RemoteException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    .........

}

}

【讨论】:

    【解决方案5】:

    我使用了 Handler 及其 post() 方法。而不是 runOnUiThread()。无需将 Context 转换为 Activity。它是 runOnUiThread() 的替代方法

    Handler handler = new Handler();
    Runnable runnable = new Runnable() {
        private long startTime = System.currentTimeMillis();
        public void run() {
            while (gameState == GameState.Playing) {  
                try {
                    Thread.sleep(1000);
                }    
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
                handler.post(new Runnable(){
                    public void run() {
                       tvTime.setText("" + ((System.currentTimeMillis() - this.startTime) / 1000));
                }
            });
            }
        }
    };
    new Thread(runnable).start();
    

    【讨论】:

      【解决方案6】:

      在我的情况下,我想用传入的短信更新我的活动中的文本视图,我所做的是使用 putextra 添加所需的信息,然后从广播接收器开始活动:

      Intent intentone = new Intent(context.getApplicationContext(), enroll.class);
      intentone.putExtra("pinbody",message);
      intentone.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
      context.startActivity(intentone);    
      

      然后在接收端,即在注册活动中,我使用以下内容提取了该信息并更新了我的文本视图:

      Bundle extras = getIntent().getExtras();
      String message = extras.getString("pinbody");
      

      不确定它是否有帮助,但肯定会引导你找到你想要的。

      干杯!

      【讨论】:

        【解决方案7】:

        我的情况是更新 MainActivity 托管的片段之一中的文本字段。我找到的最简单的解决方案是:--

        在我的 MainActivity 类中检索到 MainActivtiy 的运行实例。这是我的 MAinActivity

        private static MainActivity mainActivityRunningInstance;
            public static MainActivity  getInstace(){
                return mainActivityRunningInstance;
            }
          @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                mainActivityRunningInstance =this;
        ----------
        }
        

        现在在广播接收者的 onRecieve 方法中,获取该实例并调用更新方法

         @Override
            public void onReceive(Context context, Intent intent) {
                if (intent.getAction().matches(Intents.PROVIDER_CHANGED_ACTION)) {
                        String textValue="the text field value";
        // the null check is for, if the activity is not running or in paused state, in my case the update was required onlyif the main activity is active or paused
                    if(MainActivity.getInstace()!=null)
                        MainActivity.getInstace().updateUI(textValue);
        }
        

        现在对于我们需要在UIThread中运行更新的部分,MainActivity的updateUI方法会调用fragments更新方法。

          public void updateUI(final String str) {
                MainActivity.this.runOnUiThread(new Runnable() {
                    public void run() {
             //use findFragmentById for fragments defined in XML ((SimpleFragment)getSupportFragmentManager().findFragmentByTag(fragmentTag)).updateUI(str);
                    }
                });
            }
        

        最后一步是更新片段的文本字段

        public void updateUI(String str){
                tv_content.setText(str);
            }
        

        和宾果游戏,它完成了。我参考了this 链接来解决我的问题。希望对其他人有所帮助。

        【讨论】:

        • 很棒的概念性答案
        • 不错的答案,但将 android 上下文放置在静态字段中可能会导致内存泄漏
        【解决方案8】:

        您应该将广播接收器作为内部类,这样它就可以访问所有字段来更新 UI。

        查看 nick butcher 的这款格子图案应用 Plaid- Nick Butcher(Github)

        【讨论】:

          【解决方案9】:

          使用getter和setter可以轻松解决这个问题。

          只需如下声明一个类。

          public class DetailsGetters {
              private View view;
              public View getview() {
                  return view;
              }
          
              public void setview(View view) {
                  this.view = view;
              }
          }
          

          在您的主要活动或片段活动上,创建一个 DetailsGetters 类的实例。

          DetailsGetters newdetailsinstance=new DetailsGetters();
          

          返回前查看电话newdetailsinstance.setview(view);

          在广播接收器上获得newdetailsinstance.getview(view);的视图

          以下可用于从片段中获取视图并使用 setText 等更新 Ui..

          要在主要活动上更新 UI,请传递 Textview 并更改 DeailsGetters 类并为 Textview 创建 getter 和 setter。希望这对某人有所帮助。

          【讨论】:

            【解决方案10】:

            如何在任何 Activity 中重用 BroadcastReceiver?


            考虑以下情况,

            • 你想在不同的地方使用相同的BroadcastReceiver 活动
            • 您已将BroadcastReceiver 写在单独的文件中,并希望访问Activity 方法、UI 元素等。

            在这些情况下,使用接口是个好主意。我将更详细地解释优势和用例。但首先,让我们看看它是如何完成的。

            1) 创建界面

            public interface MyBroadcastListener{
            
                public void doSomething(String value);
            
            }
            

            2) 在你的 BroadcastReceiver 中初始化监听器

            public class MyReceiver extends BroadcastReceiver {
            
                private MyBroadcastListener listener;
            
                public MyReceiver(MyBroadcastListener listener){
                    this.listener = listener;
                }
            
                @Override
                public void onReceive(Context context, Intent intent) {
            
                    listener.doSomething("Some Result");
            
                }
            }
            

            3) 在你的Activity中实现接口并覆盖方法

             public MainActivity extends AppCompatActivity implements MyBroadcastListener{
            
                  private BroadcastReceiver receiver;
            
                  @Override
                  public void onCreate(Bundle sis){
            
                       // Related code 
            
                       receiver = new MyReceiver(this);   // This is how you initialise receiver
            
                      // Register receiver etc
                  }
            
            
                  public void updateUI(String msg) {
                       TextView textView = (TextView) findViewById(R.id.textView);
                       textView .setText(msg);
                  }
            
                  @Override
                  public void doSomething(String result){
                       updateUI(result);        // Calling method from Interface
                  }
            
             }
            

            优势和用例?

            使用接口方法使 BroadcastReceiver 独立于任何 活动。假设将来你想使用这个 BroadCastReceiver 与另一个从 BroadcastReceiver 获取结果的活动 并启动一个DetailActivity。

            请注意,这是一个完全 不同的任务,但您可以使用 相同的 BroadcastReceiver,甚至不需要 BroadcastReceiver 中的任何代码更改。

            怎么做?
            简单的!在Activity 中实现接口并覆盖该方法。就是这样!

            public ListActivity extends AppCompatActivity implements MyBroadcastListener{
            
                  // Your Activity code goes here
            
                 public void startDetailActivity(String title) {
                     Intent i = new Intent(ListActivity,this, DetailActivity.class);
                     i.putExtra("Title", title);
                     startActivity(i);
                 }
            
                  @Override
                  public void doSomething(String result){
                       startDetailActivity(String title);    // Calling method from Interface
                  }
            
            }
            

            【讨论】:

            • 您是否使用空意图过滤器注册接收器?
            • @OmarBoshra 好吧,这在您的活动代码中。所以你可以在那里写。但是,如果您想添加另一个抽象级别。你可以为它写一个抽象方法。看到这个stackoverflow.com/questions/34331284/…
            • 收到错误为“无法启动接收器 MyBroadcastReceiver: java.lang.NullPointerException:”
            【解决方案11】:

            通过广播接收器获取当前日期时间和秒并在 UI 中更新

                package com.keshav.broadcasttest;
            
                import java.util.Date;
            
                import android.app.Service;
                import android.content.Intent;
                import android.os.Handler;
                import android.os.IBinder;
                import android.util.Log;
            
                public class BroadcastService  extends Service {
                   private static final String TAG = "BroadcastService";
                   public static final String BROADCAST_ACTION = "com.keshav.broadcasttest.displayevent";
                   private final Handler handler = new Handler();
                   Intent intent;
                   int counter = 0;
            
                   @Override
                   public void onCreate() {
                      super.onCreate();
            
                       intent = new Intent(BROADCAST_ACTION); 
                   }
            
                    @Override
                    public void onStart(Intent intent, int startId) {
                        handler.removeCallbacks(sendUpdatesToUI_Runnable);
                        handler.postDelayed(sendUpdatesToUI_Runnable, 1000); // 1 second
            
                    }
            
                    private Runnable sendUpdatesToUI_Runnable = new Runnable() {
                       public void run() {
                          DisplayLoggingInfo();         
                           handler.postDelayed(this, 10000); // 10 seconds
                       }
                    };    
            
                    private void DisplayLoggingInfo() {
                       Log.d(TAG, "entered DisplayLoggingInfo");
            
                       intent.putExtra("time", new Date().toLocaleString());
                       intent.putExtra("counter", String.valueOf(++counter));
                       sendBroadcast(intent);
                    }
            
                   @Override
                   public IBinder onBind(Intent intent) {
                      return null;
                   }
            
                   @Override
                   public void onDestroy() {     
                        handler.removeCallbacks(sendUpdatesToUI_Runnable);
                      super.onDestroy();
                   }     
                }
                ============================================
            
                package com.keshav.broadcasttest;
            
            import android.app.Activity;
            import android.content.BroadcastReceiver;
            import android.content.Context;
            import android.content.Intent;
            import android.content.IntentFilter;
            import android.os.Bundle;
            import android.util.Log;
            import android.widget.TextView;
            
            public class BroadcastTest extends Activity {
                private static final String TAG = "BroadcastTest";
                private Intent intent;
            
                @Override
                public void onCreate(Bundle savedInstanceState) {
                    super.onCreate(savedInstanceState);
                    setContentView(R.layout.main);
            
                    intent = new Intent(this, BroadcastService.class);
                }
            
                private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
                    @Override
                    public void onReceive(Context context, Intent intent) {
                        updateUI(intent);       
                    }
                };    
            
                @Override
                public void onResume() {
                    super.onResume();       
                    startService(intent);
                    registerReceiver(broadcastReceiver, new IntentFilter(BroadcastService.BROADCAST_ACTION));
                }
            
                @Override
                public void onPause() {
                    super.onPause();
                    unregisterReceiver(broadcastReceiver);
                    stopService(intent);        
                }   
            
                private void updateUI(Intent intent) {
                    String counter = intent.getStringExtra("counter"); 
                    String time = intent.getStringExtra("time");
                    Log.d(TAG, counter);
                    Log.d(TAG, time);
            
                    TextView txtDateTime = (TextView) findViewById(R.id.txtDateTime);   
                    TextView txtCounter = (TextView) findViewById(R.id.txtCounter);
                    txtDateTime.setText(time);
                    txtCounter.setText(counter);
                }
            }
                =============================
            
                <?xml version="1.0" encoding="utf-8"?>
                <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                    android:orientation="vertical"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                    >
                    <TableLayout android:id="@+id/tableGPS"
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="7px"
                        android:stretchColumns="1">
                        <TableRow
                            android:layout_margin="1dip">
                            <TextView android:layout_gravity="right"
                                android:text="Time:"
                                android:layout_marginRight="7px"
                                android:layout_width="130px" />
                            <TextView android:id="@+id/txtDateTime"
                                android:gravity="left"
                                android:layout_span="2" />
                        </TableRow>
                        <TableRow
                            android:layout_margin="1px">
                            <TextView android:layout_gravity="right"
                                android:text="Counter:"
                                android:layout_marginRight="7px"
                                android:layout_width="160px" />
                            <TextView android:id="@+id/txtCounter"
                                android:gravity="left" />
                        </TableRow>
                    </TableLayout>
                </LinearLayout>
            
                ===========================================
            
                <?xml version="1.0" encoding="utf-8"?>
                <manifest xmlns:android="http://schemas.android.com/apk/res/android"
                    package="com.keshav.broadcasttest">
            
                    <application
                        android:allowBackup="true"
                        android:icon="@mipmap/ic_launcher"
                        android:label="@string/app_name"
                        android:roundIcon="@mipmap/ic_launcher_round"
                        android:supportsRtl="true"
                        android:theme="@style/AppTheme">
                        <activity android:name=".BroadcastTest">
                            <intent-filter>
                                <action android:name="android.intent.action.MAIN" />
            
                                <category android:name="android.intent.category.LAUNCHER" />
                            </intent-filter>
                        </activity>
            
                        <service android:name=".BroadcastService" />
                    </application>
            
                </manifest>
            

            【讨论】:

              【解决方案12】:

              BroadcastReceiver的onReceive()方法运行在主线程中,所以你可以在这个方法中修改UI,也因为onReceive()运行在主线程中,避免长时间运行 从这里开始操作。

              来自官方文档:

              因为接收者的 onReceive(Context, Intent) 方法在主线程上运行,它应该快速执行并返回。如果您需要执行长时间运行的工作,请注意生成线程或启动后台服务,因为系统可以在 onReceive() 返回后终止整个进程。有关详细信息,请参阅对进程状态的影响 要执行长时间运行的工作,我们建议:

              在接收器的 onReceive() 方法中调用 goAsync() 并将 BroadcastReceiver.PendingResult 传递给后台线程。这在从 onReceive() 返回后保持广播活动。然而,即使采用这种方法,系统仍希望您很快完成广播(不到 10 秒)。它确实允许您将工作转移到另一个线程以避免主线程出现故障。 使用 JobScheduler 调度作业。有关详细信息,请参阅智能作业调度。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2018-02-10
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2018-05-24
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多