跟着官方学习Android(安卓)— Services
跟着官方学习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自身。
@Overridepublic 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 connectionmConnection = 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@Overridepublic 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@Overridepublic IBinder onBind(Intent mIntent) { ToastUtils.shortShow(getApplicationContext(), "绑定服务中"); return mMessenger.getBinder();}
在Client中定义ServiceConnection类,来接受Service对象
// MessengerService ConnectionmessengerServiceConnection = 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。也感谢各位多多评论和指点,谢谢!
更多相关文章
- Android(安卓)10适配注意的问题
- Android(安卓)View架构总结
- android 多用户管理UserManager
- Android(安卓)开发:第一日——明白Android(安卓)Activity生命周期
- 应聘Android开发工程师-Java笔试部分的答案及解析
- android-async-http开源请求库
- Android中系统自带数据库文件中的多表联合查询疑问
- 转:Android界面刷新的方法
- Android(安卓)官方架构 --- Lifecycle分析