远程Service(AIDL)的简单使用

参考:
https://blog.csdn.net/guolin_blog/article/details/9797169
注意本文环境Android 4.4因此没有报不能使用隐式意图启动Service的错。

本地Service

开始讲述远程Service前,先回顾一下本地Service的主要代码(包括start和bind service)

public class MyService extends Service {

    private static final String TAG = "MyService";

    @Override
    public void onCreate() {
        Log.v(TAG, "onCreate");
        Log.d(TAG, "MyService thread id is " + Thread.currentThread().getId());
        Log.d(TAG, "MyService process id is " + Process.myPid());
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v(TAG, "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.v(TAG, "onDestroy");
        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.v(TAG, "onBind");
        return new MyBinder();
    }

    public void test() {
        // do sth
        Log.v(TAG, "test");
    }

    class MyBinder extends Binder {
        public MyService getService() {
            return MyService.this;
        }

        public void startDownload() {
            Log.d(TAG, "startDownload");
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 执行具体的下载任务
                }
            }).start();
        }
    }
}
public class MainActivity extends Activity implements OnClickListener {
    protected static final String TAG = "MainActivity";
    Button start, stop, bind, unbind;
    boolean isBound = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, " thread id is " + Thread.currentThread().getId());
        Log.d(TAG, " process id is " + Process.myPid());
        setContentView(R.layout.activity_main);
        start = (Button) findViewById(R.id.start);
        stop = (Button) findViewById(R.id.stop);
        bind = (Button) findViewById(R.id.bind);
        unbind = (Button) findViewById(R.id.unbind);

        start.setOnClickListener(this);
        stop.setOnClickListener(this);
        bind.setOnClickListener(this);
        unbind.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        Intent i = new Intent();
        i.setAction("com.test.chj");
        switch (v.getId()) {
        case R.id.start:
            startService(i);
            break;
        case R.id.stop:
            stopService(i);
            break;
        case R.id.bind:
            bindService(i, conn, Context.BIND_AUTO_CREATE);
            isBound = true;
            break;
        case R.id.unbind:
            if (isBound) {
                unbindService(conn);
                isBound = false;
            }
            break;
        default:
            break;
        }
    }

    protected void onDestroy() {
        super.onDestroy();
        Log.v(TAG, "activity onDestroy");
    };

    ServiceConnection conn = new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.v(TAG, "onServiceDisconnected");
        }

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.v(TAG, "onServiceConnected");
            MyBinder binder = (MyBinder) service;
            binder.startDownload();
            MyService serv = binder.getService();
            serv.test();
        }
    };

}

我们可以发现Service的onBind方法应该和Activity内部类ServiceConnection的onServiceDisconnected有某种联系,onBind的返回值应该是作为了onServiceDisconnected的参数。这一点需要留意,远程Service和本地Service的不同点,这里是个关键点。

Service进化–远程Service (AIDL)

如何创建远程Service?

很简单,对Service添加一个属性即可

        <service android:name=".MyService"  android:process=":remote">
            <intent-filter>
                <action android:name="com.test.chj" />
            </intent-filter>
        </service>

android:process的含义是让Service另起一个进程(用在Service上时)。Linux中的远程即不在相同进程,即跨进程的意思。此时我们点击start service有如下log:

05-08 13:58:48.088  3869  3869 D MainActivity:  thread id is 1
05-08 13:58:48.089  3869  3869 D MainActivity:  process id is 3869
05-08 13:58:51.193  3957  3957 V MyService: onCreate
05-08 13:58:51.193  3957  3957 D MyService: MyService thread id is 1
05-08 13:58:51.193  3957  3957 D MyService: MyService process id is 3957
05-08 13:58:51.194  3957  3957 V MyService: onStartCommand

我们发现进程号确实不一样了,但是线程号好像一样?我们知道MainActivity肯定线程号代表主线程,那他们是不是都是主线程呢?很好验证。我们先去掉android:process=”:remote”,然后再Service的onCreate执行如下代码

try {
            Thread.sleep(1000*20);
            Log.d(TAG, "sleep over");
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

会发现有如下log

05-08 14:10:49.332  5019  5019 D MainActivity:  thread id is 1
05-08 14:10:49.332  5019  5019 D MainActivity:  process id is 5019
05-08 14:10:52.496  5019  5019 V MyService: onCreate
05-08 14:10:52.496  5019  5019 D MyService: MyService thread id is 1
05-08 14:10:52.496  5019  5019 D MyService: MyService process id is 5019

从log可以看出没有加android:process=”:remote”,Service和Activity进程相同,而且从Service onCreate之后一会就发生ANR挂了的现象看,Service也是运行在主线程的。

那么加上android:process=”:remote”,在同样睡眠20s呢?
打印如下log

05-08 14:14:32.554  5253  5253 D MainActivity:  thread id is 1
05-08 14:14:32.554  5253  5253 D MainActivity:  process id is 5253
05-08 14:14:34.519  5283  5283 V MyService: onCreate
05-08 14:14:34.520  5283  5283 D MyService: MyService thread id is 1
05-08 14:14:34.520  5283  5283 D MyService: MyService process id is 5283
05-08 14:14:54.522  5283  5283 D MyService: sleep over
05-08 14:14:54.523  5283  5283 V MyService: onStartCommand

可以发现如果给Service加上android:process=”:remote”,Service就不再和Activity运行在同一进程并且也不会阻塞主线程了。

远程Service的IBinder对象

此时我们点击bind service会发现,应用程序挂了。那为什么可以用start service呢?此时如果我们将onServiceConnected中的代码注释掉,就会发现,bind service也能正常运行,那么问题就出在onServiceConnected中了。我们看下onServiceConnected到底做了什么:

扫描二维码关注公众号,回复: 848717 查看本文章
            MyBinder binder = (MyBinder) service;
            binder.startDownload();
            MyService serv = binder.getService();
            serv.test();

可以看到,这里是想获取远程Service的IBinder对象,这里在本地服务是好的,改成远程Service就挂了。再看看挂掉的信息:

java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.example.testservice.MyService$MyBinder

问题报在MyBinder binder = (MyBinder) service;这句话上,也就是说,当Service变成远程的之后,无法直接转换IBinder对象,因为远程服务返回的是个android.os.BinderProxy对象。那么如何才能正确获取到远程服务的IBinder呢?

正确获取到远程服务的IBinder对象

为了正确获取远程服务的IBinder对象,Service(服务端)和Activity(客户端)的代码都要稍作修改
1.首先创建一个aidl文件

package com.example.testservice;

interface MyAIDLService {  
    int plus(int a, int b);  
    String toUpperCase(String str);  
}  

此时在gen目录会生成MyAIDLService.java文件
2.Service(服务端)修改内部类MyBinder,并修改onBind的返回值

    @Override
    public IBinder onBind(Intent intent) {
        Log.v(TAG, "onBind");
        return myBinder;
    }

    MyAIDLService.Stub myBinder = new Stub() {
        @Override
        public String toUpperCase(String str) throws RemoteException {
            return str.toUpperCase();
        }

        public int plus(int a, int b) throws RemoteException {
            return a + b;
        }
    };

3.Activity(客户端)使用IBinder的方式略作修改

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.v(TAG, "onServiceConnected");
            MyAIDLService myservice = MyAIDLService.Stub.asInterface(service);
            try {
                String s = myservice.toUpperCase("hello world");
                int res = myservice.plus(12, 21);
                Log.v(TAG, "result= " + res + " s= " + s);
            } catch (RemoteException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

执行结果:

05-08 14:50:56.370  7715  7715 D MainActivity:  thread id is 1
05-08 14:50:56.370  7715  7715 D MainActivity:  process id is 7715
05-08 14:51:03.106  7784  7784 V MyService: onCreate
05-08 14:51:03.106  7784  7784 D MyService: MyService thread id is 1
05-08 14:51:03.106  7784  7784 D MyService: MyService process id is 7784
05-08 14:51:03.107  7784  7784 V MyService: onBind
05-08 14:51:03.108  7715  7715 V MainActivity: onServiceConnected
05-08 14:51:03.109  7715  7715 V MainActivity: result= 33 s= HELLO WORLD
05-08 14:51:06.869  7784  7784 V MyService: onDestroy

本例虽然是在同一个应用的程序,但是Service和Activity进程已经不同,所以可以假设为两个应用。和直接写两个APK,一个运行服务一个运行Activity差别不大。
可以发现远程Service和本地Service有以下几点区别:
远程Service所在进程与调用者不同
远程Service返回的IBinder对象和本地Service的返回对象类型方式均不相同。

本文仅限于远程Service的使用,原理方面没有涉及。要具体了解的话,应该需要先掌握一些Linux操作系统的知识,然后再了解Android的Binder机制,之后这边的区别便迎刃而解。目前水平尚且不够,先MARK一下。之后学完操作系统再更新Binder的为什么不同吧。

猜你喜欢

转载自blog.csdn.net/u011109881/article/details/80247359
今日推荐