跟着官方学习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。也感谢各位多多评论和指点,谢谢!

更多相关文章

  1. Android(安卓)10适配注意的问题
  2. Android(安卓)View架构总结
  3. android 多用户管理UserManager
  4. Android(安卓)开发:第一日——明白Android(安卓)Activity生命周期
  5. 应聘Android开发工程师-Java笔试部分的答案及解析
  6. android-async-http开源请求库
  7. Android中系统自带数据库文件中的多表联合查询疑问
  8. 转:Android界面刷新的方法
  9. Android(安卓)官方架构 --- Lifecycle分析

随机推荐

  1. 制作自己的android升级包(update.zip)
  2. 兼容android 9.0 明文传输(支持http请求,默
  3. 网页中android Toast效果的实现
  4. android跳转微信指定公众号界面
  5. 去除svn中方法
  6. Android(安卓)N(7.0) 遇到 android.os.File
  7. Android扫描zxing定制化界面实现扫描和图
  8. Android(安卓)studio 获取手机短信内容并
  9. android样式学习(一) 使用selector改变来动
  10. Android, App常用图标尺寸规范