在Android项目的实际开发中,或多或少的都要用到Service,比如监测APP升级、即时消息通知以及一些需要持久运行或耗时的操作,作为Android四大组件之一,Service的用法是必要掌握的,Service分为本地服务和远程服务,又可分为前台服务和后台服务,在service中,本地服务(LocalService)为普通服务,与客户端捆绑在一起,主进程停止后服务也会终止,无法独立存活,不过它节省资源,通信方便,通常可用于监测APP升级、音乐播放等;而远程服务(RemoteService)是一个独立的进程,可以独立存活,亦可以被其他不同包名的APP所共同调用,当然啦,由于是独立进程,相对来说更耗费资源,通讯也相对麻烦,既然是一个独立的进程,那么进程之间通信肯定就会用到AIDL。
今天探讨一下关于远程服务的实现及与客户端之间的通讯。
这张图把生命周期分为两种情况,即开启服务startService()和绑定服务bindService(),startService()开启服务之后需要通过stopService进行停止,bindService()绑定服务之后则需要通过onUnbind()进行解绑停止,其实即便是通过开启服务的方式启动远程服务,也是可以随时被允许绑定,即startService()之后依然可以执行bindService(),那么交叉式生命周期见下图
远程服务如果想跟客户端通信,由于不在同一进程,就必须要用到Android接口定义语言,即AIDL(Android Interface Definition Language),关于AIDL的内容,网上很多详细讲解,我就不班门弄斧了。
首先创建一个.aidl文件,AndroidStudio可以直接创建,eclipse的话,创建一个File,后缀加上“.aidl”就可以了
这个文件里根据自身需要实现用于通讯的接口
package com.demo.service;
interface IRemoteService{
void setName(String name);
String getName();
}
然后创建一个服务类RemoteService,继承于Service,在这其中创建一个嵌套类ServiceImpl ,用于通信过程中的逻辑操作,与aidl中的接口对应,继承于Sub
class ServiceImpl extends Stub {
private String _name;
@Override
public void setName(String name) throws RemoteException {
_name = name;
}
@Override
public String getName() throws RemoteException {
return _name;
}
}
接着将ServiceImpl做一个简单的单例模式
// 将ServiceImpl做一个简单的单例模式
private ServiceImpl getInstance() {
if (serviceImpl == null) {
serviceImpl = new ServiceImpl();
}
return serviceImpl;
}
这样的话,就可以在生命周期 onBind()方法中将这个单例模式返回
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return getInstance();
}
RemoteService服务类中还需要重写onCreate()、onStartCommand()、onStart()、onUnbind()、onDestroy()方法,下面直接上完整代码
package com.demo.service;
import com.demo.service.IRemoteService.Stub;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class RemoteService extends Service {
private ServiceImpl serviceImpl;
private static final String TAG = "ForegroundService";
private static final int NOTIFICATION_ID = 1; // 如果id设置为0,会导致不能设置为前台service
class ServiceImpl extends Stub {
private String _name;
@Override
public void setName(String name) throws RemoteException {
_name = name;
}
@Override
public String getName() throws RemoteException {
return _name;
}
}
// 将ServiceImpl做一个简单的单例模式
private ServiceImpl getInstance() {
if (serviceImpl == null) {
serviceImpl = new ServiceImpl();
}
return serviceImpl;
}
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return getInstance();
}
@SuppressLint("NewApi")
@Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setTicker("服务启动");
builder.setContentTitle(getString(R.string.app_name));
builder.setContentText("运行中");
Notification notification = builder.build();
notification.largeIcon = BitmapFactory.decodeResource(
this.getResources(), R.drawable.ic_launcher);
startForeground(NOTIFICATION_ID, notification);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onStart(Intent intent, int startId) {
// TODO Auto-generated method stub
super.onStart(intent, startId);
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.d(TAG, "onDestroy");
stopForeground(true);
}
@Override
public boolean onUnbind(Intent intent) {
// TODO Auto-generated method stub
return super.onUnbind(intent);
}
}
代码很简单,如果不需要实现前台服务,onCreate()方法中的代码完全可以省略掉(该onCreate()只在首次服务运行时执行一次,重复启动或绑定服务不再执行),当然,前台服务会更小几率的被系统自动回收机制kill掉,onCreate()中的那堆代码是创建一个Notification对象,将服务的运行状态显示在系统下拉列表项中
基本的远程服务端基本可以了,现在看客户端
有开启服务按钮 调用startService(),绑定服务按钮 调用bindService(),停止服务按钮 调用stopService()
startService()是单纯的开启服务,无法通过aidl进行进程间的通信,只有bindService()之后才可以
要想实现通信,需要在客户端实例化一个ServiceConnection对象,然后在它的回调方法onServiceConnected中得到IRemoteService(aidl接口),通过它,就可以在绑定服务之后调用aidl中声明的方法
private ServiceConnection sc = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
irservice = IRemoteService.Stub.asInterface(service);
}
};
绑定服务
// 绑定服务
Intent intentBind = new Intent(MainActivity.this,
RemoteService.class);
bindService(intentBind, sc, Service.BIND_AUTO_CREATE);
完整代码
package com.demo.service;
import android.app.Activity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity implements OnClickListener {
EditText editName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editName = (EditText) findViewById(R.id.editName);
findViewById(R.id.btnStart).setOnClickListener(this);
findViewById(R.id.btnBind).setOnClickListener(this);
findViewById(R.id.btnStop).setOnClickListener(this);
findViewById(R.id.btnSet).setOnClickListener(this);
findViewById(R.id.btnGet).setOnClickListener(this);
}
IRemoteService irservice = null;
private ServiceConnection sc = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// TODO Auto-generated method stub
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// TODO Auto-generated method stub
irservice = IRemoteService.Stub.asInterface(service);
}
};
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.btnStart:
// 开启服务
Intent intentStart = new Intent(MainActivity.this,
RemoteService.class);
startService(intentStart);
break;
case R.id.btnBind:
// 绑定服务
Intent intentBind = new Intent(MainActivity.this,
RemoteService.class);
bindService(intentBind, sc, Service.BIND_AUTO_CREATE);
break;
case R.id.btnSet:
//设置姓名
try {
irservice.setName(editName.getText().toString());
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
case R.id.btnGet:
//获取姓名
try {
Toast.makeText(MainActivity.this, irservice.getName(), 0)
.show();
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
case R.id.btnStop:
// 停止服务
Intent intentStop = new Intent(MainActivity.this,
RemoteService.class);
stopService(intentStop);
break;
default:
break;
}
}
}
如果仅开启服务,无法建立通信,如果仅绑定服务,那么在客户端销毁时远程服务也随之销毁,开启并绑定服务,则可以保持进程通信和持久存活。
逻辑很简单,就不上源码了,有需要的话留邮箱
忘了一事儿,还需要在AndroidManifest.xml中把远程服务注册一下
<service
android:name="com.demo.service.RemoteService"
android:enabled="true"
android:exported="false"
android:process=":remote" >
<intent-filter>
<action android:name="com.demo.service.REMOTE_SREVICE" />
intent-filter>
service>
简单说一下,android:process=":remote"
是将服务设置为远程服务,android:exported="false"
是是否允许其他application调用此服务