Service和AIDL基本使用

在讲解AIDL之前,我们需要梳理一下Service的使用,Service是Android系统中的四大组件之一,主要有两个应用场景:后台运行和跨进程访问。Service可以在后台执行长时间运行操作而不提供用户界面,除非系统必须回收内存资源,否则系统不会停止或销毁服务。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。
需要注意的是:
(1)Service是在主线程里执行操作的,可能会因为执行耗时操作而导致ANR,所以耗时操作可以在Service中开一个子线程来处理;
(2)Service可能会被系统回收,在使用Service时别忘了判断服务是否正在运行;

一、Service的三种使用方式

图片.png
(1)使用startService启动服务,使用stopService停止服务

1.定义MyService

public class MyService extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d("yunchong", "====onBind===");
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("yunchong", "====onCreate==="+MyService.class.getName());
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("yunchong", "====onStartCommand===");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("yunchong", "====onDestroy===");
    }
}

服务的主要逻辑一般在onStartCommand执行。

2.在AndroidManifest注册服务

<service android:name=".MyService"/>

这里可以配置service的一些配置,本文中不做解释。

3.启动服务和停止服务

public class MainActivity extends AppCompatActivity {

    private Button button1, button2, button3, button4;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();

        setOnClick();
    }

    private void initView(){
        button1 = findViewById(R.id.button1);
        button2 = findViewById(R.id.button2);
        button3 = findViewById(R.id.button3);
        button4 = findViewById(R.id.button4);
    }

    private void setOnClick(){

        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //启动服务
                startService(new Intent(MainActivity.this, MyService.class));
            }
        });

        button4.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //停止服务
                stopService(new Intent(MainActivity.this, MyService.class));
            }
        });
    }
}

4.Service的生命周期
第一次启动服务:onCreate-->onStartCommand
非第一次启动服务:onStartCommand
停止服务:onDestroy

这是最简单的使用方式。

(2)使用bindService绑定服务,使用unbindService解绑服务

1.定义MyService

public class MyService extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d("yunchong", "====onBind===");
        return new MyBind();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("yunchong", "====onCreate==="+MyService.class.getName());
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d("yunchong", "====onUnbind===");
        return super.onUnbind(intent);
    }

    @Override
    public void onRebind(Intent intent) {
        Log.d("yunchong", "====onRebind===");
        super.onRebind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("yunchong", "====onDestroy===");
    }

    public class MyBind extends Binder{

        //加法运算
        public int addOperation(int a, int b){
            return a + b;
        }
        //减法运算
        public int subOperation(int a, int b){
            return a - b;
        }
    }
}

服务的主要逻辑由MyBind对象来实现, Activity可以调用该对象中的方法实现某功能,而且还可以将返回值返回给客户端, 这样更加方便的实现了Activity和Service的交互。

2.在AndroidManifest注册服务

<service android:name=".MyService"/>

这里可以配置service的一些配置,本文中不做解释。

3.绑定服务和解绑服务服务

第一次点击“绑定服务”,执行顺序是:onCreate-->onBind-->onServiceConnected回调方法
非第一次点击“绑定服务”,则不执行以上方法, 官方的解释是,一个客户端只能绑定一次。
点击“解绑服务”,则解除绑定,执行顺序是onUnbind-->onDestroy,这时再点击“绑定服务”就相当于第一次点击“绑定服务”了。
第一种直接启动的方式,服务的业务逻辑一般都是在onStartCommand中处理的,而绑定服务方式的业务逻辑都是自定义的Binder对象中,本文定义了MyBind对象, 有加法和减法的逻辑, 那么怎么运用这两个业务呢?

public class MainActivity extends AppCompatActivity {

    private Button button2, button3, button5, button6;
    private TextView addText, subText;
    private MyService.MyBind myService;
    private boolean isRegistered = false;//服务是否被注册

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d("yunchong", "onServiceConnected");
            myService = (MyService.MyBind)service;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            myService = null;
            Log.d("yunchong", "服务已经断开了连接!");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();

        setOnClick();
    }

    private void initView(){
        button2 = findViewById(R.id.button2);
        button3 = findViewById(R.id.button3);
        button5 = findViewById(R.id.button5);
        button6 = findViewById(R.id.button6);
        addText = findViewById(R.id.addText);
        subText = findViewById(R.id.subText);
    }

    private void setOnClick(){

        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //绑定服务,如果返回true,则说明服务已经被注册
                isRegistered = bindService(new Intent(MainActivity.this, MyService.class), connection, BIND_AUTO_CREATE);
            }
        });

        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(isRegistered){
                    //解绑服务,第一次接触绑定服务会被销毁,如果解除一个已被销毁的服务,那么将会报错
                    unbindService(connection);
                    isRegistered = false;                    }
            }
        });

        button5.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //加法运算
                if(myService != null){
                    addText.setText(String.valueOf(myService.addOperation(10, 2)));
                }
            }
        });

        button6.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //减法运算
                if(myService != null){
                    subText.setText(String.valueOf(myService.subOperation(10, 2)));
                }
            }
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(isRegistered){
            //解绑服务
            unbindService(connection);
            isRegistered =false;
        }
    }
}

通过ServiceConnection的onServiceConnected方法回调获取MyBind对象, 然后用该对象调用对象中的业务。

看图:


23.gif

4.Service的生命周期
第一次启动服务:onCreate-->onBind
非第一次启动服务:无生命周期
停止服务: onUnbind-->onDestroy

(3)startService和bindService混用
图片.png

如图, 新增按钮“启动且绑定服务”和“解绑服务”。

关键代码如下:

//启动且绑定服务
Intent intent = new Intent(MainActivity.this, MyService.class);
startService(intent);//启动服务
isRegistered = bindService(intent, connection, BIND_AUTO_CREATE);//绑定服务,如果返回true,则说明服务已经被注册

生命周期是:
第一次点击“启动且绑定”按钮:onCreate-->onStartCommand-->onBind
非第一次点击“启动且绑定”按钮:onStartCommand

解除绑定服务

if(isRegistered){
     //解绑服务
     unbindService(connection);
     isRegistered =false;
}

在启动并绑定服务的前提下,点击“解绑服务”按钮:onUnbind
在解绑服务的前提下,点击“停止服务”按钮:onDestroy

如果使用这种方式, 只有先unbindService再stopService才能真正销毁服务,所以从启动服务到停止服务的流程是:

startService-->bindService-->unbindService-->stopService

最后介绍一下Service的onRebind回调,上面说了那么多,onRebind一直没有用到。
首先我们改写一下Service的onUnbind回调方法

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d("yunchong", "====onUnbind===");
        return super.onUnbind(intent);
    }

改成

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d("yunchong", "====onUnbind===");
        return true;
    }

然后,当操作顺序是启动-->绑定-->解绑-->绑定时,onRebind将被执行。

也就是说,onRebind的执行条件是:
(1)Service类中的onUnbind回调返回值为true;
(2)服务依然是启动状态,没有被销毁,并且服务二次绑定的时候;

二、AIDL基本使用

AIDL 意思即 Android Interface Definition Language,翻译过来就是Android接口定义语言,是用于定义服务器和客户端通信接口的一种描述语言,可以拿来生成用于IPC的代码。从某种意义上说AIDL其实是一个模板,因为在使用过程中,实际起作用的并不是AIDL文件,而是据此而生成的一个IInterface的实例代码,AIDL其实是为了避免我们重复编写代码而出现的一个模板
设计AIDL这门语言的目的就是为了实现进程间通信。在Android系统中,每个进程都运行在一块独立的内存中,在其中完成自己的各项活动,与其他进程都分隔开来。可是有时候我们又有应用间进行互动的需求,比较传递数据或者任务委托等,AIDL就是为了满足这种需求而诞生的。通过AIDL,可以在一个进程中获取另一个进程的数据和调用其暴露出来的方法,从而满足进程间通信的需求
通常,暴露方法给其他应用进行调用的应用称为服务端,调用其他应用的方法的应用称为客户端,客户端通过绑定服务端的Service来进行交互。

需要注意的是:

(1)AIDL文件以 .aidl 为后缀名
(2)AIDL支持的数据类型分为如下几种:
八种基本数据类型:byte、char、short、int、long、float、double、boolean
String,CharSequence
实现了Parcelable接口的数据类型
List 类型。List承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
Map类型。Map承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
(3)AIDL文件可以分为两类。一类用来声明实现了Parcelable接口的数据类型,以供其他AIDL文件使用那些非默认支持的数据类型。还有一类是用来定义接口方法,声明要暴露哪些接口给客户端调用,定向Tag就是用来标注这些方法的参数值
定向Tag。定向Tag表示在跨进程通信中数据的流向,用于标注方法的参数值,分为 in、out、inout 三种。其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。此外,如果AIDL方法接口的参数值类型是:基本数据类型、String、CharSequence或者其他AIDL文件定义的方法接口,那么这些参数值的定向 Tag 默认是且只能是 in,所以除了这些类型外,其他参数值都需要明确标注使用哪种定向Tag。
(4)明确导包。在AIDL文件中需要明确标明引用到的数据类型所在的包名,即使两个文件处在同个包名下。

要想实现AIDL,需要准备两个进程, 一个作为服务端进程,一个作为客户端进程。

(1)我们新建项目,搭建服务端代码

由于服务进程是另起一个单独的app, 所以我们需要做到以下几点:

* 隐藏桌面图标
    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:theme="@style/TranslucentTheme">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
                <data
                    android:host="myhost"
                    android:scheme="myscheme"/>
                <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

核心代码是:

                <data
                    android:host="myhost"
                    android:scheme="myscheme"/>

只要在AndroidManifest中添加以上代码就可以隐藏桌面图标。

* 启动该app时界面完全透明

去除Activity默认的布局代码

setContentView(R.layout.activity_main);//直接删掉,不要了

在配置文件中设置当前Activity为透明主题

    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:theme="@style/TranslucentTheme">


<style name="TranslucentTheme" parent="Theme.AppCompat.NoActionBar">
    <item name="android:windowBackground">@android:color/transparent</item>
    <item name="android:colorBackgroundCacheHint">@null</item>
    <item name="android:windowIsTranslucent">true</item>
    <item name="android:windowAnimationStyle">@android:style/Animation</item>
    <item name="android:windowNoTitle">true</item>
    <item name="android:windowActionBar">false</item>
    <item name="android:statusBarColor">@android:color/transparent</item>
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowContentOverlay">@null</item>
</style>
* 让当前Activity可以被其他应用调用
图片.png

准备工作做好之后, 开始进入正题:
(1)服务端目录结构


图片.png

(2)新建People对象,让他支持Parcelable

public class People implements Parcelable {

    private String name;

    public People(Parcel in) {
        this.name = in.readString();
    }

    public People(String name) {
        this.name = name;
    }

    public People() {
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
    }

    public void readFromParcel(Parcel dest) {
        name = dest.readString();
    }

    public static final Creator<People> CREATOR = new Creator<People>() {
        @Override
        public People createFromParcel(Parcel source) {
            return new People(source);
        }

        @Override
        public People[] newArray(int size) {
            return new People[size];
        }
    };

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                '}';
    }
}

(3)新建People.aidl文件, 用来声明Parcelable接口的数据类型(AS可以直接右击新建)

package com.jiangxi.login.servicedemo;

parcelable People;

(4)新建IMyAidlInterface.aidl文件(AS可以直接右击新建)

package com.jiangxi.login.servicedemo;

import com.jiangxi.login.servicedemo.People;//这个报名必须引用,否则编译报错

interface IMyAidlInterface {

    int addOperation(int a, int b);//加法运算,此时默认TAG是in

    double multiplicationOperation(in double a, in double b);//乘法运算

    String getPeopleInfo1(in People people);//in只允许客户端传递数据到服务端

    String getPeopleInfo2(out People people);//out只允许服务端把数据传递到客户端

    String getPeopleInfo3(inout People people);//inout双向通道,既允许服务端把数据传递到客户端,也允许客户端传递数据到服务端

}
  • 声明加法运算addOperation接口,用于验证AIDL是否支持int类型;
  • 声明乘法运算multiplicationOperation接口,用于验证AIDL是否支持double 类型;
  • 声明getPeopleInfo1、getPeopleInfo2、getPeopleInfo3接口,用于验证AIDL是否支持对象类型;
  • 声明in、out、inout三种定向Tag,用于验证三者之间的区别;

(5)新建服务类

public class MyService extends Service {

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Log.d("yunchong", "====onBind===");
        return new MyBind();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("yunchong", "====onCreate==="+MyService.class.getName());
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d("yunchong", "====onStartCommand===");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d("yunchong", "====onDestroy===");
    }

    public class MyBind extends IMyAidlInterface.Stub{

        @Override
        public int addOperation(int a, int b) throws RemoteException {
            return a + b;
        }

        @Override
        public double multiplicationOperation(double a, double b) throws RemoteException {
            return a * b;
        }

        @Override
        public String getPeopleInfo1(People people) throws RemoteException {
            Log.d("yunchong", "getPeopleInfo1:"+people.toString());
            people.setName("服务端赋值--getPeopleInfo1");
            return people.toString();
        }

        @Override
        public String getPeopleInfo2(People people) throws RemoteException {
            Log.d("yunchong", "getPeopleInfo2:"+people.toString());
            people.setName("服务端赋值--getPeopleInfo2");
            return people.toString();
        }

        @Override
        public String getPeopleInfo3(People people) throws RemoteException {
            Log.d("yunchong", "getPeopleInfo3:"+people.toString());
            people.setName("服务端赋值--getPeopleInfo3");
            return people.toString();
        }
    }
}

定义MyBind内部类,用来具体实现那些接口。

(6)注册服务类

    <service
        android:name=".MyService"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="com.jiangxi.login.servicedemo.aidldemo" />
        </intent-filter>
    </service>

(7)第一启动activity的处理

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        startService();
    }

    private void startService(){
        final Intent intent = new Intent(MainActivity.this, MyService.class);
        //启动服务
        startService(intent);
        finish();
    }
}

它的作用就是:当该app启动时,自动开启在后台运行的service。

(2)我们再新建项目,搭建客户端代码

(1)布局展示


图片.png

(2)将服务端的两个aidl文件复制到客户端项目,且要求路劲一模一样

图片.png

(3)将服务端定义的People类拷贝到客户端,内容不变,路劲也不变(如上图)。

(4)调用

public class MainActivity extends AppCompatActivity {

    private Button button1, button2, button3, button4, button5, button6, button7;

    private IMyAidlInterface iMyAidlInterface;

    private String servicePackname = "com.jiangxi.login.servicedemo";

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            iMyAidlInterface = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();

        setOnClick();

    }

    /**
     * 启动服务
     */
    private void startMyService(){

        if (checkPackInfo(servicePackname)) {
            Intent intent = new Intent(Intent.ACTION_MAIN);
            ComponentName componentName = new ComponentName(servicePackname, servicePackname+".MainActivity");
            intent.setComponent(componentName);
            try {
                startActivity(intent);
            }catch (ActivityNotFoundException e){
                e.printStackTrace();
            }
        }else{
            Log.d("yunchong", "没有安装服务程序!");
        }
    }

    private void initView(){
        button1 = findViewById(R.id.button1);
        button2 = findViewById(R.id.button2);
        button3 = findViewById(R.id.button3);
        button4 = findViewById(R.id.button4);
        button5 = findViewById(R.id.button5);
        button6 = findViewById(R.id.button6);
        button7 = findViewById(R.id.button7);
    }

    private void setOnClick(){

        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //启动服务
                startMyService();
                Toast.makeText(MainActivity.this, "启动服务!", Toast.LENGTH_SHORT).show();
            }
        });

        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //绑定服务

                final Intent intent = new Intent();
                intent.setAction("com.jiangxi.login.servicedemo.aidldemo");
                intent.setPackage("com.jiangxi.login.servicedemo");
                if(isServiceRunning(MainActivity.this, "com.jiangxi.login.servicedemo.MyService")){
                    bindService(intent, connection, BIND_AUTO_CREATE);
                }else{
                    startService(intent);
                    bindService(intent, connection, BIND_AUTO_CREATE);
                }
                Toast.makeText(MainActivity.this, "绑定服务!", Toast.LENGTH_SHORT).show();
            }
        });

        button3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //加法计算
                try {
                    Toast.makeText(MainActivity.this, "结果:"+iMyAidlInterface.addOperation(10, 2), Toast.LENGTH_SHORT).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        button4.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //乘法计算
                try {
                    Toast.makeText(MainActivity.this, "结果:"+iMyAidlInterface.multiplicationOperation(10.0, 2.0), Toast.LENGTH_SHORT).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        button5.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //获取人的信息1
                try {
                    People people = new People("张三");
                    Toast.makeText(MainActivity.this, "获取返回值信息1:"+iMyAidlInterface.getPeopleInfo1(people), Toast.LENGTH_SHORT).show();
                    Toast.makeText(MainActivity.this, "获取对象信息1:"+people.toString(), Toast.LENGTH_SHORT).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        button6.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //获取人的信息2
                try {
                    People people = new People("李四");
                    Toast.makeText(MainActivity.this, "获取返回值信息2:"+iMyAidlInterface.getPeopleInfo2(people), Toast.LENGTH_SHORT).show();
                    Toast.makeText(MainActivity.this, "获取对象信息2:"+people.toString(), Toast.LENGTH_SHORT).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        button7.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //获取人的信息3
                try {
                    People people = new People("王五");
                    Toast.makeText(MainActivity.this, "获取返回值信息3:"+iMyAidlInterface.getPeopleInfo3(people), Toast.LENGTH_SHORT).show();
                    Toast.makeText(MainActivity.this, "获取对象信息3:"+people.toString(), Toast.LENGTH_SHORT).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }


    /**
     * 判断服务是否开启
     * @return
     */
    public static boolean isServiceRunning(Context context, String ServiceName) {
        if (TextUtils.isEmpty(ServiceName)){
            return false;
        }
        ActivityManager myManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        ArrayList<ActivityManager.RunningServiceInfo> runningService = (ArrayList<ActivityManager.RunningServiceInfo>) myManager.getRunningServices(30);
        for (int i = 0; i < runningService.size(); i++) {
            if (runningService.get(i).service.getClassName().toString().equals(ServiceName)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 检查安装包是否存在
     *
     * @param packname
     * @return
     */
    private boolean checkPackInfo(String packname) {
        PackageInfo packageInfo = null;
        try {
            packageInfo = getPackageManager().getPackageInfo(packname, 0);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return packageInfo != null;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

我们来依次点击这几个按钮:
1.启动服务并绑定服务


24.gif

测试接口之前,必须做的两个操作

2.加法运算


25.gif

计算结果是12,完全正确。

3.乘法运算


26.gif

计算结果是20.0,完全正确。

4.分别点击“获取人的信息1”、“获取人的信息2”、“获取人的信息3”

服务端的日志如下


图片.png

这就是AIDL的定向tag的神奇之处了,这里有必要说明一下三种定向tag的作用:

定向tag是针对于方法的形参来说的, 如以下代码:

    String getPeopleInfo1(in People people);//in只允许客户端传递数据到服务端

    String getPeopleInfo2(out People people);//out只允许服务端把数据传递到客户端

    String getPeopleInfo3(inout People people);//inout双向通道,既允许服务端把数据传递到客户端,也允许客户端传递数据到服务端
  • in:只允许客户端传递数据到服务端
  • out:只允许服务端把数据传递到客户端
  • inout:双向通道,既允许服务端把数据传递到客户端,也允许客户端传递数据到服务端

其中getPeopleInfo2方法中的形参,它的定向tag修饰符是out,客户端传递给服务端的对象是没有值的。

我们来点击“获取人的信息1”按钮:


27.gif

对应的客户端代码如下:

People people = new People("张三");
Toast.makeText(MainActivity.this, "获取返回值信息1:"+iMyAidlInterface.getPeopleInfo1(people), Toast.LENGTH_SHORT).show();
Toast.makeText(MainActivity.this, "获取对象信息1:"+people.toString(), Toast.LENGTH_SHORT).show();

对应的服务端代码如下:

        @Override
        public String getPeopleInfo1(People people) throws RemoteException {
            Log.d("yunchong", "getPeopleInfo1:"+people.toString());
            people.setName("服务端赋值--getPeopleInfo1");
            return people.toString();
        }

现象:如果定向tag为int,返回值变了,但是原本对象没变。

我们来点击“获取人的信息2”按钮:


28.gif

对应的客户端代码如下:

People people = new People("李四");
Toast.makeText(MainActivity.this, "获取返回值信息2:"+iMyAidlInterface.getPeopleInfo2(people), Toast.LENGTH_SHORT).show();
Toast.makeText(MainActivity.this, "获取对象信息2:"+people.toString(), Toast.LENGTH_SHORT).show();

对应的服务端代码如下:

        @Override
        public String getPeopleInfo2(People people) throws RemoteException {
            Log.d("yunchong", "getPeopleInfo2:"+people.toString());
            people.setName("服务端赋值--getPeopleInfo2");
            return people.toString();
        }

现象:如果定向tag为out,返回值变了,原本对象也变了。

我们来点击“获取人的信息3”按钮:

29.gif

对应的客户端代码如下:

People people = new People("王五");
Toast.makeText(MainActivity.this, "获取返回值信息3:"+iMyAidlInterface.getPeopleInfo3(people), Toast.LENGTH_SHORT).show();
 Toast.makeText(MainActivity.this, "获取对象信息3:"+people.toString(), Toast.LENGTH_SHORT).show();

对应的服务端代码如下:

        @Override
        public String getPeopleInfo3(People people) throws RemoteException {
            Log.d("yunchong", "getPeopleInfo3:"+people.toString());
            people.setName("服务端赋值--getPeopleInfo3");
            return people.toString();
        }

现象:如果定向tag为intout,返回值变了,但是原本对象也变了。

总结:

到目前为止AIDL的使用已经讲解完毕了, 但是很多地方都没有讲解清楚,因为真正的讲解清楚需要大量的篇幅,网上的大部分博客其实都没有讲解清楚。这里说几个注意点,剩下的大家就自己体会了:
(1)Service的使用方法有三种,大家根据具体情况选择其中一种就可以了
第一种通过startService启动服务,处理的业务逻辑主要是在onStartCommand;其启动到停止的生命周期是:
onCreate-->onStartCommand-->onDestroy
第二种通过绑定的方式,处理的业务逻辑主要是自定义的Binder对象里的方法;其绑定到解绑的生命周期是:
onCreate-->onBind-->onUnbind-->onDestroy
第三种两种结合的方式主要是涉及到onRebind回调以及,其启动并绑定到解绑并停止的生命周期是:
onCreate-->onStartCommand-->onBind-->onUnbind-->onRebind-->onUnbind-->onDestroy(只是其中的一种情况)

(2)AIDL其实就是客户端和服务端的交互,两者之间所支持的类型上文已经说明了, 尤其要注意的是Parcelable类型;

(3)注意aidl中的导包,比如“import com.jiangxi.login.servicedemo.People;”, 这个必须加,即使People和aidl文件在同一个目录下也加,否则编译失败;

(4)文章中额外讲解了一个app怎么隐藏图标,怎么让app启动透明,让用户看不出效果;

(5)文章是通过检查服务app是否安装,检查服务是否启动,如果没有启动服务,则直接启动该服务app。这样可以保证service正常工作。和service保活没半毛线关系,所以大家不要误解了;

(6)文章中重点介绍了定向tag,本人也是重点研究之后才敢写这个博客的,经过不断的调试代码,得出的结论是:
1.in、out、inout只是用来修饰接口形参的,不能修饰返回值;
2.in:只允许客户端传递数据到服务端;
3.out:只允许服务端把数据传递到客户端,服务端传递数据到客户端不是通过返回值来传递的,传递数据的载体是对象本身;
4.inout:就是双向传递数据,没毛病;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351