两个App通过AIDL传递数据

最近项目使用组件化开发进行重构,每个模块可以是独立的App也可以是Module依赖在主工程上,其中登录页面在用户模块中,此时在其他模块如果需要用到登录页面和用户数据,这里我采用的是AIDL进行通讯。
AIDL是Android的接口定义语言, 可以实现跨进程通讯。具体的可以看下官方给的解释。下面开始撸代码,并且把一些注意的坑整理一下。
* 目录
image.png

上图中Demo分为了两个App一个lib依赖, 其中aidl文件、实体类、和服务写在aidl_library中,然后oneApp和twoApp分别依赖此lib。下面开始仔细讲讲其中的使用方式。先从AIDL文件说起;⤴️

* AIDL文件

我这里通过AIDL接口传递的主要是一个自定义类型User,所以先创建User.class文件,需要实现序列化。⬇️

public class User implements Parcelable {

    public int userId;

    public String userName;

    public String userSex;

    public User() {
    }

    protected User(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
        userSex = 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) {
        dest.writeInt(userId);
        dest.writeString(userName);
        dest.writeString(userSex);
    }

    public void readFromParcel(Parcel in) {
        userId = in.readInt();
        userName = in.readString();
        userSex = in.readString();
    }
}

创建User.aidl文件,声明 parcelable User; (注意: 因为前面创建了User.class, 此时再创建User.aidl会提示名字冲突错误导致无法创建,可以先随便起个名字,创建完后再改成User)⬇️

// User.aidl
package app.demo.aidl_library;

parcelable User;

创建AIDL接口,IUserCallBack.aidl文件,这里定义了一个add方法和一个get方法;(注意: 由于我们是自定义类型,所在在addUser中我们必须指定in或者out或者inout具体的解释劳烦各位自己查一下,然后必须导入自定义类型import,否则无法识别),如果类型申明为inout,则需要在User.class中手动添加readFromParcel方法,因为这个方法无法自动生成。 ⬇️

// IUserCallBack.aidl
package app.demo.aidl_library;

// 手动导包
import app.demo.aidl_library.User;

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

interface IUserCallBack {

    /**
     * 申明为 inout 时需要手动在User.class添加readFromParcel()方法
     */
     void addUser(inout User user);

     User getUser();

}

最后我们需要一个Service来帮助实现建立桥梁,在Service中我们需要实现IuserCallBack接口然后和Service的onBind进行联系。

public class UserService extends Service {

    // 支持并发读写
    private CopyOnWriteArrayList<User> users = new CopyOnWriteArrayList<>();

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

    IUserCallBack.Stub mStub = new IUserCallBack.Stub() {
        @Override
        public void addUser(User user) throws RemoteException {
            users.clear();
            users.add(user);
        }

        @Override
        public User getUser() throws RemoteException {
            return users.get(0);
        }
    };

}

服务创建好了当然也不要忘记注册这个服务,在AndroidManifest.xml进行注册,因为跨进程所以属性exported为true,代表可以被其他appliction启动;还需要定义好启动服务的Action。

<service
            android:name="app.demo.aidl_library.UserService"
            android:enabled="true"
            android:exported="true"
            android:process=":remote">
            <intent-filter>
                <!-- 定义Action -->
                <action android:name="aidl_user_service" />
            </intent-filter>
        </service>

到此,AIDL的”服务端“算是完成了。
先总结一下其中主要的地方:

  1. 自定义类型需要实现Parcelable接口
  2. 名字冲突错误导致无法创建,可以先随便起个名字,创建完后再改成和AIDL文件名字一致。
  3. 如果类型申明为inout,则需要在User.class中手动添加readFromParcel方法。
  4. 注册Service时需要添加exported=true,并定义Action。
* 数据传递

完成后咱们就来开始实现两个App之间的数据传递。我们需要从App1跳转到App2,然后由App2来发送数据,所以首选要知道跳转到App2的哪个页面,然后在清单文件中在该Activity添加android:exported=true允许被其他Appliction调用(这种方式不建议, 最好通过定义uri的方式进行跳转)

<!-- App2 -->
<activity
      android:name=".MainTwoActivity"
      android:exported="true"/>

然后从App1编写跳转到该MainTwoActivity的代码 (这种方式不建议, 最好通过定义uri的方式进行跳转),

// 首先判断我们拉起(跳转)的第三方APP是否存在
if (!isApkInstalled("com.demo.twoapp")) {
     return;
}
Intent intent = new Intent(Intent.ACTION_MAIN);
// 参数1:目标包名 参数2:目标activity的具体路径
ComponentName componentName = new ComponentName("com.demo.twoapp", "com.demo.twoapp.MainTwoActivity");
intent.setComponent(componentName);
startActivity(intent);

//......分隔符.....

// 判断第三方APP是否存在
public boolean isApkInstalled(String packageName) {
     if (TextUtils.isEmpty(packageName)) {
         return false;
     }
     try {
         ApplicationInfo info = getPackageManager().getApplicationInfo(packageName, PackageManager.GET_UNINSTALLED_PACKAGES);
         return true;
     } catch (PackageManager.NameNotFoundException e) {
         e.printStackTrace();
         return false;
        }
    }

此时如果不出意外的话会正常打开App2的MainTwoActivity的页面, 点击该页面的按钮, 开始发送数据。
先new ServiceConnection可以取到一个IBinder对象, 通过该对象可以拿到我们的之前实现的IUserCallBack实例IUserCallBack callback = IUserCallBack.Stub.asInterface(service)。然后通过call可以调取接口的回调方法callBack.addUser(user);

private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            callBack = IUserCallBack.Stub.asInterface(service);
            try {
                User user = new User();
                user.userId = 101;
                user.userName = "张三";
                user.userSex = "男";
                callBack.addUser(user);
                Log.e("XXX", "发送数据  == " + user.toString());
                Toast.makeText(MainTwoActivity.this, "发送成功, 数据看Log日志", Toast.LENGTH_LONG).show();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

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

有了ServiceConnection我们可以开始绑定服务了,(绑定服务时需要注意的是:Action是注册Service时定义的,Package是发送端的包名),如果isBind返回false则代表服务绑定失败, 就需要查看Action是否和注册时一致,Package是否正确。

public void onClick(View v) {
    Intent intent = new Intent();
    intent.setAction("aidl_user_service"); //Action是注册Service时定义的
    intent.setPackage("com.demo.twoapp");  //Package是发送端的包名
    boolean isbind = bindService(intent, conn, BIND_AUTO_CREATE);
    Log.e("XXX", "发送数据 isbind == " + isbind);
}

App2的数据已经发送了, 那么我们回到App1中接收数据,接收数据也很简单,可以把代码copy过来,把addUser()改为getUser()

private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            callBack = IUserCallBack.Stub.asInterface(service);
            try {
                User user = callBack.getUser();
                if (user != null) {
                    Log.e("XXX", "接收数据  == " + user.toString());
                    Toast.makeText(MainOneActivity.this, "接收到数据, 数据看Log日志", Toast.LENGTH_LONG).show();
                }
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

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

绑定服务的代码可以copy过来, 一定要注意的是, 接收数据时Intent的Package也一定是发送端的包名,也就是App2的包名。不然bindService会返回false。

最后不要忘记在销毁页面时unbindService(conn);

  • 纠结的地方:App2发送数据后页面关闭时调用onDestroy,在该回调中调用unbindService(conn), 会导致App1接收的数据为null。切记切记...

再总结一下数据传递时容易发生的坑

  1. 目标Activity要exported=true (建议使用uri方式)
  2. Action是注册Service时定义的
  3. 不管在哪个App绑定service, intent的Package一定是发送数据端的包名。
  4. bindService返回true时多检查action和package。
* 完毕。
image.png

App2发送数据

image.png

App1接收数据

demo地址

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