源碼解讀騰訊 GT 的性能測試方案

前言

本文將整理騰訊GT各個性能測試項的測試方法,目的是為了幫助移動性能專項測試同學快速過一遍騰訊GT各個性能數(shù)據(jù)是如何獲取的。

一.GT性能測試方案之CPU測試

1.簡要流程

初始化cpu的數(shù)據(jù)

提供了兩種方法獲取CPU數(shù)據(jù) getCpuUsage: 整機的CPU使用水平,主要用于實時刷新GT上的CPU數(shù)據(jù)。通過讀取/proc/stat的數(shù)據(jù),將每一個核的cpu使用跟閑置數(shù)據(jù)提取。使用率永遠是增量式計算。計算方法為100*(cpu忙時增量-cpu整體增量),從計算方法來看,可能會導致負數(shù)出現(xiàn)。 getProcessCpuUsage:計算進程的CPU使用率,主要通過"/proc/" + pid + "/stat"來計算,在這里回京過一系列計算,拿到進程的CPU時間片

2.代碼流程

cpu數(shù)據(jù)初始化 經(jīng)過初始化,讓CPU整體使用跟進程的CPU占用都為0

public?CpuUtils()?{

initCpuData();}private?void?initCpuData()?{

pCpu?=?o_pCpu?=?0.0;

aCpu?=?o_aCpu?=?0.0;}

通過不同的調(diào)用棧,來監(jiān)控不同的CPU數(shù)據(jù)

整體CPU使用率:由于getCpuUsage是通過后臺線程不斷刷新來實現(xiàn)的,因此,o_cpu/o_idle數(shù)據(jù)不斷在實時更新

RandomAccessFile?reader?=?null;try?{reader?=?new?RandomAccessFile("/proc/stat",?"r");String?load;load?=?reader.readLine();String[]?toks?=?load.split("?");double?c_idle?=?Double.parseDouble(toks[5]);double?c_cpu?=?Double.parseDouble(toks[2])

+?Double.parseDouble(toks[3])

+?Double.parseDouble(toks[4])

+?Double.parseDouble(toks[6])

+?Double.parseDouble(toks[8])

+?Double.parseDouble(toks[7]);if?(0?!=?((c_cpu?+?c_idle)?-?(o_cpu?+?o_idle)))?{

//?double?value?=?(100.00?*?((c_cpu?-?o_cpu)?)?/?((c_cpu?+

//?c_idle)?-?(o_cpu?+?o_idle)));

usage?=?DoubleUtils.div((100.00?*?((c_cpu?-?o_cpu))),

((c_cpu?+?c_idle)?-?(o_cpu?+?o_idle)),?2);

//?Log.d("CPU",?"usage:?"?+?usage);

if?(usage?<?0)?{

usage?=?0;

}

else?if?(usage?>?100)

{

usage?=?100;

}

//?BigDecimal?b?=?new?BigDecimal(Double.toString(value));

//?usage?=?b.setScale(2,

//?BigDecimal.ROUND_HALF_UP).doubleValue();

//?Log.d("CPU",?"usage:?"?+?usage);}o_cpu?=?c_cpu;o_idle?=?c_idle;}?catch?(IOException?e)?{e.printStackTrace();}?finally?{FileUtil.closeRandomAccessFile(reader);}

進程的CPU使用時間片獲取

public?String?getProcessCpuUsage(int?pid)?{

String?result?=?"";

String[]?result1?=?null;

String[]?result2?=?null;

if?(pid?>=?0)?{

result1?=?getProcessCpuAction(pid);

if?(null?!=?result1)?{

pCpu?=?Double.parseDouble(result1[1])

+?Double.parseDouble(result1[2]);

}

result2?=?getCpuAction();

if?(null?!=?result2)?{

aCpu?=?0.0;

for?(int?i?=?2;?i?<?result2.length;?i++)?{

aCpu?+=?Double.parseDouble(result2[i]);

}

}

double?usage?=?0.0;

if?((aCpu?-?o_aCpu)?!=?0)?{

usage?=?DoubleUtils.div(((pCpu?-?o_pCpu)?*?100.00),

(aCpu?-?o_aCpu),?2);

if?(usage?<?0)?{

usage?=?0;

}

else?if?(usage?>?100)

{

usage?=?100;

}

}

o_pCpu?=?pCpu;

o_aCpu?=?aCpu;

result?=?String.valueOf(usage)?+?"%";

}

p_jif?=?pCpu;

return?result;}

二.GT性能測試方案之內(nèi)存測試

1.簡要流程

內(nèi)存測試主要通過handleMessage來觸發(fā),根據(jù)msg參數(shù)來決定任務執(zhí)行

內(nèi)存數(shù)據(jù)獲?。簝H僅簡單通過dumpsys meminfo來獲取內(nèi)存數(shù)據(jù),然后通過解析來得到pss_Native/naticeHeapSize/naticeAllocated/pss_OtherDev/pss_graphics/pss_gl/pss_UnKnown/pss_total數(shù)據(jù)

dumpHeap:通過am dumpheap 來獲取heap文件

GC:直接kill -10 $pid 來完成GC

2.測試方法

內(nèi)存數(shù)據(jù)獲取

public?static?MemInfo?getMemInfo(String?packageName){

MemInfo?result?=?null;

String?resultString?=?null;

try?{

resultString?=?runCMD("dumpsys?meminfo?"?+?packageName);

}?catch?(Exception?e)?{

e.printStackTrace();

return?MemInfo.EMPTY;

}

if(Env.API?<?14)

{

result?=?parseMemInfoFrom2x(resultString);

}

else?if?(Env.API?<?19)

{

result?=?parseMemInfoFrom4x(resultString);

}

else

{

result?=?parseMemInfoFrom44(resultString);

}

return?result;}

dumpHeap

private?void?dumpHeap()?{

String?pid?=?String.valueOf(ProcessUtils

.getProcessPID(AUTManager.pkn.toString()));

if?(!pid.equals("-1"))?{

boolean?isSucess?=?true;

ProcessBuilder?pb?=?null;

String?sFolder?=?Env.S_ROOT_DUMP_FOLDER?+?AUTManager.pkn.toString()?+?"/";

File?folder?=?new?File(sFolder);

if?(!folder.exists())

{

folder.mkdirs();

}

String?cmd?=?"am?dumpheap?"?+?pid?+?"?"http://?命令

+?Env.S_ROOT_DUMP_FOLDER?+?AUTManager.pkn.toString()?+?"/"http://?輸出路徑

+?"dump_"?+?pid?+?"_"?+?GTUtils.getSaveDate()?+?".hprof";?//?輸出文件名

pb?=?new?ProcessBuilder("su",?"-c",?cmd);

Process?exec?=?null;

pb.redirectErrorStream(true);

try?{

exec?=?pb.start();

InputStream?is?=?exec.getInputStream();

BufferedReader?reader?=?new?BufferedReader(

new?InputStreamReader(is));

while?((reader.readLine())?!=?null)?{

isSucess?=?false;

}

}?catch?(Exception?e)?{

e.printStackTrace();

isSucess?=?false;

}

//?至此命令算是執(zhí)行成功

if?(isSucess)

{

handler.sendEmptyMessage(6);

}

}?else?{

Log.d("dump?error",?"pid?not?found!");

}}

GC

private?void?gc()?{

String?pid?=?String.valueOf(ProcessUtils

.getProcessPID(AUTManager.pkn.toString()));

if?(!pid.equals("-1"))?{

boolean?isSucess?=?true;

ProcessBuilder?pb?=?null;

String?cmd?=?"kill?-10?"?+?pid;

pb?=?new?ProcessBuilder("su",?"-c",?cmd);

Process?exec?=?null;

pb.redirectErrorStream(true);

try?{

exec?=?pb.start();

InputStream?is?=?exec.getInputStream();

BufferedReader?reader?=?new?BufferedReader(

new?InputStreamReader(is));

while?((reader.readLine())?!=?null)?{

isSucess?=?false;

}

}?catch?(Exception?e)?{

e.printStackTrace();

isSucess?=?false;

}

//?至此命令算是執(zhí)行成功

if?(isSucess)

{

handler.sendEmptyMessage(5);

}

}?else?{

Log.d("gc?error",?"pid?not?found!");

}}

三.GT性能測試方案之內(nèi)存填充

1.簡要流程

內(nèi)存填充,主要是通過調(diào)用系統(tǒng)的malloc來分配內(nèi)存。內(nèi)存釋放,則是通過系統(tǒng)free來釋放。

2. 代碼流程

內(nèi)存分配以及釋放的函數(shù)

const?int?BASE_SIZE?=?1024*1024;?//?1Mint?fill(int?blockNum){

int?memSize?=?blockNum?*?BASE_SIZE;

p?=?(char?*)malloc(memSize);

int?i;

for?(i?=?0;?i?<?memSize;?i++)

{

p[i]?=?0;

}

return?0;}int?freeMem(){

free(p);

return?0;}

加載com_tencent_wstt_gt_api_utils_MemFillTool.c,并提供度應對操作接口

public?class?MemFillTool?{

public?MemFillTool()?{

}

public?static?MemFillTool?instance?=?null;

public?static?MemFillTool?getInstance()?{

if?(instance?==?null)?{

System.loadLibrary("mem_fill_tool");

instance?=?new?MemFillTool();

}

return?instance;

}

//?填充xxxMB內(nèi)存

public?native?int?fillMem(int?blockNum);

//?釋放剛才填充的內(nèi)存

public?native?int?freeMem();}

四.GT性能測試方案之幀率測試

1.簡要流程

FPS數(shù)據(jù)收集是一個定時任務(4.3后1s一次),通過異步線程中不斷獲取FPS數(shù)據(jù)來刷新到前端頁面。而廣播模式調(diào)用,則直接從緩存的field中獲取數(shù)據(jù)即可。

在這里GT獲取fps數(shù)據(jù),也是通過采用surfaceflinger來獲取,但我感覺好像是有問題的。因為,一般surfaceflinger數(shù)據(jù)獲取的命令是adb shell dumpsys SurfaceFlinger --latency ,在這里直接定義了把"service call SurfaceFlinger 1013"字符串寫到流里,沒看明白這個操作跟幀率獲取有什么關系。剛去了解了下,Runtime.getRuntime()原來執(zhí)行多條命令時后續(xù)只要拿到process的DataOutputStream對象,繼續(xù)writeBytes就可以保證是在同一個上下文中執(zhí)行多條命令了。

2.代碼流程

判斷當前root狀態(tài),如果沒有root直接返回,避免消耗系統(tǒng)資源

if?(!?GTFrameUtils.isHasSu()){

return;}

計算一個周期內(nèi)的幀率數(shù)據(jù),由于比較簡單(除了在1中surfaceflinger數(shù)據(jù)存疑外),直接把核心代碼發(fā)出來

startTime?=?System.nanoTime();if?(testCount?==?0)?{

try?{

lastFrameNum?=?getFrameNum();

}?catch?(IOException?e)?{

e.printStackTrace();

}}int?currentFrameNum?=?0;try?{

currentFrameNum?=?getFrameNum();}?catch?(IOException?e)?{

e.printStackTrace();}int?FPS?=?currentFrameNum?-?lastFrameNum;if?(realCostTime?>?0.0F)?{

int?fpsResult?=?(int)?(FPS?*?1000?/?realCostTime);

defaultClient.setOutPara("FPS",?fpsResult);}lastFrameNum?=?currentFrameNum;testCount?+=?1;

幀率獲取的部分也發(fā)一下。另外感謝@codeskyblue?的指點,service call SurfaceFlinger 1013這個命令是獲取系統(tǒng)的總的刷新幀率(返回的是16進制)

public?static?synchronized?int?getFrameNum()?throws?IOException?{

String?frameNumString?=?"";

String?getFps40?=?"service?call?SurfaceFlinger?1013";

if?(process?==?null)

{

process?=?Runtime.getRuntime().exec("su");

os?=?new?DataOutputStream(process.getOutputStream());

ir?=?new?BufferedReader(

new?InputStreamReader(process.getInputStream()));

}

os.writeBytes(getFps40?+?"\n");

os.flush();

String?str?=?"";

int?index1?=?0;

int?index2?=?0;

while?((str?=?ir.readLine())?!=?null)?{

if?(str.indexOf("(")?!=?-1)?{

index1?=?str.indexOf("(");

index2?=?str.indexOf("??");

frameNumString?=?str.substring(index1?+?1,?index2);

break;

}

}

int?frameNum;

if?(!frameNumString.equals(""))?{

frameNum?=?Integer.parseInt(frameNumString,?16);

}?else?{

frameNum?=?0;

}

return?frameNum;}

五.GT性能測試方案之流暢度測試

1.簡要流程

騰訊的流暢度測試比較簡單粗暴,測試方式是通過初始化choreographer日志級別,生成Choreographer日志來得到當前操作的丟幀。通過一系列計算后來計算流暢度。

2.測試方法

執(zhí)行setprop debug.choreographer.skipwarning 1

View.OnClickListener?button_write_property?=?new?View.OnClickListener()?{

@Override

public?void?onClick(View?v)?{

String?cmd?=?"setprop?debug.choreographer.skipwarning?1";

ProcessBuilder?execBuilder?=?new?ProcessBuilder("su",?"-c",?cmd);

execBuilder.redirectErrorStream(true);

try?{

execBuilder.start();

}?catch?(IOException?e)?{

e.printStackTrace();

}

}

};

執(zhí)行getprop debug.choreographer.skipwarning判斷,為1則可以進行測試

View.OnClickListener?button_check_status?=?new?View.OnClickListener()?{

@Override

public?void?onClick(View?v)?{

String?cmd?=?"getprop?debug.choreographer.skipwarning";

ProcessBuilder?execBuilder?=?new?ProcessBuilder("sh",?"-c",?cmd);

execBuilder.redirectErrorStream(true);

try?{

TextView?textview?=?(TextView)?findViewById(R.id.textviewInformation);

Process?p?=?execBuilder.start();

InputStream?is?=?p.getInputStream();

InputStreamReader?isr?=?new?InputStreamReader(is);

BufferedReader?br?=?new?BufferedReader(isr);

Boolean?flag?=?false;

String?line;

while?((line?=?br.readLine())?!=?null)?{

if?(line.compareTo("1")?==?0)?{

flag?=?true;

break;

}

}

if?(flag)?{

textview.setText("OK");

}?else?{

textview.setText("NOT?OK");

}

}?catch?(IOException?e)?{

e.printStackTrace();

}

}

};

執(zhí)行adb logcat -v time -s Choreographer:I *:S

過濾獲取當前pid丟幀值

protected?void?onHandleIntent(Intent?intent)?{

try?{

String?str?=?intent.getStringExtra("pid");

int?pid?=?Integer.parseInt(str);

List?args?=?new?ArrayList(Arrays.asList("logcat",?"-v",?"time",?"Choreographer:I",?"*:S"));

dumpLogcatProcess?=?RuntimeHelper.exec(args);

reader?=?new?BufferedReader(new?InputStreamReader(dumpLogcatProcess.getInputStream()),?8192);

String?line;

while?((line?=?reader.readLine())?!=?null?&&?!killed)?{

//?filter?"The?application?may?be?doing?too?much?work?on?its?main?thread."

if?(!line.contains("uch?work?on?its?main?t"))?{

continue;

}

int?pID?=?LogLine.newLogLine(line,?false).getProcessId();

if?(pID?!=?pid){

continue;

}

line?=?line.substring(50,?line.length()?-?71);

Integer?value?=?Integer.parseInt(line.trim());

SMServiceHelper.getInstance().dataQueue.offer(value);

}

}?catch?(IOException?e)?{

Log.e(TAG,?e.toString()?+?"unexpected?exception");

}?finally?{

killProcess();

}

}

數(shù)據(jù)處理得到sm值 騰訊這邊的處理方案是:當丟幀<60時,流暢度SM =60-frame; 當丟幀frame>60時,流暢度SM = 60-frame%60。不過這種處理方式是有問題的。在這里要先說下流暢度計算的原理:

VSync機制可以通過其Loop來了解當前App最高繪制能力,固定每隔16.6ms執(zhí)行一次,這樣最高的刷新的幀率就控制在60FPS以內(nèi),Choreographer日志可以打印當前丟幀數(shù),因此通過計算,得到當前APP的流暢度。

而計算這樣來計算可能會更加準確(個人看法,歡迎討論): SM= 60-丟幀frame/每兩行同一線程的丟幀時間差(單位:s),如果只關心UI線程,那就只需要統(tǒng)計UI線程即可。

while?(true)?{

if?(pause)?{

break;

}

int?x?=?count.getAndSet(0);

//?卡頓大于60時,要將之前幾次SM計數(shù)做修正

if?(x?>?60)?{

int?n?=?x?/?60;

int?v?=?x?%?60;

TagTimeEntry?tte?=?OpPerfBridge.getProfilerData(key);

int?len?=?tte.getRecordSize();

//?補償參數(shù)

int?p?=?n;

//Math.min(len,?n);

/*

*?n?>?len是剛啟動測試的情況,日志中的亡靈作祟,這種情況不做補償;

*?并且本次也記為60。本邏輯在兩次測試間會清理數(shù)據(jù)的情況生效。

*/

if?(n?>?len)?{

globalClient.setOutPara(key,?60);//??????????globalClient.setOutPara(SFKey,?0);

}?else?{

for?(int?i?=?0;?i?<?p;?i++)?{

TimeEntry?te?=?tte.getRecord(len?-?1?-?i);

te.reduce?=?0;

}

globalClient.setOutPara(key,?v);//??????globalClient.setOutPara(SFKey,?60?-?v);

}

}?else?{

int?sm?=?60?-?x;

globalClient.setOutPara(key,?sm);//??????globalClient.setOutPara(SFKey,?x);

}

六.GT性能測試方案之流量測試

1.簡要流程

流量測試有三種方案,默認采用方案1

通過讀取"/proc/uid_stat/" + uid + "/tcp_snd"獲取發(fā)送跟接收流量

直接調(diào)用android的api:TrafficStats.getUidTxBytes(uid)來獲取流量數(shù)據(jù)(該方法號稱是獲取到指定 uid 發(fā)送流量的總和,但實測情況是只有 tcp 層的流量)

第三種方案居然空在那里,那實際上只有兩種方案

2.代碼流程

初始化流量

public?void?initProcessNetValue(String?pName)?{

p_t_base?=?getOutOctets(pName);

p_r_base?=?getInOctets(pName);

p_t_add?=?0;

p_r_add?=?0;}

其中getOutOctets/getInOctets具體對應什么方法,需要看設備是不是支持uid流量數(shù)據(jù)獲取

獲取增加的流量

public?String?getProcessNetValue(String?pName)?{

StringBuffer?sb?=?new?StringBuffer();

java.text.DecimalFormat?df?=?new?java.text.DecimalFormat("#.##");

p_t_cur?=?getOutOctets(pName);

p_r_cur?=?getInOctets(pName);

p_t_add?=?(p_t_cur?-?p_t_base)?/?B2K;

p_r_add?=?(p_r_cur?-?p_r_base)?/?B2K;

sb.append("t");

sb.append(df.format(p_t_add));

sb.append("KB|r");

sb.append(df.format(p_r_add));

sb.append("KB");

return?sb.toString();}

矯正處理

//?modify?on?20120616?過濾有的手機進程流量偶爾輸出負數(shù)的情況if?((nowT?!=?lastT?||?nowR?!=?lastR)?&&?nowT?>=?0?&&?nowR?>=?0)?{

OpPerfBridge.addHistory(op,?value,?new?long[]{(long)?nowT,?(long)?nowR});}return?value;

七.GT性能測試方案之電量測試

1.簡單流程

關注指標:

電量測試關注的指標有四個: 電流,電壓,電量跟溫度。

數(shù)據(jù)獲取方式:

通過ReadPowerTimerTask任務去set關注的電量指標,當update方法調(diào)用時,才把數(shù)據(jù)set進去。電量數(shù)據(jù)調(diào)用的系統(tǒng)命令/sys/class/power_supply/battery/uevent

具體流程:

接收"com.tencent.wstt.gt.plugin.battery.startTest"廣播后,update各個指標

注冊跟設置出參,并設置刷新頻率跟初始化屏幕電量

開啟定時任務ReadPowerTimerTask,這個任務的作用就是去獲取/sys/class/power_supply/battery/uevent下的電量數(shù)據(jù)并解析,最后設置出參。刷新頻率默認是250ms一次

最后stop時,銷毀異步定時任務

2.代碼流程

整個生命周期如下,當BATTERY_START_TEST行為被捕獲時,開始執(zhí)行電量測試

String?action?=?intent.getAction();if?(action?==?null)?return;if?(action.equals(BATTERY_START_TEST))?{

int?refreshRate?=?intent.getIntExtra("refreshRate",?250);

int?brightness?=?intent.getIntExtra("brightness",?100);

boolean?updateI?=?intent.getBooleanExtra("I",?true);

GTBatteryEngine.getInstance().updateI(updateI);

boolean?updateU?=?intent.getBooleanExtra("U",?false);

GTBatteryEngine.getInstance().updateU(updateU);

boolean?updateT?=?intent.getBooleanExtra("T",?false);

GTBatteryEngine.getInstance().updateT(updateT);

boolean?updateP?=?intent.getBooleanExtra("P",?false);

GTBatteryEngine.getInstance().updateP(updateP);

GTBatteryEngine.getInstance().doStart(refreshRate,?brightness);}?else?if?(action.equals(BATTERY_END_TEST))?{

GTBatteryEngine.getInstance().doStop();}

update操作很簡單,只是注冊跟set出參

public?void?updateI(boolean?isChecked){

if?(isChecked)

{

globalClient.registerOutPara(GTBatteryEngine.OPI,?"I");

globalClient.setOutparaMonitor(GTBatteryEngine.OPI,?true);

}

else

{

globalClient.unregisterOutPara(GTBatteryEngine.OPI);

}

state_cb_I?=?isChecked;

GTPref.getGTPref().edit().putBoolean(GTBatteryEngine.KEY_I,?isChecked).commit();

for?(BatteryPluginListener?listener?:?listeners)

{

listener.onUpdateI(isChecked);

}}

初始化操作略過,這里展示異步任務,處理流程直接看下面的關鍵代碼即可,方法在GTBatteryEngine.java中

timer=newTimer(true);timer.schedule(newReadPowerTimerTask(),refreshRate,refreshRate);@Overridepublicvoidrun(){BufferedReaderbr=null;try{FileReaderfr=newFileReader(f);br=newBufferedReader(fr);Stringline="";while((line=br.readLine())!=null){intfound=0;if(line.startsWith("POWER_SUPPLY_VOLTAGE_NOW=")){U=line.substring(line.lastIndexOf("=")+1);// since 2.1.1 從μV轉(zhuǎn)成mVlongvolt=Long.parseLong(U)/1000;globalClient.setOutPara(OPU,volt+"mV");OutParaop=globalClient.getOutPara(OPU);if(null!=op){OpPerfBridge.addHistory(op,U,volt);}found++;}if(line.startsWith("POWER_SUPPLY_CURRENT_NOW=")){I=line.substring(line.lastIndexOf("=")+1);// since 2.1.1 從μA轉(zhuǎn)成mA since 2.2.4 華為本身就是mAlongcurrent=Long.parseLong(I);if(isHuawei){current=-current;}elseif(isLGg3){current=current>>1;// 經(jīng)驗值估算LG g3的數(shù)據(jù)除以2后比較接近真實}else{current=current/1000;}globalClient.setOutPara(OPI,current+"mA");OutParaop=globalClient.getOutPara(OPI);if(null!=op){OpPerfBridge.addHistory(op,I,current);}found++;}if(line.startsWith("POWER_SUPPLY_CAPACITY=")){StringlastBattery=POW;POW=line.substring(line.lastIndexOf("=")+1);if(!lastBattery.equals(POW))// 電池百分比變化了{if(startBattry!=-1){lastBatteryChangeTime=(System.currentTimeMillis()-startBattry)/1000+"s";StringtempValue=POW+"% | -1% time:"+lastBatteryChangeTime;globalClient.setOutPara(OPPow,tempValue);GTLog.logI(LOG_TAG,tempValue);// 將電量加入歷史記錄OutParaop=globalClient.getOutPara(OPPow);if(null!=op){OpPerfBridge.addHistory(op,tempValue,Long.parseLong(POW));}}startBattry=System.currentTimeMillis();}globalClient.setOutPara(OPPow,POW+"% | -1% time:"+lastBatteryChangeTime);found++;}if(line.startsWith("POWER_SUPPLY_TEMP=")){TEMP=line.substring(line.lastIndexOf("=")+1);intiTemp=Integer.parseInt(TEMP);iTemp=iTemp/10;if(iTemp>-273){TEMP=iTemp+"℃";}globalClient.setOutPara(OPTemp,TEMP);OutParaop=globalClient.getOutPara(OPTemp);if(null!=op&&iTemp!=INT_TEMP){OpPerfBridge.addHistory(op,TEMP,iTemp);GTLog.logI(LOG_TAG,TEMP);INT_TEMP=iTemp;}found++;}if(found>=4){return;}}}catch(Exceptione){doStop();}finally{FileUtil.closeReader(br);}}

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容