这是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();
}
建议都使用第二种,大数据效率,小数据差不多,虽然写起来麻烦点!
其他就没有别的好解释了,如果连接什么的不懂的话,可以看上一篇,上面已经写过了,隐式启动服务!
整个项目会在,我写完全部之后发上来!