为了账号安全,请及时绑定邮箱和手机立即绑定

Android AIDL Service 跨进程传递复杂数据

标签:
Java Android
黑夜

黑夜给了我黑色的眼睛,我却用它寻找光明~

传值方式

AIDL是允许跨进程传递值的,一般来说有三种方式:

  • 广播;这种算是比较常见的一种方式了,传递小数据不错
  • 文件;这个是保存到文件中,然后读取,传递大数据不错
  • Service Bind模式;这个算是居中的一种方式,不过效率要高的多,唯一麻烦的是编写代码较为麻烦。特别是复杂类型数据传递麻烦。
    其是,还有一些其他的办法进行数据传递,另外传递也并不是只可以使用一种,可以采用几种结合的方式进行。
    今天要说的就是Service Bind进行复杂数据的传递。

传递类型

在AIDL中之所以使用Bind进行传递会比较麻烦是因为:其在跨进程的情况下只允许传递如下类型数据:

  • String
  • CharSequence
  • android.os.Parcelable
  • java.util.List
  • java.util.Map

虽然可以使用 List与Map但是其类型一样不能使用复杂类型;当然上面 int、long、bool就没有单独写出来了。
如过要进行复杂数据传递,如传递User类的实例,此时就要使用Parcelable来辅助完成。

简单流程

图片描述

其调用方式依然为:绑定服务->得到目标服务的Binder->调用对应方法
跨进程传递数据麻烦就在于打包/解包Parcelable的操作。

目标:开启一个独立进程的服务,在主进程中绑定目标服务,调用服务的方法。
实现:将需要进行复杂传递的数据类,继承Parcelable,并实现其中的序列化与反序列化方法。

传递类

传递类包括两个:User.java、User.aidl
其中java类是具体的实现,aidl文件仅仅只是用于对java文件的声明。告知进程其可以看作Parcelable处理。

User.Java
package net.qiujuer.sample.service.bean;

import android.os.Parcel;
import android.os.Parcelable;

import java.util.UUID;

/**
 * Created by qiujuer on 15/7/15.
 */
public class User implements Parcelable {
    private UUID id;
    private int age;
    private String name;

    public User(int age, String name) {
        this.age = age;
        this.name = name;
        this.id = UUID.randomUUID();
    }

    protected User(Parcel in) {
        // Id
        long m = in.readLong();
        long l = in.readLong();
        id = new UUID(m, l);

        age = in.readInt();
        name = in.readString();
    }

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

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

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        // ID
        long m = id.getMostSignificantBits();
        long l = id.getLeastSignificantBits();
        dest.writeLong(m);
        dest.writeLong(l);

        dest.writeInt(age);
        dest.writeString(name);
    }

    @Override
    public String toString() {
        return "Id:" + id.toString() + " Age:" + age + " Name:" + name;
    }
}

在类中,包含三个属性,两个基本类型,一个UUID,对于基本类型可以直接序列化,而UUID则不能,此时两个方案,一种是把UUID看作String进行处理,当解包时则把String转换为UUID即可。
第二种则是得到其中关键的数据,分别是两个long值,然后对long值进行传递,并反序列化。

在类中,我们实现了Parcelable接口,则需要完成两个方法与一个静态值操作。

  • describeContents 为描述方法,通常返回0
  • writeToParcel 具体的写入操作,在这里对需要传输的数据进行写入,请一定需要注意的是写入顺序则决定了读取顺序
  • CREATOR 此静态属性,则是为了反编译时使用,在Parcelable进行反编译时将会调用该属性,所以名称写法基本是固定不变的。
  • User 在该类的构造函数中,我们对其进行了反序列化读取数据操作。
User.aidl
// User.aidl
package net.qiujuer.sample.service.bean;

parcelable User;

在该文件中,只需要两行代码就OK,一个指定包名,一个为parcelable申明。

# Service类
这个部分主要包含两个文件,一个AIDL申明文件

IServiceAidlInterface.aidl
// IServiceAidlInterface.aidl
package net.qiujuer.sample.service;
import net.qiujuer.sample.service.bean.User;

// Declare any non-default types here with import statements

interface IServiceAidlInterface {
    void addAge();
    void setName(String name);
    User getUser();
}

在该文件中,定义了一个接口,其中有一个方法getUser(),该方法返回服务中的User类的实例;当然需要使用该类所以需要加上import net.qiujuer.sample.service.bean.User;

IndependentService.java
package net.qiujuer.sample.service;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;

import net.qiujuer.sample.service.bean.User;

public class IndependentService extends Service {
    private ServiceBinder mBinder;

    public IndependentService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        if (mBinder == null)
            mBinder = new ServiceBinder();
        return mBinder;
    }

    class ServiceBinder extends IServiceAidlInterface.Stub {
        private User user;

        public ServiceBinder() {
            user = new User(21, "XiaoMing");
        }

        @Override
        public void addAge() throws RemoteException {
            user.setAge(user.getAge() + 1);
        }

        @Override
        public void setName(String name) throws RemoteException {
            user.setName(name);
        }

        @Override
        public User getUser() throws RemoteException {
            return user;
        }
    }

}

该类为具体的服务实现,在该服务中实现了服务接口,并进行了简单实现。

文件梳理与配置
文件结构

这里写图片描述

独立进程配置

由于我们需要让服务为独立进程,所以需要在AndroidManifest文件中Service申明的地方加上process属性:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="net.qiujuer.sample.service">

    <application
        android:allowBackup="true"
        android:label="@string/app_name">
        <service
            android:name=".IndependentService"
            android:enabled="true"
            android:exported="true"
            android:permission="1000"
            android:process=":AidlService" />
    </application>

</manifest>

在这里我设置的是:android:process=":AidlService"

使用

在APP Model中,我建立了一个MainActivity,并在其中调用服务的方法。

核心代码
public class MainActivity extends AppCompatActivity {
    private final String TAG = this.getClass().getSimpleName();
    private TextView mText;

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

        mText = (TextView) findViewById(R.id.txt_str);

        bindService();
    }

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

    private IServiceAidlInterface mService = null;

    private ServiceConnection mConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mService = IServiceAidlInterface.Stub.asInterface(iBinder);
            if (mService != null)
                run();
            else
                showText("Bind Error.");
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mService = null;
        }
    };

    private void bindService() {
        // UnBind
        unBindService();

        Intent intent = new Intent(this, IndependentService.class);
        bindService(intent, mConn, Context.BIND_AUTO_CREATE);
    }

    private void unBindService() {
        // Service
        IServiceAidlInterface service = mService;
        mService = null;
        if (service != null) {
            unbindService(mConn);
        }
    }

    private void run() {
        User user = null;
        try {
            user = mService.getUser();
            showText(user.toString());

            mService.addAge();
            user = mService.getUser();
            showText(user.toString());

            mService.setName("FangFang");
            user = mService.getUser();
            showText(user.toString());

        } catch (RemoteException e) {
            e.printStackTrace();
            showText(e.toString());
        }
    }

    private void showText(String str) {
        Log.d(TAG, str);
        mText.append("\n");
        mText.append(str);
    }
}
打印日志

MainActivity﹕ Id:cdfb5bb4-7674-4a8a-b754-92256dfea8f4 Age:21 Name:XiaoMing
MainActivity﹕ Id:cdfb5bb4-7674-4a8a-b754-92256dfea8f4 Age:22 Name:XiaoMing
MainActivity﹕ Id:cdfb5bb4-7674-4a8a-b754-92256dfea8f4 Age:22 Name:FangFang

进程

这里写图片描述

可以看出,服务的进程的确是独立于主进程的。

引申

在这里,我们是传递的一个User,假如传递的User中又包含一个Account类呢?Account中又包含其他的类呢?这个该如何办?

有两种办法:
第一种:使用上面UUID传递类似的方式,得到其中的核心数据,然后传递,在解包时进行还原。
第二种:将类也实现Parcelable接口,这样就能完美的解决了。

在这里简单写一下第二种的代码:
Account.java

public class Account implements Parcelable {
    private String name;

    protected Account(Parcel in) {
        name = in.readString();
    }

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

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

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

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

User.java
这里只写改动部分;其实这些改动的东西,完全可以借助编译工具自动生成,你只需要写好属性,然后继承接口;然后让编译工具帮助你完成接口对应方法就OK。

public class User implements Parcelable {
    private Account account;

    protected User(Parcel in) {
        ...
        account = in.readParcelable(Account.class.getClassLoader());
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        ...
        dest.writeParcelable(account, flags);
    }
}

另外需要注意的是,在aidl中申明类,仅仅只需要申明aidl接口中需要传递的类就OK,在这里直接传递的类只有User,所以只需要写一个User.aidl文件就OK,就算User中包含了Account类,但不需要写Account.aidl文件来申明。

点击查看更多内容
12人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
移动开发工程师
手记
粉丝
1.8万
获赞与收藏
774

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消