java - 在远程服务中使用处理程序

标签 java android multithreading service aidl

假设我们有一个简单的 Android 应用程序,它通过 IPC 连接到远程服务,安排一个相对较长的任务,然后继续工作,同时等待回调并返回一些结果。 AIDL接口(interface):

IRemoteService.aidl

package com.var.testservice;
import com.var.testservice.IServCallback;

interface IRemoteService {

    void scheduleHeavyTask(IServCallback callback);

}

IRemoteService.aidl

package com.var.testservice;

interface IServCallback {

    void onResult(int result);

}

Activity 代码:

package com.var.testclient;

import com.var.testservice.IServCallback;
import com.var.testservice.IRemoteService;

import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;

public class MainActivity extends Activity {

    private static final String TAG = "TestClientActivity"; 

    private IServCallback.Stub servCallbackListener = new IServCallback.Stub(){
        @Override
        public void onResult(int result) throws RemoteException {
            Log.d(TAG, "Got value: " + result);
        }

};

private ServiceConnection servConnection = new ServiceConnection(){

    @Override
    public void onServiceConnected(ComponentName name, IBinder binder) {
        service = IRemoteService.Stub.asInterface(binder);          
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        service = null;         
    }

    };

    private IRemoteService service;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(!bindService(new Intent(IRemoteService.class.getName()), servConnection, Context.BIND_AUTO_CREATE)){
            Log.d(TAG, "Service binding failed");
        } else {
            Log.d(TAG, "Service binding successful");
        }
    }

    @Override
    protected void onDestroy() {
        if(service != null) {
            unbindService(servConnection);
        }
        super.onDestroy();
    }

    public void onButtonClick(View view){
        Log.d(TAG, "Button click");
        if(service != null){
            try {
                service.scheduleHeavyTask(servCallbackListener);
            } catch (RemoteException e) {
                Log.d(TAG, "Oops! Can't schedule task!");
                e.printStackTrace();
            }
        }
    }

}

服务代码:

package com.var.testservice;

import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;

public class TestService extends Service {

    private static final String TAG = "TestService";

    class TestServiceStub extends IRemoteService.Stub {

        private IServCallback servCallback;
        //These 2 fields will be used a bit later
        private Handler handler;
        private int result;

        //The simpliest implementation. In next snippets I will replace it with
        //other version
        @Override
        public void scheduleHeavyTask(IServCallback callback)
                    throws RemoteException {
            servCallback = callback;
            result = doSomethingLong();
            callback.onResult(result);
        }

        private int doSomethingLong(){
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                return 42;
            }
        }

    }

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

}

这个版本虽然非常愚蠢(它使应用程序的 UI 线程挂起 5 秒,导致 ANR),但它成功地通过 IPC 执行所有调用,将结果传递给 Activity。 如果我尝试将计算放入单独的线程中,就会出现问题:

@Override
public void scheduleHeavyTask(IServCallback callback)
        throws RemoteException {
    servCallback = callback;
    Runnable task = new Runnable(){

        @Override
        public void run() {
            result = doSomethingLong();
            try {
                Log.d(TAG, "Sending result!");
                servCallback.onResult(result);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

    };
    new Thread(task).start();
}

在这种情况下,回调不会传递到 Activity :服务成功调用 servCallback.onResult(result);,但 Activity 内不会调用任何内容。没有异常(exception),没有线索,没有幸存者:完美的调用谋杀。我找不到有关此类行为的可能原因的任何信息,因此如果有人能够澄清这里发生的情况,我将不胜感激。我的建议是,有某种安全机制,跟踪绑定(bind)了哪些确切的线程,并忽略来自其他线程的“不安全”调用(当我们尝试弄乱来自非 UI 线程的 UI 元素时,会发生类似的情况),但我不能确保。

最明显的解决方案是将回调调用发布到绑定(bind)线程,所以我这样做了:

@Override
public void scheduleHeavyTask(IServCallback callback)
        throws RemoteException {
    Log.d(TAG, "Schedule request received.");
    servCallback = callback;
    if(Looper.myLooper() == null) {
        Looper.prepare();
    }

    handler = new Handler();
    Runnable task = new Runnable(){

        @Override
        public void run() {
            result = doSomethingLong();
            Log.d(TAG, "Posting result sender");
            handler.post(new Runnable(){

                @Override
                public void run() {
                    try {
                        Log.d(TAG, "Sending result!");
                        servCallback.onResult(result);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    Looper.myLooper().quit();
                    Log.d(TAG, "Looper stopped");
                }

            });
        }

    };
    new Thread(task).start();
    Looper.loop();
}

这里我又遇到了两个问题:

  1. 我必须调用 Looper.loop() 来启用回调可运行对象的处理,但它会阻塞 IPC,因此我得到的结果与开始时相同 - 没有实际的多线程;
  2. 第二次注册回调(第一个周期完成并返回值后)会导致异常:

    java.lang.RuntimeException: Handler (android.os.Handler) sending message to a Handler on a dead thread
    at android.os.MessageQueue.enqueueMessage
    at android.os.Handler.sendMessageAtTime
    at android.os.Handler.sendMessageDelayed
    at android.os.Handler.post
    at com.var.testservice.TestService$TestServiceStub$1.run
    at java.lang.Thread.run
    

这让我完全困惑:我从实际的Looper创建了一个新实例,它怎么会指向死线程?

服务能够对任务进行排队并在任务完成时进行回调的整个想法对我来说听起来并不疯狂,所以我希望更有经验的人可以解释我:

  1. 为什么我实际上无法从不同线程进行 IPC 调用?
  2. 我的处理程序出了什么问题?
  3. 我应该使用什么工具/架构来创建一个干净、正确的队列机制,以便它可以在正确的线程上调用 IPC 方法,而无需不断调用 Looper.loop()/Looper。退出()

谢谢。

最佳答案

我无法解释为什么你的程序无法运行。但涉及线程和异步回调的版本:

@Override
public void scheduleHeavyTask(IServCallback callback)
        throws RemoteException {
    servCallback = callback;
    Runnable task = new Runnable(){

        @Override
        public void run() {
            result = doSomethingLong();
            try {
                Log.d(TAG, "Sending result!");
                servCallback.onResult(result);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

    };
    new Thread(task).start();
}

应该可以正常工作。

以下是 Android 如何为 AIDL 和其他类型的 Binder 事务安排线程。

  • 如果调用者和被调用者在同一个进程中,则变成简单的方法调用。因此,您应该注意 scheduleHeavyTask 是从与 onButtonClick 相同的线程调用的。同样,您应该知道对 onResult 的调用应该是来自运行任务的线程的简单方法调用。
  • 如果调用者和被调用者位于不同的进程中,Android 将从被调用者进程内的线程池中运行 Binder 事务(它们称为 Binder #1 等)。即使在这里,它也非常聪明 - 所以如果 onButtonClick 调用了scheduleHeavyTask,它从同一个线程回调到onResult,然后onButtonClick会直接出现调用onResult 在调用者进程中。

绝对没有任何机制可以避免来自“不安全”或“未绑定(bind)”线程的调用 - 因此您发布的这个简单方法应该有效。正如你所说,这是一种常见的模式,我已经使用过很多次了。因此,我建议继续这样做,而不是摆弄额外的循环器和处理程序。

这里有一些想法:

  • 在调试器中运行它。假设您的服务和 Activity 位于同一进程中,您应该能够设置断点并查看发生的合理方法调用。
  • 即使不能,也可以从命令行启动ddms,或者在 Eclipse 中显示“设备”和“线程” View 。这将使您准确地了解每个线程正在做什么 - 您可以获得一个调用堆栈。即使在全面的调试器不方便的情况下,通常也可以使用它。
  • 您是否在任何地方提到过“同步”一词?可能发生的情况是 onResult 想要继续运行,但它在某些监视器上被阻止。一旦解锁,它很可能会运行。

关于java - 在远程服务中使用处理程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14591865/

相关文章:

java - 从 java 调用一个 c++ 函数,该函数使用其他一些引用包含文件的函数

java - 如何从集合中返回对象。

java - 如何按位置设置 ListView 中特定项目的背景颜色?

java - 使用java从文本文件中读取数据行

java - 使用 '|' 拆分字符串

android - Opencl 在 ARM 上找不到 gpu

java - 应用无法在我的真实设备上加载插页式广告

java - 异步迭代器

c# - ASP.NET 中的全局变量

c# - 完成后合并两个后台worker的结果