android五中方式调用服务service中的方法
阅读原文时间:2021年04月20日阅读:4

今天看到一张图片,上面有句话 —- 没有伞的孩子,要快跑

android五中方式调用服务service中的方法分别是
- 通过重复调用startService 来根据意图Intent传递的参数调用
- 通过Handle调用
- 通过bindService调用
- 通过AIDL调用
- 通过BroadcastReceiver调用

再讲解这五中方法之前我们先来回顾一下Service的生命周期:

从图片可以看到service的一般生命周期,但当service已经启动过了这时再调用startService,服务不会走onCreat了而是走onStartCommand,而且原始的onStartCommand中并没有什么影响程序运行的东西,所以我们可以多次调用startService而不用担心会产生什么对服务不好的Bug.至于startService和bindService的混合调用,混合调用也要特别注意生命周期,先start,再bind,然后unbind,最后stop.不然会引发一些匪夷所思的bug.好了,我们来实现这五中调用吧O(∩_∩)O~


通过重复调用startService 来根据意图Intent传递的参数调用

先看效果:

`service中的代码`

`public class MyService extends Service {

private Messenger mMessenger;
private Toast mToast;

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return null;
}

@Override
public void onCreate() {
    mToast = Toast.makeText(this, "参数错误", Toast.LENGTH_SHORT);
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {

    String command = intent.getStringExtra("command");

    if ("eat".equals(command)) {
        eat();
    }
    else if ("drink".equals(command)){

        drink();
    }
    else if ("sleep".equals(command)) {
        sleep();
    }
    else {

        showToast("参数命令应为[ eat | drink | sleep ]");
    }

    return super.onStartCommand(intent, flags, startId);
}

public void showToast(String result) {
    mToast.setText(result);
    mToast.show();
}

private void eat() {
    showToast("我是服务方法,我在吃东西");
}

private void drink() {
    showToast("我是服务方法,我在喝白开水");
}

private void sleep() {
    showToast("我是服务方法,我在睡懒觉");
}

}`

然后我们通过点击按钮来重复调用startService,根据输入的命令调用服务中相应的方法

Intent intent = new Intent(this, MyService.class);
intent.putExtra(“command”,mEditText.getText().toString().trim());
startService(intent);


通过Handle调用

上面我们调用startService传递参数给startService的时候还可以传递报信者Messenger

intent.putExtra(“Messenger”,new Messenger(mHandler));

报信者拿着我们的handler就可以正常工作了,MyService做以下修改

`@Override
public int onStartCommand(Intent intent, int flags, int startId) {

    Messenger messenger = (Messenger) intent.getExtras().get("Messenger");
    if (messenger != null){

        Message msg = new Message();
        msg.obj = new MiddleMan();        //把中间人对象放到消息中
        try {
            messenger.send(msg);           //方送消息,由MainActivity中的handelMessage处理
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    return super.onStartCommand(intent, flags, startId);
}


//增加一个中间人类
class MiddleMan implements MyInterface{

    @Override
    public void mEat() {
        eat();
    }

    @Override
    public void mDrink() {
        drink();
    }

    @Override
    public void mSleep() {
        sleep();
    }
}`

//中间人要实现的中间方法

public interface MyInterface {

void mEat();
void mDrink();
void mSleep();

}

handle处理,这里可以根据发的参数调用中间人的各种方法

public Handler mHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {

        MyInterface myInterface = (MyInterface) msg.obj;
        myInterface.mSleep();
    }
};

效果如下

其实我们也可以把mHandle改为静态的,这样就不用传报信者了,直接在service中拿到mHandle发消息

public static Handler mHandler = new Handler(){

额,看到这是不是觉得我有点坑……这点我也不是很理解,但老话说的好,存在必有其的道理嘛,对吧.望能解释的不吝赐教O(∩_∩)O哈!


通过bindService调用

bindService(Intent service, ServiceConnection conn,int flags)
第一个参数为意图,第二个我们实现ServiceConnection接口来绑定服务的一个回调,第三个我们一般都直接填BIND_AUTO_CREATE也就是1,service中自己定义的连接flag

好,来让我们实现这个接口回调ServiceConnection
class MyServiceConnection implements ServiceConnection{

    @Override  //连接时候调用
    public void onServiceConnected(ComponentName name, IBinder service) {

    }

    @Override //unbindServeric成功断开服务时调用
    public void onServiceDisconnected(ComponentName name) {

    }
}

我们可以看到这个接口回调成功绑定服务的时候回调用

onServiceConnected(ComponentName name, IBinder service)

第二个参数,我们会拿到一个IBinder对象,那这个IBinder对象是从哪里来的,这就要注意service的生命周期了,当第一次执行bindService时,会执行这个方法

public IBinder onBind(Intent intent) {
    return null;
}

默认它是返回null的,即如果我们什么都不做,即使我们绑定了服务得到的也是一个空对象,那从上面我们通过handle调用服务中的方法,我们是拿到服务中的中间人对象来调用服务中的方法的,所以我们可以返回一个继承了IBinder的中间人对象不就好了么.我们先通过Ctrl+H查看IBinder的类机构,看看它是一个实现类还是接口,由此我们的中间人是继承还是实现接口.

从图中可以看出IBinder是一个接口,但幸运的是它有一个实现类Binder,因为继承系统的接口一般都要实现很多方法,而且一般都很难写,甚至写不出来,所以我们应该让我们的中间人类去继承Binder,所以我们的代码相对于上面只要做一点小修改就好了

class MiddleMan extends Binder implements MyInterface

然后在onBind()方法中

public IBinder onBind(Intent intent) {
return new MiddleMan();
}

然后在我们ServiceConnection的实现类中

@Override  //连接时候调用
    public void onServiceConnected(ComponentName name, IBinder service) {
        MyInterface myInterface = (MyInterface) service;
        myInterface.mEat();
    }

因为中间人即是Binder的子类也是MyInterface的实现类,所以我们应该最终要拿到的是接口对象,因为接口对象中都是我们所要调用的服务中的方法,有时候中间人还有自己的私人方法,我们不应该调用,或我们不能在MainActivity中引入中间人类的包,这在aidl(安卓定义接口语言)中也是这么设计的,我们后面会讲到.


通过aidl调用

前面通过bindService调用中,我们讲到中间人,这里我画个图加深下理解

也就是说其实我们并不是要调用中间人的方法,我们是要通过中间人去调用服务的方法,中间人自己私有的方法我们是一点都不关心的,我们只关心调用服务的方法,所以我们把这些方法放到一个接口中,让中间人去实现,而且我们得到中间人对象后,这个中间人也基本是服务中的内部类,所以大部分情况 我们是拿不到它的,这样就不能进行强转了.而接口,我们往往把它作为一个单独的文件,在同一应用我们是可以拿到的,而AIDL是跨进程即跨应用通信的,我们拿到AIDL即可跨应用的通信,它的原理其实就是由一个接口改造而来,来看看我们怎么制作AIDL文件

  • 我们现在我们应用的main建立AIDL文件

  • 然后会自动生成一个aidl文件

你说这像不像一个接口文件,其实Eclipse是要自己改的…..我们把接口内容改一下

- 怎么用呢?

别急,我们先Gradle Build一下,然后看应用目录/build/generated/source/aidl/debug

看到了吧,接口文件在这里呢,这是系统自动帮我们生成的接口文件,我们进去看看吧(不是排版问题,就是这么乱,看不懂很正常,其实只要看几行就好了,让大家感受下)

/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: D:\CSDN\Notes\servicenotes\src\main\aidl\com\it\servicenotes\IMyAidlInterface.aidl
*/
package com.it.servicenotes;
// Declare any non-default types here with import statements
public interface IMyAidlInterface extends android.os.IInterface
{
/* Local-side IPC implementation stub class. /
public static abstract class Stub extends android.os.Binder implements com.it.servicenotes.IMyAidlInterface
{
private static final java.lang.String DESCRIPTOR = “com.it.servicenotes.IMyAidlInterface”;
/* Construct the stub at attach it to the interface. /
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.it.servicenotes.IMyAidlInterface interface,
* generating a proxy if needed.
*/
public static com.it.servicenotes.IMyAidlInterface asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.it.servicenotes.IMyAidlInterface))) {
return ((com.it.servicenotes.IMyAidlInterface)iin);
}
return new com.it.servicenotes.IMyAidlInterface.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_mEat:
{
data.enforceInterface(DESCRIPTOR);
this.mEat();
reply.writeNoException();
return true;
}
case TRANSACTION_mDrink:
{
data.enforceInterface(DESCRIPTOR);
this.mDrink();
reply.writeNoException();
return true;
}
case TRANSACTION_mSleep:
{
data.enforceInterface(DESCRIPTOR);
this.mSleep();
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.it.servicenotes.IMyAidlInterface
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public void mEat() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_mEat, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void mDrink() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_mDrink, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
@Override public void mSleep() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_mSleep, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_mEat = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_mDrink = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_mSleep = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
}
public void mEat() throws android.os.RemoteException;
public void mDrink() throws android.os.RemoteException;
public void mSleep() throws android.os.RemoteException;
}

我们通过IMyAidlInterface.可以看到它只有一个属性Stub,所以我们点进去看看嘛

然后我们可以看到它继承了Binder和实现了服务的接口方法

public static abstract class Stub extends android.os.Binder implements com.it.servicenotes.IMyAidlInterface

等等,这不是我们的中间人对象么,还是有点不一样的它是个抽象类啊,所以我们的中间人对象可以改改了

class MiddleMan extends IMyAidlInterface.Stub

但是onServiceConnected中还是这样子好像很low的样子

我们再看看这个AIDL生成的接口文件中还有什么方法

看,interface(⊙o⊙)…,八成就是这个了,来试试就是这样

效果

好了,这就是aidl的使用了,大家看到这会不会说为什么要用aidl啊,用前面的bindService不就好了,其实在我们自己的应用中调用是不需要AIDL的,AIDL是为了跨应用通信的,我们可以绑定别人的应用的服务来完成一些做好的功能,比如支付等,但是我们怎么调用人家的方法啊,难道说把别人的接口文件拷过来么…..这样是不行的,不同应用之间是不可以这样的,你看我们用aidl,SDK帮我们生成接口文件还做了大量的事情呢.所以说跨应用是需要AIDL的,我们只要把对方应用的AIDL文件拷到我们应用就行了,然后SDK会帮我们生成适合我们这个应用相应的接口文件.拷贝别人AIDL文件的时候要注意aidl文件所在的包名要与AIDL中的包名一致,不能修改,不然无法工作.


通过BroadcastReceiver调用

好了,终于到最后一个方法了,写完就去吃饭嘿嘿.广播接收者是android四大组件之一,我们常常用它来进行组件通信的.觉得安卓最迷人的地方就是自定义控件放的开,还有事件传递机制很好,还有就是这个消息传递了,做的丰富多彩,去年寒假学它的时候一下就爱上它了.扯多了O(∩_∩)O~.我们在服务中自定义自己的广播接收者.

 class MyBroadcastReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if ("com.it.setviceNote.MyBroadcastReceiver".equals(action)){

            String command = intent.getStringExtra("command");
            if ("eat".equals(command)) {
                eat();
            }
            else if ("drink".equals(command)){

                drink();
            }
            else if ("sleep".equals(command)) {
                sleep();
            }
            else {

                showToast("参数错误");
            }
        }
    }
}

一个广播接收者可以有多个intentFilter,我们可以注册,发送不同的intentfilter,来执行不同的调用.广播接受者可以在清单文件中注册,也可以在代码中注册,一般一些列如开机的系统广播我们才在清单文件中配置,所以我们在service的onCreat中注册

@Override
public void onCreate() {
mToast = Toast.makeText(this, “参数错误”, Toast.LENGTH_SHORT);
mReceiver = new MyBroadcastReceiver();
IntentFilter filter = new IntentFilter(“com.it.setviceNote.MyBroadcastReceiver”);
registerReceiver(mReceiver,filter);
}

在service的onDestory中注销

@Override
public void onDestroy() {
if (mReceiver != null){
unregisterReceiver(mReceiver);
mReceiver = null;
}
super.onDestroy();
}

然后我们先启动服务,再我们的广播调用我们的方法吧

 Intent intent = new Intent("com.it.setviceNote.MyBroadcastReceiver");
 intent.putExtra("command","sleep");
 sendBroadcast(intent);

好了,终于弄完了,尽我所能去组织语言了,感觉还是表达的不够好,额,有错的地方,请大家指出.觉得我写的还行的可以转载到自己播客,这个版权声明我不知道怎么弄…..

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器