【问题标题】:Android Activity and Service communication: how to keep the UI up-to-dateAndroid Activity 和 Service 通信:如何让 UI 保持最新
【发布时间】:2013-09-11 16:03:17
【问题描述】:

我有一个 Activity A(不是主 Activity),它启动了一个 Service S,它在后台执行一些操作,同时应该进行一些更改到 A 的 UI。

假设 S 计数从 0 到 100A 应该实时显示该计数。由于 S 的实际工作相当复杂且消耗 CPU,因此我不想使用 AsyncTask 来处理它(确实 “AsyncTasks 应该理想地用于短操作(最多几秒钟。)[ ...]")但只是一个普通的Service 在一个新线程中开始(IntentService 也可以)。

这是我的活动 A

public class A extends Activity {
    private static final String TAG = "Activity";
    private TextView countTextView;    // TextView that shows the number
    Button startButton;                // Button to start the count
    BResultReceiver resultReceiver;


    /**
     * Receive the result from the Service B.
     */
    class BResultReceiver extends ResultReceiver {
        public BResultReceiver(Handler handler) {
            super(handler);
        }

        @Override
        protected void onReceiveResult(int resultCode, Bundle resultData) {
            switch ( resultCode )  {
                case B.RESULT_CODE_COUNT:
                        String curCount = resultData.getString(B.RESULT_KEY_COUNT);
                        Log.d(TAG, "ResultReceived: " + curCount + "\n");
                        runOnUiThread( new UpdateUI(curCount) );  // NOT WORKING AFTER onResume()!!!
                   break;
            }
        }
    }


    /**
     * Runnable class to update the UI.
     */
    class UpdateUI implements Runnable {
        String updateString;

        public UpdateUI(String updateString) {
            this.updateString = updateString;
        }

        public void run() {
            countTextView.setText(updateString);
        }
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.counter);

        countTextView = (TextView) findViewById(R.id.countTextView);
        startButton = (Button) findViewById(R.id.startButton);

        resultReceiver = new BResultReceiver(null);
    }


    public void startCounting(View view) {
        startButton.setEnabled(false);

        //Start the B Service:
        Intent intent = new Intent(this, B.class);
        intent.putExtra("receiver", resultReceiver);
        startService(intent);
    }
}

这是我的服务 B

public class B extends Service {
    private static final String TAG = "Service";
    private Looper serviceLooper;
    private ServiceHandler serviceHandler;
    private ResultReceiver resultReceiver;
    private Integer count;

    static final int RESULT_CODE_COUNT = 100;
    static final String RESULT_KEY_COUNT = "Count";


    /**
     * Handler that receives messages from the thread.
     */
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            while ( count < 100 ) {
                count++;
                //Sleep...
                sendMessageToActivity(RESULT_CODE_COUNT, RESULT_KEY_COUNT, count.toString());
            }

            //Stop the service (using the startId to avoid stopping the service in the middle of handling another job...):
            stopSelf(msg.arg1);
        }
    }


    @Override
    public void onCreate() {
        //Start up the thread running the service:
        HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();

        this.count = 0;

        //Get the HandlerThread's Looper and use it for our Handler
        serviceLooper = thread.getLooper();
        serviceHandler = new ServiceHandler(serviceLooper);
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        this.resultReceiver = intent.getParcelableExtra("receiver");

        //For each start request, send a message to start a job and deliver the start ID so we know which request we're stopping when we finish the job:
        Message msg = serviceHandler.obtainMessage();
        msg.arg1 = startId;
        serviceHandler.sendMessage(msg);

        //If we get killed, after returning from here, restart:
        return START_REDELIVER_INTENT;
    }


    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    /**
     * Send a message from to the activity.
     */
    protected void sendMessageToActivity(Integer code, String name, String text) {
        Bundle bundle = new Bundle();
        bundle.putString(name, text);

        //Send the message:
        resultReceiver.send(code, bundle);
    }
}

一切正常,但如果我点击后退按钮(或主页按钮)然后我重新打开活动A,然后是A的用户界面不再更新(它只显示了 A 的初始配置 - 即“startButton”仍然可点击并且未显示计数 - 似乎 runOnUiThread(...)不再工作了)。但是,Service B 仍在后台运行,我可以看到正确的计数已传递给 Log.d(...) 中的 Activity A。最后,如果我再次点击“startButton”,计数不会从头(0)开始,而是从 B 到达的位置开始(我已经通过在通知栏中显示它来仔细检查)。

如何解决此问题?我希望,当我重新打开 Activity A 时,它会自动继续接收和更新来自 Service B 的数据。或者,换句话说,Service 使 Activity A 的 UI 保持最新。

请给我一些提示、链接或一段代码。谢谢!

【问题讨论】:

    标签: android android-activity android-service android-ui android-handler


    【解决方案1】:

    当您单击返回按钮时,您的 Activity 将被销毁。当您再次启动Activity 时,您将获得一个新的Activity。旋转设备时也会发生这种情况。这是Android lifecycle event

    该活动不适合繁重的业务逻辑,仅用于显示内容/控制内容。 您需要做的是创建一个简单的 MVC,Model View Controller。视图 (Activity) 只能用于显示结果和控制事件流。

    Service 可以保存count 的数组,当您的Activity 启动时,它将onBind() 您正在运行的服务(或者如果未运行将启动Service,因为您绑定到它)让 Activity(View) 获取结果数组并显示它。这个简单的设置不包括 (M)Model 业务逻辑。

    更新
    稍加阅读,这是 Android 官方文档和完美的开始,因为它可以满足您的要求。正如您在onStart() 的示例中看到的那样,Activity 与服务建立连接,而在onStop() 中,连接被删除。在onStop() 之后建立连接毫无意义。就像你要求的那样。我会采用这种设置,不要让Service 持续发送数据,因为这会消耗资源,而且Activity 并不总是在监听,因为它会在后台停止。
    Here's an activity that binds to LocalService and calls getRandomNumber() when a button is clicked:

    【讨论】:

    • 感谢您的回答!对于 MVC 模型来说还可以,但是从性能的角度来看,如果 Service 自动向 Activity 发送数据,而不是让 Activity 不断地请求它们,那会不会更好? Activity onResume()/onStart()方法中没有办法重新连接“新Activity”和“旧Service”吗?
    • 是的,它可以通过两种方式完成,这一切都取决于什么对你有用。最好阅读它以获得清晰的图片,在 Android 文档中非常直接。我更新了我的答案..
    • 我有点搞砸了,但还是没有运气!我不明白为什么即使在 onRestart() 事件之后我也可以在 Activity A 内的 Log.d(...) 中看到正确的数字,但是直到我再次单击按钮后 TextView 才会更新...可能问题是我没有将服务 B 绑定到活动 A?
    • 当您执行此操作时,Service 中的 NullPointerException count++;。添加一些android.util.Log.e(TAG, "shit errror")。也许您已经在使用 LogCat,除非它显示 NullPointerException
    • 当然,这不是问题所在。不知道为什么,当我之前编辑代码时,我已经删除了 onCreate() 方法中的初始化(this.count = 0)。无论如何,我的问题仍然存在。我找到了这个相关的帖子:stackoverflow.com/questions/2476005/…
    猜你喜欢
    • 1970-01-01
    • 2013-01-19
    • 1970-01-01
    • 2016-03-07
    • 1970-01-01
    • 2012-05-17
    • 1970-01-01
    • 2014-01-02
    • 1970-01-01
    相关资源
    最近更新 更多