AIDL 应用间简单通信demo及基本问题

AIDL:Android Interface Definition Language,即Android接口定义语言。

他的作用大家已经知道了,有些童鞋对于其中的使用细节可能会有一些理解误差,并且会造成一些异常或者通讯失败。

我们先看几个关键点再看代码,如果项目不符合这几点,肯定会造成通讯失败或异常:

        1、客户端、服务端的aidl文件必须包名一致,以下错误就是这个问题引起的,

java.lang.SecurityException: Binder invocation to an incorrect interface

       2、可以自定义类型

        但是aidl路径中的类型文件,比如main/aidl包下的一个类型文件com.demo.aidl.bean.TestBean.aidl,必须在main/java 包下创建一个同路径的文件,并且实现Parcelable接口,也就是com.demo.aidl.bean.TestBean.java。TestBean.aidl文件内只需要写两行代码

第一行跟普通java文件一样,文件路径,第二行相当于一个声明

package com.jiao.aidlservicedemo.bean;

parcelable TestBean;

 Java同路径文件内容则是跟平常写代码一样,

package com.jiao.aidlservicedemo.bean;

import android.os.Parcelable;

public class TestBean implements Parcelable {
...
}

3、 绑定服务:

        因为是不同项目,跨进程了,并且提供服务端Service(Sevice通讯核心组件)代码也不在用一项目中,所以要注意绑定服务的方式

Intent intent = new Intent();
intent.setComponent(new ComponentName("com.jiao.aidlservicedemo", "com.jiao.aidlservicedemo.TalksService")); 
intent.setAction("com.jiao.aidlservicedemo.TalksService.aidl");
bindService(intent, conn, BIND_AUTO_CREATE);

ComponentName 里面两个参数

        Param1:所要通讯的项目(Service端)包名,也就是applicationId,一般与AndroidManifest.xml 中最外层标签 <manifest package="com.jiao.aidlclientdemo">中的package的值相同,但是如果在module里面的build.gradle中 applicationId 的值与项目包名不一致,则使用applicationId。

        Param2:Service端的Service 组件的路径+文件名(无后缀)

Param3:setAction

这个是组件Service在清单文件AndroidManifest.xml中注册时的<action android:name=“”>,如:

 <service
            android:name=".TalksService"
            android:enabled="true"
            android:exported="true">

            <intent-filter>
                <action android:name="com.jiao.aidlservicedemo.TalksService.aidl" />
            </intent-filter>
        </service>
enabled、exported,两个属性要为true

4、Android 11 及以上系统 跨应用通讯

        想通讯需要让客户端知道手机上是否有服务端这项目,此时需要在客户端的清单文件中注册一下权限:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jiao.aidlclientdemo">
    <!--    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"-->
    <!--        tools:ignore="QueryAllPackagesPermission" />-->
    <queries>
        <package android:name="com.jiao.aidlservicedemo" />
    </queries>
<application>
...
</application>

两种方法 ,第一种获取手机中安装APP列表权限,第二种比较安全一点,只是用<queries>申请一下服务端这个项目的安装情况<queries> 中可以写多个包名,里面package 的值,是Service所在的包名或者applicationId。

Demo代码

        项目 结构图  (ITalkAidlInterface.aidl为aidl核心类,IServiceCallback.aidl回调接口,TalkContent.aidl数据模型)

        

 ITalkAidlInterface.aidl

// ITalkAidlInterface.aidl
package com.jiao.aidlservicedemo;

// Declare any non-default types here with import statements
import com.jiao.aidlservicedemo.bean.TalkContent;
import com.jiao.aidlservicedemo.IServiceCallback;

interface ITalkAidlInterface {
       /**
        *   接收消息
        */
        void leavingMessage(in TalkContent talk);

         /**
          *
          * 提供消息
          */
        void getAllTalks( IServiceCallback iServicecallBack);
}

        IServiceCallback.aidl

// IServiceCallback.aidl
package com.jiao.aidlservicedemo;

interface IServiceCallback {

    void taks(inout Map talks);
}

 TalkContent.aidl

// TalkContent.aidl
package com.jiao.aidlservicedemo.bean;

parcelable TalkContent;

以上是aidl代码,客户端服务端都一样,可以直接复制过去,aidl部分写完之后,需要 编译一下项目 Make Prokect,自动生成一些文件到build中,然后开始写业务代码。

 TalkContent

package com.jiao.aidlservicedemo.bean;

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

/**
 * @author: jiaojunfeng
 * @date: 1/10/23
 * @describe:
 */
public class TalkContent implements Parcelable {
    private String talkContent;
    private String id;
    private String name;

    public TalkContent(String talkContent, String id, String name) {
        this.talkContent = talkContent;
        this.id = id;
        this.name = name;
    }

    public String getTalkContent() {
        return talkContent;
    }

    public void setTalkContent(String talkContent) {
        this.talkContent = talkContent;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    protected TalkContent(Parcel in) {
        talkContent = in.readString();
        id = in.readString();
        name = in.readString();
    }

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

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

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

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

    @Override
    public String toString() {
        return "TalkContent{" +
                "talkContent='" + talkContent + '\'' +
                ", id='" + id + '\'' +
                ", name='" + name + '\'' +
                '}';
    }
}

服务端主要代码(MainActivity中没业务代码,可以忽略)

TalksService.java

package com.jiao.aidlservicedemo;

import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

import com.jiao.aidlservicedemo.bean.TalkContent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import androidx.annotation.Nullable;

/**
 * @author: jiaojunfeng
 * @date: 1/10/23
 * @describe:
 */
public class TalksService extends Service {

    private BinderService iTalkAidlInterface;

    @Override
    public void onCreate() {
        super.onCreate();
        iTalkAidlInterface = new BinderService(this);
    }

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

    public static class BinderService extends ITalkAidlInterface.Stub {
        Context context;

        public BinderService(Context context) {
            this.context = context;
        }

        @Override
        public void leavingMessage(TalkContent talk) throws RemoteException {
            Log.i("TalksService",talk.toString());
//此处是我做了一个本地化持久保存, 可以用arraylist来代替测试          
  SharedPreferencesUtil.saveTalk(context, talk.getId(), talk);
        }



        @Override
        public void getAllTalks( IServiceCallback iServiceCallback  ) throws RemoteException {
//此处是我做了一个本地化持久保存, 可以用arraylist来代替测试
            Map<String, ?> map = SharedPreferencesUtil.getTalks(context);
            iServiceCallback.taks(map);

        }
    }
}

客户端代码 主要是MainActivity

MainActivity

package com.jiao.aidlclientdemo;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;

import com.google.gson.Gson;
import com.jiao.aidlservicedemo.IServiceCallback;
import com.jiao.aidlservicedemo.ITalkAidlInterface;
import com.jiao.aidlservicedemo.bean.TalkContent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private LinearLayout llyControl;
    private TextView tvBind;
    private TextView tvAdd;
    private TextView tvGet;
    private EditText etEditContent;
    private ListView lvHistory;
    Intent intent;

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

    private ITalkAidlInterface iPersonManager;
    ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.d("MainActivity", "onServiceConnected: ");
            iPersonManager = ITalkAidlInterface.Stub.asInterface(iBinder);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.d("MainActivity", "onServiceDisconnected  ");
            iPersonManager = null;
        }
    };
    ArrayList<TalkContent> arrayList = new ArrayList<>();
    Talksadapter talksadapter;

    private void initView() {
        llyControl = findViewById(R.id.lly_control);
        tvBind = findViewById(R.id.tv_bind);
        tvAdd = findViewById(R.id.tv_add);
        tvGet = findViewById(R.id.tv_get);
        etEditContent = findViewById(R.id.et_edit_content);
        lvHistory = findViewById(R.id.lv_history);
        talksadapter = new Talksadapter(this, arrayList);
        lvHistory.setAdapter(talksadapter);
        tvBind.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //如果是两个project,则客户端需用隐式调用,setPackage、setAction等
//        Intent intent1 = new Intent(getApplicationContext(), MyAidlService.class);
//        bindService(intent1, mConnection, BIND_AUTO_CREATE);
                intent = new Intent();
                intent.setComponent(new ComponentName("com.jiao.aidlservicedemo", "com.jiao.aidlservicedemo.TalksService"));
//        intent.setPackage("com.jiao.aidlservicedemo");
                intent.setAction("com.jiao.aidlservicedemo.TalksService.aidl");
                boolean isBind = bindService(intent, conn, BIND_AUTO_CREATE);
                Log.d("MainActivity", "isBind= " + isBind);

            }
        });
        tvGet.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (iPersonManager != null) {
//
                    try {
                        iPersonManager.getAllTalks(new IServiceCallback.Stub() {
                            @Override
                            public void taks(Map talks) throws RemoteException {

                                HashMap<String, Set> talkContentHashMap = (HashMap<String, Set>) talks;
                                for (String str : talkContentHashMap.keySet()) {
                                    Set<String> talkContent = talkContentHashMap.get(str);
                                    if (talkContent != null) {
                                        for (String talkC : talkContent) {
                                            arrayList.add(new Gson().fromJson(talkC, TalkContent.class));
                                        }

                                    }
                                    talksadapter.notifyDataSetChanged();
                                }
                            }
                        });
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        tvAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    if (iPersonManager != null) {
                        iPersonManager.leavingMessage(new TalkContent("hallo", "001", "张三"));
                    } else {
                        Log.d("MainActivity", "tvAdd iPersonManager=null ");
                    }
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

Talksadapter

package com.jiao.aidlclientdemo;

import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.jiao.aidlservicedemo.bean.TalkContent;

import java.util.ArrayList;

/**
 * @author: jiaojunfeng
 * @date: 1/11/23
 * @describe:
 */
public class Talksadapter extends BaseAdapter {

    private Context context;
    private ArrayList<TalkContent> arrayList;

    public Talksadapter(Context context, ArrayList<TalkContent> arrayList) {
        this.context = context;
        this.arrayList = arrayList;
    }

    @Override
    public int getCount() {
        return arrayList.size();
    }

    @Override
    public Object getItem(int position) {
        return arrayList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        convertView = View.inflate(context, R.layout.adapter_talks_item, null);
        TextView tvContent = convertView.findViewById(R.id.tv_content);
        TextView tvName = convertView.findViewById(R.id.tv_name);
        Log.d("MainActivity", "arrayList.get(position)=" + arrayList.get(position));
        TalkContent talkContent = arrayList.get(position);
        try {
            if (talkContent != null) {
                String content = talkContent.getTalkContent();
                tvContent.setText(content);
                tvName.setText(talkContent.getName());
            }
        } catch (Exception e) {
        }

        return convertView;
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/lly_control"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/tv_bind"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:background="@color/colorAccent"
            android:gravity="center"
            android:padding="10dp"
            android:text="绑定" />

        <TextView
            android:id="@+id/tv_add"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:layout_marginLeft="5dp"
            android:background="@color/colorAccent"
            android:gravity="center"
            android:padding="10dp"
            android:text="添加数据" />

        <TextView
            android:id="@+id/tv_get"
            android:layout_width="wrap_content"
            android:layout_height="50dp"
            android:layout_marginLeft="5dp"
            android:background="@color/colorAccent"
            android:gravity="center"
            android:padding="10dp"
            android:text="获取数据" />
    </LinearLayout>

    <EditText
        android:id="@+id/et_edit_content"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_alignParentBottom="true"
        android:hint="请输入对话内容" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/et_edit_content"
        android:layout_below="@+id/lly_control">

        <ListView
            android:id="@+id/lv_history"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </LinearLayout>


</RelativeLayout>

adapter_talks_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <TextView
        android:id="@+id/tv_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:minHeight="50dp"
        android:paddingLeft="4dp"
        android:textColor="#000000"
        android:textSize="14sp" />
    <TextView
        android:id="@+id/tv_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:minHeight="50dp"
        android:paddingLeft="14dp"
        android:textColor="#000000"
        android:textSize="14sp" />
</LinearLayout>

服务端清单文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.jiao.aidlservicedemo">


    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!--        // enabled 是否可以被系统实例化,-->
        <!--        // 默认为 true 因为父标签 也有 enable 属性,-->
        <!--        // 所以必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活。-->
        <!--       -->
        <!--        // exported 是否支持其它应用调用当前组件。-->
        <!--        // 默认值:如果包含有intent-filter 默认值为true;-->
        <!--        // 没有intent-filter默认值为false。-->

        <!--            //该Service可以响应带有com.bard.gplearning.IMyAidlInterface这个action的Intent。-->
        <!--            //此处Intent的action必须写成“服务器端包名.aidl文件名”-->
        <service
            android:name=".TalksService"
            android:enabled="true"
            android:exported="true"
          >

            <intent-filter>
                <action android:name="com.jiao.aidlservicedemo.TalksService.aidl" />
            </intent-filter>
        </service>
    </application>

</manifest>

客户端清单文件

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jiao.aidlclientdemo">
    <!--    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"-->
    <!--        tools:ignore="QueryAllPackagesPermission" />-->
    <queries>
        <package android:name="com.jiao.aidlservicedemo" />
    </queries>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

代码 我用到了

implementation 'com.squareup.retrofit2:converter-gson:2.0.0'

文中若有缺失或不对的地方 欢迎指证,谢谢!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值