前言
本文將整理騰訊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);}}