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操作
-
生成tmp_pkg_name(在binderApplication中甬道)和pkg_info
-
計算week_day:如果時週二~週六,則為true,否則為false
-
計算time_slot:4 ~ 12時,為1; 12 ~ 17時,為2; 17 ~ 21時,為3; 0~4或者21~24,為4
-
呼叫update_ux_pkg_details更新ux_pkg_tbl表資訊,pkg_use_count和timeslot_count_欄位+1
-
呼叫update_palm_table更新tracker資訊,當應用kill,則cached為false,當和上次啟動時間小於2s,do_not_launch為true,當應用launch,則cached為true當啟啟動應用個數達到12個時,更新ux_lat_tbl資訊,並設定權重為10,這裡的權重暫未使用
-
檢測應用是否在應用歷史列表中,如果在應用歷史列表中,則退出;否則,假如應用歷史個數為7,則重置為0,複製包名到應用歷史列表中
-
呼叫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的呼叫流程為:
- 任務歷史中切換應用ActivityTaskManagerService::moveTaskToFrontLocked
---ActivityStackSupervisor::findTaskToMoveToFront--------ActivityStackSupervisor::acquireAppLaunchPerfLock(當TopActivity為空或者DESTROYED)時被呼叫BoostFramework::perfIOPrefetchStart
- 應用啟動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
呼叫流程
ActivityThread::handleBindApplication完成後,呼叫ux_perf.perfUXEngine_events(BoostFramework.UXE_EVENT_BINDAPP, 0,pkg_name,bindApp_dur,//handleBindApplication執行時間pkgDir);//data/app/pck/base.apk
ActivityManagerService::appDiedLockedmUxPerf.perfUXEngine_events(BoostFramework.UXE_EVENT_KILL, 0, app.processName, 0);
ProcessRecord::killux_perf.perfUXEngine_events(BoostFramework.UXE_EVENT_KILL, 0, this.processName, 0);activity顯示完成,呼叫ActivityMetricsLogger.logAppDisplayed
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完成後呼叫
比較in_pkg_name和tmp_pkg_name(在IopStart獲取)是否一致 ,launching(IopStart時賦為true)是否為true
呼叫get_total_pkg_bindapp_count查詢ux_lat_tbl表中bindApp_ct獲取bindApplication次數
呼叫get_ux_lat_pkg獲取ux_lat_tbl表中資訊
計算lat_thres,為當前binderApplication時間和上次時間百分比,如果lat_thres為[50, 500]之間,且小於4s,則計算平均啟動時間bindApp_dur
沒有呼叫過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時間
比較in_pkg_name和tmp_pkg_name(在IopStart獲取)是否一致 ,launching(IopStart時賦為true)是否為true
呼叫get_total_pkg_use_count查詢ux_pkg_tbl表中pkg_use_count,即應用activity開啟次數
呼叫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啟動流程
pkg_count - 1為真,代表應用activity被開啟過兩次以上,
計算lat_thres,為當前displayed activity時間和上次時間百分比,如果lat_thres為[50, 500]之間,且小於5s,則計算平均啟動時間disAct_dur
呼叫update_ux_lat_details更新ux_lat_tbl表中bindApp_dur(平均binderApplication時間)和disAct_dur(如果第一次為0,否則為db中儲存的)資訊,並使bindApp_ct+1(曾經被uxe拉起過,則non_empty_launch為0,不+1)
狀態重置:呼叫get_preferred_apps更新tracker列表呼叫update_palm_table設定為以啟動模式(cached為true)
UXE_EVENT_KILL流程,應用被殺死後的流程
呼叫update_palm_table,設定tracker列表的cached和empty為false
如果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的主要流程為:
-
呼叫compute_time_day_slot計算當前是否工作日和時間段
-
根據工作日和時間段呼叫get_top_ux_pkg_list獲取ux_pkg_tbl和ux_lat_tbl中的應用啟動次數(pkg_use_count),啟動時間(bindApp_dur、disAct_dur)、時間段啟動次數(timeslot_count_)等資訊
-
獲取到最大PREFERRED_APP_COUNT(12)個應用資訊,放入out_list
-
呼叫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
-
根據最新的可能要啟動應用列表,構建一個最新的tracker
-
如果應用displayed完成,lunch為true,返回home,lunch為false
-
當應用kill時,被置為true,當返回home時,進入set_palm_table,然後走else流程,將empty置為true,然後被拉起,empty為true
-
如果應用2s內被殺兩次,則do_not_launch為true
-
如果應用已啟動,則cached為true
-
上述四種情況,則從out列表移除
-
如果是當前時間段不再tracker的應用(comp為false),(比如工作日/非工作日切換,常用應用個數大於12個時候,使用次數變更到底等),則如果已經啟動,更新預言成功(predict_success)權重+10,否則更新預言失敗(predict_fail)權重pr_launch/PREFERRED_APP_COUNT*10。這裡的predict_success和predict_fail暫未使用,在android R中不在使用