- 创建辅助文件 [日志文件、进程文件]
- 创建 aidl 文件
- 创建独立进程的远程服务 service
- 启动远程服务 service
- 在 aidl 中使用继承 Parcelable 接口的数据
一、创建辅助文件
- 创建辅助文件 Logger,用于输出日志
// Logger.kt
package com.remote.service.util
import android.util.Log
const val DEFAULT_TAG = "JshRemoteService"
private fun log(type: Int, tag: String, vararg args: Any) =
Log.println(type, tag, args.joinToString(";"))
fun logA(vararg args: Any) = logTA(DEFAULT_TAG, *args)
fun logTA(tag: String, vararg args: Any) = log(Log.ASSERT, tag, *args)
fun logE(vararg args: Any) = logTE(DEFAULT_TAG, *args)
fun logTE(tag: String, vararg args: Any) = log(Log.ERROR, tag, *args)
fun logD(vararg args: Any) = logTD(DEFAULT_TAG, *args)
fun logTD(tag: String, vararg args: Any) = log(Log.DEBUG, tag, *args)
- 创建辅助文件 ProcessUtil ,用于获取进程信息
import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.List;
public class ProcessUtil {
/**
* @return 当前进程名
*/
@Nullable
public static String getCurrentProcessName(@NonNull Context context) {
//1)通过Application的API获取当前进程名
String currentProcessName = getCurrentProcessNameByApplication();
if (!TextUtils.isEmpty(currentProcessName)) {
return currentProcessName;
}
//2)通过反射ActivityThread获取当前进程名
currentProcessName = getCurrentProcessNameByActivityThread();
if (!TextUtils.isEmpty(currentProcessName)) {
return currentProcessName;
}
//3)通过ActivityManager获取当前进程名
currentProcessName = getCurrentProcessNameByActivityManager(context);
return currentProcessName;
}
/**
* 通过Application新的API获取进程名,无需反射,无需IPC,效率最高。
*/
public static String getCurrentProcessNameByApplication() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
return Application.getProcessName();
}
return null;
}
/**
* 通过反射ActivityThread获取进程名,避免了ipc
*/
public static String getCurrentProcessNameByActivityThread() {
String processName = null;
try {
final Method declaredMethod = Class.forName("android.app.ActivityThread", false, Application.class.getClassLoader())
.getDeclaredMethod("currentProcessName", (Class<?>[]) new Class[0]);
declaredMethod.setAccessible(true);
final Object invoke = declaredMethod.invoke(null, new Object[0]);
if (invoke instanceof String) {
processName = (String) invoke;
}
} catch (Throwable e) {
e.printStackTrace();
}
return processName;
}
/**
* 通过ActivityManager 获取进程名,需要IPC通信
*/
public static String getCurrentProcessNameByActivityManager(@NonNull Context context) {
if (context == null) {
return null;
}
int pid = android.os.Process.myPid();
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (am != null) {
List<ActivityManager.RunningAppProcessInfo> runningAppList = am.getRunningAppProcesses();
if (runningAppList != null) {
for (ActivityManager.RunningAppProcessInfo processInfo : runningAppList) {
if (processInfo.pid == pid) {
return processInfo.processName;
}
}
}
}
return null;
}
}
二、创建 aidl 文件
1、创建 aidl 文件目录:src -> main -> aidl -> [packageName] ->[AidlFileName].aidl
![](https://img.haomeiwen.com/i16251880/82aa5a98eb869b72.png)
![](https://img.haomeiwen.com/i16251880/3ddfe1088ae8b816.png)
2、在 aidl 文件中编写 service 提供给 client 的可调用方法。
示例代码,编写完 aidl 文件需要make module '[aidl所在模块]'
,来生成aidl文件对应的Java文件。
// IAidlService.aidl
package com.remote.service;
// Declare any non-default types here with import statements
interface IAidlService {
/**
* Demonstrates some basic types that
* you can use as parameters and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, char aChar, String aString);
}
// AIDL中支持以下的数据类型
// 1. 基本数据类型
// 2. String 和CharSequence
// 3. List 和 Map ,List和Map 对象的元素必须是AIDL支持的数据类型;
// 4. AIDL自动生成的接口(需要导入-import)
// 5. 实现android.os.Parcelable 接口的类(需要导入-import)
编写 aild 文件需要注意事项:
- AIDL支持的基本数据类型 (int、long、boolean、float、double、char)、String和CharSequence,集合接口类型List和Map,这些不需要import 导入。
- 在AIDL中使用其他AIDL接口类型,需要import,即使是在相同包结构下。
- AIDL允许传递实现Parcelable接口的类,需要import。
- 需要特别注意的是,对于非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。
AIDL只支持接口方法,不能公开static变量。
![](https://img.haomeiwen.com/i16251880/3f71a0381d780dec.png)
三、创建独立进程的远程服务 service
- 编写远程服务
RemoteService.kt
// RemoteService.kt
package com.remote.service.remote
import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.remote.service.IAidlService
import com.remote.service.util.ProcessUtil
import com.remote.service.util.logA
class RemoteService : Service() {
// 实现aidl中定义的方法
private val binder = object : IAidlService.Stub() {
override fun basicTypes(
anInt: Int, aLong: Long, aBoolean: Boolean,
aFloat: Float, aDouble: Double, aChar: Char, aString: String?
) {
logA(
"""
Remote Service ->
【当前线程:${ProcessUtil.getCurrentProcessName(this@RemoteService)}】
【参数:$anInt, $aLong, $aBoolean, $aFloat, $aDouble, $aChar, ${aString ?: "null"}】
""".trimIndent()
)
}
}
override fun onCreate() {
super.onCreate()
logA("Remote Service -> onCreate")
}
override fun onBind(intent: Intent?): IBinder? = binder
override fun onUnbind(intent: Intent?): Boolean {
logA("Remote Service -> onUnbind")
return super.onUnbind(intent)
}
override fun onDestroy() {
super.onDestroy()
logA("Remote Service -> onDestroy")
}
}
- 在
AndroidManifest.xml
文件中注册 service
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.remote.service">
<application>
.......................
<service
android:name=".remote.RemoteService"
// enabled 是否可以被系统实例化,
// 默认为 true 因为父标签 也有 enable 属性,
// 所以必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活。
android:enabled="true"
// exported 是否支持其它应用调用当前组件。
// 默认值:如果包含有intent-filter 默认值为true;
// 没有intent-filter默认值为false。
android:exported="false"
android:process=":remote">
<intent-filter>
//该Service可以响应带有com.remote.service.IAidlService这个action的Intent。
//此处Intent的action必须写成“服务器端包名.aidl文件名”
<action android:name="com.remote.service.IAidlService" />
</intent-filter>
</service>
</application>
</manifest>
注意:
1、action 中的 name 需写成 【服务器端包名.aidl文件名】
2、android:process=":remote",代表在应用程序里,当需要该service时,会自动创建新的进程。而如果是android:process="remote",没有【“:”分号】的,则创建全局进程,不同的应用程序共享该进程。
3、设置了 android:process 属性将组件运行到另一个进程,相当于另一个应用程序,所以在另一个线程中也将新建一个 Application 的实例。因此,每新建一个进程 Application 的 onCreate 都将被调用一次。 如果在 Application 的 onCreate 中有许多初始化工作并且需要根据进程来区分的,那就需要特别注意了。
四、启动远程服务 service
// 示例代码
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
logA("当前进程名字 ${ProcessUtil.getCurrentProcessName(this)}")
}
private val serviceConnection = object : ServiceConnection {
override fun onServiceDisconnected(name: ComponentName?) = Unit
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
flag = true
binder = IAidlService.Stub.asInterface(service)
logA("remote service -> 已经绑定服务")
}
}
var flag: Boolean = false
private var binder: IAidlService? = null
fun bindRemoteService(v: View?) {
v ?: return
logA("当前进程名字 ${ProcessUtil.getCurrentProcessName(this)}")
if (flag) return
//通过Intent指定服务端的服务名称和所在包,与远程Service进行绑定
//参数与服务器端的action要一致,即"服务器包名.aidl接口文件名"
val intent = Intent("com.remote.service.IAidlService").also {
//Android5.0后无法只通过隐式Intent绑定远程Service
//需要通过setPackage()方法指定包名
it.`package` = "com.remote.service"
}
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}
fun unbindRemoteService(v: View?) {
v ?: return
if (!flag) return
unbindService(serviceConnection)
flag = false
}
fun doServiceBasicTypes(v: View?) {
v ?: return
binder?.basicTypes(1, 2L, false, 1.0f, 2.0, 'a', "String")
}
}
场景一、service 和 client 在同一个 project 中。
-
使用上述代码运行后结果:
场景二、service 和 client 分属于不同的 project 中。
- 将service项目中的 aidl 文件夹原封不动的拷贝到client项目中的对应位置
service 和 client 中 aidl 位置对比
- 对上面 service 的
AndroidManifest.xml
文件进行修改,把<service>
标签中的android:exported
属性设置为 true。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.remote.service">
<application>
.......................
<service
android:name=".remote.RemoteService"
android:enabled="true"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="com.remote.service.IAidlService" />
</intent-filter>
</service>
</application>
</manifest>
-
使用上述示例代码运行后结果:
五、在 aidl 中使用继承 Parcelable 接口的数据
-
在 java -> [packageName] 下创建继承 Parcelable 的数据类(Person)
数据类位置示例
// 数据类代码示例
package com.remote.service.bean
import android.os.Parcel
import android.os.Parcelable
data class Person(val name: String, var age: Int) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readInt()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeInt(age)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Person> {
override fun createFromParcel(parcel: Parcel): Person {
return Person(parcel)
}
override fun newArray(size: Int): Array<Person?> {
return arrayOfNulls(size)
}
}
}
- 在 aidl 文件夹下对应位置(包名路径一致)添加一个 Person.aidl 文件
aidl 中 Person.aidl 文件位置示例
// Person.aidl 文件内容示例
package com.remote.service.bean;
parcelable Person;
- 修改 IAidlService.aidl 文件,增加对 Person 对象的调用方法
package com.remote.service;
// Declare any non-default types here with import statements
import com.remote.service.bean.Person;
interface IAidlService {
/**
* Demonstrates some basic types that
* you can use as parameters and return values in AIDL.
*/
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, char aChar, String aString);
// Demonstrates Parcelable type that you can use as parameters and return values in AIDL.
void setMaster(in Person person);
}
注意:
1、import 导入 Parcelable 类型数据的路径
2、需要有方向指示设置;包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。
- 由于 IAidlService.aidl 增加了新的方法,对应的 service 也要增加相应方法
class RemoteService : Service() {
// 实现aidl中定义的方法
private val binder = object : IAidlService.Stub() {
..........
override fun setMaster(person: Person?) {
logA(
"""
Remote Service ->
【当前线程:${ProcessUtil.getCurrentProcessName(this@RemoteService)}】
【参数:${person.toString()}】
""".trimIndent()
)
}
}
.........
}
- 启动服务示例代码,增加一个 setMaster 的调用方法
// 示例代码
class MainActivity : AppCompatActivity() {
...........
fun doServiceSetMaster(v: View?) {
v ?: return
binder?.setMaster(Person("封雪彦", 43))
}
}
场景一、service 和 client 在同一个 project 中。
-
使用上述代码运行后结果:
场景二、service 和 client 分属于不同的 project 中。
-
将service项目中的 aidl 文件夹原封不动的拷贝到client项目中的对应位置
service 和 client 中 aidl 位置对比
-
将service项目中的 Person.kt 文件夹原封不动(包名路径不变)的拷贝到client项目中的对应位置
service 和 client 中 Person.kt 位置对比
-
对上面 service 的
AndroidManifest.xml
文件进行修改,把<service>
标签中的android:exported
属性设置为 true。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.remote.service">
<application>
.......................
<service
android:name=".remote.RemoteService"
android:enabled="true"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="com.remote.service.IAidlService" />
</intent-filter>
</service>
</application>
</manifest>
-
使用上述启动服务示例代码运行后结果:
网友评论