跟着官方学习Android — Services

现在让我来聊下Android四大组件之一,Services。这是小弟的第一次写Blog,这些内容都是跟官方差不多根据自己的理解翻译过来的,可能写得不够好,不够细,只能浅谈好了。官方的内容好多,我只看了一些我能懂的内容来总结。如果想更多了解,可以查阅官方。官方地址是:http://developer.android.com/guide/components/services.html

在这里,我也做了一个小Demo来展示下Service,也是根据官方提供并修改一些的。边展示边说明下。

android 系统服务 DownloadManager_ide

Service是运行在后台的应用组件,且没有提供UI的。同时,其他的应用程序也能启动某应用的服务。另外,可以让一个应用绑定一个Service,甚至可以在不用进程用通信,简称IPC。比如,在处理网络事务,下载,播放音乐,读写文件,与Content Provider交互等等,但都是运行在后台的。

先来引用官方提供的Service Lifecycle,觉得要好好理解这个周期。

android 系统服务 DownloadManager_Services_02


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。也感谢各位多多评论和指点,谢谢!