Android HAL到Framework

一、为什么需要Framwork?

Framework实际上是⼀个应⽤程序的框架,提供了很多服务:

1、丰富⽽⼜可扩展的视图(Views),

可以⽤来构建应⽤程序,它包括列表(lists),⽹格(grids),⽂本框(text boxes),按钮(buttons),甚⾄可嵌⼊的web浏览器。

2、内容提供器(Content Providers)

使得应⽤程序可以访问另⼀个应⽤程序的数据(如联系⼈数据库),或者共享它们⾃⼰的数据

3、资源管理器(Resource Manager)

提供⾮代码资源的访问,如本地字符串,图形,和布局⽂件(layout files)。

4、通知管理器(Notification Manager)

使得应⽤程序可以在状态栏中显⽰⾃定义的提⽰信息。

5、活动管理器(Activity Manager)

⽤来管理应⽤程序⽣命周期并提供常⽤的导航回退功能。

二、应用层访问硬件,如何自定义系统Service?

1、应用层如何访问硬件

(1)Linux

        对于Linux来说的话,就比较简单,应用层的APP直接通过open一类的接口直接访问我们底层的驱动文件

(2)Android 

        对于Android来说的话,它就会有多种方式去访问,

1) APP ----- JNI ----- Kernel:

        这种就很直接明了,上层app访问JNI,再去访问kernel

2)APP ----- Service ----- JNI ----- Kernel:

        当我们要往系统里添加一个硬件的话,我们更希望把它封装为一个系统的服务,就可以以这种方式去访问到底层

3)APP ----- Service ----- JNI ----- HAL ----- Kernel:

        一些驱动厂商的一个源码呢他是不希望开放给我们的一个开发者是吧,但是他们又依赖着Android的开源框架,所以就有一种比较好的方法,既不需要公开源码,又可以实现同样的功能。就是把它封装成库,这样可以让厂家去提供一个现成的库,然后我们直接去使用,他就不用开放这一层的源码,这就是HAL层的存在意义。

 为什么需要JNI? 

        应⽤使⽤java编写,驱动⼀般使⽤c/cpp编写,提供⼀种Java访问c/cpp的⽅法。也就是Java代码可通过JNI接⼝调⽤C/C++⽅法。

 JNI开发流程的步骤: 

1)编写JNI⽅法表并注册
2)实现JNI的.c⽂件

2、自定义系统Service 

        Framework还有一个很重要的功能,就是系统server。所有的硬件呢都是通过我们的系统server去进行管理,那我们怎样为我们的硬件接口去添加一个自定义的系统serve呢?

(1)建立aidl通信接口;

(2)在system_server中注册service到servicemanager;

(3)实现service,对应aidl中的接口函数。

(4)client向servicemanager请求service,成功后,调用aidl接口函数,建立client进程和service进程的通信关系。

总结来说就是:

1)system_server完成注册功能;
2)servicemanager完成服务管理功能;
3)aidl完成通讯功能;

(1)建立aidl通信接口

在frameworks/base/core/java/android/os/路径下新建对应名称的一个aidl文件

下面我们以顾凯歌的一个蓝牙模块的服务为大家举例:

路径:frameworks/base/core/java/android/os/IGocsdkService.aidl:
(因为他是Interface的一个接口,所有在前面加个 "I")+ package android.os;+ interface IEmbededService {
+   interface IFmService {
+
+       //蓝牙状态回调注册去注销 
+       void registerCallback(IGocsdkCallback callback);
+       // 注销蓝牙状态
+       void unregisterCallback(IGocsdkCallback callback);
+       
+       //注释后面带的为操作后相应的回调回复
+       //蓝牙协议软复位  ---》 onInitSucceed()
+       void restBluetooth();
+       
+       //获取本地蓝牙名称  ---》onCurrentDeviceName()
+       void getLocalName();
+       
+       //设置本地蓝牙名称  ---》onCurrentDeviceName()
+       void setLocalName(String name);+        ..................//等等一些,都为接口函数,会在下面实现}

编译到系统

路径:frameworks/base/Android.mk diff --git a/android/frameworks/base/Android.bp b/android/frameworks/base/Android.bp
old mode 100644
new mode 100755
index d8a7f06..953759c
--- a/android/frameworks/base/Android.bp
+++ b/android/frameworks/base/Android.bp
@@ -265,6 +265,11 @@ java_defaults {"core/java/android/os/IRecoverySystemProgressListener.aidl","core/java/android/os/IRemoteCallback.aidl","core/java/android/os/ISchedulingPolicyService.aidl",
+        "core/java/android/os/IGocsdkService.aidl",":statsd_aidl","core/java/android/os/ISystemUpdateManager.aidl","core/java/android/os/IThermalEventListener.aidl",

(2)在system_server中注册EmbededServicer到servicemanager

路径:frameworks/base/services/java/com/android/server/SystemServer.java

使用ServiceManager.addService添加我们自定义的server

@@ -1097,6 +1097,13 @@ public final class SystemServer {
}Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);+      try {+          Slog.i(TAG, "IGocsdkService");+           ServiceManager.addService("gocsdkService ", new GocsdkService());+   } catch (Throwable e) { +       Slog.e(TAG, "Failure starting Gocsdk Service", e);+   }

(3)实现EmbededService,对应aidl中的接⼝函数

路径:frameworks/base/services/java/com/android/server/EmbededService.java

package com.android.server;
import android.content.Context;
import android.os.IGocsdkService;
import android.util.Slog;
public class GocsdkService extends IGocsdkService.Stub {private static final String TAG = "GocsdkService";GocsdkService(){Slog.i(TAG,"GocsdkService init");}public void registerCallback(IGocsdkCallback callback){return xxx;}public void unregisterCallback(IGocsdkCallback callback){return xxx;}public void getLocalName(){return xxx;}.......................
}

(4)在app中使⽤IEmbededService的大致流程如下

        很好理解吧,把我们对应的一个服务导入,然后去初始化一个类,然后通过ServiceManager去找到我们自定义的这个server,然后使用自定义服务的函数获取数据。

import android.os.IGocsdkService; //导入private IGocsdkService mGocsdkService = null; //初始化类
mGocsdkService = IGocsdkService .Stub.asInterface(ServiceManager.getService("gocsdkService"));int version= mEmbededService.getLocalName();
String text = String.value(localName);

(5)编译service,烧录

直接全sdk编译,防止有遗漏

(6)验证

使⽤service list查看是否有EmbededService

xxx:/ $ service list | grep gocsdkServicegocsdkService: [android.os.IGocsdkService]

三、为什么需要Android HAL?

        Hardware Abstract Layer 硬件抽象层,由于Linux Kernel需要遵循GPL开源协议,硬件⼚商为了保护⾃⼰硬件⽅⾯的各项参数不被外泄,⽽⼀个设备的驱动程序包含了硬件的⼀些重要参数,所以驱动的开源势必会使硬件⼚商蒙受损失,Google为了保护硬件⼚商的利益,所以在Android系统中加⼊了HAL层,在HAL层中不必遵循GPL协议,所以代码可以封闭。
        所以如果硬件驱动开源的写在Kernel⾥,Framework直接调⽤,⽽不愿意开源的就写在HAL层⾥,实现闭源。也就是说,编写驱动分为两个部分,⼀个是HAL层的驱动代码,⼀个是Kernel层的驱动代码。


1、内核实现HAL驱动的⽅法有两种:

(1)采⽤直接调⽤so动态链接库⽅式

        采⽤共享库形式,在编译时会调⽤到。由于采⽤function call形式调⽤,因此可被多个进程使⽤,但会被mapping到多个进程空间中,造成浪费,同时需要考虑代码能否安全重⼊的问题。

(2)采⽤Stub代理⽅式调⽤

        采⽤HAL module和HAL stub结合形式,HAL stub不是⼀个share library,编译时
上层只拥有访问HAL stub的函数指针,并不需要HAL stub。上层通过HAL module提供的统⼀接⼝获取并操作HAL stub,so⽂件只会被mapping到⼀个进程,也不存在重复mapping和重⼊问题。

2、如何编写HAL层驱动

        我们现在一般都是采用第二种方式,基于HAL框架提供了三个结构体,分别为hw_device_t、hw_module_t、hw_module_methods_t,编写HAL层驱动则是依据这三个结构体作扩展,我们创建⾃⼰驱动的device_t,module_t代码,并且写hw_module_methods_t这个结构体中⽅法的实现代码,最后JNI层通过hw_get_module调⽤。

(1)在 android/hardware/libhardware/modules/xxx 路径下创建我们的HAL文件夹,例如LED:

mkdirhardware/libhardware/modules/ledpath:hardware/libhardware/include/hardware/led_hal.h
path:hardware/libhardware/modules/embeded/led_hal.c

(1)led_hal.c:

#define LOG_TAG "dLed"
#include <hardware/hardware.h>
#include <hardware/led_hal.h>
#include <fcntl.h>
#include <errno.h>
#include <cutils/log.h>
#include <cutils/atomic.h>
#define DEVICE_NAME "sys/led/embeded_blue_led"
#define MODULE_NAME "EmLed"/*设备打开和关闭接⼝*/
static int embededled_device_open(const struct hw_module_t* module, const
char* name, struct hw_device_t** device);
static int embededled_device_close(struct hw_device_t* device);/*设备访问接⼝*/
static int embededled_set_val(struct embededled_device_t* dev, int val);
static int embededled_get_val(struct embededled_device_t* dev, int* val);
static int embededled_device_open(const struct hw_module_t* module, const
char* name, struct hw_device_t** device) {struct embededled_device_t* dev;dev = (structembededled_device_t*)malloc(sizeof(struct embededled_device_t));if(!dev) {ALOGI("embededled Stub: failed to alloc space");return -EFAULT;}memset(dev, 0, sizeof(struct embededled_device_t));//初始化设备相关信息,实现访问接⼝函数dev->common.tag = HARDWARE_DEVICE_TAG;dev->common.version = 0;dev->common.module = (hw_module_t*)module;dev->common.close = embededled_device_close;dev->set_val = embededled_set_val;dev->get_val = embededled_get_val;if((dev->fd = open(DEVICE_NAME, O_RDWR)) == -1) {ALOGI("embededled Stub: failed to opensys/embededled/embeded_blue_led -- %s.", strerror(errno));free(dev);return -EFAULT;}int status = 0;write(dev->fd, &status, sizeof(status));*device = &(dev->common);ALOGI("embededled Stub: open sys/embededled/embeded_blue_ledsuccessfully.");return 0;
}static int embededled_device_close(struct hw_device_t* device) {struct embededled_device_t* embededled_device = (structembededled_device_t*)device;if(embededled_device) {close(embededled_device->fd);free(embededled_device);}return 0;
}static int embededled_set_val(struct embededled_device_t* dev, int val) {ALOGI("embededled Stub: set value %d to device.", val);write(dev->fd, &val, sizeof(val));return 0;}static int embededled_get_val(struct embededled_device_t* dev, int* val) {
if(!val) {ALOGI("embededled Stub: error val pointer");return -EFAULT;
}read(dev->fd, val, sizeof(*val));ALOGI("embededled Stub: get value %d from device", *val);return 0;
}/*模块⽅法表*/
static struct hw_module_methods_t embededled_module_methods = {
open: embededled_device_open
};/*模块实例变量*/
struct embededled_module_t HAL_MODULE_INFO_SYM = {
common: {tag: HARDWARE_MODULE_TAG,version_major: 1,version_minor: 0,id: EMBEDEDLED_HARDWARE_MODULE_ID,name: MODULE_NAME,author: MODULE_AUTHOR,methods: &embededled_module_methods,
}
};

led_hal.h:
path:hardware/libhardware/include/hardware/led_hal.h

#ifndef ANDROID_LED_INTERFACE_H
#define ANDROID_LED_INTERFACE_H
#include <hardware/hardware.h>
__BEGIN_DECLS
/*定义模块ID*/
#define EMBEDEDLED_HARDWARE_MODULE_ID "led_hal"
/*硬件模块结构体*/
struct led_module_t {struct hw_module_t common;
};
/*硬件接⼝结构体*/
struct embededled_device_t {struct hw_device_t common;int fd;int (*set_val)(struct led_device_t* dev, int val);int (*get_val)(struct led_device_t* dev, int* val);
};
__END_DECLS
#endif

四、JNI层添加

JNI开发流程的步骤:

第1步:编写JNI⽅法表并注册
第2步:实现JNI的.c⽂件

里面呢就是我们要实现的三个函数,然后再把对应的方法注册到我们的server里面去

Android.mk
1 diff --git a/frameworks/base/services/core/jni/Android.mkb/frameworks/base/services/core/jni/Android.mk
2     index 0f0124bd46..305773298a 100644
3     --- a/frameworks/base/services/core/jni/Android.mk
4     +++ b/frameworks/base/services/core/jni/Android.mk
5     @@ -36,6 +36,7 @@ LOCAL_SRC_FILES += \
6     $(LOCAL_REL_DIR)/com_android_server_UsbHostManager.cpp \
7     $(LOCAL_REL_DIR)/com_android_server_VibratorService.cpp \
8     $(LOCAL_REL_DIR)/com_android_server_PersistentDataBlockService.cpp \
9     + $(LOCAL_REL_DIR)/com_android_server_EmbededLedService.cpp \
10     $(LOCAL_REL_DIR)/onload.cpp
11
12     LOCAL_SRC_FILES += \
把注册JNI⽅法函数添加到系统中
1 diff --git a/frameworks/base/services/core/jni/onload.cppb/frameworks/base/services/core/jni/onload.cpp
2     index d5861f8c41..b52f7917fd 100644
3     --- a/frameworks/base/services/core/jni/onload.cpp
4     +++ b/frameworks/base/services/core/jni/onload.cpp
5     @@ -47,6 +47,7 @@ intregister_android_server_PersistentDataBlockService(JNIEnv* env);
6     int register_android_server_Watchdog(JNIEnv* env);
7     int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
8     int register_com_android_server_rkdisplay_RkDisplayModes(JNIEnv* env);
9     +int register_android_server_EmbededLedService(JNIEnv* env);
10     };
11
12     using namespace android;
13     @@ -89,7 +90,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
14     register_android_server_Watchdog(env);
15     register_android_server_HardwarePropertiesManagerService(env);
16     register_com_android_server_rkdisplay_RkDisplayModes(env);
17     -
18     -
19     + register_android_server_EmbededLedService(env);
20     return JNI_VERSION_1_4;
21         }
然后按照同样的方法去建立AIDL&Service

1、AIDL:

1 package android.os;
2
3 interface IEmbededLedService {
4     void setVal(int val);
5     int getVal();
6 }
添加下⾯mk⽂件内容后,编译⽣成接⼝
1 diff --git a/frameworks/base/Android.mk b/frameworks/base/Android.mk
2 index b9692de0e1..c426a3cd99 100755
3 --- a/frameworks/base/Android.mk
4 +++ b/frameworks/base/Android.mk
5 @@ -240,6 +240,7 @@ LOCAL_SRC_FILES += \
6     core/java/android/os/IUpdateLock.aidl \
7     core/java/android/os/IUserManager.aidl \
8     core/java/android/os/IVibratorService.aidl \
9     + core/java/android/os/IEmbededLedService.aidl \
10     core/java/android/os/IDisplayDeviceManagementService.aidl \
11     core/java/android/os/IRkDisplayDeviceManagementService.aidl \
12     core/java/android/security/IKeystoreService.aidl \

2、Service

frameworks/base/services/java/com/android/server/EmbededLedService.java
1 package com.android.server;
2 import android.content.Context;
3 import android.os.IEmbededLedService;
4 import android.util.Slog;
5     public class EmbededLedService extends IEmbededLedService.Stub {
6         private static final String TAG = "EmbededLedService";
7         EmbededLedService() {
8
9     boolean status = init_native();
10     Slog.i(TAG,"EmbededLedService Stub init"+status);
11     }
12     public void setVal(int val) {
13         setVal_native(val);
14     }
15     public int getVal() {
16     return getVal_native();
17 }
18
19 //JNI⽅法
20 private static native boolean init_native();
21 private static native void setVal_native(int val);
22 private static native int getVal_native();
23 };

3、添加Service到System启动

1 diff --git
a/frameworks/base/services/java/com/android/server/SystemServer.java
b/frameworks/base/services/java/com/android/server/SystemServer.java
2 index cc6f1850e6..b22ecda734 100644
3 --- a/frameworks/base/services/java/com/android/server/SystemServer.java
4 +++ b/frameworks/base/services/java/com/android/server/SystemServer.java
5 @@ -1086,6 +1086,15 @@ public final class SystemServer {
6 } catch (Throwable e) {
7 reportWtf("starting DiskStats Service", e);
8 }
9 +
10 + try {
11 + Slog.i(TAG, "Embededled Service");
12 + ServiceManager.addService("embededled", new
EmbededLedService());
13 + } catch (Throwable e) {
14 + Slog.e(TAG, "Failure starting Embededled Service", e);
15 + }
16 +
17 +
18 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
19
20 if (!disableSamplingProfiler) {

4、编译&烧写

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

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

相关文章

闲话 .NET(4):为什么要跨平台?

前言 .NET Core 有一个关键词就是跨平台&#xff0c;为什么要跨平台呢&#xff1f;Windows 操作系统不香吗&#xff1f;今天我们来聊聊这个 原因一&#xff1a;安全考虑 Windows OS 是闭源的&#xff0c;而 Linux 是开源的&#xff0c;因此有些公司的技术负责人就认为 Linux…

如何将老板的游戏机接入阿里云自建K8S跑大模型(下)- 安装nvidia/gpu-operator支持GPU在容器中共享

文章目录 安装nvidia/gpu-operator支持GPU在容器中共享 安装nvidia/gpu-operator支持GPU在容器中共享 安装 nvidia/gpu-operator遇到两个问题&#xff1a; 由于我们都懂的某个原因&#xff0c;导致某些镜像一直现在不成功。 解决办法&#xff0c;准备一个&#x1fa9c;&#…

车间人员作业行为智能检测 AI视觉在生产车间制造中的应用

车间人员作业行为智能检测系统基于神经网络人工智能视觉算法&#xff0c;车间人员作业行为智能检测通过对车间监控摄像头获取的视频图像进行分析和识别&#xff0c;实现了对人员操作行为的智能检测。系统对工人的操作环节进行分解&#xff0c;根据时间、动作标准等方面制定了规…

MemoryDB 2024 论文分享

论文地址点这里。 TL;DR MemoryDB 通过底层依赖 AWS 内部系统 Multi-AZ Transaction Log 实现了 11 个 9 的持久性保证。 通过依赖 Transaction Log 的 Condition API 和租约机制来实现了一致性和可用性保证。 通过周期性调度 Off-box 节点来外部 Rewrite binlog 避免了内存…

C语言基础——循环(2)+关机程序

欢迎点赞支持 个人主页&#xff1a;励志不掉头发的内向程序员&#xff1b; 专栏主页&#xff1a;C语言基础&#xff1b; 文章目录 目录 前言 一、for循环的补充 二、循环的嵌套 1、嵌套的介绍 1.1 练习&#xff1a; 题目解析&#xff1a; 优化&#xff1a; 三、goto语句 1、go…

3DEXPERIENCE DELMIA Role: RFP - Fabrication Robot Programmer

Discipline: Robotics Role: RFP - Fabrication Robot Programmer 在虚拟工厂中定义、验证和编程机器人弧焊和密封剂沉积系统 模拟和验证完整的焊接、密封剂沉积和搬运机器人系统&#xff0c;以消除代价高昂的碰撞并优化生产率提供精确的生产就绪型机器人程序&#xff0c;同…

Day 38 防火墙技术IPtables

一&#xff1a;防火墙简介 1.简介 ​ iptables其实并不是真正的防火墙&#xff0c;我们可以把他理解为一个客户端的代理&#xff0c;用户是通过iptables这个代理&#xff0c;将用户的安全设定执行到对应的“安全框架”中&#xff0c;这个“安全框架”才是真正的防火墙。这个框…

mac安装Redis

官网&#xff1a; https://redis.io中文网&#xff1a; Redis中文网 安装 brew install redis 查看版本 redis-server --version 开启关闭服务 方式一&#xff08;不推荐&#xff09; 这种方式不太建议&#xff0c;因为控制台不用输出相应的日志 开启服务 brew service…

K8S认证|CKA题库+答案| 1. 权限控制RBAC

1、权限控制RBAC 您必须在以下Cluster/Node上完成此考题&#xff1a; Cluster Master node Worker node k8s master …

园区网的基本了解

园区网使用的典型技术---IEEE802.3标准/IEEE802.11标准 封闭式园区网络 ---由内部人员使用&#xff0c;不能访问互联网。 ---制订各式各样的规章制度 ---NAC&#xff0c;网络接入控制 开放式园区网络 ---服务于公众的&#xff0c;认证 园区网的发展 第一代&#xff1a;…

神经网络模型结构和参数可视化

神经网络模型结构和参数可视化 一、前言二、Netron2.1Netron简介2.2TensorFlow、Keras、Caffe模型文件实测结果2.3PyTorch、scikit-learn模型文件实测结果 三、NN-SVG四、Netscope五、PlotNeuralNet六、Graphviz七、总结参考文档 一、前言 在神经网络的某些应用场景中&#xf…

.NET快速实现网页数据抓取

网页数据抓取需求 本文我们以抓取博客园10天推荐排行榜第一页的文章标题、文章简介和文章地址为示例&#xff0c;并把抓取下来的数据保存到对应的txt文本中。 请求地址&#xff1a;https://www.cnblogs.com/aggsite/topdiggs 创建控制台应用 创建名为DotnetSpiderExercise的控…

【Spring security】【pig】Note01-pig登录验证过程

&#x1f338;&#x1f338; pig 登录验证 &#x1f338;&#x1f338; pig后端源码 一、大概执行顺序&#xff0c;便于理解 pig spring-security 二、配置过滤器 配置SecurityFilterChain 三、执行过程分析 请求拦截&#xff1a; 当客户端发送请求时&#xff0c;Sprin…

Kubernetes——Pod详解

目录 一、Pod基础概念 1.概念 2.使用方式 3.Pause容器 3.1网络 3.2存储 4.Pod容器分类 4.1自主式Pod 4.2控制器管理的Pod 二、Pod的分类 1.基础容器&#xff08;infrastructure container&#xff09; 2.初始化容器&#xff08;initcontainers&#xff09; 2.1Ini…

CR80清洁卡都能用在什么地方?

CR80清洁卡&#xff08;也被称为ISO 7810 ID-1清洁卡&#xff09;的规格确实使其在各种需要读取磁条或接触式智能卡的设备中都有广泛的用途。这些设备包括但不限于&#xff1a; ATM自动终端机&#xff1a;当ATM机的磁条读卡器出现故障或读卡不灵敏时&#xff0c;可以使用CR80清…

Python 渗透测试:MySQL 数据库 弱密码测试.(3306 端口)

什么是 MySQL 数据库 MySQL 是一种开源的关系型数据库管理系统(RDBMS)。它是目前世界上最流行的数据库之一,被广泛应用于各种类型的网站和应用程序中。MySQL 数据库爆破是一种非法和危险的黑客攻击手段。它指的是通过暴力猜测密码的方式,试图获取未授权访问 MySQL 数据库的行为…

【qt】下拉列表组件

下拉列表组件 一.Combo Box1.可以直接双击编辑下拉内容2.代码初始化下拉内容3.一次性添加多个下拉内容4.下拉框手动编辑5.下拉内容添加附加值6.下拉添加图标7.获取下拉值 二.总结 一.Combo Box 还是老样子&#xff0c;咱们边做边练 目标图&#xff1a; 1.可以直接双击编辑下…

存储+调优:存储-Cloud

存储调优&#xff1a;存储-Cloud Master Server 配置&#xff1a; IP192.168.1.254 useradd mfs tar zxf mfs-1.6.11.tar.gz.gz cd mfs-1.6.11 ./configure --prefix/usr --sysconfdir/etc --localstatedir/var/lib --with-default-usermfs --with-default-groupmfs --disabl…

掌握一个面试小心机,就业离你只差这一步!

马上进6月份&#xff0c;大家是已经在工作岗位上了&#xff0c;还是正在面试呀&#xff01;不知道大家在面试过程中有没有遇到这样的问题&#xff0c;面试完几家公司之后进行总结&#xff0c;还是不知道自己为什么被pass掉&#xff0c;今天小编带大家搞清测试岗位面试的底层逻辑…

成为领导心腹:测试新人如何提高影响力?

作为一名新入行的测试人员&#xff0c;如何提高自己在工作中的影响力呢&#xff1f; 可能有人会问了&#xff1a;“测试人员不是只要安分守己的做好自己的测试工作不就行了吗&#xff1f;又不是当管理者&#xff0c;为什么要提高影响力呢&#xff1f;” 说实话&#xff0c;我…