【Android Framework系列】第9章 AMS之Hook实现登录页跳转

news2024/11/15 13:25:49

1 前言

前面章节我们学习了【Android Framework系列】第5章 AMS启动流程和【Android Framework系列】第6章 AMS原理之Launcher启动流程,大概了解了AMS的原理及启动流程,这一章节我们通过反射和动态代理对不同Android版本下的AMS进行Hook实现登录页面的跳转

这里我们只简单介绍一下HookAMS思路和重点代码,需要详细了解的请到文末处项目地址下载查看。

1.1 实现打开统一登录页面

我们在Android的APP中,一般会有登录状态。如果非登录状态下,除了闪屏页和登录页外,其他页面打开需要先登录。往往登录状态也会有有效期的说法,如果在有效期到了跳转,我们则需要跳转到登录页面,而不是继续打开页面,这种情况下我们通过HookAMS可以实现。

1.2 实现打开动态插件下发页面

另外通过HookAMS还可以实现动态下发插件的功能,比如动态下发的ActivityAndroidManifest.xml里是没有注册,要想打开则需要通过HookAMS的方式,使用代理页面在AndroidManifest.xml注册,在跳转时动态切换到下发下来的插件内Activity

2 实现

2.1 实现思路

通过动态代理的方式,将AMSstartActivity方法拦截下来,把要跳转的意图替换成我们要打开的Activity。由于不同的Android版本AMS源码有所差别,所以这里区分SDK<=23SDK<=28SDK>28这三种情况做HookAMS适配。下面我们来看看项目结构

2.2 项目结构

在这里插入图片描述
上图我们可以看到项目结构如下:

// Config					常量配置类
// HookAMSApplication		Application进行HookAMS初始化
// HookAMSUtils				HookAMS工具类,主要的Hook逻辑	
// ListActivity				数据页面,登录后才可打开
// LoginActivity			登录页
// MainActivity				首页,这里打开数据页面
// ProxyActivity			代理页,用于欺瞒AMS,跳转时动态替换为真正Activity

首先我们来看是怎么HookAMSApplication

2.3 HookAMSApplication

package com.yvan.hookams;

import android.app.Application;
import android.os.Handler;
import android.os.Looper;

/**
 * @author yvan
 * @date 2023/7/28
 * @description
 */
public class HookAMSApplication extends Application {

    private final Handler handler = new Handler(Looper.getMainLooper());

    @Override
    public void onCreate() {
        super.onCreate();
        handler.post(this::hookAMS);
    }

    public void hookAMS() {
        try {
            HookAMSUtils hookUtils = new HookAMSUtils(this, ProxyActivity.class);
            hookUtils.hookAms();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在App启动时,ApplicationonCreate()方法内通过Handlerpost方法进行对HookAMSUtils类的hookAms()方法调用。为什么要使用Handler呢?初始化ApplicationonCreate()初始化还没完成,直接调hookAms()方法会崩溃,这里加了post,将任务加入到主线程的队列里,这样就不会出现崩溃异常。

我们继续看HookAMSUtils类:

2.4 HookAMSUtils

package com.yvan.hookams;

import static android.os.Build.VERSION.SDK_INT;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

/**
 * @author yvan
 * @date 2023/7/28
 * @description Hook AMS工具类
 */
public class HookAMSUtils {

    private static final String TAG = HookAMSUtils.class.getSimpleName();
    private Context context;
    private Class<?> proxyActivity;

    /**
     * proxyActivity 传入一个有注册在AndroidManifest的就行
     *
     * @param context
     * @param proxyActivity
     */
    public HookAMSUtils(Context context, Class<?> proxyActivity) {
        this.context = context;
        this.proxyActivity = proxyActivity;
    }

    public void hookAms() throws Exception {
        if (SDK_INT <= 23) {
            hookAmsFor6();
        } else if (SDK_INT <= 28) {
            hookAmsFor9();
        } else {
            hookAmsFor10();
        }
        hookSystemHandler();
    }

    public void hookAmsFor10() throws Exception {
        Class<?> iActivityManagerClazz = Class.forName("android.app.IActivityTaskManager");
        Class<?> clazz = Class.forName("android.app.ActivityTaskManager");
        Field singletonField = clazz.getDeclaredField("IActivityTaskManagerSingleton");
        singletonField.setAccessible(true);
        Object singleton = singletonField.get(null);

        Class<?> singletonClass = Class.forName("android.util.Singleton");
        Field mInstanceField = singletonClass.getDeclaredField("mInstance");

        mInstanceField.setAccessible(true);
        final Object mInstance = mInstanceField.get(singleton);
        Object proxyInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader()
                , new Class[]{iActivityManagerClazz}, new AmsInvocationHandler(mInstance));

        mInstanceField.setAccessible(true);
        mInstanceField.set(singleton, proxyInstance);
    }

    public void hookAmsFor9() throws Exception {
        // 1.反射获取类>ActivityTaskManager,这个就是AMS实例
        Class ActivityManagerClz = Class.forName("android.app.ActivityManager");
        // 2.获取IActivityManagerSingleton,并设置访问权限
        Field iActivityManagerSingletonFiled = ActivityManagerClz.getDeclaredField("IActivityManagerSingleton");
        iActivityManagerSingletonFiled.setAccessible(true);
        // 因为是静态变量,所以获取的到的是默认值
        final Object iActivityManagerSingletonObj = iActivityManagerSingletonFiled.get(null);
        // 3.现在创建我们的AMS实例
        // 由于IActivityManager是一个接口,那么其实我们可以使用Proxy类来进行代理对象的创建
        // 结果被摆了一道,IActivityManager这玩意居然还是个AIDL,动态生成的类,编译器还不认识这个类,怎么办?反射咯
        // 反射创建一个Singleton的class
        Class SingletonClz = Class.forName("android.util.Singleton");
        Field mInstanceField = SingletonClz.getDeclaredField("mInstance");
        mInstanceField.setAccessible(true);
        // 4.获取AMS Proxy
        Object iActivityManagerObj = mInstanceField.get(iActivityManagerSingletonObj);
        // 5.获取需要实现的接口IActivityManager实现类
        Class iActivityManagerClz = Class.forName("android.app.IActivityManager");
        // 6.动态生成接口对象
        // 构建代理类需要两个东西用于创建伪装的Intent
        // 拿到AMS实例,然后用代理的AMS换掉真正的AMS,代理的AMS则是用 假的Intent骗过了 activity manifest检测.
        Object proxyIActivityManager = Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                new Class<?>[]{iActivityManagerClz}, new AmsInvocationHandler(iActivityManagerObj));
        mInstanceField.setAccessible(true);
        // 7.替换掉系统的变量
        mInstanceField.set(iActivityManagerSingletonObj, proxyIActivityManager);
    }

    public void hookAmsFor6() throws Exception {
        //1.反射获取类>ActivityManagerNative
        Class ActivityManagerClz = Class.forName("android.app.ActivityManagerNative");

        //2.获取变量>gDefault
        Field IActivityManagerSingletonFiled = ActivityManagerClz.
                getDeclaredField("gDefault");
        //2.1 设置访问权限
        IActivityManagerSingletonFiled.setAccessible(true);

        //3. 获取变量的实例值
        Object IActivityManagerSingletonObj = IActivityManagerSingletonFiled.get(null);

        //4.获取mInstance
        Class SingletonClz = Class.forName("android.util.Singleton");
        Field mInstanceField = SingletonClz.getDeclaredField("mInstance");
        mInstanceField.setAccessible(true);
        //5.获取AMS Proxy
        Object AMSProxy = mInstanceField.get(IActivityManagerSingletonObj);
        //6.由于不能去手动实现IActivityManager实现类,
        //  所以只能通过动态代理去动态生成实现类

        //6.1 获取需要实现的接口
        Class IActivityManagerClz = Class.forName("android.app.IActivityManager");
        //6.2 动态生成接口对象
        Object proxyIActivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                new Class[]{IActivityManagerClz}, new AmsInvocationHandler(AMSProxy));
        mInstanceField.setAccessible(true);
        //7.替换掉系统的变量
        mInstanceField.set(IActivityManagerSingletonObj, proxyIActivityManager);
    }

    private class AmsInvocationHandler implements InvocationHandler {

        private Object iActivityManagerObject;

        public AmsInvocationHandler(Object iActivityManagerObject) {
            this.iActivityManagerObject = iActivityManagerObject;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("startActivity".contains(method.getName())) {
                Intent intent = null;
                int index = 0;
                for (int i = 0; i < args.length; i++) {
                    Object arg = args[i];
                    if (arg instanceof Intent) {
                        intent = (Intent) args[i]; // 原意图,过不了安检
                        index = i;
                        break;
                    }
                }
                Intent proxyIntent = new Intent();
                ComponentName componentName = new ComponentName(context, proxyActivity);
                proxyIntent.setComponent(componentName);
                proxyIntent.putExtra("realIntent", intent);
                //替换原有的intent为我们自己生成的,为了骗过PMS
                //为跳到我们的传入的proxyActivity
                args[index] = proxyIntent;
            }
            return method.invoke(iActivityManagerObject, args);
        }
    }

    //上面的主要是替换成我们自己的intent,骗过系统
    //下面的主要是将我们上面替换的intent中,取出我们真正的意图(也就是正在要启动的Activity)
    //
    //下面是为了拿到mH对象,但是mH是一个非static 的值,那我们就只能拿到他的持有对象,也就是ActivityThread
    //正好发现在ActivityThread类中有一个static变量sCurrentActivityThread值可以拿到ActivityThread类,那我们就从他入手
    public void hookSystemHandler() throws Exception {
        //1.反射ActivityThread
        Class ActivityThreadClz = Class.forName("android.app.ActivityThread");
        //2. 获取sCurrentActivityThread 是一个static变量
        Field field = ActivityThreadClz.getDeclaredField("sCurrentActivityThread");
        field.setAccessible(true);
        //3.获取ActivityThread对象
        Object ActivityThreadObj = field.get(null);
        //4.通过ActivityThreadObj获取到mH变量
        Field mHField = ActivityThreadClz.getDeclaredField("mH");
        mHField.setAccessible(true);
        //5.获取到mH的对象
        Handler mHObj = (Handler) mHField.get(ActivityThreadObj);//ok,当前的mH拿到了
        //到这里,获取到mH的对象了,那我们怎么去监听他的方法调用呢?
        //能不能通过动态代理?不能,因为它不是个接口
        //由于在Handler的源码中,我们知道如果mCallback如果不等于空,就会调用mCallback的handleMessage方法。
        //6.获取mH的mCallback
        Field mCallbackField = Handler.class.getDeclaredField("mCallback");
        mCallbackField.setAccessible(true);
        //7.创建我们自己的Callback,自己处理handleMessage
        Handler.Callback proxyMHCallback = getMHCallback();
        //8.给系统的mH(Handler)的mCallback设值(proxyMHCallback)
        mCallbackField.set(mHObj, proxyMHCallback);
    }

    private Handler.Callback getMHCallback() {
        if (SDK_INT <= 23) {
            return new ProxyHandlerCallbackFor6();
        } else if (SDK_INT <= 28) {
            return new ProxyHandlerCallbackFor();
        } else {
            return new ProxyHandlerCallbackFor();
        }
    }

    private class ProxyHandlerCallbackFor6 implements Handler.Callback {
        private int LAUNCH_ACTIVITY = 100;

        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == LAUNCH_ACTIVITY) {
                try {
                    Class ActivityClientRecord = Class.forName("android.app.ActivityThread$ActivityClientRecord");
                    //判断传过来的值(msg.obj)是不是ClientTransaction对象
                    if (!ActivityClientRecord.isInstance(msg.obj)) return false;

                    //获取ActivityClientRecord的intent变量
                    Field intentField = ActivityClientRecord.getDeclaredField("intent");
                    intentField.setAccessible(true);
                    if (intentField == null) return false;
                    Intent mIntent = (Intent) intentField.get(msg.obj);
                    if (mIntent == null) return false;

                    //获取我们之前传入的realIntent,也就是我们真正要打开的Activity
                    Intent realIntent = mIntent.getParcelableExtra("realIntent");
                    if (realIntent == null) {
                        return false;
                    }
                    realStartActivity(mIntent, realIntent);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return false;
        }
    }

    private class ProxyHandlerCallbackFor implements Handler.Callback {
        private int EXECUTE_TRANSACTION = 159;

        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == EXECUTE_TRANSACTION) {
                try {
                    Class ClientTransactionClz = Class.forName("android.app.servertransaction.ClientTransaction");
                    //判断传过来的值(msg.obj)是不是ClientTransaction对象
                    if (!ClientTransactionClz.isInstance(msg.obj)) return false;

                    Class LaunchActivityItemClz = Class.forName("android.app.servertransaction.LaunchActivityItem");

                    //获取ClientTransaction的mActivityCallbacks变量
                    Field mActivityCallbacksField = ClientTransactionClz
                            .getDeclaredField("mActivityCallbacks");//ClientTransaction的成员
                    //设值成
                    mActivityCallbacksField.setAccessible(true);
                    //获取到ASM传递过来的值(ClientTransaction对象)里的mActivityCallbacks变量
                    Object mActivityCallbacksObj = mActivityCallbacksField.get(msg.obj);
                    List list = (List) mActivityCallbacksObj;
                    if (list.size() == 0) return false;
                    Object LaunchActivityItemObj = list.get(0);
                    if (!LaunchActivityItemClz.isInstance(LaunchActivityItemObj)) return false;

                    //获取mIntent变量
                    Field mIntentField = LaunchActivityItemClz.getDeclaredField("mIntent");
                    mIntentField.setAccessible(true);
                    //获取mIntent对象
                    Intent mIntent = (Intent) mIntentField.get(LaunchActivityItemObj);
                    //获取我们之前传入的realIntent,也就是我们真正要打开的Activity
                    Intent realIntent = mIntent.getParcelableExtra("realIntent");
                    if (realIntent == null) {
                        return false;
                    }
                    realStartActivity(mIntent, realIntent);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return false;
        }
    }

    private void realStartActivity(Intent mIntent, Intent realIntent) {
        //登录判断
        SharedPreferences share = context.getSharedPreferences(Config.SP_NAME,
                Context.MODE_PRIVATE);

        if (share.getBoolean(Config.SP_KEY_LOGIN, false)) {
            mIntent.setComponent(realIntent.getComponent());
        } else {
            Log.i(TAG, "handleLauchActivity: " + realIntent.getComponent().getClassName());
            ComponentName componentName = new ComponentName(context, LoginActivity.class);
            mIntent.putExtra("extraIntent", realIntent.getComponent()
                    .getClassName());
            mIntent.setComponent(componentName);
        }
    }

}

从上面代码我们能看到:

  1. hookAms()方法分别是SDK<=23、SDK<=28、SDK>28三种情况进行HookAMS,其实都是大同小异。实际上是获取到IActivityManager对象,通过动态代理Proxy.newProxyInstance()Hook到其所有方法,通过AmsInvocationHandler进行方法调用的回调。
  2. hookSystemHandler()方法在hookAms()方法调用后立刻执行,通过反射获取android.app.ActivityThread类对象的sCurrentActivityThread属性和Handler实例mH,将Handler的回调handleMessage()方法进行拦截。根据SDK<=23、SDK<=28、SDK>28三种情况不同来区别处理。
  3. 1中的AmsInvocationHandler负责hookAms()内Hook到的方法调用的处理,在Hook到的Callback中判断为startActivity()方法则拦截下来,将我们真正要跳转的Activity意图存在Extra内,由于在原来的Intent中隐藏了真正的Activity意图,所以只需要将真正的意图拿出来替换将其Intent的意图替换为要打开的Activity
  4. 2中handleMessage()其实就是将startActivity()方法进行拦截,判断如果是未登录状态,则将真正要跳转的ActivityExtra内拿出来进行跳转,已登录则不替换Intent意图跳转。
    在这里插入图片描述

3 总结

文章只做核心HookAMS代码思路的分析,这里是项目地址,小伙伴可以自行下载查看,别忘了点Star喔,谢谢!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/821337.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ansible —— playbook 剧本

Ansible —— playbook 剧本 一、playbook的概述1.playbook简介2.什么是Ansible playbook剧本&#xff1f;3.Ansible playbook剧本的特点4.如何使用Ansible playbook剧本&#xff1f;5.playbooks 本身由以下各部分组成 二、playbook示例1.运行playbook2.定义、引用变量3.指定远…

低代码PAAS平台源码,采用对象式和勾选式实现企业应用程序开发

管理后台低代码PaaS平台是一款基于 Salesforce Platform 的开源替代方案&#xff0c;旨在为企业提供高效、灵活、易于使用的低代码开发平台。低代码PaaS平台的10大核心引擎功能&#xff1a;1.建模引擎 2.移动引擎 3.流程引擎 4.页面引擎 5.报表引擎 6.安全引擎 7.API引擎 8.应用…

C++多线程环境下的单例类对象创建

使用C无锁编程实现多线程下的单例模式 贺志国 2023.8.1 一、尺寸较小的类单例对象创建 如果待创建的单例类SingletonForMultithread内包含的成员变量较少&#xff0c;整个类占用的内存空间较小&#xff0c;则可以使用如下方法来创建单例对象&#xff08;如果类的尺寸较大则不…

【Linux命令200例】用rcp复制远程文件到本地

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;阿里云社区专家博主&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;本文已收录于专栏&#xff1a;Linux命令大全。 &#x1f3c6;本专栏我们会通过具体的系统的命令讲解加上鲜…

再添新证书!数据库云管平台 zCloud 与 OceanBase 完成产品兼容互认证

近日&#xff0c;云和恩墨 zCloud 数据库云管平台&#xff08;简称&#xff1a;zCloud&#xff09;与北京奥星贝斯科技有限公司&#xff08;简称&#xff1a;OceanBase&#xff09;完成产品兼容性互认证。本次测试结果表明&#xff1a;zCloud 具备对 OceanBase 的纳管、监控告警…

工业机器视觉系统开发流程简介

工业机器视觉系统的开发过程主要包括以下几个阶段&#xff1a; 需求分析和系统设计&#xff1a;与用户合作&#xff0c;明确系统的功能和性能需求&#xff0c;并设计系统的整体架构。 软、硬件选型&#xff1a;根据需求分析结果&#xff0c;选择适合的软、硬件设备&#xff0…

NASA和uAvionix在AAM测试场部署SkyLine C2指挥和控制服务

蒙大拿州比格福克和弗吉尼亚州汉普顿2023年07月28日——美国宇航局和uAvionix签署了一项太空法案协议&#xff0c;为城市环境中的无人机系统 (UAS)开发先进的超视距(BVLOS)指挥和控制(C2)技术。根据协议&#xff0c;NASA将与uAvionix合作&#xff0c;利用基于互联网的基础设施和…

qiankun框架vue3项目,子应用什么情况下会卸载

1.手动卸载 2.路由不匹配的时候&#xff0c;会自动卸载

STM32基础入门学习笔记:基础知识和理论 开发环境建立

文件目录&#xff1a; 一&#xff1a;基础知识和理论 1.ARM简介 2.STM32简介 3.STM32命名规范 4.STM32内部功能* 5.STM32接口定义 二&#xff1a;开发环境建立 1.开发板简介 2.ISP程序下载 3.最小系统电路 4.KEIL的安装 5.工程简介与调试流程 6.固件库的安装 7.编…

K3s vs K8s:轻量级对决 - 探索替代方案

在当今云原生应用的领域中&#xff0c;Kubernetes&#xff08;简称K8s&#xff09;已经成为了无可争议的领导者。然而&#xff0c;随着应用规模的不断增长&#xff0c;一些开发者和运维人员开始感受到了K8s的重量级特性所带来的挑战。为了解决这一问题&#xff0c;一个名为K3s的…

【数据结构与算法】单链表反转、双链表反转(含相关题型)

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ 链表反转 1. 单链表反转 实现2. 双链表反转 实…

IT技术中最被人忽略的网络安全方向~超乎你的想象~

正文 听说网络工程师已成为IT人的最底层了&#xff1f;标题的问题是知乎上很多网工在提问的问题之一&#xff0c;总是在说网工行业已经不行了、网工成为最低成、网工已经没落了…… 今天针对这些问题&#xff0c;我们先明确一个看法&#xff1a; 这些都是对网工行业的误解。 …

Vue(五)git

Vue 三十二、git1.工具引入2.本地仓库3.远程仓库4.两人协作&#xff08;1&#xff09;非冲突&#xff08;2&#xff09;冲突&#xff08;3&#xff09;可视化 5.分支6.vue项目git注意 三十三、三十四、三十五、 三十二、git 1.工具引入 项目内容合并 传统&#xff1a;U盘&…

Twitter 劲敌 Threads,“魔改”了哪些 Python 技术栈?

Meta 创始人 Mark Zuckerberg 昨天在 Threads 上宣布&#xff0c;周三正式上线的 Threads 注册量已突破三千万。 Threads 是一个基本文本的社交应用&#xff0c;由 Instagram 团队开发。虽然它在功能上还无法真正取代 Twitter&#xff0c;但目前看来事实上已是 Twitter 的替代方…

前端学习--vue2--插槽

写在前面&#xff1a; 这个用法是在使用组件和创建组件中 文章目录 介绍简单使用多个插槽省写默认/后备内容作用域插槽常用实例Element-ui的el-table 废弃用法slot attributeslot-scope attribute 介绍 我们在定义一些组件的时候&#xff0c;由于组件内文字想要自定义&#…

minio-分布式文件存储系统

minio-分布式文件存储系统 minio的简介 MinIO基于Apache License v2.0开源协议的对象存储服务&#xff0c;可以做为云存储的解决方案用来保存海量的图片&#xff0c;视频&#xff0c;文档。由于采用Golang实现&#xff0c;服务端可以工作在Windows,Linux, OS X和FreeBSD上。配置…

手机图片怎么转换成pdf?掌握这几种转换方法就够了

手机图片怎么转换成PDF&#xff1f;现在&#xff0c;随着手机拍照功能的不断升级&#xff0c;手机摄影已成为人们记录生活、工作的一种重要方式。但是&#xff0c;有时候需要将手机拍摄的图片转换成PDF&#xff0c;以便更好地编辑和分享。那么&#xff0c;如何将手机图片转换成…

vue3常用API之学习笔记

目录 一、setup函数 vue2与vue3变量区别 二、生命周期 三、reactive方法 四、ref方法 1、简介 2、使用 3、ref与reactive 4、获取标签元素或组件 五、toRef 1、简介 2、ref与toRef的区别 六、toRefs 七、shallowReactive 浅reactive 1、简介 2、shallowreactiv…

详解顺序表功能

前言 随着我们C语言的不断深入学习&#xff0c;我们要开始学习一点数据结构来增加我们的内功了&#xff0c;虽说现在很多高级语言的顺序表&#xff0c;链表等可以不用自己实现&#xff0c;但在C语言中是需要我们自己来实现的&#xff0c;这并不能说明C语言和其他语言比C语言很…

AP AUTOSAR在软件定义汽车生态系统中的角色

AP AUTOSAR在软件定义汽车生态系统中的角色 AP AUTOSAR是AUTOSAR(汽车开放系统架构)的最新版本之一,它是一种面向服务的软件平台,旨在满足未来汽车电子系统的需求,特别是高性能计算、高带宽通信、软件无线更新(OTA)等方面。 AP AUTOSAR在软件定义汽车生态系统中扮演着…