跟着官方学习Android — Services
现在让我来聊下Android四大组件之一,Services。这是小弟的第一次写Blog,这些内容都是跟官方差不多根据自己的理解翻译过来的,可能写得不够好,不够细,只能浅谈好了。官方的内容好多,我只看了一些我能懂的内容来总结。如果想更多了解,可以查阅官方。官方地址是:http://developer.android.com/guide/components/services.html
在这里,我也做了一个小Demo来展示下Service,也是根据官方提供并修改一些的。边展示边说明下。
Service是运行在后台的应用组件,且没有提供UI的。同时,其他的应用程序也能启动某应用的服务。另外,可以让一个应用绑定一个Service,甚至可以在不用进程用通信,简称IPC。比如,在处理网络事务,下载,播放音乐,读写文件,与Content Provider交互等等,但都是运行在后台的。
先来引用官方提供的Service Lifecycle,觉得要好好理解这个周期。
Service基本上可分为两种形式:
* Started/Unbound
这是直接在组件中通过startService()方法启动。比如在下载东西,把它扔在后台,完成之后自己stop。
Client(activity,fragment,或其他组件)startService()会调动服务端的startCommand()。Client stopService()调用服务端的stopService(),但也可以服务端自身停止stopSelf()。
这个Demo是在Fragment上启动的
// 这是启动服务 @Onclick是用了butterknife框架
@OnClick(R.id.btn_start_service)
void startService() {
mIntent = new Intent(getContext(), HelloServices.class);
getActivity().startService(mIntent);
}
之后会调用Service中的这个方法。这里我用了Handler来控制Service自身。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId; // startId是Service的标识,在stopSelf()方法用到
mServiceHandler.sendMessage(msg);
return START_STICKY;
}
这对Handler做处理,自身stopSelf
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper mLooper) {
super(mLooper);
}
@Override
public void handleMessage(Message msg) {
try {
Thread.sleep(10000);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
stopSelf(msg.arg1);
Log.d(TAG, "handleMessage: " + "stop by itself.");
}
}
也可以在组件中停止Service
// 停止服务
@OnClick(R.id.btn_stop_service)
void stopService() {
getActivity().stopService(mIntent);
}
- Bound
这是在某个组件应用中绑定Service的,多个组件可以绑定一个Service。绑定Service可以与Client交互,可以做些跨进程通信(IPC)。
Client需要创建ServiceConnection对象,并实现onServiceConnected()方法,之后调用Server的onBind()方法,return一个IBinder对象,Client就能得到这个IBinder对象,并可以与Service交互,也就是说IBinder用于Client和Service之间的通信。如果client不能绑定server,系统会destroy service,除非绑定成功。
有三种方式实现IBinder接口:
第一种 继承Binder类
如果你的服务仅仅提供给自己的应用使用,这是首选。
首先创建ServiceConnection对象,这接口要实现两个方法。
// IBinderService connection
mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName mComponentName, IBinder mIBinder) {
mService = ((ExtendBinderService.LocalBinder) mIBinder).getService();
ToastUtils.shortShow(getActivity(), "绑定成功");
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName mComponentName) {
ToastUtils.shortShow(getActivity(), "绑定失败");
mService = null;
mBound = false;
}
};
然后在创建一个继承Binder的类,这个类我在Service中定义,方便返回该Service。
public class LocalBinder extends Binder {
public ExtendBinderService getService() {
return ExtendBinderService.this;
}
}
接着在创建这个IBinder对象,并在onBind()返回,这个在Ibinder对象是在ServiceConnection连接成功后,在onServiceConnected()方法中返回,之后转变为Service。对应上面的代码。
private final IBinder mBinder = new LocalBinder();
@Nullable
@Override
public IBinder onBind(Intent mIntent) {
return mBinder;
}
第二种 Messenger
如果需要在不同进程间通信,就需要用到Messenger,你要为Service上创建Messenger接口。service定义的Handler来响应不用类型的Message,这个Handler是与Messenger为基础,并且与Client共享IBinder。也允许Client通过Message发信息给Server,但在Client也要定义Messenger。
这算是简单的IPC,因为Messenger队列请求都在单线程上,每次只能接受一个请求,其他都有等待,因此也不用担心线程安全。
这里是使用Messenger的一些总结:
1. Service需要实现Handler,它能接受每个Client的回调。
2. Handler用于创建Messenger。
3. Messenger创建Binder对象,Service在onBind()中返回给Client。getBinder()方法
4. Client可以IBinder实例化Messenger,Messenger是Handler的引用,Client可以用它来向Service发送Message对象。
5. 在handleMessage()方法中,Service用Handler接受每一个message。
6. 如果Service要回响应,首先在Client中创建Messenger,之后在发送给Service的Message对象的一个变量replyTo设置为在刚创建的Messenger,顺便在Message对象中发送给Service。
实现的方法和第一种方法差不多,
首先要在Service中定义Handler类,来接受Client的handler,并做相应的处理。
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Bundle mBundle = msg.getData();
String str = mBundle.getString("client");
ToastUtils.shortShow(getApplicationContext(), str);
// 向Client响应
Messenger mMessenger = msg.replyTo;
Message mMessage = Message.obtain(null, ServiceFragment.SERVICE_RESPONSE);
Bundle mBundle1 = new Bundle();
mBundle1.putString("service", "我已经收到了");
mMessage.setData(mBundle1);
try {
mMessenger.send(mMessage);
} catch (RemoteException mE) {
mE.printStackTrace();
}
default:
super.handleMessage(msg);
}
}
}
创建Messenger对象,要用到Handler对象,并在onBind()中返回。
public Messenger mMessenger = new Messenger(new IncomingHandler());
@Nullable
@Override
public IBinder onBind(Intent mIntent) {
ToastUtils.shortShow(getApplicationContext(), "绑定服务中");
return mMessenger.getBinder();
}
在Client中定义ServiceConnection类,来接受Service对象
// MessengerService Connection
messengerServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName mComponentName, IBinder mIBinder) {
mServiceMessenger = new Messenger(mIBinder);
mBound = true;
ToastUtils.shortShow(getActivity(), "绑定成功");
mClientMessenger = new Messenger(new IncomingHandler());
}
@Override
public void onServiceDisconnected(ComponentName mComponentName) {
mBound = false;
mServiceMessenger = null;
ToastUtils.shortShow(getActivity(),"绑定失败");
}
};
第三种 AIDL
AIDL可以多线程处理接受的请求,并且构建线程安全,但并不是所有的应用都适合用AIDL来绑定Service,因为有可能导致复杂的处理。Messenger底层其实也是用了AIDL来实现。
使用AIDL的三个步骤,但在实现过程中算是有点麻烦:
1.创建.aidl文件
AIDL定义和定义接口一样的。默认情况下,AIDL支持所有的原始数据类型(int,long,char, boolean), String, CharSequence, List, Map。不是原始数据类型要求定向标识数据的方式(in, out, inout),如果这里没做标识,编译时会报错。原始数据类型默认in。我们定义的.aidl文件,系统自动在build/generated/source/aidl/debug目录下生成一个.java文件。
这是我定义了IRemoteService.aidl文件,并且定义了calculateCir接口方法。Rectange这是自定义的类,这里需要显示地导入自定义类的位置,这个位置是java包下的路径。下面会讲述自定义类的我出现的问题。
import com.example.demo1.Rectangle;
interface IRemoteService {
void calculateCir(in Rectangle mRectangle);
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
2.实现接口
通过Stub来抽象你定义AIDL(比如,YourInterface.Stub),这也继承Binder的接口,然后实现和继承你定义的AIDL的方法。Stub实例化了Binder,就可以在Client中与Service交互了。
在Service中实现IRemoteService的方法。IRemoteService这个类是系统生成,在写完AIDL文件后,最好编译一下。
由于这个Service是在不用的进程运行,实现IRemoteService的方法为有所不同,需要在UIThread中执行。先getMainLooper,再创建Handler(),之后创建的线程就在UIThread中执行了。这个我也不太懂,只能是套路了。等我以后再深入了解好了,或等各位大神帮我解答。
public final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
@Override
public void calculateCir(final Rectangle mRectangle) throws RemoteException {
Handler mHandler = new Handler(Looper.getMainLooper());
mHandler.post(new Runnable() {
@Override
public void run() {
ToastUtils.shortShow(getApplicationContext(),"周长: " + (mRectangle.getHeight() + mRectangle.width)*2);
}
});
}
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
};
3.在Client中执行接口
Client用onServiceConnected()回调得到Binder对象,这时通过YourServiceInterface.Stub.asInterface(mIBinder)实例化Service。
// RemoteService Connection
remoteServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName mComponentName, IBinder mIBinder) {
mRemoteService = IRemoteService.Stub.asInterface(mIBinder);
mBound = true;
ToastUtils.shortShow(getActivity(), "绑定成功");
}
@Override
public void onServiceDisconnected(ComponentName mComponentName) {
mBound = false;
ToastUtils.shortShow(getActivity(), "绑定失败");
}
} ;
还有如果在IPC中传递对象,该对象需要集成Parcelable接口。如果传递是自定义的对象,需要定义一个与该对象同名.aidl文件。
由于Android Studio在创建AIDL文件时,会创建一个跟java包同级的aidl包,而我们需要在java包的根目录创建自定义类的文件,不然的话在编译时会出现cannot find symbol class,原先是放在我项目的models目录下的,就出现这种情况,然后我移动根目录下就可以找到那个类了,这个问题我也不太清楚,我已经在AIDL文件import这个自定义类路径,就是不行,这个问题真的求大神解答
需要调用远程的Service的方法时,在该Service的方法中要用Handler来控制UIThread,如果在同一个进程中用到的Service,也就是用Stub来创建,就直接new一个Handler可以了,不在不同一个进程中,就需要用到UIThread的Looper来创建Handler,然后在里面做操作,不然无法在组件中调用service方法,这个问题也需要大神们解答的,我自己实在也不太懂。如果Service需要在在不同进程运行,如果在manifest文件中定义。这个Deme的第三个Service是在另外一个进程运行。
<service android:name=".services.RemoteService" android:process=":remote"/>
如果不明可以看看可以源码。
https://github.com/jessyuan24/MyAndroidDemo
这个源码不止是这个Service Demo的源码,还有我边学习边做的其他Demo的源码,很简单的。但是我更想希望大家能把这个项目完善,把一些好的Demo和源码放到这个项目中,变成一个更好的项目集合,可以给更多的人学习和参考。
第一次写Blog就这样的啦,自己也算是新手,太多东西没有过于了解,但以后我继续努力学习,深入地了解Android。也感谢各位多多评论和指点,谢谢!