【安卓12源码】adb 的dumpsys和cmd实现原理

 主要分析下列 2 个流程:

// dump activity 的一些信息

adb shell dumpsys activity > D:\dumpsys\activity2.txt


// a11 打开 ActivityManagerDebugConfig 的开关
adb shell cmd activity log_switch enable DEBUG_ALL

1. 分析 adb shell dumpsys 实现过程

还可以使用下列命令dump 具体的组件

adb shell dumpsys activity -a a // -a 是dump全部,a 是 activities

/frameworks/native/cmds/dumpsys/main.cpp

int main(int argc, char* const argv[]) {signal(SIGPIPE, SIG_IGN);sp<IServiceManager> sm = defaultServiceManager();fflush(stdout);if (sm == nullptr) {ALOGE("Unable to get default service manager!");std::cerr << "dumpsys: Unable to get default service manager!" << std::endl;return 20;}Dumpsys dumpsys(sm.get());// 执行 dumpsys 的 main 方法return dumpsys.main(argc, argv);
}

// 执行 dumpsys 的 main 方法

/frameworks/native/cmds/dumpsys/dumpsys.cpp

int Dumpsys::main(int argc, char* const argv[]) {Vector<String16> services;Vector<String16> args;String16 priorityType;Vector<String16> skippedServices;Vector<String16> protoServices;Type type = Type::DUMP;
。。。optind = 1;
。。。for (int i = optind; i < argc; i++) {if (skipServices) {skippedServices.add(String16(argv[i]));} else {// 将输入的servicename 保存到 services 中,如adb shell dumpsys activity -a a
// 则将 activity 保存到 services 中if (i == optind) {services.add(String16(argv[i]));} else {const String16 arg(argv[i]);
// args 保存其他参数 -a aargs.add(arg);const size_t N = services.size();
。。。。for (size_t i = 0; i < N; i++) {const String16& serviceName = services[i];if (IsSkipped(skippedServices, serviceName)) continue;// 调用startDumpThread 方法  type 类型为 DUMPif (startDumpThread(type, serviceName, args) == OK) {bool addSeparator = (N > 1);if (addSeparator) {writeDumpHeader(STDOUT_FILENO, serviceName, priorityFlags);}std::chrono::duration<double> elapsedDuration;size_t bytesWritten = 0;
// 输出结果status_t status =writeDump(STDOUT_FILENO, serviceName, std::chrono::milliseconds(timeoutArgMs),asProto, elapsedDuration, bytesWritten);==========
// 调用startDumpThread 方法  type 类型为 DUMPstatus_t Dumpsys::startDumpThread(Type type, const String16& serviceName,const Vector<String16>& args) {// 找到对应服务的客户端sp<IBinder> service = sm_->checkService(serviceName);if (service == nullptr) {std::cerr << "Can't find service: " << serviceName << std::endl;return NAME_NOT_FOUND;}int sfd[2];
// 管道通信if (pipe(sfd) != 0) {std::cerr << "Failed to create pipe to dump service info for " << serviceName << ": "<< strerror(errno) << std::endl;return -errno;}redirectFd_ = unique_fd(sfd[0]);unique_fd remote_end(sfd[1]);sfd[0] = sfd[1] = -1;// dump blocks until completion, so spawn a thread..activeThread_ = std::thread([=, remote_end{std::move(remote_end)}]() mutable {status_t err = 0;switch (type) {case Type::DUMP:
// binder 通信调用dump 方法err = service->dump(remote_end.get(), args);break;

// binder 通信调用dump 方法

/frameworks/native/libs/binder/BpBinder.cpp

status_t BpBinder::dump(int fd, const Vector<String16>& args)
{Parcel send;Parcel reply;
// send 封装了文件描述符send.writeFileDescriptor(fd);const size_t numArgs = args.size();send.writeInt32(numArgs);for (size_t i = 0; i < numArgs; i++) {
// 保存了 args 参数send.writeString16(args[i]);}status_t err = transact(DUMP_TRANSACTION, send, &reply);return err;
}=========
status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{// Once a binder has died, it will never come back to life.if (mAlive) {bool privateVendor = flags & FLAG_PRIVATE_VENDOR;// don't send userspace flags to the kernelflags = flags & ~FLAG_PRIVATE_VENDOR;// user transactions require a given stability levelif (code >= FIRST_CALL_TRANSACTION && code <= LAST_CALL_TRANSACTION) {using android::internal::Stability;auto stability = Stability::get(this);auto required = privateVendor ? Stability::VENDOR : Stability::kLocalStability;if (CC_UNLIKELY(!Stability::check(stability, required))) {ALOGE("Cannot do a user transaction on a %s binder in a %s context.",Stability::stabilityString(stability).c_str(),Stability::stabilityString(required).c_str());return BAD_TYPE;}}
/// 通过binder 驱动去找到对端服务器端status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);

===========服务端执行流程===========

走到服务器端的 BBinder

/frameworks/base/core/jni/android_util_Binder.cpp

// javabinder 继承了 BBinder
class JavaBBinder : public BBinder
{
public:JavaBBinder(JNIEnv* env, jobject /* Java Binder */ object): mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object)){。。。status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0) override{JNIEnv* env = javavm_to_jnienv(mVM);ALOGV("onTransact() on %p calling object %p in env %p vm %p\n", this, mObject, env, mVM);IPCThreadState* thread_state = IPCThreadState::self();const int32_t strict_policy_before = thread_state->getStrictModePolicy();//printf("Transact from %p to Java code sending: ", this);//data.print();//printf("\n");// 调用java 层的execTransact 方法jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);// mExecTransact 为java 层的 execTransact 方法gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact", "(IJJI)Z");

// 调用java 层的execTransact 方法

/frameworks/base/core/java/android/os/Binder.java

    @UnsupportedAppUsageprivate boolean execTransact(int code, long dataObj, long replyObj,int flags) {// At that point, the parcel request headers haven't been parsed so we do not know what// WorkSource the caller has set. Use calling uid as the default.final int callingUid = Binder.getCallingUid();final long origWorkSource = ThreadLocalWorkSource.setUid(callingUid);try {return execTransactInternal(code, dataObj, replyObj, flags, callingUid);===========private boolean execTransactInternal(int code, long dataObj, long replyObj, int flags,int callingUid) {final BinderInternal.Observer observer = sObserver;final CallSession callSession =observer != null ? observer.callStarted(this, code, UNSET_WORKSOURCE) : null;Parcel data = Parcel.obtain(dataObj);Parcel reply = Parcel.obtain(replyObj);boolean res;// Log any exceptions as warnings, don't silently suppress them.// If the call was FLAG_ONEWAY then these exceptions disappear into the ether.final boolean tracingEnabled = Binder.isTracingEnabled();try {if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0) {AppOpsManager.startNotedAppOpsCollection(callingUid);try {res = onTransact(code, data, reply, flags);} finally {AppOpsManager.finishNotedAppOpsCollection();}} else {
// 调用 onTransact 方法res = onTransact(code, data, reply, flags);}=============protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply,int flags) throws RemoteException {// 如果是dump 命令,则执行 dump 方法} else if (code == DUMP_TRANSACTION) {ParcelFileDescriptor fd = data.readFileDescriptor();String[] args = data.readStringArray();if (fd != null) {try {dump(fd.getFileDescriptor(), args);// 如果是 adb shell cmd,则调用 shellCommand 方法} else if (code == SHELL_COMMAND_TRANSACTION) {ParcelFileDescriptor in = data.readFileDescriptor();ParcelFileDescriptor out = data.readFileDescriptor();ParcelFileDescriptor err = data.readFileDescriptor();String[] args = data.readStringArray();ShellCallback shellCallback = ShellCallback.CREATOR.createFromParcel(data);ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data);try {if (out != null) {shellCommand(in != null ? in.getFileDescriptor() : null,out.getFileDescriptor(),err != null ? err.getFileDescriptor() : out.getFileDescriptor(),args, shellCallback, resultReceiver);}=========public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) {FileOutputStream fout = new FileOutputStream(fd);PrintWriter pw = new FastPrintWriter(fout);try {doDump(fd, pw, args);=========void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {final String disabled = sDumpDisabled;if (disabled == null) {try {dump(fd, pw, args);

 由 aidl 自动生成的  IActivityManager.Stub 是继承于Binder 对象的


public IActivityManager IRemoteService extends android.os.IInterface {public static abstract class Stub extends android.os.Binder implements xxx

而 ActivityManagerService 是继承了 IActivityManager.Stub,所以走到其 dump(fd, pw, args) 方法

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

10510      @Override
10511      protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
// 传入参数是 mPriorityDumper
10512          PriorityDump.dump(mPriorityDumper, fd, pw, args);
10513      }=============

调用 PriorityDump 工具类的dump 方法

/frameworks/base/services/core/java/com/android/server/utils/PriorityDump.java 

    public static void dump(PriorityDumper dumper, FileDescriptor fd, PrintWriter pw,String[] args) {boolean asProto = false;@PriorityType int priority = PRIORITY_TYPE_INVALID;
...String[] strippedArgs = new String[args.length];int strippedCount = 0;for (int argIndex = 0; argIndex < args.length; argIndex++) {if (args[argIndex].equals(PROTO_ARG)) {asProto = true;} else if (args[argIndex].equals(PRIORITY_ARG)) {if (argIndex + 1 < args.length) {argIndex++;priority = getPriorityType(args[argIndex]);}} else {
// 分离出命令args 来strippedArgs[strippedCount++] = args[argIndex];}}if (strippedCount < args.length) {strippedArgs = Arrays.copyOf(strippedArgs, strippedCount);}switch (priority) {
...default: {
// 调用 PriorityDumper  去dumpdumper.dump(fd, pw, strippedArgs, asProto);return;}}

// 调用 PriorityDumper  去dump

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

674      private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
675          @Override
676          public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
677                  boolean asProto) {
678              if (asProto) return;
679              doDump(fd, pw, new String[]{"activities"}, asProto);
680              doDump(fd, pw, new String[]{"service", "all-platform-critical"}, asProto);
681          }。。。。。
687  
688          @Override
689          public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {// dump 方法
690              doDump(fd, pw, args, asProto);
691          }
692      };========

// dump 方法

10666      private void doDump(FileDescriptor fd, PrintWriter pw, String[] args, boolean useProto) {
10667          if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
10668  
10669          boolean dumpAll = false;
10670          boolean dumpClient = false;
10671          boolean dumpCheckin = false;
10672          boolean dumpCheckinFormat = false;
10673          boolean dumpNormalPriority = false;
10674          boolean dumpVisibleStacksOnly = false;
10675          boolean dumpFocusedStackOnly = false;
10676          String dumpPackage = null;
10677  
10678          int opti = 0;
10679          while (opti < args.length) {
10680              String opt = args[opti];
10681              if (opt == null || opt.length() <= 0 || opt.charAt(0) != '-') {
10682                  break;
10683              }
10684              opti++;// 先判断输入命令是否有 -a ,如果有则dump全部信息
10685              if ("-a".equals(opt)) {
10686                  dumpAll = true;。。。// useProto 为false
10718          if (useProto) {
10719              final ProtoOutputStream proto = new ProtoOutputStream(fd);
10720              String cmd = opti < args.length ? args[opti] : "";
10721              opti++;
。。。
10786          int dumpAppId = getAppId(dumpPackage);
10787          boolean more = false;
10788          // Is the caller requesting to dump a particular piece of data?
10789          if (opti < args.length) {
10790              String cmd = args[opti];
10791              opti++;// DUMP_ACTIVITIES_CMD 为 activities,DUMP_ACTIVITIES_SHORT_CMD 为a
10792              if (DUMP_ACTIVITIES_CMD.equals(cmd) || DUMP_ACTIVITIES_SHORT_CMD.equals(cmd)
10793                      || DUMP_LASTANR_CMD.equals(cmd) || DUMP_LASTANR_TRACES_CMD.equals(cmd)
10794                      || DUMP_STARTER_CMD.equals(cmd) || DUMP_CONTAINERS_CMD.equals(cmd)
10795                      || DUMP_RECENTS_CMD.equals(cmd) || DUMP_RECENTS_SHORT_CMD.equals(cmd)) {
10796                  mAtmInternal.dump(
10797                          cmd, fd, pw, args, opti, true /* dumpAll */, dumpClient, dumpPackage);

调用 ActivityTaskManagerService 的方法

6979          @Override
6980          public void dump(String cmd, FileDescriptor fd, PrintWriter pw, String[] args, int opti,
6981                  boolean dumpAll, boolean dumpClient, String dumpPackage) {
6982              synchronized (mGlobalLock) {
6983                  if (DUMP_ACTIVITIES_CMD.equals(cmd) || DUMP_ACTIVITIES_SHORT_CMD.equals(cmd)) {
6984                      dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);

2. 分析 adb shell cmd 实现过程

adb shell cmd activity -h

/frameworks/native/cmds/cmd/main.cpp

int main(int argc, char* const argv[]) {signal(SIGPIPE, SIG_IGN);std::vector<std::string_view> arguments;arguments.reserve(argc - 1);// 0th argument is a program name, skipping.for (int i = 1; i < argc; ++i) {arguments.emplace_back(argv[i]);}// 调用 cmdMain 方法return cmdMain(arguments, android::aout, android::aerr, STDIN_FILENO, STDOUT_FILENO,STDERR_FILENO, RunMode::kStandalone);

// 调用 cmdMain 方法

/frameworks/native/cmds/cmd/cmd.cpp

int cmdMain(const std::vector<std::string_view>& argv, TextOutput& outputLog, TextOutput& errorLog,int in, int out, int err, RunMode runMode) {sp<ProcessState> proc = ProcessState::self();proc->startThreadPool();#if DEBUGALOGD("cmd: starting");
#endifsp<IServiceManager> sm = defaultServiceManager();int argc = argv.size();// 如果命令是:adb shell cmd -l 则列出所有的service 的名字if ((argc == 1) && (argv[0] == "-l")) {Vector<String16> services = sm->listServices();services.sort(sort_func);
// 会打印出下列string 值outputLog << "Currently running services:" << endl;for (size_t i=0; i<services.size(); i++) {sp<IBinder> service = sm->checkService(services[i]);if (service != nullptr) {outputLog << "  " << services[i] << endl;}}return 0;}// 这里是 false 的bool waitForService = ((argc > 1) && (argv[0] == "-w"));
// index 的值为 0int serviceIdx = (waitForService) ? 1 : 0;
// cmd 为第一个参数为 servicenameconst auto cmd = argv[serviceIdx];Vector<String16> args;
// 通过cmd 获取到 servicenameString16 serviceName = String16(cmd.data(), cmd.size());// 遍历service 后面的参数,保存到 args 中for (int i = serviceIdx + 1; i < argc; i++) {args.add(String16(argv[i].data(), argv[i].size()));}sp<IBinder> service;if(waitForService) {service = sm->waitForService(serviceName);} else {// 获取到对应的serviceservice = sm->checkService(serviceName);}。。。// TODO: block until a result is returned to MyResultReceiver.
// 调用 shellCommand 方法status_t error = IBinder::shellCommand(service, in, out, err, args, cb, result);if (error < 0) {

// 调用 shellCommand 方法

 /frameworks/native/libs/binder/Binder.cpp

status_t IBinder::shellCommand(const sp<IBinder>& target, int in, int out, int err,Vector<String16>& args, const sp<IShellCallback>& callback,const sp<IResultReceiver>& resultReceiver)
{Parcel send;Parcel reply;
// 将传入的参数封装为 Parcel send.writeFileDescriptor(in);send.writeFileDescriptor(out);send.writeFileDescriptor(err);const size_t numArgs = args.size();send.writeInt32(numArgs);for (size_t i = 0; i < numArgs; i++) {send.writeString16(args[i]);}send.writeStrongBinder(callback != nullptr ? IInterface::asBinder(callback) : nullptr);send.writeStrongBinder(resultReceiver != nullptr ? IInterface::asBinder(resultReceiver) : nullptr);// 调用对应的service 的客户端去binder 通信return target->transact(SHELL_COMMAND_TRANSACTION, send, &reply);
}

如前面的分析也会调用 java 层的execTransact 方法

/frameworks/base/core/java/android/os/Binder.java

    private boolean execTransactInternal(int code, long dataObj, long replyObj, int flags,int callingUid) {final BinderInternal.Observer observer = sObserver;final CallSession callSession =observer != null ? observer.callStarted(this, code, UNSET_WORKSOURCE) : null;Parcel data = Parcel.obtain(dataObj);Parcel reply = Parcel.obtain(replyObj);boolean res;// Log any exceptions as warnings, don't silently suppress them.// If the call was FLAG_ONEWAY then these exceptions disappear into the ether.final boolean tracingEnabled = Binder.isTracingEnabled();try {if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0) {AppOpsManager.startNotedAppOpsCollection(callingUid);try {res = onTransact(code, data, reply, flags);} finally {AppOpsManager.finishNotedAppOpsCollection();}} else {
// 调用 onTransact 方法res = onTransact(code, data, reply, flags);}=============protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply,int flags) throws RemoteException {// 如果是dump 命令,则执行 dump 方法} else if (code == DUMP_TRANSACTION) {ParcelFileDescriptor fd = data.readFileDescriptor();String[] args = data.readStringArray();if (fd != null) {try {dump(fd.getFileDescriptor(), args);// 如果是 adb shell cmd,则调用 shellCommand 方法} else if (code == SHELL_COMMAND_TRANSACTION) {ParcelFileDescriptor in = data.readFileDescriptor();ParcelFileDescriptor out = data.readFileDescriptor();ParcelFileDescriptor err = data.readFileDescriptor();String[] args = data.readStringArray();ShellCallback shellCallback = ShellCallback.CREATOR.createFromParcel(data);ResultReceiver resultReceiver = ResultReceiver.CREATOR.createFromParcel(data);try {if (out != null) {shellCommand(in != null ? in.getFileDescriptor() : null,out.getFileDescriptor(),err != null ? err.getFileDescriptor() : out.getFileDescriptor(),args, shellCallback, resultReceiver);}================
// shellCommand 方法的实现public void shellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,@Nullable FileDescriptor err,@NonNull String[] args, @Nullable ShellCallback callback,@NonNull ResultReceiver resultReceiver) throws RemoteException {// 同样去调用 ams 的 onShellCommand 方法onShellCommand(in, out, err, args, callback, resultReceiver);}

// 同样去调用 ams 的 onShellCommand 方法

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

10502      @Override
10503      public void onShellCommand(FileDescriptor in, FileDescriptor out,
10504              FileDescriptor err, String[] args, ShellCallback callback,
10505              ResultReceiver resultReceiver) {// 创建 ActivityManagerShellCommand 对象去执行 exec
10506          (new ActivityManagerShellCommand(this, false)).exec(
10507                  this, in, out, err, args, callback, resultReceiver);
10508      }

// 创建 ActivityManagerShellCommand 对象去执行 exec

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java

// 继承了 ShellCommand  方法
124  final class ActivityManagerShellCommand extends ShellCommand {
125      public static final String NO_CLASS_ERROR_CODE = "Error type 3";

/frameworks/base/core/java/android/os/ShellCommand.java

// ShellCommand 又继承了 BasicShellCommandHandler 
36  public abstract class ShellCommand extends BasicShellCommandHandler {
37      private ShellCallback mShellCallback;
38      private ResultReceiver mResultReceiver;
39  // 执行 exec 方法
40      public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
41              String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
42          mShellCallback = callback;
43          mResultReceiver = resultReceiver;// 调用父类BasicShellCommandHandler 的 exec 方法
44          final int result = super.exec(target, in, out, err, args);
45  // 发送结果给cmd 进程
46          if (mResultReceiver != null) {
47              mResultReceiver.send(result, null);
48          }
49  
50          return result;
51      }

// 调用父类BasicShellCommandHandler 的 exec 方法

 /frameworks/base/core/java/android/os/BasicShellCommandHandler.java

75      public int exec(Binder target, FileDescriptor in, FileDescriptor out, FileDescriptor err,
76              String[] args) {
77          String cmd;
78          int start;
79          if (args != null && args.length > 0) {// 获取adb shell cmd activity 后面的第一个参数 
80              cmd = args[0];
81              start = 1;
82          } else {
83              cmd = null;
84              start = 0;
85          }
86          init(target, in, out, err, args, start);
87          mCmd = cmd;
88  
89          if (DEBUG) {
90              RuntimeException here = new RuntimeException("here");
91              here.fillInStackTrace();
92              Log.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
93              Log.d(TAG, "Calling uid=" + Binder.getCallingUid()
94                      + " pid=" + Binder.getCallingPid());
95          }
96          int res = -1;
97          try {// 回调子类ActivityManagerShellCommand.java的方法 onCommand
98              res = onCommand(mCmd);
99              if (DEBUG) Log.d(TAG, "Executed command " + mCmd + " on " + mTarget);

// 回调子类ActivityManagerShellCommand.java的方法 onCommand

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java

    @Overridepublic int onCommand(String cmd) {if (cmd == null) {return handleDefaultCommands(cmd);}final PrintWriter pw = getOutPrintWriter();try {switch (cmd) {// 启动activity 命令case "start":case "start-activity":return runStartActivity(pw);// 启动service 的命令case "startservice":case "start-service":return runStartService(pw, false);//如果都不满足上述情形,则执行 default:return handleDefaultCommands(cmd);}

/frameworks/base/core/java/android/os/BasicShellCommandHandler.java

// 调用到 父类的方法
289      public int handleDefaultCommands(String cmd) {
290          if (cmd == null || "help".equals(cmd) || "-h".equals(cmd)) {
291              onHelp();

又调用到子类的方法

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerShellCommand.java

    @Overridepublic void onHelp() {PrintWriter pw = getOutPrintWriter();
// mDumping 为falsedumpHelp(pw, mDumping);}============static void dumpHelp(PrintWriter pw, boolean dumping) {if (dumping) {
。。。。。} else {pw.println("Activity manager (activity) commands:");pw.println("  help");pw.println("      Print this help text.");pw.println("  start-activity [-D] [-N] [-W] [-P <FILE>] [--start-profiler <FILE>]");

总结:

adb shell cmd 命令:

1. 对应命令会调用到service 的  onCommand

2. 帮助命令会调用到  onHelp

adb shell dumpsys 命令:

对应命令调用到 service 的 dump方法

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

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

相关文章

Bootstrap在弹框Povoper中显示图片

项目开发需要实现这个效果&#xff0c;当鼠标划过这个按钮的时候&#xff0c;会显示出指定的图片出来 HTML代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"…

Crow:黑魔法 调用Rule绑定的handler_

Crow:基于req.rul查找路由Rule对象及匹配参数-CSDN博客 介绍了当接收到http请求后如何查找到Rule对象 Connection::do_read -> HTTPParser::feed -> 而feed实际上会依此调用定义于http_parser_settings中的所有函数,并完成http信息的解析 const static http_parser_se…

高德地图+Vue中使用出现的问题

最近在做高德地图的逆向地理编码API出现了问题 按着官方的方式写代码运行时出现了问题&#xff0c;随后问了技术人员。 添加之后成功运行

“分割“安卓用户,对标iOS;鸿蒙将携手程序员的春天

近期关于“华为于明年推出不兼容安卓的鸿蒙版本”的消息传出&#xff0c;引起了业界的热议关注。自从2019年8月&#xff0c;美国制裁下&#xff0c;华为不再能够获得谷歌安卓操作系统相关付费服务&#xff0c;如此情况下&#xff0c;华为“备胎”鸿蒙操作系统一夜转正。华为鸿蒙…

IDEA新建jdk8 spring boot项目

今天新建spring boot项目发现JDK版本最低可选17。 但是目前用的最多的还是JDK8啊。 解决办法 Server URL中设置&#xff1a; https://start.aliyun.com/设置完成后&#xff0c;又可以愉快的用jdk8创建项目了。 参考 https://blog.csdn.net/imbzz/article/details/13469117…

新能源汽车生产污废水需要哪些工艺及设备

新能源汽车的快速发展带来了许多环境问题&#xff0c;其中之一就是生产过程中产生的污废水。由于新能源汽车的生产过程与传统汽车有所不同&#xff0c;因此需要采用特定的工艺和设备来处理和处理这些废水。 首先&#xff0c;新能源汽车生产过程中产生的污废水主要来自洗涤和冷却…

LeetCode263. Ugly Number

文章目录 一、题目二、题解 一、题目 An ugly number is a positive integer whose prime factors are limited to 2, 3, and 5. Given an integer n, return true if n is an ugly number. Example 1: Input: n 6 Output: true Explanation: 6 2 3 Example 2: Input: …

Certbot实现 HTTPS 免费证书(Let‘s Encrypt)自动续期

Certbot实现 HTTPS 自动续期 以前阿里云支持申请一年的免费https证书&#xff0c;那每年我们手动更新证书并没什么大问题&#xff0c;但现在阿里云的免费证书仅支持3个月&#xff0c;这意味着每三个月都要要申请一下证书显得非常麻烦。 下面我们使用Certbot实现ssl证书的自动…

后端打印不了trace等级的日志?-SpringBoot日志打印-Slf4j

在调用log变量的方法来输出日志时&#xff0c;有以上5个级别对应的方法&#xff0c;从不太重要&#xff0c;到非常重要 调用不同的方法&#xff0c;就会输出不同级别的日志。 trace&#xff1a;跟踪信息debug&#xff1a;调试信息info&#xff1a;一般信息warn&#xff1a;警告…

Java基础语法面试题

注释 什么Java注释 定义&#xff1a;用于解释说明程序的文字 分类 单行注释 格式&#xff1a; // 注释文字 多行注释 格式&#xff1a; /* 注释文字 /文档注释 格式&#xff1a;/* 注释文字 */ 作用 在程序中&#xff0c;尤其是复杂的程序中&#xff0c;适当地加入注释可…

结构体概念及应用

1.结构体类型的概念 在C语言中提供了很多基本的数据类型&#xff0c;但在实际开发中&#xff0c;无法满足程序中各种复杂数据的要求。有时需要将不同类型的数据组合成一个有机的整体&#xff0c;一边引用。例如&#xff1a; numnamesexagescore001lemonF18 90 在图中列举了…

VRRP协议详解

目录 一、基础概念 1、概念 2、VRRP的基本结构 状态机 二、VRRP主备备份工作过程 1、备份工作过程 2、VRRP的负载分担工作 三、实验 一、基础概念 1、概念 VRRP能够在不改变组网的情况下&#xff0c;将多台路由器虚拟成一个虚拟路由器&#xff0c;通过配置虚拟路由器的I…

【STM32入门】4.1中断基本知识

1.中断概览 在开展红外传感器遮挡计次的实验之前&#xff0c;有必要系统性的了解“中断”的基本知识. 中断是指&#xff1a;在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转…

Oracle MongoDB

听课的时候第一次碰到&#xff0c;可以了解一下吧&#xff0c;就直接开了墨者学院的靶场 #oracle数据库 Oracle数据库注入全方位利用 - 先知社区 这篇写的真的很好 1.判断注入点 当时找了半天没找到 看样子是找到了&#xff0c;测试一下看看 id1 and 11 时没有报错 2.判断字段…

JMeter下载与安装

文章目录 前言一、安装java环境&#xff08;JDK下载与安装&#xff09;二、JMeter下载三、JMeter安装1.解压缩2.配置环境变量 四、JMeter启动&#xff08;启动成功则代表JMeter安装成功&#xff09;五、JMeter汉化&#xff08;将JMeter修改成中文&#xff09;1.方法一&#xff…

深圳锐科达IP网络广播系统

深圳锐科达IP网络广播系统 网络音频广播系统是一种基于TCP/IP网络的纯数字音频广播系统。该网络音频广播系统在物理结构上与标准IP网络完全集成。它不仅真正实现了基于TCP/IP网络的数字音频的广播、直播和点播&#xff0c;而且利用TCP/IP网络的优势&#xff0c;突破了传统模拟广…

DeepStream--调试Gstreamer

DeepStream是基于Gstreamer开发的。有时候需要在Gstreamer加日志&#xff0c;比如想在rtpjitterbuffer里加日志。 首先&#xff0c;执行gst-inspect-1.0 rtpjitterbuffer命令。 从结果中可以看到&#xff0c;rtpjitterbuffer插件的源码是gst-plugins-good&#xff0c;版本是1…

Tomcat的结构分析和请求处理原理解析

目录 Tomcat服务器&#xff1f;Tomcat结构处理请求流程Tomcat作用其他的web服务器 Tomcat服务器&#xff1f; 我们经常开口闭口“服务器”、“服务器”的&#xff0c;其实“服务器”是个很容易引发歧义的概念 其实&#xff0c;Tomcat服务器 Web服务器 Servlet/JSP容器&#…

最新Redis7主从复制(保姆级教程)

前提准备&#xff1a;三台云服务器&#xff08;吐血消费&#xff0c;点赞回血&#xff09;也可以使用虚拟机创建三台&#xff0c;但是我搞了一天也连接不上&#xff0c;要是又可以连接上的大家可以教我一下&#xff0c;也可以参考一下或者大家可以参考一下这个大佬的配置&#…

人工智能与天文:技术前沿与未来展望

人工智能与天文&#xff1a;技术前沿与未来展望 一、引言 随着科技的飞速发展&#xff0c;人工智能&#xff08;AI&#xff09;在各个领域的应用越来越广泛。在天文领域&#xff0c;AI也发挥着越来越重要的作用。本文将探讨人工智能与天文学的结合&#xff0c;以及这种结合带…