参考:
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到底做了什么:
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的为什么不同吧。