I/O Prefetcher是高通本身提供的一套优化方案,可以用在Android手机App冷启动的时候。本文基于android Q

主要分libqti-iopd、vendor.qti.hardware.iop@2.0-impl、libqti-iopd-client_system、libqti-perfd-client_system、libperfconfig、libqti_performance,编译后在/vendor/lib/目录下,其中libqti-iopd、vendor.qti.hardware.iop@2.0-impl为服务端

  • libqti-perfd-client_system

主要提供perf_get_prop、perf_wait_get_prop等方法,这里主要调用libperfconfig中的方法去读取配置文件中的信息

代码路径:vendor/qcom/proprietary/commonsys-intf/android-perf/mp-ctl/client.cpp

  • libperfconfig

主要是读取/vendor/etc/perf/perfconfigstore.xml文件获取配置信息,此文件时在编译时拷贝

vendor/qcom/proprietary/android-perf/configs/XXX/perfconfigstore.xml到vendor/etc/perf/目录下

代码路径:vendor/qcom/proprietary/android-perf/perf-hal/Perf.h

  • libqti-iopd-client_system

主要提供perf_io_prefetch_start、perf_io_prefetch_stop、perf_ux_engine_events、perf_ux_engine_trigger方法,这些方法主要调用vendor.qti.hardware.iop@2.0-impl中的iopStart、iopStop、uxEngine_events、uxEngine_trigger

代码路径: vendor/qcom/proprietary/commonsys-intf/android-perf/io-p/client.cpp

  • libqti_performance

vendor/qcom/proprietary/commonsys/android-perf/QPerformance/src/com/qualcomm/qti/Performance.java的jni实现

  • vendor.qti.hardware.iop@2.0-impl

主要提供iopStart、iopStop和uxEngine_events、uxEngine_trigger方法,是对libqti-iopd的方法的封装uxEngine_XX会判断perfconfigstore.xml中vendor.iop.enable_uxe属性是否为1来判断是否启用,如果未启用则直接返回代码路径: vendor/qcom/proprietary/android-perf/iop-hal/Iop.cpp

  • libqti-iopd

主要服务端实现库,见下文分析

代码路径:vendor/qcom/proprietary/android-perf/io-p/io-p.cpp

首先看服务端的实现

IO Prefetcher的初始化

vendor.qti.hardware.iop的实现比较简单,主要调用libqti-iopd中对应方法。在初始化时,打开libqti-iopd.so库,然后找到iop_server_init、iop_server_exit、iop_server_submit_request、uxe_server_submit_request四个方法,并调用iop_server_init进行初始化。

对应关系如下:

  • 在调用iopStart、iopStop和uxEngine_events时,调用libqti-iopd中iop_server_submit_request,并传递cmd分别为IOP_CMD_PERFLOCK_IOPREFETCH_START、IOP_CMD_PERFLOCK_IOPREFETCH_STOP和UXENGINE_CMD_PERFLOCK_EVENTS的消息,并放入IOPevqueue队列中

  • 在调用uxEngine_trigger时,调用libqti-iopd中uxe_server_submit_request,并传递cmd为UXENGINE_CMD_PERFLOCK_TRIGGER的消息,并放入UXEevqueue队列中

* vendor/qcom/proprietary/android-perf/iop-hal/Iop.cpp
IIop* HIDL_FETCH_IIop(const char* /* name */) {
ALOGE("IOP-HAL: inside HIDL_FETCH_IIop");
Iop *iopObj = new (std::nothrow) Iop();
ALOGE("IOP-HAL: boot Address of iop object");
if (iopObj != NULL) {
iopObj->LoadIopLib();
ALOGE("IOP-HAL: loading library is done");
if (iopObj->mHandle.iop_server_init != NULL ) {
(*(iopObj->mHandle.iop_server_init))();
}
}
return iopObj;
} //加载libqti-iopd库,并找到对应的方法
void Iop::LoadIopLib() {
const char *rc = NULL;
char buf[PROPERTY_VALUE_MAX]; if (!mHandle.is_opened) {
mHandle.dlhandle = dlopen("libqti-iopd.so", RTLD_NOW | RTLD_LOCAL);
...
*(void **) (&mHandle.iop_server_init) = dlsym(mHandle.dlhandle, "iop_server_init");
...
*(void **) (&mHandle.iop_server_exit) = dlsym(mHandle.dlhandle, "iop_server_exit");
...
*(void **) (&mHandle.iop_server_submit_request) = dlsym(mHandle.dlhandle, "iop_server_submit_request");
...
*(void **) (&mHandle.uxe_server_submit_request) = dlsym(mHandle.dlhandle, "uxe_server_submit_request");
...
mHandle.is_opened = true;
} return;
}

真正的初始化:

* vendor/qcom/proprietary/android-perf/io-p/io-p.cpp
int iop_server_init() {
...
//创建IOP服务,进入无限循环来等待消息
rc1 = pthread_create(&iop_server_thread, NULL, iop_server, NULL);
...
//读取vendor.iop.enable_uxe是否为1来判断是否启用,1则启用
if (uxe_disabled()) {
uxe_prop_disable = 1;
} else {
uxe_prop_disable = 0;
...
//创建UXE服务,进入无限循环来等待消息
rc2 = pthread_create(&uxe_server_thread, NULL, uxe_server, NULL);
uba_rc = init_uba();
...
uxe_init = true;
} return 1;
...
}

IOP服务消息处理,从IOPevqueue消息队列中获取消息并处理:

* vendor/qcom/proprietary/android-perf/io-p/io-p.cpp
static void *iop_server(void *data)
{
...
/* Main loop */
for (;;) {
//wait for perflock commands
EventData *evData = IOPevqueue.Wait();
...
//判断vendor.post_boot.parsed表示是否为1来判断系统是否启动完成
if(!is_boot_complete())
{
QLOGE("io prefetch is disabled waiting for boot_completed");
continue;
}
//创建DB
if(is_db_init == false)
{
if(create_database() == 0)
{
//Success
is_db_init = true;
}
}
...
switch (cmd) {
//uxEngine_events方法实现
case UXENGINE_CMD_PERFLOCK_EVENTS:
{
...
break;
}
//iopStart方法实现
case IOP_CMD_PERFLOCK_IOPREFETCH_START:
{
...
break;
}
//iopStop方法实现
case IOP_CMD_PERFLOCK_IOPREFETCH_STOP:
{
stop_capture();
break;
}
}
}
}

UXE消息处理,从UXEevqueue队列中获取一个消息,并处理

* vendor/qcom/proprietary/android-perf/io-p/io-p.cpp
static void *uxe_server(void *data)
{
for (;;) {
QLOGI("UXEngine: Waiting on uxe_server_submit_req in uxe_server\n");
EventData *evData = UXEevqueue.Wait();
if (!evData || !evData->mEvData) {
continue;
}
cmd = evData->mEvType;
msg = (iop_msg_t *)evData->mEvData; switch (cmd) {
//uxEngine_trigger方法实现
case UXENGINE_CMD_PERFLOCK_TRIGGER:
{
...
break;
}
}
}
}

所以可以看出先是创建了一个database,及4个表,源码在dblayer.cppDB存储路径为/data/vendor/iop/io-prefetcher.db

* vendor/qcom/proprietary/android-perf/io-p/io-prefetch/dblayer.cpp
/******************************************************************************
DESCRIPTION pkg_file_tbl pkg_tbl
|-----------------| |-----------------|
| pkg_name | | pkg_name |
| file_name | |-----------------|
|-----------------| | pkg_use_time |
| | | num_of_launches |
| file_use_ctr | |-----------------|
| file_time_stamp |
| file_size |
| mark_for_delete |
| file_modify_time|
| file_iop_size |
| study_finish |
| mincore_array |
| cache_dropped |
| disabled |
|-----------------|
******************************************************************************/ /******************************************************************************
ux_pkg_tbl ux_lat_tbl
|--------------------| |-----------------|
| pkg_name | | pkg_name |
| week_day | |-----------------|
|--------------------| | bindApp_dur |
| pkg_last_use | | disAct_dur |
| num_of_launches | | wakeLock_dur |
| num_of_sublaunches | | memory_st |
| timeslot_1_count | | bindApp_ct |
| timeslot_2_count | | predict_success |
| timeslot_3_count | | predict_fail |
| timeslot_4_count | | not_game |
|--------------------| |-----------------|
******************************************************************************/

iopStart

* vendor/qcom/proprietary/android-perf/io-p/io-p.cpp
case IOP_CMD_PERFLOCK_IOPREFETCH_START:
{
static bool is_in_recent_list = false;
char enable_prefetch_property[PROPERTY_VALUE_MAX];
char enable_prefetch_ofr_property[PROPERTY_VALUE_MAX];
int enable_prefetcher = 0;
int enable_prefetcher_ofr = 0;
//IOP是否启用
strlcpy(enable_prefetch_property,perf_get_prop("vendor.enable.prefetch" , "0").value, PROPERTY_VALUE_MAX);
enable_prefetch_property[PROPERTY_VALUE_MAX-1]='\0'; enable_prefetcher = strtod(enable_prefetch_property, NULL); strlcpy(enable_prefetch_ofr_property,perf_get_prop("vendor.iop.enable_prefetch_ofr" , "0").value, PROPERTY_VALUE_MAX);
enable_prefetch_ofr_property[PROPERTY_VALUE_MAX-1]='\0'; enable_prefetcher_ofr = strtod(enable_prefetch_ofr_property, NULL); // if PID < 0 consider it as playback operation
if(msg->pid < 0)
{
int ittr = 0;
char *week_day = NULL;
week_day = (char *) malloc(6*sizeof(char));
// Insert package into the table
if (week_day == NULL) {
//Malloc failed. Most-probably low on memory.
break;
}
strlcpy(pkg_info.pkg_name,msg->pkg_name,PKG_NAME_LEN);
strlcpy(tmp_pkg_name,pkg_info.pkg_name,PKG_NAME_LEN);
bindApp_dur = 0;
disAct_dur = 0;
launching = true;
//更新DB信息
time(&pkg_info.last_time_launched);
//计算week_day:如果时周二~周六,则为true,否则为false
//time_slot:4~12时,为1
//12~17时,为2
//17~21时,为3
//0~4,21~24,为4
compute_time_day_slot(week_day, &time_slot);
QLOGI("UXEngine Updating Details: pkg_name: %s, week_day: %s, time_slot: %d %s\n", pkg_info.pkg_name, week_day, time_slot, tmp_pkg_name);
//更新ux_pkg_tbl表信息
update_ux_pkg_details(pkg_info, week_day, time_slot, 0);
//更新tracker信息,当启启动应用个数达到12个时,更新ux_lat_tbl信息,并设置权重为10
update_palm_table(msg->pkg_name, 0, 1); QLOGI("UXEngine finished ux_pkg_details update \n");
free(week_day);
is_in_recent_list = false;
if(!enable_prefetcher)
{
QLOGE("io prefetch is disabled");
break;
} //检测应用是否在应用历史列表中
for(ittr = 0; ittr < IOP_NO_RECENT_APP; ittr++)
{
if(0 == strcmp(msg->pkg_name,recent_list[ittr]))
{
is_in_recent_list = true;
QLOGE("is_in_recent_list is TRUE");
break;
}
}
// 如果在应用历史列表中,则退出
if(true == is_in_recent_list)
{
QLOGE("io prefetch is deactivate");
break;
}
//假如在应用历史个数为7,则重置为0
if(recent_list_cnt == IOP_NO_RECENT_APP)
recent_list_cnt = 0; //拷贝包名到应用历史列表中
strlcpy(recent_list[recent_list_cnt],msg->pkg_name,PKG_LEN);
recent_list_cnt++; stop_capture();
stop_playback();
start_playback(msg->pkg_name);
}
// if PID > 0 then consider as capture operation
if(msg->pid > 0)
{
if(!enable_prefetcher)
{
QLOGE("io prefetch is disabled");
break;
}
if(true == is_in_recent_list)
{
QLOGE("io prefetch Capture is deactivated ");
break;
}
stop_capture();
//关键函数,开始进行capture抓取,启动一个线程,调用capture_thread
start_capture(msg->pid,msg->pkg_name,msg->code_path,enable_prefetcher_ofr);
} break;
}

主要在应用启动时候调用IopStart

  • 假如pid小于0,则进行playback操作
  1. 生成tmp_pkg_name(在binderApplication中甬道)和pkg_info

  2. 计算week_day:如果时周二~周六,则为true,否则为false

  3. 计算time_slot:4 ~ 12时,为1; 12 ~ 17时,为2; 17 ~ 21时,为3; 0~4或者21~24,为4

  4. 调用update_ux_pkg_details更新ux_pkg_tbl表信息,pkg_use_count和timeslot_count_字段+1

  5. 调用update_palm_table更新tracker信息,当应用kill,则cached为false,当和上次启动时间小于2s,do_not_launch为true,当应用launch,则cached为true当启启动应用个数达到12个时,更新ux_lat_tbl信息,并设置权重为10,这里的权重暂未使用

  6. 检测应用是否在应用历史列表中,如果在应用历史列表中,则退出;否则,假如应用历史个数为7,则重置为0,拷贝包名到应用历史列表中

  7. 调用start_playback,启动一个线程,调用start_playback_thread假如pid大于0,则进行start_capture操作,这里主要启动一个线程,然后执行capture_thread

从第6步开始,为IOP流程,1~5为iop和uxe共通

IOP流程关键函数为capture_thread和start_playback_thread

* vendor/qcom/proprietary/android-perf/io-p/io-prefetch/list_capture.cpp
void * capture_thread(void * arg) {
...
//获取polling_enable标示是否启用,默认为启用
property_get("vendor.polling_enable", property, "0");
polling_enable = atoi(property);
ATRACE_BEGIN("capture_thread");
capture_thread_arg * arg_bundle = (capture_thread_arg *)arg; //获取包名:之前的字符
pkg_len = strlen(arg_bundle->pkg_name);
i = 0;
while(i < pkg_len && arg_bundle->pkg_name[i] != ':')
{
i++;
}
arg_bundle->pkg_name[i] = '\0';
strlcpy(list_pkg_name,arg_bundle->pkg_name,PKG_NAME_LEN); QLOGI("pkg_name = %s",arg_bundle->pkg_name); if(polling_enable)
{
//假如开启轮询,则循环3000/50次get_snapshot,每次休眠50秒
while (duration_counter < halt_counter) {
if (halt_thread) goto cleanup;
QLOGI("Getting snapshot %d\n", arg_bundle->pid);
//获取当前pid打开的文件描述符,并放入file_list中
get_snapshot(list_pkg_name,arg_bundle->pid);
duration_counter++;
usleep(read_fd_interval_ms * 1000);
}
}
//获取到代码apk、oat位置,路径为/data/app/××,这里会扫描并打开文件并放入file_list中
get_priv_code_files(arg_bundle->pkg_name);
//获取文件位置,路径为/data/user/0/pkg和/data/data/pkg
get_priv_files(arg_bundle);
QLOGI("pkg_name = %s total_files = %d ",arg_bundle->pkg_name,total_files); // Insert package into the table
strlcpy(pkg_info.pkg_name,arg_bundle->pkg_name,PKG_NAME_LEN);
time(&pkg_info.last_time_launched);
// Update Mincore data
if(arg_bundle->ofr)
{
for(index = 0; index < total_files;index++)
{
if(update_mincore_data(file_list[index]) != 0)
{
file_list[index]->mincore_array = NULL;
}
}
}
//更新在database中,更新io_pkg_tbl,主要更新应用最后启动时间
update_pkg_details(pkg_info);
//更新io_pkg_file_tbl,将打开的文件信息存入数据库中
update_file_details(arg_bundle->pkg_name, file_list, total_files);
delete_mark_files();
cleanup:
//log a report about how many files need insert or update this time
QLOGE("# Final entry : pkg_name file_name file_time_stamp filesize file_iop_size");
for(i = 0; i < total_files; i++) {
QLOGE("%d. Final entry : %s %s %d %d %d\n",i
,arg_bundle->pkg_name,file_list[i]->file_name
, file_list[i]->file_time_stamp, file_list[i]->filesize, file_list[i]->file_iop_size);
if (file_list[i]->mincore_array) {
free(file_list[i]->mincore_array);
file_list[i]->mincore_array = NULL;
}
free(file_list[i]);
}
ATRACE_END(); free(arg_bundle->pkg_name);
free(arg_bundle);
QLOGI("Exit capture_thread");
return NULL;
}
  • 获取polling_enable标示是否启用,默认未启用,如果启用,则循环3000/50次get_snapshot,每次休眠50秒,获取/proc/pid/fd打开的文件描述符,并放入file_list中

  • 获取包名:之前的字符

  • 获取到代码apk、oat位置,路径为/data/app/××,这里会扫描并打开文件并放入file_list中

  • 获取文件位置,路径为/data/user/0/pkg和/data/data/pkg,并放入file_list中

  • 更新io_pkg_tbl,主要更新应用最后启动时间

  • 更新io_pkg_file_tbl,将打开的文件信息存入数据库中

至此,可以看到,在打开了iop之后,在打开应用的时候,会查看该pid打开的文件描述符列表,将一些需要打开的文件先缓存到数据库中。那么缓存到数据库中,该怎么使用?其实就是调用start_playback_thread,从数据库中查询到对应的文件,然后进行打开操作

start的调用流程为:

  1. 任务历史中切换应用ActivityTaskManagerService::moveTaskToFrontLocked

---ActivityStackSupervisor::findTaskToMoveToFront--------ActivityStackSupervisor::acquireAppLaunchPerfLock(当TopActivity为空或者DESTROYED)时被调用BoostFramework::perfIOPrefetchStart

  1. 应用启动RootActivityContainer::findTask----ActivityDisplay::findTaskLocked--------ActivityDisplay::acquireAppLaunchPerfLock------------BoostFramework::perfIOPrefetchStar

最终调用com.qualcomm.qti.Performance.perfIOPrefetchStart,直接调用hidl的iopStart方法及com.qualcomm.qti.UxPerformance.perfIOPrefetchStart 这里会通过MappedByteBuffer和FileChannel打开/data/app/下的文件映射到虚拟内存中

uxEngine_events

调用流程

  1. ActivityThread::handleBindApplication完成后,调用ux_perf.perfUXEngine_events(BoostFramework.UXE_EVENT_BINDAPP, 0,pkg_name,bindApp_dur,//handleBindApplication执行时间pkgDir);//data/app/pck/base.apk

  2. ActivityManagerService::appDiedLockedmUxPerf.perfUXEngine_events(BoostFramework.UXE_EVENT_KILL, 0, app.processName, 0);

  3. ProcessRecord::killux_perf.perfUXEngine_events(BoostFramework.UXE_EVENT_KILL, 0, this.processName, 0);activity显示完成,调用ActivityMetricsLogger.logAppDisplayed

  4. mUxPerf.perfUXEngine_events(BoostFramework.UXE_EVENT_DISPLAYED_ACT, 0, info.packageName, info.windowsDrawnDelayMs);

case UXENGINE_CMD_PERFLOCK_EVENTS:
{
if(uxe_prop_disable || !is_boot_complete())
{
QLOGE("UXEngine is disabled");
break;
} int opcode = msg->opcode;
char in_pkg_name[PKG_NAME_LEN];
strlcpy(in_pkg_name, msg->pkg_name, PKG_NAME_LEN); //Opcode = 2 : Client is sending bindApp duration
if(opcode == UXE_EVENT_BINDAPP)
{
...
bindApp_dur = msg->lat;
//获取bindApplication次数
bindApp_count = get_total_pkg_bindapp_count(in_pkg_name);
QLOGI("UXEngine: Received bindApp duration: %d pkg_name %s tmp_pkg_name: %s\n",
bindApp_dur, in_pkg_name, tmp_pkg_name);
if (bindApp_count) {
int lat_thres = 0;
//获取ux_lat_tbl表中信息
get_ux_lat_pkg(in_pkg_name, &ux_lat);
QLOGI("UXEngine: Average bindApp before: %d bindApp_count : %d\n",
ux_lat.bindApp_dur, bindApp_count);
//计算lat_thres,为当前binderApplication时间和上次时间百分比
if (ux_lat.bindApp_dur != 0)
lat_thres = (bindApp_dur*100)/ux_lat.bindApp_dur;
//如果lat_thres为[50, 500]之间,且小于4s,则计算平均启动时间
if (ux_lat.bindApp_dur != 0 && (LAT_LOW_THRESHOLD < lat_thres && LAT_HIGH_THRESHOLD > lat_thres) && bindApp_dur < 4000) {
avg_bindApp = ((bindApp_count) * ux_lat.bindApp_dur + bindApp_dur) / (bindApp_count+1);
} else {
avg_bindApp = ux_lat.bindApp_dur;
if (ux_lat.bindApp_dur == 0)
avg_bindApp = bindApp_dur;
}
avg_disAct = ux_lat.disAct_dur;
QLOGI("UXEngine: Average bindApp: %d for app: %s bindApp_count: %d\n",
avg_bindApp, in_pkg_name, bindApp_count);
bindApp_dur = avg_bindApp;
} else {
avg_disAct = 0;
}
//这种情况下为没有调用过iopStart,即为启动empty app情况下,
//比如清除任务后,通过uxEngine_trigger获取信息后调用startActivityAsUserEmpty情况下
if (!pkg_match)
{
// Empty app launch. Update db table.
QLOGI("UXEngine: Updating bindApp duration: %d pkg_name %s\n",
bindApp_dur, in_pkg_name);
update_ux_lat_details(in_pkg_name, bindApp_dur, avg_disAct, 0, 1);
bindApp_dur = 0;
avg_bindApp = 0;
avg_disAct = 0;
}
} //Opcode = 3 : Client is sending DisplayedActivity
if (opcode == UXE_EVENT_DISPLAYED_ACT)
{
int non_empty_launch = 1;
bool pkg_match = true;
if (tmp_pkg_name[0] == 0)
goto disAct_cleanup;
//比较in_pkg_name和tmp_pkg_name(在IopStart获取)是否一致 ,launching(IopStart时赋为true)是否为true
if (!strncmp(in_pkg_name, tmp_pkg_name, PKG_NAME_LEN)) {
pkg_match = true;
} else {
//Received unexpected displayed activity.
//Update regardless, but for the correct app.
//skip update
QLOGI("UXEngine: Skip. Received weird DA: %s\n", in_pkg_name);
goto disAct_cleanup;
} disAct_dur = msg->lat;
//调用get_total_pkg_use_count查询ux_pkg_tbl表中pkg_use_count,即应用activity打开次数
pkg_count = get_total_pkg_use_count(in_pkg_name);
// Empty app launch
//调用iopStart,但是未执行binderApplication流程,则bindApp_dur为0,launching为true
if ((bindApp_dur == 0 && launching) || !pkg_match) {
QLOGI("UXEngine: Empty app launch. Just update DA. pkg_name: %s \n", in_pkg_name);
bindApp_count = get_total_pkg_bindapp_count(in_pkg_name);
get_ux_lat_pkg(in_pkg_name, &ux_lat);
// Launching process would have definitely had a bindApp.
// If count=0, something wrong. Skip update.
if(bindApp_count)
bindApp_dur = ux_lat.bindApp_dur;
else
goto disAct_cleanup;
avg_disAct = ux_lat.disAct_dur;
//空app启动流程,曾经被uxe拉起过
non_empty_launch = 0;
} QLOGI("UXEngine: bindApp duration: %d, DisplayedActivity: %d\n", bindApp_dur, disAct_dur);
//代表应用activity被打开过两次以上
if (pkg_count - 1) {
QLOGI("UXEngine: Average displayed activity before: %d pkg_count :%d\n", avg_disAct, pkg_count);
int lat_thres = 0;
if (avg_disAct != 0)
lat_thres = (disAct_dur*100)/avg_disAct;
if (avg_disAct != 0 && pkg_count != 0 && (LAT_LOW_THRESHOLD < lat_thres && LAT_HIGH_THRESHOLD > lat_thres) && disAct_dur < 5000) {
avg_disAct = ((pkg_count - 1) * avg_disAct + disAct_dur) / pkg_count;
disAct_dur = avg_disAct;
}
QLOGI("UXEngine: Average displayed activity after: %d, bindApp_count: %d\n", avg_disAct);
if(avg_disAct != 0)
disAct_dur = avg_disAct;
}
QLOGI("UXEngine: Updating bindApp & DA duration: %d %d pkg_name %s\n",
bindApp_dur, disAct_dur, in_pkg_name);
update_ux_lat_details(in_pkg_name, bindApp_dur, disAct_dur, 0, non_empty_launch); disAct_cleanup:
// Refresh preferred apps & palm table after launch.
char *final_out[PREFERRED_APP_COUNT];
int uba_return = 0, i = 0;
for (i = 0; i < PREFERRED_APP_COUNT; i++) {
final_out[i] = (char*) malloc(PKG_NAME_LEN);
final_out[i][0] = '\0';
}
uba_return = get_preferred_apps(final_out, 0, NULL, -1, true);
update_palm_table(in_pkg_name, 0, 1);
//Cleanup
for (i = 0; i < PREFERRED_APP_COUNT; i++) {
free(final_out[i]);
}
disAct_dur = 0;
bindApp_dur = 0;
avg_bindApp = 0;
avg_disAct = 0;
pkg_count = 0;
launching = false;
memset(tmp_pkg_name, 0, PKG_NAME_LEN);
QLOGI("UXEngine: Finished ux_lat update & reset \n");
}
//应用被杀,则更新tracker及信息ux_lat_tbl信息
if(opcode == UXE_EVENT_KILL)
{
QLOGI("UXEngine: Received app no-AM kill: %s\n", in_pkg_name);
update_palm_table(in_pkg_name, 1, 0);
} if(opcode == UXE_EVENT_GAME)
{
char *week_day = NULL;
week_day = (char *) malloc(6*sizeof(char));
if (week_day == NULL) {
//Malloc failed. Most-probably low on memory.
break;
}
strlcpy(pkg_info.pkg_name,in_pkg_name,PKG_NAME_LEN);
time(&pkg_info.last_time_launched);
compute_time_day_slot(week_day, &time_slot);
QLOGI("UXEngine Updating sub_launch details: \
pkg_name: %s, week_day: %s, time_slot: %d %s\n",
pkg_info.pkg_name, week_day, time_slot, tmp_pkg_name);
update_ux_pkg_details(pkg_info, week_day, time_slot, 1);
update_palm_table(msg->pkg_name, 0, 1);
free(week_day);
}
if(opcode == UXE_EVENT_SUB_LAUNCH)
{
...
}
//应用卸载,删除db信息
if(opcode == UXE_EVENT_PKG_UNINSTALL)
{
QLOGI("UXEngine: Received pkg uninstall - %s\n", in_pkg_name);
int userId = msg->lat;
update_palm_table(in_pkg_name, 1, 0);
uninstall_pkg(in_pkg_name);
}
if(opcode == UXE_EVENT_PKG_INSTALL)
{
...
}
break;
}

UXE_EVENT_BINDAPP 流程,在应用bindApplication完成后调用

  1. 比较in_pkg_name和tmp_pkg_name(在IopStart获取)是否一致 ,launching(IopStart时赋为true)是否为true

  2. 调用get_total_pkg_bindapp_count查询ux_lat_tbl表中bindApp_ct获取bindApplication次数

  3. 调用get_ux_lat_pkg获取ux_lat_tbl表中信息

  4. 计算lat_thres,为当前binderApplication时间和上次时间百分比,如果lat_thres为[50, 500]之间,且小于4s,则计算平均启动时间bindApp_dur

  5. 没有调用过iopStart(即tmp_pkg_name为上次iopStart时应用包名),即为启动empty app情况下,比如清除任务后,通过uxEngine_trigger获取信息后调用startActivityAsUserEmpty情况下,则调用update_ux_lat_details更新ux_lat_tbl表中bindApp_dur(平均binderApplication时间)和disAct_dur(如果第一次为0,否则为db中存储的)信息,并使bindApp_ct+1

UXE_EVENT_DISPLAYED_ACT流程,在应用activity启动完成后,类似activity启动的displayed时间

  1. 比较in_pkg_name和tmp_pkg_name(在IopStart获取)是否一致 ,launching(IopStart时赋为true)是否为true

  2. 调用get_total_pkg_use_count查询ux_pkg_tbl表中pkg_use_count,即应用activity打开次数

  3. 调用iopStart,但是未执行binderApplication流程,则bindApp_dur为0,launching为true,即为空app启动流程,曾经被uxe拉起过。调用get_total_pkg_bindapp_count获取ux_lat_tbl中bindApp_ct,即binderApplication次数,调用get_ux_lat_pkg获取ux_lat_tbl中对应包名的信息,这里貌似有点鸡肋,get_total_pkg_bindapp_count为多余的。并之non_empty_launch为0,代表空app启动流程

  4. pkg_count - 1为真,代表应用activity被打开过两次以上,

  5. 计算lat_thres,为当前displayed activity时间和上次时间百分比,如果lat_thres为[50, 500]之间,且小于5s,则计算平均启动时间disAct_dur

  6. 调用update_ux_lat_details更新ux_lat_tbl表中bindApp_dur(平均binderApplication时间)和disAct_dur(如果第一次为0,否则为db中存储的)信息,并使bindApp_ct+1(曾经被uxe拉起过,则non_empty_launch为0,不+1)

  7. 状态重置:调用get_preferred_apps更新tracker列表调用update_palm_table设置为以启动模式(cached为true)

UXE_EVENT_KILL流程,应用被杀死后的流程

  1. 调用update_palm_table,设置tracker列表的cached和empty为false

  2. 如果kill时间距离displayed activity时间或者返回home时间间隔2s内,则do_not_launch为true,在下次返回home或者displayed时,从返回列表中移除

uxEngine_trigger

获取需要预启动的app信息

case UXENGINE_CMD_PERFLOCK_TRIGGER:
{
static int back_to_home = 1;
pa_ready = false;
//判断是否启用
if(uxe_prop_disable || !is_boot_complete())
{
QLOGI("UXEngine is disabled");
pthread_cond_signal(&ux_trigger_cond);
break;
}
...
// get_preferred_apps.
if(msg->opcode == UXE_TRIGGER || msg->opcode == UXE_ULMK_TRIGGER)
{
...
QLOGE("UXEngine, Received back to home");
//final_out个数为12
char *final_out[PREFERRED_APP_COUNT];
int uba_return = 0, i = 0; for (i = 0; i < PREFERRED_APP_COUNT; i++) {
final_out[i] = (char*) malloc(PKG_NAME_LEN);
final_out[i][0] = '\0';
} if (msg->opcode == UXE_TRIGGER) {
uba_return = get_preferred_apps(final_out, 0, NULL, -1, false);
} else {
uba_return = get_preferred_apps(final_out, 1, NULL, -1, false);
}
QLOGE("Final OUTPUT: Preferred Apps returned : %d\n", uba_return); for (i = 0; i < uba_return ; i++) {
if (final_out[i][0] != 0) {
QLOGI("Final OUTPUT: Apps returned: %s\n", final_out[i]);
strlcat(preferred_apps,final_out[i],strcat_sz);
strlcat(preferred_apps,"/",strcat_sz);
}
}
pa_ready = true;
// return to F/W once HIDL is implemented
back_to_home = 1;
for (i = 0; i < PREFERRED_APP_COUNT; i++) {
free(final_out[i]);
}
QLOGE("UXEngine: Set preferred apps in uxe_server: %s\n", preferred_apps);
pthread_cond_signal(&ux_trigger_cond);
}
else if (msg->opcode == UXE_TRIGGER_LIST_FAV_APPS) {
//暂未使用
...
}
break;
} * vendor/qcom/proprietary/android-perf/io-p/io-prefetch/uba.cpp
int get_preferred_apps(char **out_list, int disable_palm, char * all_week_day, int all_time_slot, bool launch)
{
long cur_time = (long) now_secs();
char *week_day = NULL;
pkg_ux_top_details *cur_list = NULL;
int total_pkgs = 0, index = 0, table_size = 0, num_pkgs = 0;
//临时个数为18
int tmp_size = PREFERRED_APP_COUNT + PREFERRED_APP_COUNT/2;
//week_day为true或者false
week_day = (char *) malloc(6*sizeof(char));
if (week_day == NULL) {
//Malloc failed. Most-probably low on memory.
goto out;
}
//计算week_day:如果时周二~周六,则为true,否则为false
//time_slot:4~12时,为1
//12~17时,为2
//17~21时,为3
//0~4,21~24,为4
compute_time_day_slot(week_day, &time_slot);
//获取ux_pkg_tbl中应用个数
total_pkgs = get_total_ux_pkgs(0); if (total_pkgs == 0) {
goto out;
}
cur_list = (pkg_ux_top_details *) malloc ((tmp_size+1) * sizeof(pkg_ux_top_details)); if (cur_list == NULL) {
goto out;
} QLOGI("Get_Preferred_Apps: Current Time stats: Week_day: %s, Time_slot: %d, Total_pkgs: %d\n", week_day, time_slot, total_pkgs);
//根据time_slot获取ux_pkg_tbl中应用列表,即所有记录的应用的binderApplication时间,打开次数等信息
if (disable_palm == 1 && all_week_day != NULL && all_time_slot != -1)
num_pkgs = get_top_ux_pkg_list(cur_list, all_week_day, all_time_slot, tmp_size, 1);
else
num_pkgs = get_top_ux_pkg_list(cur_list, week_day, time_slot, tmp_size, 1);
QLOGI("Get_Preferred_Apps: Get Top pkg list Returned pkgs: %d\n", num_pkgs); if (num_pkgs == 0) {
goto out;
} for (index = 0 ; (index < PREFERRED_APP_COUNT && index < num_pkgs) ; index++) {
int total_count = cur_list[index].num_launches;
int tslot_count = cur_list[index].timeslot_count_select;
QLOGI("Get_Preferred_Apps: Accessing cur list: %s pkg_launch_count: %d bindApp_dur: %d disAct_dur%d\n", cur_list[index].pkg_name, cur_list[index].num_launches,
cur_list[index].bindApp_dur, cur_list[index].disAct_dur);
strlcpy(out_list[index], cur_list[index].pkg_name, PKG_NAME_LEN);
QLOGI("Get_Preferred_Apps: Accessing copied list: %s\n", out_list[index]);
table_size++;
} if (disable_palm == 0) {
QLOGI("Get_Preferred_Apps: Accessing table_size %d\n", table_size);
set_palm_table(out_list, table_size, cur_time, launch);
}
else if (disable_palm == 1) {
QLOGI("Get_Preferred_Apps: Return all Fav apps List with size = %d\n", table_size);
} out:
if (cur_list != NULL) {
free(cur_list);
}
if (week_day != NULL) {
free(week_day);
}
return table_size;
}

调用逻辑为:清除后台任务

ActivityStackSupervisor::removeTaskByIdLocked----ActivityStackSupervisor::cleanUpRemovedTaskLocked-------PreferredAppsTask::doInBackground-------------BoostFramework::perfUXEngine_trigger

切换到launcher界面

ActivityStack::resumeTopActivityInnerLocked----PreferredAppsTask::doInBackground

切换到home或者进入任务列表

ActivityRecord::completeResumeLocked----PreferredAppsTask::doInBackground

get_preferred_apps根据应用的启动时间和星期几,来预测要启动的应用,DB中只存储12个应用当通过perfUXEngine_trigger获取到应用信息后,调用startActivityAsUserEmpty来启动一个空的应用,在再次启动时,直接跳过binderApplication阶段,从而加快启动速度,这里startActivityAsUserEmpty直接调用startProcessLocked创建一个进程,并执行bindApplication流程,因为startProcessLocked时,指定启动空activity(传入new HostingRecord(null)),从而在RootActivityContainer::attachApplication时,不在调用realStartActivityLocked

* frameworks/base/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
class PreferredAppsTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
String res = null;
final Intent intent = new Intent(Intent.ACTION_MAIN);
int trimLevel = 0;
try {
trimLevel = ActivityManager.getService().getMemoryTrimLevel();
} catch (RemoteException e) {
return null;
}
if (mUxPerf != null
&& trimLevel < ProcessStats.ADJ_MEM_FACTOR_CRITICAL) {
res = mUxPerf.perfUXEngine_trigger(BoostFramework.UXE_TRIGGER);
if (res == null)
return null;
String[] p_apps = res.split("/");
if (p_apps.length != 0) {
ArrayList<String> apps_l = new ArrayList(Arrays.asList(p_apps));
Bundle bParams = new Bundle();
if (bParams == null)
return null;
bParams.putStringArrayList("start_empty_apps", apps_l);
final Message msg = PooledLambda.obtainMessage(
ActivityManagerInternal::startActivityAsUserEmpty, mService.mAmInternal, bParams);
mService.mH.sendMessage(msg);
}
}
return null;
}
}

get_preferred_apps的主要流程为:

  1. 调用compute_time_day_slot计算当前是否工作日和时间段

  2. 根据工作日和时间段调用get_top_ux_pkg_list获取ux_pkg_tbl和ux_lat_tbl中的应用启动次数(pkg_use_count),启动时间(bindApp_dur、disAct_dur)、时间段启动次数(timeslot_count_)等信息

  3. 获取到最大PREFERRED_APP_COUNT(12)个应用信息,放入out_list

  4. 调用set_palm_table进行应用信息过滤,并更新tracker列表

    int set_palm_table(char **list, int tb_size, long cur_time, bool launch) {

    //Compare old and new list, and send update to PALM

    //Check for empty conditions.

    int i = 0, j = 0, match = 0, match_cached = 0;

     palm_table *cur_tracker;
    palm_table *tmp_tracker;
    cur_tracker = (palm_table *) malloc (tb_size * sizeof(palm_table)); if (cur_tracker == NULL) {
    QLOGE("Set_Palm_Table: No memory to set temp table. Return");
    return 0;
    } QLOGI("Set_Palm_Table: Entry, Table size: %d, old table size: %d, Current time: %ld \n", tb_size, old_tbl_size, cur_time);
    for(i = 0; i < tb_size; i++)
    {
    strlcpy(cur_tracker[i].pkg_name, list[i], PKG_NAME_LEN);
    cur_tracker[i].cached = launch;
    cur_tracker[i].empty = !launch;
    cur_tracker[i].memory_rss = 0; //Use getMemory or db call later on.
    cur_tracker[i].last_empty_time = cur_time;
    cur_tracker[i].do_not_launch = false;
    cur_tracker[i].hit = false;
    cur_tracker[i].comp = false;
    QLOGI("Set_Palm_Table: %s", cur_tracker[i].pkg_name);
    for(j = 0; j < old_tbl_size; j++)
    {
    if(!strncmp(tracker[j].pkg_name, list[i], PKG_NAME_LEN))
    {
    cur_tracker[i].memory_rss = tracker[j].memory_rss;
    cur_tracker[i].last_empty_time = tracker[j].last_empty_time;
    cur_tracker[i].cached = tracker[j].cached;
    cur_tracker[i].empty = tracker[j].empty;
    cur_tracker[i].do_not_launch = tracker[j].do_not_launch;
    cur_tracker[i].hit = tracker[j].hit;
    tracker[j].comp = true;
    strlcpy(cur_tracker[i].pkg_name, tracker[j].pkg_name, PKG_NAME_LEN);
    //
    if(launch || tracker[j].empty || tracker[j].cached) {
    list[i][0] = 0;
    list[i][1] = '\0';
    QLOGI("Set_Palm_Table: Matched empty from previous table. Pkg_name: %s Pkg State--Cached: %d, Empty: %d\n", tracker[j].pkg_name, tracker[j].cached, tracker[j].empty);
    strlcpy(cur_tracker[i].pkg_name, tracker[j].pkg_name, PKG_NAME_LEN);
    match++;
    }
    //如果应用2s内被杀两次(do_not_launch为true)
    else if(tracker[j].do_not_launch) {
    QLOGI("Set_Palm_Table: Not allowed for launch due to looping: %s\n", tracker[j].pkg_name);
    list[i][0] = 0;
    list[i][1] = '\0';
    cur_tracker[i].cached = false;
    cur_tracker[i].empty = false;
    cur_tracker[i].do_not_launch = true;
    if ((cur_time - tracker[j].last_empty_time) > empty_restart_threshold) {
    cur_tracker[i].do_not_launch = false;
    QLOGE("Set_Palm_Table: Timeout expired. Allowed to start as empty: %s\n", tracker[j].pkg_name);
    }
    }
    //应用被杀后,再次返回home会执行此流程,被拉起,将empty付为空
    else {
    cur_tracker[i].empty = true;
    cur_tracker[i].cached = false;
    QLOGI("Set_Palm_Table: Matched, but app was killed. Resetting. Pkg_name: %s Pkg State--Cached: %d, Empty: %d\n", cur_tracker[i].pkg_name, cur_tracker[i].cached, cur_tracker[i].empty);
    }
    }
    }
    }
    old_tbl_size = tb_size;
    //memcpy(tracker, cur_tracker, (tb_size*sizeof(palm_table)));
    tmp_tracker = tracker;
    tracker = cur_tracker;
    free(tmp_tracker);
    if (match == tb_size)
    return 0;
    else
    return (tb_size - match);

    }

set_palm_table

  1. 根据最新的可能要启动应用列表,构建一个最新的tracker

  2. 如果应用displayed完成,lunch为true,返回home,lunch为false

  3. 当应用kill时,被置为true,当返回home时,进入set_palm_table,然后走else流程,将empty置为true,然后被拉起,empty为true

  4. 如果应用2s内被杀两次,则do_not_launch为true

  5. 如果应用已启动,则cached为true

  6. 上述四种情况,则从out列表移除

  7. 如果是当前时间段不再tracker的应用(comp为false),(比如工作日/非工作日切换,常用应用个数大于12个时候,使用次数变更到底等),则如果已经启动,更新预言成功(predict_success)权重+10,否则更新预言失败(predict_fail)权重pr_launch/PREFERRED_APP_COUNT*10。这里的predict_success和predict_fail暂未使用,在android R中不在使用

android IO Prefetch源码分析的更多相关文章

  1. Android网络框架源码分析一---Volley

    转载自 http://www.jianshu.com/p/9e17727f31a1?utm_campaign=maleskine&utm_content=note&utm_medium ...

  2. OpenGL—Android 开机动画源码分析一

    .1 Android开机动画实现方式目前实现Android开机动画的方式主要是逐帧动画和OpenGL动画. ?逐帧动画 逐帧动画是一种常见的动画形式(Frame By Frame),其原理是在“连续的 ...

  3. Android分包MultiDex源码分析

    转载请标明出处:http://blog.csdn.net/shensky711/article/details/52845661 本文出自: [HansChen的博客] 概述 Android开发者应该 ...

  4. Android开源框架源码分析:Okhttp

    一 请求与响应流程 1.1 请求的封装 1.2 请求的发送 1.3 请求的调度 二 拦截器 2.1 RetryAndFollowUpInterceptor 2.2 BridgeInterceptor ...

  5. Android消息机制源码分析

    本篇主要介绍Android中的消息机制,即Looper.Handler是如何协同工作的: Looper:主要用来管理当前线程的消息队列,每个线程只能有一个Looper Handler:用来将消息(Me ...

  6. Android 开机动画源码分析

    Android系统在启动SystemServer进程时,通过两个阶段来启动系统所有服务,在第一阶段启动本地服务,如SurfaceFlinger,SensorService等,在第二阶段则启动一系列的J ...

  7. android hardware.c 源码分析

    android的jni通过ID来找hal模块,使用了hw_get_module()函数,本文就通过这个函数的来分析一下hal层的模块是如何匹配的. 首先要了解三个结构体hw_module_t,hw_m ...

  8. Android -- 消息处理机制源码分析(Looper,Handler,Message)

    android的消息处理有三个核心类:Looper,Handler和Message.其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,因 ...

  9. 我的Android进阶之旅------>Android中AsyncTask源码分析

    在我的<我的Android进阶之旅------>android异步加载图片显示,并且对图片进行缓存实例>文章中,先后使用了Handler和AsyncTask两种方式实现异步任务机制. ...

  10. [Android]简略的Android消息机制源码分析

    相关源码 framework/base/core/java/andorid/os/Handler.java framework/base/core/java/andorid/os/Looper.jav ...

随机推荐

  1. Jmeter二次开发函数 - 文本替换

    此篇文章将在Jmeter创建一个新函数,实现替换文本中的指定内容功能.效果图如下 1.eclipse项目创建步骤此处省略,可参考上一篇Jmeter二次开发函数之入门 2.新建class命名为" ...

  2. Jmeter函数助手32-UUID

    UUID函数用于返回一个伪随机类型4通用唯一标识符(UUID).该函数没有参数,直接引用即可

  3. 人形机器人从人类演示(demenstration)数据中学习人类行为(behavior)的几种方式

    人形机器人从人类演示(demenstration)数据中学习的几种方式 使用仿真环境,在仿真环境中生成近似人类的行为数据,然后利用仿真生成的数据训练机器人. 该种方式最为传统,也最为易行,但是由于仿真 ...

  4. 【转载】 拒绝遗忘:高效的动态规划算法 —— “到底什么是动态规划”—— An intro to Algorithms: Dynamic Programming

    原文地址(英文): https://medium.freecodecamp.org/an-intro-to-algorithms-dynamic-programming-dd00873362bb   ...

  5. tmux开启鼠标模式

    在tmux的配置文件中进行配置: vim ~/.tmux.conf set -g mouse on

  6. TensorFlow图像预处理函数

    预处理图像 文件名:       cat.jpg 读取.打印图片 import matplotlib.pyplot as plt import tensorflow as tf import nump ...

  7. (续) MindSpore 如何实现一个线性回归 —— Demo示例

    前文: https://www.cnblogs.com/devilmaycry812839668/p/14975860.html 前文中我们使用自己编写的损失函数和单步梯度求导来实现算法,这里是作为扩 ...

  8. 后端开发学习敏捷需求-->专题的目标与价值成效

    专题的目标与价值成效 什么是专题 公司或企业为了抓住业务机会或者解决痛点问题,而采取的具体的行动和举措 专题的目标分析 1.业务调研了解目标的预期 利用5W2H来进行专题分析 what--是什么?目的 ...

  9. poi的excel导出

    poi的excel导出 这个导出依赖于模板文件,可便捷设置表头样式. 也可以不使用模板,直接创建. 1.引入poi依赖 <dependency> <groupId>org.ap ...

  10. AI驱动的PlantUML:快速生成专业级UML图表

    **对于程序员来说,编写验收文档中的各种UML图是最让人头疼的事情之一,相信各位读者对此深有体会.** 本文将探讨如何利用AI驱动的PlantUML来快速生成专业级别的UML图表,从而减轻这一负担. ...