您当前的位置:首页 > 电脑百科 > 程序开发 > 移动端 > Android

android6.0系统Healthd深入分析

时间:2019-11-27 16:52:32  来源:  作者:

概述

Healthd是Android4.4之后提出来的一种中介模型,该模型向下监听来自底层的电池事件,向上传递电池数据信息给Framework层的BatteryService用以计算电池电量相关状态信息,BatteryServcie通过传递来的数据来计算电池电量显示,剩余电量,电量级别等信息,如果收到过温报警或者严重低电报警等信息,系统会直接关机,保护硬件。

主模块处理流程

Healthd模块代码是在system/core/healthd/,其模块入口在healthd的main函数,函数代码如下:

int main(int argc, char **argv) {
 
int ch;
 
int ret;
 
klog_set_level(KLOG_LEVEL);
 
healthd_mode_ops = &android_ops;
 
 
 
if (!strcmp(basename(argv[0]), "charger")) {
 
 healthd_mode_ops = &charger_ops;
 
} else {
 
 while ((ch = getopt(argc, argv, "cr")) != -1) {
 
 switch (ch) {
 
 case 'c':
 
 healthd_mode_ops = &charger_ops;
 
 break;
 
 case 'r':
 
 healthd_mode_ops = &recovery_ops;
 
 break;
 
 case '?':
 
 default:
 
 KLOG_ERROR(LOG_TAG, "Unrecognized healthd option: %cn",
 
 optopt);
 
 exit(1);
 
 }
 
 }
 
 }
 
ret = healthd_init();
 
 if (ret) {
 
 KLOG_ERROR("Initialization failed, exitingn");
 
 exit(2);
 
 }
 
 
 
 healthd_mainloop();
 
 KLOG_ERROR("Main loop terminated, exitingn");
 
 return 3;
 
}

可以看出Main函数并不长,但是其作用确实巨大的,main函数起着一个统筹兼顾的作用,其他各个模块函数去做一些具体相应的工作,最后汇总到main函数中被调用。

代码中开始便是解析参数,healthd_mode_ops是一个关于充电状态结构体变量,结构体变量里的参数是函数指针,在初始化时指向各个不同的操作函数,当开机充电时变量赋值为&android_ops,关机充电时候变量赋值为&charger_ops。

在ret = healthd_init();中进行一些初始化工作。

static int healthd_init() {
 
epollfd = epoll_create(MAX_EPOLL_EVENTS);
 
 if (epollfd == -1) {
 
 KLOG_ERROR(LOG_TAG,
 
 "epoll_create failed; errno=%dn",
 
 errno);
 
 return -1;
 
 }
 
 
 
 healthd_board_init(&healthd_config);
 
 healthd_mode_ops->init(&healthd_config);
 
 wakealarm_init();
 
 uevent_init();
 
 gBatteryMonitor = new BatteryMonitor();
 
 gBatteryMonitor->init(&healthd_config);
 
 return 0;
 
}

创建一个epoll的变量将其赋值给epollfd,在healthd_board_init中未作任何事便返回了。

healthd_mode_ops->init调用有两种情况:关机情况下调用charger_ops的init函数;开机情况下调用android_ops的init函数,这里就开机情况来分析。android_ops的init函数指针指向healthd_mode_android_init函数

代码如下:

void healthd_mode_android_init(struct healthd_config* /*config*/) {
 ProcessState::self()->setThreadPoolMaxThreadCount(0);//线程池里最大线程数
 IPCThreadState::self()->disableBackgroundScheduling(true);//禁用后台调度
 IPCThreadState::self()->setupPolling(&gBinderFd);//
 
 if (gBinderFd >= 0) {
 if (healthd_register_event(gBinderFd, binder_event))
 KLOG_ERROR(LOG_TAG,
 "Register for binder events failedn");
 }
 
 gBatteryPropertiesRegistrar = new BatteryPropertiesRegistrar();
 gBatteryPropertiesRegistrar->publish();
}

再来看看wakealarm_init函数:

static void wakealarm_init(void) {
 wakealarm_fd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK);
 if (wakealarm_fd == -1) {
 KLOG_ERROR(LOG_TAG, "wakealarm_init: timerfd_create failedn");
 return;
 }
 
 if (healthd_register_event(wakealarm_fd, wakealarm_event))
 KLOG_ERROR(LOG_TAG,
 "Registration of wakealarm event failedn");
 
 wakealarm_set_interval(healthd_config.periodic_chores_interval_fast);
}

首先创建一个wakealarm_fd的定时器与之对应的文件描述符,healthd_register_event将wakealarm事件注册到wakealarm_fd文件节点用以监听wakealarm事件,wakealarm_set_interval设置alarm唤醒的间隔

再看看uevent_init函数:

static void uevent_init(void) {
 uevent_fd = uevent_open_socket(64*1024, true);
 
 if (uevent_fd < 0) {
 KLOG_ERROR(LOG_TAG, "uevent_init: uevent_open_socket failedn");
 return;
 }
 
 fcntl(uevent_fd, F_SETFL, O_NONBLOCK);
 if (healthd_register_event(uevent_fd, uevent_event))
 KLOG_ERROR(LOG_TAG,
 "register for uevent events failedn");
}

创建并打开一个64k的socket文件描述符uevent_fd,设置文件状态标志为非阻塞模,将uevent事件注册到uevent_fd文件节点用以监听uevent事件。

我们可以看到android利用epoll监听了三个文件节点的改变事件,分别是:通过gBinderfd监听线程Binder通信事件;通过wakealarm_fd监听wakealarm事件;通过uevent_fd监听wakealarm事件。至于如何监听后面做详细分析

在healthd_init中最后创建BatteryMonitor的对象,并将其初始化。BatteryMonitor主要接受healthd传来的数据,做电池状态的计算并更新。

我们可以看到在BatterMonitor中的init函数中有以下语句:

DIR* dir = opendir(POWER_SUPPLY_SYSFS_PATH);
 
struct dirent* entry;
 
。。。。。。。
 
 while ((entry = readdir(dir))) {
 
 const char* name = entry->d_name;
 
。。。。。。
 
}

POWER_SUPPLY_SYSFS_PATH定义为"/sys/class/power_supply",在init函数中打开系统该文件夹,然后一一读取该文件夹下的文件内容,在while循环中判断该文件夹下各个文件节点的内容,并将其初始化给相关的参数.

至此,healthd_init函数就分析完了,其主要工作就是:创建了三个文件节点用来监听相应的三种事件改变;创建BatteryMonitor对象,并通过读取/sys/class/power_supply将其初始化。

Healthd_init走完之后,接着就是调用healthd_mainloop函数,该函数维持了一个死循环,代码如下:

static void healthd_mainloop(void) {
 
while (1) {
 
 struct epoll_event events[eventct];
 
 int nevents;
 
 int timeout = awake_poll_interval;
 
 int mode_timeout;
 
 mode_timeout = healthd_mode_ops->preparetowait();
 
 if (timeout < 0 || (mode_timeout > 0 && mode_timeout < timeout))
 
 timeout = mode_timeout;
 
 nevents = epoll_wait(epollfd, events, eventct, timeout);
 
 if (nevents == -1) {
 
 if (errno == EINTR)
 
 continue;
 
 KLOG_ERROR(LOG_TAG, "healthd_mainloop: epoll_wait failedn");
 
 break;
 
 }
 
 for (int n = 0; n < nevents; ++n) {
 
 if (events[n].data.ptr)
 
 (*(void (*)(int))events[n].data.ptr)(events[n].events);
 
 }
 
 if (!nevents)
 
 periodic_chores();
 
 healthd_mode_ops->heartbeat();
 
 }
 
 return;
 
}

Healthd_mainloop中维持了一个死循环,死循环中变量nevents 表示从epollfd中轮循中监听得到的事件数目,这里介绍一下轮询机制中重要函数epoll_waite().

epoll_wait运行的道理是:等侍注册在epfd上的socket fd的事务的产生,若是产生则将产生的sokct fd和事务类型放入到events数组中。且timeout如果为-1则为阻塞式,timeowout为0则表示非阻塞式。可以看到代码中timeout为-1,故为阻塞式轮询,当epollfd上有事件发生,则会走到下面的处理逻辑。事件处理主要在for循环中:

在periodic_chores()中调用到healthd_battery_update()更新电池状态。

void healthd_battery_update(void) {
 
 int new_wake_interval = gBatteryMonitor->update() ?
 
 healthd_config.periodic_chores_interval_fast :
 
 healthd_config.periodic_chores_interval_slow;
 
 
 
 if (new_wake_interval != wakealarm_wake_interval)
 
 wakealarm_set_interval(new_wake_interval);
 
 if (healthd_config.periodic_chores_interval_fast == -1)
 
 awake_poll_interval = -1;
 
 Else
 
 awake_poll_interval = new_wake_interval == healthd_config.periodic_chores_interval_fast ?
 
 -1 : healthd_config.periodic_chores_interval_fast * 1000;
 
}

可以看出该函数并不长,new_wake_interval表示新的wakealarm唤醒间隔,通过调用BatteryMonitor的update函数(后面详细分析如何更新),其返回值为是否处于充电状态,当处于充电状态,则唤醒间隔为healthd_config.periodic_chores_interval_fast(短间隔),当不再充电状态时唤醒间隔为healthd_config.periodic_chores_interval_slow(长间隔)

当新的间隔变量new_wake_interval与旧的变量wakealarm_wake_interval不一样,则将新的唤醒间隔设置成wakealarm的唤醒间隔;

awake_poll_internal作为下一次epoll_waite的timeout参数,在这里将其更新,在充电状态下awake_poll_internal为-1,没有充电的状态下awake_poll_internal为60000ms

healthd主流程都是在main函数中处理,至此main已经分析完成,其简要流程图如下

android6.0系统Healthd深入分析

 

Healthd处理逻辑

初始化处理

前面将healthd模块中main函数分析完了,其主要工作流程有个大概的了解,但是其详细处理逻辑并未做分析,在此之后,对Healthd的初始化,事件处理,状态更新将做一个详细的分析。

前面已经说过在healthd_init中创建了三个文件节点gBinderfd,uevent_fd,wakealarm_fd,并用以注册监听三种事件,注册监听都是通过healthd_register_event函数实现的。

healthd_register_event(gBinderFd, binder_event);

healthd_register_event(wakealarm_fd, wakealarm_event);

healthd_register_event(uevent_fd, uevent_event);

其healthd_register_event实现代码如下:

int healthd_register_event(int fd, void (*handler)(uint32_t)) {
 
struct epoll_event ev;
 
 ev.events = EPOLLIN | EPOLLWAKEUP;
 
 ev.data.ptr = (void *)handler;
 
 if (epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1) {
 
 KLOG_ERROR(LOG_TAG,
 
 "epoll_ctl failed; errno=%dn", errno);
 
 return -1;
 
 }
 
 eventct++;
 
 return 0;
 
}

函数将相应的文件节点事件赋值为函数的第二个形参,也就是说相应的gBinderfd的事件处理函数为binder_event函数,同理wakealarm_fd,ueven_fd的事件事件处理分别为wakealarm_event,uevent_event函数。然后将其三个文件节点加入到epollfd中。

事件获取与处理

Healthd中维持了一个阻塞式的死循环healthd_mainloop,在该函数中提供阻塞式的监听已发送的事件函数epoll_wait(),healthd_mainloop中有如下代码

nevents = epoll_wait(epollfd, events, eventct, timeout);
 
 
 
for (int n = 0; n < nevents; ++n) {
 
if (events[n].data.ptr)
 
 (*(void (*)(int))events[n].data.ptr)(events[n].events);
 
}

当epoll_waite接受到gBinderfd,wakealarm_fd,uevent_fd其中的事件,便会将监听到的事件加入到event数组中。在for循环中做处理,for循环中代码看起来非常难懂,其实if判断的便是event有没有相应的处理函数,在前面注册事件时候已经提到,三种句柄上的事件都有对应的处理函数,也就是当收到gBinderfd上的事件,便用binder_event函数处理,当收到uevent_fd上的事件便用uevent_event处理,当收到wakealarm_fd上的事件便用wakealarm_event处理。

这里以较为重要的uevent_event事件处理为例:

#define UEVENT_MSG_LEN 2048
 
static void uevent_event(uint32_t /*epevents*/) {
 
char msg[UEVENT_MSG_LEN+2];
 
 char *cp;
 
 int n;
 
 
 
 n = uevent_kernel_multicast_recv(uevent_fd, msg, UEVENT_MSG_LEN);
 
 if (n <= 0)
 
 return;
 
 if (n >= UEVENT_MSG_LEN) /* overflow -- discard */
 
 return;
 
 
 
 msg[n] = '';
 
 msg[n+1] = '';
 
 cp = msg;
 
 
 
 while (*cp) {
 
 if (!strcmp(cp, "SUBSYSTEM=" POWER_SUPPLY_SUBSYSTEM)) {
 
 healthd_battery_update();
 
 break;
 
 }
 
 
 
 /* advance to after the next  */
 
 while (*cp++)
 
 ;
 
 }
 
}

处理函数首先从uevent_fd 获取事件数目,然后循环判断是否是来自与power_supply目录下的事件,如果是,则调用到healthd_battery_update中去更新电池状态。

更新电池状态

当收到事件,做一些判断工作便需要更新电池状态,其更新函数为healthd.cpp下的healthd_battery_update函数,但是主要更新并不在heathd中完成的,而是在BatteryMonitor中的update函数,其代码较多,分段分析:

props.chargerAcOnline = false;
 
props.chargerUsbOnline = false;
 
props.chargerWirelessOnline = false;
 
props.batteryStatus = BATTERY_STATUS_UNKNOWN;
 
props.batteryHealth = BATTERY_HEALTH_UNKNOWN;
 
 
 
if (!mHealthdConfig->batteryPresentPath.isEmpty())
 
props.batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath);
 
else
 
 props.batteryPresent = mBatteryDevicePresent;
 
 
 
props.batteryLevel = mBatteryFixedCapacity ?
 
 mBatteryFixedCapacity :
 
 getIntField(mHealthdConfig->batteryCapacityPath);
 
props.batteryVoltage = getIntField(mHealthdConfig->batteryVoltagePath) / 1000;
 
 
 
props.batteryTemperature = mBatteryFixedTemperature ?
 
 mBatteryFixedTemperature :
 
 getIntField(mHealthdConfig->batteryTemperaturePath);
 
const int SIZE = 128;
 
char buf[SIZE];
 
String8 btech;
 
 
 
if (readFromFile(mHealthdConfig->batteryStatusPath, buf, SIZE) > 0)
 
 props.batteryStatus = getBatteryStatus(buf);
 
 
 
if (readFromFile(mHealthdConfig->batteryHealthPath, buf, SIZE) > 0)
 
 props.batteryHealth = getBatteryHealth(buf);
 
 
 
if (readFromFile(mHealthdConfig->batteryTechnologyPath, buf, SIZE) > 0)
 
 props.batteryTechnology = String8(buf);

在init函数中将healthd_config 对象传入,并且将里面的成员的一些地址信息去初始化保存起来。主要是保存一些地址信息,以及充电方式。在BatteryMonitor初始化中,heathd_config传入init函数中,赋值为mHealthdConfig,上面一段主要是读取/sys/class/power_supply下的文件节点信息初更新电池数据属性值,

path.AppendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH,
 
 mChargerNames[i].string());
 
 
 
if (readFromFile(path, buf, SIZE) > 0) {
 
if (buf[0] != '0') {
 
 path.clear();
 
 path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH,
 
 mChargerNames[i].string());
 
 switch(readPowerSupplyType(path)) {
 
 case ANDROID_POWER_SUPPLY_TYPE_AC:
 
 props.chargerAcOnline = true;
 
 break;
 
 case ANDROID_POWER_SUPPLY_TYPE_USB:
 
 props.chargerUsbOnline = true;
 
 break;
 
 case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
 
 props.chargerWirelessOnline = true;
 
 break;
 
 default:
 
 KLOG_WARNING(LOG_TAG, "%s: Unknown power supply typen",
 
 mChargerNames[i].string());
 
 }
 
 }
 
}

将电池当前的电量级别,电压,温度,健康状况,电池状态以及充放电倍率存入dmesgline变量中,在后面会将电池充电类型,电池使用时间都以字符串存入dmesgline变量中,然后:

KLOG_WARNING(LOG_TAG, "%sn", dmesgline);//向log记录电池当前各种状态信息

}

healthd_mode_ops->battery_update(&props);//更新电池

return props.chargerAcOnline | props.chargerUsbOnline |

props.chargerWirelessOnline;//返回是否在充电状态

整个update函数做完更新数据,记录数据到log之后,然后调用到BatteryPropertiesRegistrar的update函数继续更新电池状态,最后返回值为是否处于充电状态。

BatteryPropertiesRegistrar的update函数未作任何操作调用Healthd_mode_android.cpp中的healthd_mode_android_battery_update函数,我们可以看看该函数

void healthd_mode_android_battery_update(
struct android::BatteryProperties *props) {
 
 if (gBatteryPropertiesRegistrar != NULL)
 
 gBatteryPropertiesRegistrar->notifyListeners(*props);
 
 
 
 return;
 
}

这里这里直接调用到BatteryPropertiesRegistrar的notifyListeners去通知props改变了,props是什么呢?props是定义的一个BatteryProperties属性集,里面的成员变量包含了所有的电池状态信息,在update开始便通过读取各个文件节点的实时数据更新电池属性props,更新完成后通过BatteryPropertiesRegistrar通知其属性监听者去更新状态,但是谁是监听呢?

我们可以看到framework层中的BatteryService.JAVA的onStart函数中有如下代码:

public void onStart() {
 
IBinder b = ServiceManager.getService("batteryproperties");
 
 final IBatteryPropertiesRegistrar batteryPropertiesRegistrar =
 
 IBatteryPropertiesRegistrar.Stub.asInterface(b);
 
 try {
 
 batteryPropertiesRegistrar.registerListener(new BatteryListener());
 
 } catch (RemoteException e) {
 
 // Should never happen.
 
 }

我们在初始化的时候已经提到过,当healthd初始化时候会创建BatteryPropertiesRegistrar的对象并将其publish注册到系统服务中,注册服务的语句如下:

defaultServiceManager()->addService(String16("batteryproperties"), this);

所以BatteryService在这里获取该服务,并以此注册其监听器为BatteryListener(),该监听器监听到BatteryProperties改变便会调用到BatteryService的update函数,去做电池电量相关计算以及显示。

至此更新操作基本分析完成,其简要流程如下图所示

android6.0系统Healthd深入分析

 

总结

Healthd是framework层传递来自底层电池事件信息并调用相关模块更新电池状态的一个中间层,其向下监听来自底层PMU驱动上报的uevent电池事件,向上调用BatteryService去计算电池,电量,使用等相关信息,它通过一个阻塞式的死循环不断监听底层三个文件节点上的事件信息,当监听到事件便调用到BatteryMonitor执行更新操作,通过BatteryService.java中注册监听电池属性改变的函数,当电池属性信息发生改变,即回调到BatteryService中做更新操作,更新完成一次电池事件的上报到更新整个流程就完成;总之Healthd是连接Battery模块framework中java层与HAL层交互的主要通道。



Tags:android   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
今天面试遇到同学说做过内存优化,于是我一般都会问那 Bitmap 的像素内存存在哪?大多数同学都回答在 java heap 里面,就比较尴尬,理论上你做内存优化,如果连图片这个内存大户内存...【详细内容】
2021-12-23  Tags: android  点击:(8)  评论:(0)  加入收藏
Android logcat日志封装logcat痛点在Android开发中使用logcat非常频繁,logcat能帮我们定位问题,但是在日常使用中发现每次使用都需要传递tag,并且会遇到输出频率很高的log,在多...【详细内容】
2021-12-22  Tags: android  点击:(8)  评论:(0)  加入收藏
对项目的基本介绍 1.整个框架主要是给MVVM框架使用的,自己写完interface接口后,通过自定义的注解就能自动生成接口方法 2.用Kotlin的Flow去代替Rxjava,因为我发现RxJava功能很...【详细内容】
2021-12-08  Tags: android  点击:(17)  评论:(0)  加入收藏
前言在Android开发过程中,有些时候会根据需要引用别的项目到当前项目里面,而且以Module形式引用。所以本篇博文就来分享一下怎么以Module形式引用别的项目到当前项目中,方便开...【详细内容】
2021-12-07  Tags: android  点击:(22)  评论:(0)  加入收藏
新型Android恶意木马程序伪装成数十款街机、射击和策略游戏,通过华为应用市场AppGallery进行分发,从而窃取设备信息和用户的手机号码,全球目前至少有930万台Android设备被该恶...【详细内容】
2021-12-01  Tags: android  点击:(24)  评论:(0)  加入收藏
作者:fundroid这篇文章偏阅读一些,大家可以了解下 Android 的一些最新动向。每年9/10月份 Google 都会举行约为期2天的 Android Dev Summit,在活动上 Google 的技术专家们会分...【详细内容】
2021-11-30  Tags: android  点击:(15)  评论:(0)  加入收藏
一、 准备工作1、安装JDK,下载地址(可能需要一个oracle账号,大家百度一下或者自行注册一个就行。尽可能选择8或者11,这两个是长期版本)Java SE | Oracle Technology Network | Or...【详细内容】
2021-11-23  Tags: android  点击:(28)  评论:(0)  加入收藏
如果你是一名忠实的Android玩家,那么可能会知道,今年的Android 12系统在版本规划上与“往届”相比可以说是很有些特殊。具体来说,除了前段时间刚刚推出正式版的Android 12外,谷...【详细内容】
2021-11-10  Tags: android  点击:(24)  评论:(0)  加入收藏
使用Maven Publish Plugin插件。(官方支持)一、在Library的build.gradle中配置plugins { id &#39;com.android.library&#39; id &#39;kotlin-android&#39; id &#39;k...【详细内容】
2021-11-05  Tags: android  点击:(37)  评论:(0)  加入收藏
今年5月,谷歌推出了Android 12,这是原生安卓系统史上最大的设计变化,10月4日,谷歌推出全新的Android12正式版本,并且宣布会在今年晚些时候应用于安卓设备,对比Android11的挤牙膏式...【详细内容】
2021-10-29  Tags: android  点击:(125)  评论:(0)  加入收藏
▌简易百科推荐
今天面试遇到同学说做过内存优化,于是我一般都会问那 Bitmap 的像素内存存在哪?大多数同学都回答在 java heap 里面,就比较尴尬,理论上你做内存优化,如果连图片这个内存大户内存...【详细内容】
2021-12-23  像程序那样思考    Tags:Android开发   点击:(8)  评论:(0)  加入收藏
Android logcat日志封装logcat痛点在Android开发中使用logcat非常频繁,logcat能帮我们定位问题,但是在日常使用中发现每次使用都需要传递tag,并且会遇到输出频率很高的log,在多...【详细内容】
2021-12-22  YuCoding    Tags:Android   点击:(8)  评论:(0)  加入收藏
对项目的基本介绍 1.整个框架主要是给MVVM框架使用的,自己写完interface接口后,通过自定义的注解就能自动生成接口方法 2.用Kotlin的Flow去代替Rxjava,因为我发现RxJava功能很...【详细内容】
2021-12-08  网易Leo    Tags:Android开发   点击:(17)  评论:(0)  加入收藏
前言在Android开发过程中,有些时候会根据需要引用别的项目到当前项目里面,而且以Module形式引用。所以本篇博文就来分享一下怎么以Module形式引用别的项目到当前项目中,方便开...【详细内容】
2021-12-07  网易Leo    Tags:Android开发   点击:(22)  评论:(0)  加入收藏
作者:fundroid这篇文章偏阅读一些,大家可以了解下 Android 的一些最新动向。每年9/10月份 Google 都会举行约为期2天的 Android Dev Summit,在活动上 Google 的技术专家们会分...【详细内容】
2021-11-30  像程序那样思考    Tags:Android开发   点击:(15)  评论:(0)  加入收藏
一、 准备工作1、安装JDK,下载地址(可能需要一个oracle账号,大家百度一下或者自行注册一个就行。尽可能选择8或者11,这两个是长期版本)Java SE | Oracle Technology Network | Or...【详细内容】
2021-11-23  永沧    Tags:Android   点击:(28)  评论:(0)  加入收藏
使用Maven Publish Plugin插件。(官方支持)一、在Library的build.gradle中配置plugins { id &#39;com.android.library&#39; id &#39;kotlin-android&#39; id &#39;k...【详细内容】
2021-11-05  羊城小阳    Tags:Android   点击:(37)  评论:(0)  加入收藏
谷歌离推出Play Store应用程序的新数据隐私部分又近了一步。应用程序开发人员现在可以通过谷歌在Play控制台的新 "数据安全表 "填写相关细节。该公司表示,所需信息将从2022年...【详细内容】
2021-10-20    中关村在线  Tags:安卓   点击:(58)  评论:(0)  加入收藏
架构究竟是什么?如何更好的理解架构?我们知道一个APP通常是由class组成,而这些class之间如何组合,相互之间又如何产生作用,就是影响这个APP的关键点。细分的话我们可以将其分为类...【详细内容】
2021-09-17  像程序那样思考    Tags:Android架构   点击:(52)  评论:(0)  加入收藏
概述当Android应用程序需要访问设备上的敏感资源时,应用程序开发人员会使用权限模型。虽然该模型使用起来非常简单,但开发人员在使用权限时容易出错,从而导致安全漏洞。本文中,...【详细内容】
2021-09-07  SecTr安全团队    Tags:Android开发   点击:(66)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条