IPC跨进程交互(2)AIDL的使用

这是IPC交互的第二篇,大部分参考了任玉刚老师的《Android开发艺术探索》,以前也有一篇AIDL的使用,那是一篇比较简单的使用,本篇会写到一些常用的功能,监听,认证等

新增一个高版本说明

AIDL接入Android 8.0和Android 11以上版本需要添加的设置​​​​​​​

开始就来演示下功能吧!

具体就是Client软件操作Service软件中的SharedPreferences进行一些操作。并且Service监听了添加事件,并反馈事件!

还有就是对操作数据的时候进行了认证处理

2个软件下面的log

Service

Client

下面就开始贴代码和讲解了!

1.创建AIDL文件

首先在Service项目中创建两个AIDL文件

IMyAidlInterface.aidl

// IMyAidlInterface.aidl
package com.gjn.myservice;

import com.gjn.myservice.IOnNewStringListener;

interface IMyAidlInterface {
//获取key
String getString(String key);
//设置key
void setString(String key,String val);
//获取全部
Map getAll();
//清空全部
void clear();
//绑定监听
void registerListener(IOnNewStringListener listener);
//解绑监听
void unregisterListener(IOnNewStringListener listener);
}

这是一个功能AIDL文件,写下了所有要实现的功能

IOnNewStringListener.aidl

// IOnNewStringListener.aidl
package com.gjn.myservice;

interface IOnNewStringListener{
    //添加新的key
    void onNewStringListener(String key);
}

由于AIDL不支持自定义的参数,所以上面实现功能中监听的参数是自己自定义的,需要单独创建一个aidl文件来让系统知道。

然后重新rebuild一下项目。

这两个aidl可以直接拷贝到Client项目下,记得包名要一模一样。

注:这两个文件要放在相同的包下面。

现在我们来实现AIDL上面的全部功能。

2.创建服务

在Service项目下创建一个新的服务,代码如下

AIDLService.java

package com.gjn.myservice;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;

import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

public class AIDLService extends Service {
    private static final String TAG = "AIDLService";

    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);

    private RemoteCallbackList<IOnNewStringListener> mListeners = new RemoteCallbackList<>();

    private Binder mBinder = new IMyAidlInterface.Stub() {

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            int check = checkCallingOrSelfPermission("com.gjn.permission.A");
            if (check == PackageManager.PERMISSION_DENIED) {
                Log.e(TAG, "onTransact: 权限认证失败");
                return false;
            }

            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
            }
            if (!packageName.startsWith("com.gjn")) {
                Log.e(TAG, "onTransact: 包名开头认证失败");
                return false;
            }

            return super.onTransact(code, data, reply, flags);
        }

        @Override
        public String getString(String key) throws RemoteException {
            SharedPreferences sharedPreferences = getSharedPreferences("SP", Context.MODE_PRIVATE);
            String result = sharedPreferences.getString(key, "");

            Log.e(TAG, "getString: " + result);
            return result;
        }

        @Override
        public void setString(String key, String val) throws RemoteException {
            SharedPreferences.Editor editor = getSharedPreferences("SP",
                    Context.MODE_PRIVATE).edit();
            editor.putString(key, val);
            Log.e(TAG, "setString: " + key + "=" + val);
            editor.apply();
            OnNewString(key);
        }

        @Override
        public Map getAll() throws RemoteException {
            SharedPreferences sharedPreferences = getSharedPreferences("SP", Context.MODE_PRIVATE);
            Log.e(TAG, "getAll");
            return sharedPreferences.getAll();
        }

        @Override
        public void clear() throws RemoteException {
            SharedPreferences sharedPreferences = getSharedPreferences("SP", Context.MODE_PRIVATE);
            sharedPreferences.edit().clear().apply();
            Log.e(TAG, "clear");
        }

        @Override
        public void registerListener(IOnNewStringListener listener) throws RemoteException {
            mListeners.register(listener);
            Log.e(TAG, "registerListener: 监听中");
        }

        @Override
        public void unregisterListener(IOnNewStringListener listener) throws RemoteException {
            mListeners.unregister(listener);
            Log.e(TAG, "unregisterListener: 解绑监听成功");
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.gjn.permission.A");
        if (check == PackageManager.PERMISSION_DENIED) {
            Log.e(TAG, "onBind: 认证失败");
            return null;
        }
        return mBinder;
    }

    @Override
    public void onDestroy() {
        mIsServiceDestoryed.set(true);
        super.onDestroy();
    }

    private void OnNewString(String key) {
        final int N = mListeners.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewStringListener listener = mListeners.getBroadcastItem(i);
            if (listener != null) {
                try {
                    listener.onNewStringListener(key);
                    Log.e(TAG, "OnNewString: 添加 " + key);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        mListeners.finishBroadcast();
    }
}

这是一个很长的代码。来一步一步解释下吧!

首先这是其实就是拿来判断是否需要解绑监听的

private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);


之后由于是跨进程的listener监听,所以系统自带了listener的绑定和解绑,就是RemoteCallbackList

private RemoteCallbackList<IOnNewStringListener> mListeners = new RemoteCallbackList<>();

下来就是最重要的实现了。

private Binder mBinder = new IMyAidlInterface.Stub() {

        @Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            int check = checkCallingOrSelfPermission("com.gjn.permission.A");
            if (check == PackageManager.PERMISSION_DENIED) {
                Log.e(TAG, "onTransact: 权限认证失败");
                return false;
            }

            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
            }
            if (!packageName.startsWith("com.gjn")) {
                Log.e(TAG, "onTransact: 包名开头认证失败");
                return false;
            }

            return super.onTransact(code, data, reply, flags);
        }

        @Override
        public String getString(String key) throws RemoteException {
            SharedPreferences sharedPreferences = getSharedPreferences("SP", Context.MODE_PRIVATE);
            String result = sharedPreferences.getString(key, "");

            Log.e(TAG, "getString: " + result);
            return result;
        }

        @Override
        public void setString(String key, String val) throws RemoteException {
            SharedPreferences.Editor editor = getSharedPreferences("SP",
                    Context.MODE_PRIVATE).edit();
            editor.putString(key, val);
            Log.e(TAG, "setString: " + key + "=" + val);
            editor.apply();
            OnNewString(key);
        }

        @Override
        public Map getAll() throws RemoteException {
            SharedPreferences sharedPreferences = getSharedPreferences("SP", Context.MODE_PRIVATE);
            Log.e(TAG, "getAll");
            return sharedPreferences.getAll();
        }

        @Override
        public void clear() throws RemoteException {
            SharedPreferences sharedPreferences = getSharedPreferences("SP", Context.MODE_PRIVATE);
            sharedPreferences.edit().clear().apply();
            Log.e(TAG, "clear");
        }

        @Override
        public void registerListener(IOnNewStringListener listener) throws RemoteException {
            mListeners.register(listener);
            Log.e(TAG, "registerListener: 监听中");
        }

        @Override
        public void unregisterListener(IOnNewStringListener listener) throws RemoteException {
            mListeners.unregister(listener);
            Log.e(TAG, "unregisterListener: 解绑监听成功");
        }
    };

先不看这个onTransact的方法,上面的都是在AIDL上面写好的方法!这边就是实现这些方法。

这边提下,我在set方法中添加了监听事件。

因为是系统的RemoteCallbackList数组,使用是需要结合

beginBroadcast和finishBroadcast

开始使用了beginBroadcast,结尾必须使用finishBroadcast结束

下来是onBind的方法里面,有一段存在和onTransact一模一样的代码,这边就下面在分析了。

    public IBinder onBind(Intent intent) {
        int check = checkCallingOrSelfPermission("com.gjn.permission.A");
        if (check == PackageManager.PERMISSION_DENIED) {
            Log.e(TAG, "onBind: 认证失败");
            return null;
        }
        return mBinder;
    }

下来是onDestroy,这边是销毁了监听。

    public void onDestroy() {
        mIsServiceDestoryed.set(true);
        super.onDestroy();
    }

之后来看下这个onTransact,这个是认证用到得,每次连接mBinder的时候他都会执行一次如果返回false的,那么Client软件就没办法操作Service软件的数据。

            int check = checkCallingOrSelfPermission("com.gjn.permission.A");
            if (check == PackageManager.PERMISSION_DENIED) {
                Log.e(TAG, "onTransact: 权限认证失败");
                return false;
            }

这个是判断Client项目的AndroidManifest.xml是否存在 com.gjn.permission.A这个Permission

我们来看下Client的AndroidManifest.xml文件

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

    <uses-permission android:name="com.gjn.permission.A" />

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

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.gjn.myclient.MessengerActivity" />
        <activity android:name="com.gjn.myclient.AIDLActivity" />
        <activity android:name="com.gjn.myclient.SocketActivity" />
        <activity android:name="com.gjn.myclient.BundleActivity" />
    </application>

</manifest>

这上面就有一个uses permission  就是Service判断的内容了

然后下面的

            String packageName = null;
            String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
            if (packages != null && packages.length > 0) {
                packageName = packages[0];
            }
            if (!packageName.startsWith("com.gjn")) {
                Log.e(TAG, "onTransact: 包名开头认证失败");
                return false;
            }

是判断包名开头是不是com.gjn。如果不是也会返回false。xml文件在上面有,看下package属性即可

注:由于这个Permission(com.gjn.permission.A)不是系统的,而是我们自己定义的,所以需要在Service的xml文件下面也要添加

具体代码是

    <permission
        android:name="com.gjn.permission.A"
        android:protectionLevel="normal" />

    <uses-permission android:name="com.gjn.permission.A" />

下面是全部代码

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

    <permission
        android:name="com.gjn.permission.A"
        android:protectionLevel="normal" />

    <uses-permission android:name="com.gjn.permission.A" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        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>

        <service
            android:name=".MessengerService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.gjn.myservice.Messenger_Service" />
            </intent-filter>
        </service>
        <service
            android:name=".AIDLService"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.gjn.myservice.AIDLService" />
            </intent-filter>
        </service>
    </application>

</manifest>

AIDLService也添加了action名字和上篇的功能是一样的,为了让跨进程实现隐式启动。

至此Service的项目就搭建完成了!

我们开始写Client的操作了。

先将AIDLActivity全部贴下来

布局如下

代码如下

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.gjn.myclient.AIDLActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="写入"
        android:id="@+id/btn_write"
        android:layout_marginTop="81dp"
        android:layout_alignParentTop="true"
        android:layout_alignStart="@+id/btn_read" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="读取"
        android:id="@+id/btn_read"
        android:layout_marginTop="30dp"
        android:layout_below="@+id/btn_write"
        android:layout_centerHorizontal="true" />

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true"
        android:layout_toStartOf="@+id/btn_write"
        android:layout_above="@+id/btn_read"
        android:layout_alignEnd="@+id/btn_write">

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:text="key"
                android:layout_weight="1" />

            <EditText
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:id="@+id/et_key"
                android:layout_weight="3" />
        </LinearLayout>

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal">

            <TextView
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:text="value"
                android:layout_weight="1" />

            <EditText
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:id="@+id/et_val"
                android:layout_weight="3" />
        </LinearLayout>
    </LinearLayout>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="随机添加"
        android:id="@+id/btn_save"
        android:layout_alignParentBottom="true"
        android:layout_alignStart="@+id/btn_read"
        android:layout_marginBottom="110dp" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="查看全部"
        android:id="@+id/btn_all"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="清空"
        android:id="@+id/btn_clean"
        android:layout_below="@+id/btn_all"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

AIDLActivity.java

package com.gjn.myclient;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.gjn.myservice.IMyAidlInterface;
import com.gjn.myservice.IOnNewStringListener;

import java.util.Map;
import java.util.Random;

public class AIDLActivity extends AppCompatActivity {
    private EditText et_key;
    private EditText et_val;
    private Button btn_read;
    private Button btn_write;
    private Button btn_save;
    private Button btn_all;
    private Button btn_clean;

    private IMyAidlInterface myAidlInterface;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            if (myAidlInterface == null) {
                Log.e("onServiceConnected", "onServiceConnected: 连接失败!");
            } else {
                Log.e("onServiceConnected", "onServiceConnected: 连接成功!");
            }
            try {
                myAidlInterface.registerListener(mOnNewStringListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private IOnNewStringListener mOnNewStringListener = new IOnNewStringListener.Stub() {
        @Override
        public void onNewStringListener(String key) throws RemoteException {
            Log.e("IOnNewStringListener", "onNewStringListener: " + key);
        }
    };

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

        findview();
        onclick();

        Intent intent = new Intent();
        intent.setAction("com.gjn.myservice.AIDLService");
        intent.setPackage("com.gjn.myservice");
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);

    }

    private void findview() {
        et_key = (EditText) findViewById(R.id.et_key);
        et_val = (EditText) findViewById(R.id.et_val);
        btn_read = (Button) findViewById(R.id.btn_read);
        btn_write = (Button) findViewById(R.id.btn_write);
        btn_save = (Button) findViewById(R.id.btn_save);
        btn_all = (Button) findViewById(R.id.btn_all);
        btn_clean = (Button) findViewById(R.id.btn_clean);
    }

    private void onclick() {
        btn_read.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final String key = et_key.getText().toString();
                String val = "";
                if (!key.equals("")) {
                    try {
                        val = myAidlInterface.getString(key);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                et_val.setText(val);
                if (val.equals("")) {
                    Toast.makeText(AIDLActivity.this, key + "不存在", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(AIDLActivity.this, key + "=" + val, Toast.LENGTH_SHORT).show();
                }
            }
        });

        btn_write.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final String key = et_key.getText().toString();
                final String val = et_val.getText().toString();
                if (!key.equals("") && !val.equals("")) {
                    try {
                        myAidlInterface.setString(key, val);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    Toast.makeText(AIDLActivity.this, key + "保存成功", Toast.LENGTH_SHORT).show();
                } else {
                    Toast.makeText(AIDLActivity.this, key + "保存失败", Toast.LENGTH_SHORT).show();
                }
                et_val.setText("");

            }
        });

        btn_save.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Random random = new Random();
                int r = random.nextInt(100);
                try {
                    myAidlInterface.setString("key_" + r, "val_" + r);
                    Toast.makeText(AIDLActivity.this, "key_" + r + " = val_" + r + "保存成功",
                            Toast.LENGTH_SHORT).show();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });
        btn_all.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    LogAll(myAidlInterface.getAll());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        btn_clean.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    myAidlInterface.clear();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                Toast.makeText(AIDLActivity.this, "全部清空", Toast.LENGTH_SHORT).show();
            }
        });
    }

    private void LogAll(Map all) {
        StringBuffer sb = new StringBuffer("all");
        Map<String, String> map = all;
        //遍历map   小数据效率差不多,大数据第二种效率高
        //第一种
        for (String key : map.keySet()) {
            Log.i("LogAll", key + " = " + map.get(key));
            sb.append("\n" + key + " = " + map.get(key));
        }
        //第二种
        for (Map.Entry<String, String> entry : map.entrySet()) {
            Log.e("LogAll", entry.getKey() + " = " + entry.getValue());
        }
        Toast.makeText(AIDLActivity.this, sb.toString(), Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onDestroy() {
        if (myAidlInterface != null && myAidlInterface.asBinder().isBinderAlive()) {
            try {
                Log.e("onDestroy", "解绑监听");
                myAidlInterface.unregisterListener(mOnNewStringListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(mConnection);
        super.onDestroy();
    }
}

这上面就提及一下,开始的在开始连接中加入了许多东西

        public void onServiceConnected(ComponentName name, IBinder service) {
            myAidlInterface = IMyAidlInterface.Stub.asInterface(service);
            if (myAidlInterface == null) {
                Log.e("onServiceConnected", "onServiceConnected: 连接失败!");
            } else {
                Log.e("onServiceConnected", "onServiceConnected: 连接成功!");
            }
            try {
                myAidlInterface.registerListener(mOnNewStringListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }


这上面开始判断是否连上,因为加了判断所以有可能传过来的是null的,所以这边加了一个判断。

之后下面就是设置监听事件。

    private IOnNewStringListener mOnNewStringListener = new IOnNewStringListener.Stub() {
        @Override
        public void onNewStringListener(String key) throws RemoteException {
            Log.e("IOnNewStringListener", "onNewStringListener: " + key);
        }
    };

这边监听了就只是打印一个log。

这边在讲一个,在AIDL中使用Map传数据 在AIDL中只能写Map,不能写Map<object,object>这样,执行rebuild的话会提示报错,提示无法识别。

这边我就直接用了Map,发现没有错误。在获取到Map的地方,在new一个Map<object,object>来实行获取键值就好了。

顺便这边也写了两个遍历Map的方法

    private void LogAll(Map all) {
        StringBuffer sb = new StringBuffer("all");
        Map<String, String> map = all;
        //遍历map   小数据效率差不多,大数据第二种效率高
        //第一种
        for (String key : map.keySet()) {
            Log.i("LogAll", key + " = " + map.get(key));
            sb.append("\n" + key + " = " + map.get(key));
        }
        //第二种
        for (Map.Entry<String, String> entry : map.entrySet()) {
            Log.e("LogAll", entry.getKey() + " = " + entry.getValue());
        }
        Toast.makeText(AIDLActivity.this, sb.toString(), Toast.LENGTH_SHORT).show();
    }

建议都使用第二种,大数据效率,小数据差不多,虽然写起来麻烦点!

其他就没有别的好解释了,如果连接什么的不懂的话,可以看上一篇,上面已经写过了,隐式启动服务!
 

整个项目会在,我写完全部之后发上来!
 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值