【Android12】Monkey压力测试源码执行流程分析

news2024/11/19 4:22:34

Monkey压力测试源码执行流程分析

Monkey是Android提供的用于应用程序自动化测试、压力测试的测试工具。
其源码路径(Android12)位于

/development/cmds/monkey/

部署形式为Java Binary

# development/cmds/monkey/Android.bp
// Copyright 2008 The Android Open Source Project
//

package {
    default_applicable_licenses: ["development_cmds_monkey_license"],
}

// See: http://go/android-license-faq
license {
    name: "development_cmds_monkey_license",
    visibility: [":__subpackages__"],
    license_kinds: [
        "SPDX-license-identifier-Apache-2.0",
    ],
    license_text: [
        "NOTICE",
    ],
}

//###############################################################
java_binary {
    name: "monkey",
    srcs: ["**/*.java"],
    wrapper: "monkey",
}

通过Monkey,可以模拟用户的Touch(单指、多指、手势)、按键(key)事件等,检测应用程序发生的ANR、Crash事件,并收集相关Debug信息等。
例如测试应用com.package.linduo,

adb shell monkey -p com.package.linduo --pct-touch 10 --pct-motion 20 10000
# 该命令表示,执行1万次测试事件,其中Touch事件占10%,Motion事件占20%

# 或者adb shell进入android终端,直接使用monkey命令

Monkey支持的命令

    private void showUsage() {
        StringBuffer usage = new StringBuffer();
        usage.append("usage: monkey [-p ALLOWED_PACKAGE [-p ALLOWED_PACKAGE] ...]\n");
        usage.append("              [-c MAIN_CATEGORY [-c MAIN_CATEGORY] ...]\n");
        usage.append("              [--ignore-crashes] [--ignore-timeouts]\n");
        usage.append("              [--ignore-security-exceptions]\n");
        usage.append("              [--monitor-native-crashes] [--ignore-native-crashes]\n");
        usage.append("              [--kill-process-after-error] [--hprof]\n");
        usage.append("              [--match-description TEXT]\n");
        usage.append("              [--pct-touch PERCENT] [--pct-motion PERCENT]\n");
        usage.append("              [--pct-trackball PERCENT] [--pct-syskeys PERCENT]\n");
        usage.append("              [--pct-nav PERCENT] [--pct-majornav PERCENT]\n");
        usage.append("              [--pct-appswitch PERCENT] [--pct-flip PERCENT]\n");
        usage.append("              [--pct-anyevent PERCENT] [--pct-pinchzoom PERCENT]\n");
        usage.append("              [--pct-permission PERCENT]\n");
        usage.append("              [--pkg-blacklist-file PACKAGE_BLACKLIST_FILE]\n");
        usage.append("              [--pkg-whitelist-file PACKAGE_WHITELIST_FILE]\n");
        usage.append("              [--wait-dbg] [--dbg-no-events]\n");
        usage.append("              [--setup scriptfile] [-f scriptfile [-f scriptfile] ...]\n");
        usage.append("              [--port port]\n");
        usage.append("              [-s SEED] [-v [-v] ...]\n");
        usage.append("              [--throttle MILLISEC] [--randomize-throttle]\n");
        usage.append("              [--profile-wait MILLISEC]\n");
        usage.append("              [--device-sleep-time MILLISEC]\n");
        usage.append("              [--randomize-script]\n");
        usage.append("              [--script-log]\n");
        usage.append("              [--bugreport]\n");
        usage.append("              [--periodic-bugreport]\n");
        usage.append("              [--permission-target-system]\n");
        usage.append("              COUNT\n");
        Logger.err.println(usage.toString());
    }

Monkey执行测试的源码分析

这里主要关注模式事件的执行流程

  • Monkey启动
  • Monkey生成模拟事件
  • Monkey向系统发送模拟事件
    在这里插入图片描述
Monkey启动

Monkey.java中定义了程序入口函数main,该函数中启动了Monkey程序。

// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java

public static void main(String[] args) {
	// Set the process name showing in "ps" or "top"
	Process.setArgV0("com.android.commands.monkey");

	Logger.err.println("args: " + Arrays.toString(args));
	int resultCode = (new Monkey()).run(args);
	System.exit(resultCode);
}
// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java

/**
 * Run the command!
 *
 * @param args The command-line arguments
 * @return Returns a posix-style result code. 0 for no error.
 */
private int run(String[] args) {
	// Default values for some command-line options
	mVerbose = 0;
	// 默认的测试次数
	mCount = 1000;
	// 生成radom的seed
	mSeed = 0;
	// 记录事件之间的延迟,就是每个事件执行的间隔
	mThrottle = 0;

	// prepare for command-line processing
	mArgs = args;
	
	// 解析参数
	if (!processOptions()) {
		return -1;
	}
	
	// 确定待测试的Package
	if (!loadPackageLists()) {
		return -1;
	}
	
	// now set up additional data in preparation for launch
	if (mMainCategories.size() == 0) {
		mMainCategories.add(Intent.CATEGORY_LAUNCHER);
		mMainCategories.add(Intent.CATEGORY_MONKEY);
	}
	
	if (mSeed == 0) {
		mSeed = System.currentTimeMillis() + System.identityHashCode(this);
	}
	// 获取系统服务接口(AMS、PMS、WMS)
	if (!getSystemInterfaces()) {
		return -3;
	}
	// 获取用于启动应用的Activity
	if (!getMainApps()) {
		return -4;
	}
	
	if (mScriptFileNames != null && mScriptFileNames.size() == 1) {
		// script mode, ignore other options
	} else if (mScriptFileNames != null && mScriptFileNames.size() > 1) {
	} else if (mServerPort != -1) {
	} else {
		// 创建用于产生模拟器事件的Source对象
		mEventSource = new MonkeySourceRandom(mRandom, mMainApps,
				mThrottle, mRandomizeThrottle, mPermissionTargetSystem);
		mEventSource.setVerbose(mVerbose);
		// 设置各测试类型的测试比例
		// set any of the factors that has been set
		for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
			if (mFactors[i] <= 0.0f) {
				((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);
			}
		}
		
		// 产生activity事件,该事件用来启动应用
		// in random mode, we start with a random activity
		((MonkeySourceRandom) mEventSource).generateActivity();
	}
	
	try {
		// 执行模拟测试事件
		crashedAtCycle = runMonkeyCycles();
	} finally {
		// Release the rotation lock if it's still held and restore the
		// original orientation.
		new MonkeyRotationEvent(Surface.ROTATION_0, false).injectEvent(
			mWm, mAm, mVerbose);
	}

}
Monkey解析输入参数

processOptions函数解析输入参数(就是monkey命令后跟着的参数信息),根据入参设置Monkey类中相关成员变量。

// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java
private boolean processOptions() {
	// quick (throwaway) check for unadorned command
	if (mArgs.length < 1) {
		showUsage();
		return false;
	}

	try {
		String opt;
		Set<String> validPackages = new HashSet<>();
		while ((opt = nextOption()) != null) {
			if (opt.equals("-s")) {
				mSeed = nextOptionLong("Seed");
			} else if (opt.equals("-p")) {
				validPackages.add(nextOptionData());
			} else if (opt.equals("-c")) {
				// 省略
			} else {
				Logger.err.println("** Error: Unknown option: " + opt);
				showUsage();
				return false;
			}
		}
		// 根据输入参数,设置待测试的应用
		MonkeyUtils.getPackageFilter().addValidPackages(validPackages);
	} catch (RuntimeException ex) {
		Logger.err.println("** Error: " + ex.toString());
		showUsage();
		return false;
	}

	// If a server port hasn't been specified, we need to specify
	// a count
	if (mServerPort == -1) {
		// 省略
	}

	return true;
}
Monkey获取系统服务

getSystemInterfaces函数用于获取Android系统服务,包括AMS、PMS、WMS服务。调用AMS服务的setActivityController接口,通过该接口向AMS设置IActivityController.Stub对象,通过该对象监听应用(Activity)的ANR和Crash事件。

/**
 * Attach to the required system interfaces.
 *
 * @return Returns true if all system interfaces were available.
 */
private boolean getSystemInterfaces() {
	mAm = ActivityManager.getService();
	if (mAm == null) {
		Logger.err.println("** Error: Unable to connect to activity manager; is the system "
				+ "running?");
		return false;
	}

	mWm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
	if (mWm == null) {
		Logger.err.println("** Error: Unable to connect to window manager; is the system "
				+ "running?");
		return false;
	}

	mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
	if (mPm == null) {
		Logger.err.println("** Error: Unable to connect to package manager; is the system "
				+ "running?");
		return false;
	}

	try {
		mAm.setActivityController(new ActivityController(), true);
		mNetworkMonitor.register(mAm);
	} catch (RemoteException e) {
		Logger.err.println("** Failed talking with activity manager!");
		return false;
	}

	return true;
}

/**
 * Monitor operations happening in the system.
 */
private class ActivityController extends IActivityController.Stub {
	public boolean activityStarting(Intent intent, String pkg) {
		// 省略
	}

	private boolean isActivityStartingAllowed(Intent intent, String pkg) {
		// 省略
	}

	public boolean activityResuming(String pkg) {
		// 省略
	}

	public boolean appCrashed(String processName, int pid,
			String shortMsg, String longMsg,
			long timeMillis, String stackTrace) {
		// 省略
	}

	public int appEarlyNotResponding(String processName, int pid, String annotation) {
		return 0;
	}

	public int appNotResponding(String processName, int pid, String processStats) {
		// 省略
	}

	public int systemNotResponding(String message) {
		// 省略
	}
}
Monkey获取待测试应用的Activity

monkey通过PackageManager的queryIntentActivities接口,查询带有 Intent.CATEGORY_LAUNCHERIntent.CATEGORY_MONKEY信息的Activity,并判断Activity是否属于待测试应用。将待测试应用的Activity添加到mMainApps变量中。

// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java
/**
 * Using the restrictions provided (categories & packages), generate a list
 * of activities that we can actually switch to.
 *
 * @return Returns true if it could successfully build a list of target
 *         activities
 */
private boolean getMainApps() {
	try {
		final int N = mMainCategories.size();
		for (int i = 0; i < N; i++) {
			Intent intent = new Intent(Intent.ACTION_MAIN);
			String category = mMainCategories.get(i);
			if (category.length() > 0) {
				intent.addCategory(category);
			}
			// 查找带有 Intent.CATEGORY_LAUNCHER、Intent.CATEGORY_MONKEY的Activity
			List<ResolveInfo> mainApps = mPm.queryIntentActivities(intent, null, 0,
					ActivityManager.getCurrentUser()).getList();
			
			final int NA = mainApps.size();
			for (int a = 0; a < NA; a++) {
				ResolveInfo r = mainApps.get(a);
				String packageName = r.activityInfo.applicationInfo.packageName;
				if (MonkeyUtils.getPackageFilter().checkEnteringPackage(packageName)) {
					// 如果Activity属于待测试Package,将其添加到mMainApps中。
					mMainApps.add(new ComponentName(packageName, r.activityInfo.name));
				} else {
				}
			}
		}
	} catch (RemoteException e) {
		Logger.err.println("** Failed talking with package manager!");
		return false;
	}

	if (mMainApps.size() == 0) {
		Logger.out.println("** No activities found to run, monkey aborted.");
		return false;
	}

	return true;
}
Monkey生成模拟测试事件,并执行
// development/cmds/monkey/src/com/android/commands/monkey/Monkey.java
private int run(String[] args) {
	// 创建该对象,用于产生测试事件
	mEventSource = new MonkeySourceRandom(mRandom, mMainApps,
			mThrottle, mRandomizeThrottle, mPermissionTargetSystem);

	for (int i = 0; i < MonkeySourceRandom.FACTORZ_COUNT; i++) {
		if (mFactors[i] <= 0.0f) {
			((MonkeySourceRandom) mEventSource).setFactors(i, mFactors[i]);
		}
	}

	try {
		// 执行Monkey测试
		crashedAtCycle = runMonkeyCycles();
	} finally {
		// Release the rotation lock if it's still held and restore the
		// original orientation.
		new MonkeyRotationEvent(Surface.ROTATION_0, false).injectEvent(
			mWm, mAm, mVerbose);
	}
}

runMonkeyCycles函数中调用MonkeySourceRandom的getNextEvent函数生成模拟测试事件(MonkeyEvent),调用MonkeyEventinjectEvent执行模拟测试。

private int runMonkeyCycles() {
	int eventCounter = 0;
	int cycleCounter = 0;

	boolean shouldReportAnrTraces = false;
	boolean shouldReportDumpsysMemInfo = false;
	boolean shouldAbort = false;
	boolean systemCrashed = false;

	try {
		// TO DO : The count should apply to each of the script file.
		while (!systemCrashed && cycleCounter < mCount) {
			synchronized (this) {
			// 注意:因为先执行过generateActivity,所以第一次调用会,会获得启动Activity的模拟测试事件
			MonkeyEvent ev = mEventSource.getNextEvent();
			if (ev != null) {
				int injectCode = ev.injectEvent(mWm, mAm, mVerbose);

			} else {
			}
		}
	} catch (RuntimeException e) {
		Logger.error("** Error: A RuntimeException occurred:", e);
	}
	Logger.out.println("Events injected: " + eventCounter);
	return eventCounter;
}

MonkeySourceRandom的getNextEvent,会在事件队列(存储模拟测试事件)为空时,产生测试对象。生成测试事件时,先生成一个随机数,然后根据测试类型所占比例(比例越大,生成该测试类型的概率越大),生成不同测试类型。

// development/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
/**
 * generate an activity event
 */
public void generateActivity() {
	MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
			mRandom.nextInt(mMainApps.size())));
	mQ.addLast(e);
}

/**
 * if the queue is empty, we generate events first
 * @return the first event in the queue
 */
public MonkeyEvent getNextEvent() {
	if (mQ.isEmpty()) {
		generateEvents();
	}
	mEventCount++;
	MonkeyEvent e = mQ.getFirst();
	mQ.removeFirst();
	return e;
}

/**
 * generate a random event based on mFactor
 */
private void generateEvents() {
	// 生成随机数
	float cls = mRandom.nextFloat();
	int lastKey = 0;
	// 根据Factor,即不同测试类型所占的比例,生成测试事件
	if (cls < mFactors[FACTOR_TOUCH]) {
		generatePointerEvent(mRandom, GESTURE_TAP);
		return;
	} else if (cls < mFactors[FACTOR_MOTION]) {
		generatePointerEvent(mRandom, GESTURE_DRAG);
		return;
	} else if (cls < mFactors[FACTOR_PINCHZOOM]) {
		generatePointerEvent(mRandom, GESTURE_PINCH_OR_ZOOM);
		return;
	} else if (cls < mFactors[FACTOR_TRACKBALL]) {
		generateTrackballEvent(mRandom);
		return;
	} else if (cls < mFactors[FACTOR_ROTATION]) {
		generateRotationEvent(mRandom);
		return;
	} else if (cls < mFactors[FACTOR_PERMISSION]) {
		mQ.add(mPermissionUtil.generateRandomPermissionEvent(mRandom));
		return;
	}

	// The remaining event categories are injected as key events
	for (;;) {
		if (cls < mFactors[FACTOR_NAV]) {
			lastKey = NAV_KEYS[mRandom.nextInt(NAV_KEYS.length)];
		} else if (cls < mFactors[FACTOR_MAJORNAV]) {
			lastKey = MAJOR_NAV_KEYS[mRandom.nextInt(MAJOR_NAV_KEYS.length)];
		} else if (cls < mFactors[FACTOR_SYSOPS]) {
			lastKey = SYS_KEYS[mRandom.nextInt(SYS_KEYS.length)];
		} else if (cls < mFactors[FACTOR_APPSWITCH]) {
			MonkeyActivityEvent e = new MonkeyActivityEvent(mMainApps.get(
					mRandom.nextInt(mMainApps.size())));
			mQ.addLast(e);
			return;
		} else if (cls < mFactors[FACTOR_FLIP]) {
			MonkeyFlipEvent e = new MonkeyFlipEvent(mKeyboardOpen);
			mKeyboardOpen = !mKeyboardOpen;
			mQ.addLast(e);
			return;
		} else {
			lastKey = 1 + mRandom.nextInt(KeyEvent.getMaxKeyCode() - 1);
		}

		if (lastKey != KeyEvent.KEYCODE_POWER
				&& lastKey != KeyEvent.KEYCODE_ENDCALL
				&& lastKey != KeyEvent.KEYCODE_SLEEP
				&& lastKey != KeyEvent.KEYCODE_SOFT_SLEEP
				&& PHYSICAL_KEY_EXISTS[lastKey]) {
			break;
		}
	}

	MonkeyKeyEvent e = new MonkeyKeyEvent(KeyEvent.ACTION_DOWN, lastKey);
	mQ.addLast(e);

	e = new MonkeyKeyEvent(KeyEvent.ACTION_UP, lastKey);
	mQ.addLast(e);
}

以Ttouch事件为例子,调用generatePointerEvent函数。通过DMS获取Display对象(用于得知屏幕大小),生成MonkeyTouchEvent对象。

// development/cmds/monkey/src/com/android/commands/monkey/MonkeySourceRandom.java
private void generatePointerEvent(Random random, int gesture) {
	Display display = DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);

	PointF p1 = randomPoint(random, display);
	PointF v1 = randomVector(random);

	long downAt = SystemClock.uptimeMillis();

	mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_DOWN)
			.setDownTime(downAt)
			.addPointer(0, p1.x, p1.y)
			.setIntermediateNote(false));

	// 省略

	randomWalk(random, display, p1, v1);
	mQ.addLast(new MonkeyTouchEvent(MotionEvent.ACTION_UP)
			.setDownTime(downAt)
			.addPointer(0, p1.x, p1.y)
			.setIntermediateNote(false));
}

调用MonkeyTouchEvent的injectEvent函数,使用InputManager向系统派发TouchEvent。

// development/cmds/monkey/src/com/android/commands/monkey/MonkeyMotionEvent.java
@Override
public int injectEvent(IWindowManager iwm, IActivityManager iam, int verbose) {
	MotionEvent me = getEvent();
	if ((verbose > 0 && !mIntermediateNote) || verbose > 1) {
		StringBuilder msg = new StringBuilder(":Sending ");
		msg.append(getTypeLabel()).append(" (");
		switch (me.getActionMasked()) {
			case MotionEvent.ACTION_DOWN:
				msg.append("ACTION_DOWN");
				break;
			case MotionEvent.ACTION_MOVE:
				msg.append("ACTION_MOVE");
				break;
			case MotionEvent.ACTION_UP:
				msg.append("ACTION_UP");
				break;
			case MotionEvent.ACTION_CANCEL:
				msg.append("ACTION_CANCEL");
				break;
			case MotionEvent.ACTION_POINTER_DOWN:
				msg.append("ACTION_POINTER_DOWN ").append(me.getPointerId(me.getActionIndex()));
				break;
			case MotionEvent.ACTION_POINTER_UP:
				msg.append("ACTION_POINTER_UP ").append(me.getPointerId(me.getActionIndex()));
				break;
			default:
				msg.append(me.getAction());
				break;
		}
		msg.append("):");

		int pointerCount = me.getPointerCount();
		for (int i = 0; i < pointerCount; i++) {
			msg.append(" ").append(me.getPointerId(i));
			msg.append(":(").append(me.getX(i)).append(",").append(me.getY(i)).append(")");
		}
		Logger.out.println(msg.toString());
	}
	try {
		// 派发TouchEvent
		if (!InputManager.getInstance().injectInputEvent(me,
				InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT)) {
			return MonkeyEvent.INJECT_FAIL;
		}
	} finally {
		me.recycle();
	}
	return MonkeyEvent.INJECT_SUCCESS;
}
Monkey主要相关类图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1478820.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《PyTorch深度学习实践》第九讲多分类问题

一、 1、softmax的输入不需要再做非线性变换&#xff0c;也就是说softmax之前不再需要激活函数。softmax两个作用&#xff0c;如果在进行softmax前的input有负数&#xff0c;通过指数变换&#xff0c;得到正数。所有类的概率求和为1。 2、y的标签编码方式是one-hot。one-hot是…

java爬取深圳新房备案价

Java爬取深圳新房备案价 这是我做好效果,一共分3个页面 1、列表;2、统计;3、房源表 列表 价格分析页面 房源页面 一、如何爬取 第一步:获取深圳新房备案价 链接是:http://zjj.sz.gov.cn/ris/bol/szfdc/index.aspx 第二步:通过楼盘名查询获取明细 链接:http://z…

就业班 2401--2.27 Linux Day6--管道和重定向

管道与重定向 只有在开水里&#xff0c;茶叶才能展开生命浓郁的香气. 一、重定向 标准输入、标准正确输出、标准错误输出 进程在运行的过程中根据需要会打开多个文件&#xff0c;每打开一个文件会有一个数字标识。这个标识叫文件描述符。 进程使用文件描述符来管理打开的文件…

Android PDFView 提示401 pom

背景 在开发安卓app&#xff0c;使用PDF组件来解析URL地址 &#xff0c;从github找到一个开源组件 AndroidPdfViewer 遇到一个大坑&#xff0c;一直提示下载依赖401 pom 打开控制台链接弹出需要登录jitpack 原因分析&#xff1a; 这个组件项目依赖库链接到了需要鉴权的…

【airtest】自动化入门教程(一)AirtestIDE

目录 一、下载与安装 1、下载 2、安装 3、打开软件 二、web自动化配置 1、配置chrome浏览器 2、窗口勾选selenium window 三、新建项目&#xff08;web&#xff09; 1、新建一个Airtest项目 2、初始化代码 3、打开一个网页 四、恢复默认布局 五、新建项目&#xf…

服务器数据恢复-服务器RAID5上层XFS文件系统分区数据恢复案例

服务器数据恢复环境&#xff1a; MD1200磁盘柜中的磁盘通过RAID卡创建了一组RAID5阵列&#xff0c;分配了一个LUN。在Linux操作系统层面对该LUN进行了分区&#xff0c;划分sdc1和sdc2两个分区&#xff0c;通过LVM扩容的方式将sdc1分区加入到了root_lv中&#xff1b;sdc2分区格式…

VuePress + GitHub 搭建个人博客踩坑记录

最近想给我教练搭个网站,本来选的是 VuePress 框架,也折腾完了,起码是搭建出来了,踩的坑也都总结好了 但是最近发现了一个更简洁的模板: VuePress-theme-hope ,所以最终网站使用的样式是这个 不过我觉得这里面踩坑的记录应该还是有些价值的,分享出来,看看能不能帮到一些小伙伴~…

手机AI摄影时代开启,传音引领行业标准化建设

今年春节&#xff0c;AI摄影可谓大出风头。人们在社交平台晒出自己在龙年的AI写真&#xff0c;极大地增添了节日的氛围感&#xff0c;也让我们看到了“AI摄影”的价值。新年伊始&#xff0c;手机巨头们纷纷布局该赛道&#xff0c;基于AI大模型实现的影像功能成为业界关注焦点。…

CAPL编程学习笔记--关于on 事件的详细解释

CAPL编程是比较有特色的一种面向通讯的编程语言。 1&#xff1a;on XXX类型&#xff08;即事件类型&#xff09; 维克多的官方文档对CAPL的描述是一门类C语言&#xff0c;说白了它也是用C写出来的。我们看on&#xff08;注意都是小写&#xff09;事件的代码结构 on * { }&…

算法day03_ 59.螺旋矩阵II

推荐阅读 算法day01_ 27. 移除元素、977.有序数组的平方 算法day02_209.长度最小的子数组 目录 推荐阅读59.螺旋矩阵 II题目思路解法 59.螺旋矩阵 II 题目 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形…

2023年全国职业院校技能大赛中职组大数据应用与服务赛项题库参考答案陆续更新中,敬请期待…

2023年全国职业院校技能大赛中职组大数据应用与服务赛项题库参考答案陆续更新中&#xff0c;敬请期待… 武汉唯众智创科技有限公司 2024 年 2 月 联系人&#xff1a;辜渝傧13037102709 题号&#xff1a;试题01 模块三&#xff1a;业务分析与可视化 &#xff08;一&#xff0…

【Web安全靶场】sqli-labs-master 38-53 Stacked-Injections

sqli-labs-master 38-53 Stacked-Injections 其他关卡和靶场看专栏… 文章目录 sqli-labs-master 38-53 Stacked-Injections第三十八关-报错注入第三十九关-报错注入第四十关-盲注第四十一关-盲注第四十二关-联合报错双查询注入第四十三关-报错注入第四十四关-盲注第四十五关-…

Facebook的元宇宙实践:数字化社交的新前景

近年来&#xff0c;元宇宙&#xff08;Metaverse&#xff09;这一概念备受瞩目&#xff0c;被认为是数字化社交的未来趋势之一。而在众多科技巨头中&#xff0c;Facebook&#xff08;现更名为Meta&#xff09;一直处于元宇宙发展的前沿。在本文中&#xff0c;我们将深入探讨Fac…

linux系统Jenkins工具web配置

Jenkins工具配置 插件配置系统配置系统工具配置 插件配置 下载 Maven Integration Pipeline Maven lntegration gitlab Generic webhook Trigger nodejs Blue ocean系统配置 系统配置结束系统工具配置

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的水果质量识别系统(Python+PySide6界面+训练代码)

摘要&#xff1a;本篇博客详尽介绍了一套基于深度学习的水果质量识别系统及其实现代码。系统采用了尖端的YOLOv8算法&#xff0c;并与YOLOv7、YOLOv6、YOLOv5等前代算法进行了详细的性能对比分析&#xff0c;提供在识别图像、视频、实时视频流和批量文件中水果方面的高效准确性…

32单片机基础:TIM输出比较

这个输出比较功能是非常重要的&#xff0c;它主要是用来输出PWM波形,PWM波形又是驱动电机的必要条件&#xff0c;所以你如果想用STM32做一些有电机的项目&#xff0c;比如智能车&#xff0c;机器人等。 IC: Input Capture 输入捕获 CC:Capture/Compare一般表示输入捕获和输出…

【Leetcode每日一刷】哈希表|纲领、242.有效的字母异位词、349. 两个数组的交集

纲领 &#x1f517;代码随想录理论部分 关于哈希表这个数据结构就不再重复讲了&#xff0c;下面对几个关键点记录一下&#xff1a; 哈希碰撞 解决方法1&#xff1a;拉链法 解决方法2&#xff1a;线性探测法 下面针对做题要用到的三种结构讲一下&#xff08;也是重复造轮子了…

解释一下前端框架中的虚拟DOM(virtual DOM)和实际DOM(real DOM)之间的关系。

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

windows server mysql 数据库停止 备份 恢复全流程操作方法

一,mysql备份 mysql最好是原工程文件备份.不需要sql查询的方式备份.安全高效. 比如,安装php与mysql组合后,我的mysql文件保存在: D:\phpstudy_pro\Extensions\MySQL5.7.26\data\dux 我只需要复制一份,保存起来就行. 二,mysql恢复 怎么恢复呢.我们一般是只恢复其中一个表,则找…

华为---RSTP(四)---RSTP的保护功能简介和示例配置

目录 1. 技术背景 2. RSTP的保护功能 3. BPDU保护机制原理和配置命令 3.1 BPDU保护机制原理 3.2 BPDU保护机制配置命令 3.3 BPDU保护机制配置步骤 4. 根保护机制原理和配置命令 4.1 根保护机制原理 4.2 根保护机制配置命令 4.3 根保护机制配置步骤 5. 环路保护机…