前言

本文将整理腾讯GT各个性能测试项的测试方法,目的是为了帮助移动性能专项测试同学快速过一遍腾讯GT各个性能数据是如何获取的。
另外对腾讯GT还不了解或者不知道它能做什么的同学可以看看这篇文章:https://testerhome.com/topics/9092

一.GT性能测试方案之CPU测试

1.简要流程

  • 初始化cpu的数据
  • 提供了两种方法获取CPU数据 getCpuUsage: 整机的CPU使用水平,主要用于实时刷新GT上的CPU数据。通过读取/proc/stat的数据,将每一个核的cpu使用跟闲置数据提取。使用率永远是增量式计算。计算方法为100*(cpu忙时增量-cpu整体增量),从计算方法来看,可能会导致负数出现。 getProcessCpuUsage:计算进程的CPU使用率,主要通过"/proc/" + pid + "/stat"来计算,在这里回京过一系列计算,拿到进程的CPU时间片

2.代码流程

  • cpu数据初始化 经过初始化,让CPU整体使用跟进程的CPU占用都为0
public CpuUtils() {
initCpuData();
} private void initCpuData() {
pCpu = o_pCpu = 0.0;
aCpu = o_aCpu = 0.0; }
  • 通过不同的调用栈,来监控不同的CPU数据
    整体CPU使用率:由于getCpuUsage是通过后台线程不断刷新来实现的,因此,o_cpu/o_idle数据不断在实时更新

    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性能测试方案之内存测试

1.简要流程

内存测试主要通过handleMessage来触发,根据msg参数来决定任务执行

  • 内存数据获取:仅仅简单通过dumpsys meminfo来获取内存数据,然后通过解析来得到pss_Native/naticeHeapSize/naticeAllocated/pss_OtherDev/pss_graphics/pss_gl/pss_UnKnown/pss_total数据
  • dumpHeap:通过am dumpheap 来获取heap文件
  • GC:直接kill -10 $pid 来完成GC

2.测试方法

  • 内存数据获取
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 + " "// 命令
+ Env.S_ROOT_DUMP_FOLDER + AUTManager.pkn.toString() + "/"// 输出路径
+ "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;
}
// 至此命令算是执行成功
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;
}
// 至此命令算是执行成功
if (isSucess)
{
handler.sendEmptyMessage(5);
} } else {
Log.d("gc error", "pid not found!");
}
}

三.GT性能测试方案之内存填充

1.简要流程

内存填充,主要是通过调用系统的malloc来分配内存。内存释放,则是通过系统free来释放。

2. 代码流程

  • 内存分配以及释放的函数
const int BASE_SIZE = 1024*1024; // 1M

int 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内存
public native int fillMem(int blockNum); // 释放刚才填充的内存
public native int freeMem();
}
 

四.GT性能测试方案之帧率测试

1.简要流程

FPS数据收集是一个定时任务(4.3后1s一次),通过异步线程中不断获取FPS数据来刷新到前端页面。而广播模式调用,则直接从缓存的field中获取数据即可。
在这里GT获取fps数据,也是通过采用surfaceflinger来获取,但我感觉好像是有问题的。因为,一般surfaceflinger数据获取的命令是adb shell dumpsys SurfaceFlinger --latency <window name>在这里直接定义了把"service call SurfaceFlinger 1013"字符串写到流里,没看明白这个操作跟帧率获取有什么关系。刚去了解了下,Runtime.getRuntime()原来执行多条命令时后续只要拿到processDataOutputStream对象,继续writeBytes就可以保证是在同一个上下文中执行多条命令了。

2.代码流程

  • 判断当前root状态,如果没有root直接返回,避免消耗系统资源
if (! GTFrameUtils.isHasSu())
{
return;
}
 
  • 计算一个周期内的帧率数据,由于比较简单(除了在1中surfaceflinger数据存疑外),直接把核心代码发出来
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;
 
  • 帧率获取的部分也发一下。另外感谢@codeskyblue 的指点,service call SurfaceFlinger 1013这个命令是获取系统的总的刷新帧率(返回的是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.测试方法

  • 执行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();
}
}
};
 
  • 执行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();
}
}
};
 
  • 执行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<String> args = new ArrayList<String>(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();
}
}
 
  • 数据处理得到sm值 腾讯这边的处理方案是:当丢帧<60时,流畅度SM =60-frame; 当丢帧frame>60时,流畅度SM = 60-frame%60。不过这种处理方式是有问题的。在这里要先说下流畅度计算的原理:

    • VSync机制可以通过其Loop来了解当前App最高绘制能力,固定每隔16.6ms执行一次,这样最高的刷新的帧率就控制在60FPS以内,Choreographer日志可以打印当前丢帧数,因此通过计算,得到当前APP的流畅度。
    • 而计算这样来计算可能会更加准确(个人看法,欢迎讨论): SM= 60-丢帧frame/每两行同一线程的丢帧时间差(单位:s),如果只关心UI线程,那就只需要统计UI线程即可。
while (true) {
if (pause) {
break;
}
int x = count.getAndSet(0);
// 卡顿大于60时,要将之前几次SM计数做修正
if (x > 60) {
int n = x / 60;
int v = x % 60;
TagTimeEntry tte = OpPerfBridge.getProfilerData(key);
int len = tte.getRecordSize();
// 补偿参数
int p = n;
//Math.min(len, n);
/*
* n > len是刚启动测试的情况,日志中的亡灵作祟,这种情况不做补偿;
* 并且本次也记为60。本逻辑在两次测试间会清理数据的情况生效。
*/
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"获取发送跟接收流量
  • 直接调用android的api:TrafficStats.getUidTxBytes(uid)来获取流量数据(该方法号称是获取到指定 uid 发送流量的总和,但实测情况是只有 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流量数据获取

  • 获取增加的流量
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 过滤有的手机进程流量偶尔输出负数的情况
if ((nowT != lastT || nowR != lastR) && nowT >= 0 && nowR >= 0) {
OpPerfBridge.addHistory(op, value, new long[]{(long) nowT, (long) nowR});
} return value;
 

七.GT性能测试方案之电量测试

1.简单流程

  • 关注指标:
    电量测试关注的指标有四个: 电流,电压,电量跟温度。
  • 数据获取方式:
    通过ReadPowerTimerTask任务去set关注的电量指标,当update方法调用时,才把数据set进去。电量数据调用的系统命令/sys/class/power_supply/battery/uevent
  • 具体流程:
    • 接收"com.tencent.wstt.gt.plugin.battery.startTest"广播后,update各个指标
    • 注册跟设置出参,并设置刷新频率跟初始化屏幕电量
    • 开启定时任务ReadPowerTimerTask,这个任务的作用就是去获取/sys/class/power_supply/battery/uevent下的电量数据并解析,最后设置出参。刷新频率默认是250ms一次
    • 最后stop时,销毁异步定时任务

2.代码流程

整个生命周期如下,当BATTERY_START_TEST行为被捕获时,开始执行电量测试

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 = new Timer(true);
timer.schedule(new ReadPowerTimerTask(), refreshRate, refreshRate); @Override
public void run() { BufferedReader br = null;
try {
FileReader fr = new FileReader(f);
br = new BufferedReader(fr);
String line = "";
while((line = br.readLine()) != null){ int found = 0;
if (line.startsWith("POWER_SUPPLY_VOLTAGE_NOW="))
{
U = line.substring(line.lastIndexOf("=") + 1);
// since 2.1.1 从μV转成mV
long volt = Long.parseLong(U) / 1000;
globalClient.setOutPara(OPU, volt + "mV"); OutPara op = 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转成mA since 2.2.4 华为本身就是mA
long current = Long.parseLong(I);
if (isHuawei)
{
current = -current;
}
else if (isLGg3)
{
current = current >> 1; // 经验值估算LG g3的数据除以2后比较接近真实
}
else
{
current = current / 1000;
}
globalClient.setOutPara(OPI, current + "mA"); OutPara op = globalClient.getOutPara(OPI);
if (null != op)
{
OpPerfBridge.addHistory(op, I, current);
} found++;
}
if (line.startsWith("POWER_SUPPLY_CAPACITY="))
{
String lastBattery = POW;
POW = line.substring(line.lastIndexOf("=") + 1);
if (! lastBattery.equals(POW)) // 电池百分比变化了
{
if (startBattry != -1)
{
lastBatteryChangeTime = (System.currentTimeMillis() - startBattry)/1000 + "s";
String tempValue = POW + "% | -1% time:" + lastBatteryChangeTime;
globalClient.setOutPara(OPPow, tempValue);
GTLog.logI(LOG_TAG, tempValue);
// 将电量加入历史记录
OutPara op = 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);
int iTemp = Integer.parseInt(TEMP);
iTemp = iTemp/10;
if (iTemp > -273)
{
TEMP = iTemp + "℃";
} globalClient.setOutPara(OPTemp, TEMP); OutPara op = 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 (Exception e) {
doStop();
}
finally
{
FileUtil.closeReader(br);
}
}

GT Tools支持AndroidJUnit的测试脚本用于性能指标的采集和数据监控,但是也需要你自己去调用对应的API。
另一种集成方式就是自己简单封装一层,用广播模式调用GT:http://gt.qq.com/docs/a/UseGtWithBroadcast.txt ,
然后去驱动自己的用例,我在这里有一个整体介绍:https://testerhome.com/topics/9092
在APP里执行dumpsys meminfo这个命令应该需要相应的系统权限或者root,但是没有root的手机也可以监测到内存数据,这个是为什么呢?
因为runCMD(String cmdString)在内部都是用su -c $cmdString 去执行的

【转】GT 的性能测试方案解析的更多相关文章

  1. 【原创】分布式之数据库和缓存双写一致性方案解析(三) 前端面试送命题(二)-callback,promise,generator,async-await JS的进阶技巧 前端面试送命题(一)-JS三座大山 Nodejs的运行原理-科普篇 优化设计提高sql类数据库的性能 简单理解token机制

    [原创]分布式之数据库和缓存双写一致性方案解析(三)   正文 博主本来觉得,<分布式之数据库和缓存双写一致性方案解析>,一文已经十分清晰.然而这一两天,有人在微信上私聊我,觉得应该要采用 ...

  2. 接口性能测试方案 白皮书 V1.0

    一. 性能测试术语解释 1. 响应时间 响应时间即从应用系统发出请求开始,到客户端接收到最后一个字节数据为止所消耗的时间.响应时间按软件的特点再可以细分,如对于一个 C/S 软件的响应时间可以细分为网 ...

  3. Loadrunner Http接口Get/Post方法性能测试脚本解析

    最近使用LoadRunner 11进行了一次完整的Http WEB接口性能测试,下面介绍下Http接口Get/Post方法性能测试脚本通用编写方法. 1. Http接口性能测试基本流程 首先定义了一个 ...

  4. Redis与Mysql双写一致性方案解析

    一 前言 首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用.在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作 但是在更新缓存方面,对于更新完数据库,是更新缓存呢,还是删除缓存 ...

  5. redis与mysql一致性方案解析

    一 前言 首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用.在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作 但是在更新缓存方面,对于更新完数据库,是更新缓存呢,还是删除缓存 ...

  6. REST性能测试方案

    1.REST简介 REST(代表性状态传输,Representational State Transfer)是一种Web服务设计模型.REST定义了一组体系架构原则,您可以根据这些原则设计以系统资源为 ...

  7. Web Service性能测试方案

    目录: 1.web Service简介 2.SoapUI介绍 3.使用SoapUI进行web service性能测试 4.使用LR进行web service性能测试 5.使用JMeter进行web s ...

  8. 【JavaScript】新浪微博ajax请求后改变地址栏url,但页面不跳转的方案解析

    新浪微博当你弹出一个视频的时候再点下一页时,原视频还在,而且地址栏的url的页数变了.对于这种网上讨论最多的方案有以下几种: 一.通过锚点Hash实现在这方面其实国内很早就有做了,比如淘宝画报,通过的 ...

  9. Android 截取手机屏幕两种实现方案解析

    近期在开发的过程中,遇到了一个须要截取屏幕保存为图片的需求,详细为截取webview的视图保存图片. 方法1:首先想到的思路是利用SDK提供的View.getDrawingCache()方法: pub ...

随机推荐

  1. Linux 系统下使用dd命令备份还原MBR主引导记录

    https://en.wikipedia.org/wiki/Master_boot_recordhttps://www.cyberciti.biz/faq/howto-copy-mbr/https:/ ...

  2. C++学习(四十)(C语言部分)之 学生管理系统设计

    涉及到的:指针申请内存 结构体数据结构部分排序文件操作 vs2013数据结构 排序 结构体 指针 功能:1.人工录入信息2.删除3.查找4.修改5.全部显示6.文件的读取和保存7.排序 设计:学生信息 ...

  3. immutable.js使用总结

    1. immutable相当于 JSON.parse 和 JSON.stringify: 2.引入redux中,除了 在最外层 reducer中  import { combineReducers } ...

  4. zombodb 配置设置

    主要是关于es 集群地址以及分片,复制副本的配置,配置主要在postgresql.conf,当然我们可以在函数中指定 postgresql.conf 级别的配置 es 配置 格式 zdb.defaul ...

  5. 如果debug调试的时候中断总是停在析构函数的delete[] p上

    如果debug调试的时候中断总是停在析构函数的delete[] p上,那可能 有两种情况: 1.调用析构函数的这个对象没有被分配空间,先找到调用调用析构函数出错的这个对象, 然后查看它是否被分配了空间 ...

  6. SysUtils.CompareText的注释

    两个字符串对象进行比较,忽略大小写,两个字符串缓冲区地址利用EAX和EDX两个寄存器传给该函数,字符串的长度用4个字节保存在缓冲区的前面,函数用EAX返回比较结果,结果为0表示相同. function ...

  7. FileProvider相关 Failed to find configured root that contains

    问题: 使用FileProvider构造SD卡中文件uri时异常 java.lang.IllegalArgumentException: Failed to find configured root ...

  8. Nios II Host-Based File System

    Nios II Host-Based File System 允许运行在Nios II的程序,在Debug模式下,通过Altera download cable来读写PC上当前工程目录下(及其子目录) ...

  9. django 补充和中间件

    配置 from django.conf import settings form组件 from django.forms import Formfrom django.forms import fie ...

  10. 如何在idea里面新建一个maven项目,然后在这个maven项目里创建多个子模块

    如何在idea里面配置maven我这里就不多说了 先新建一个maven项目作为总的管理项目 不用勾选什么,直接下一步 这样子一个普通的maven项目就创建成功了. 因为这个项目是用来管理多个子模块的, ...