【安卓13 源码】RescueParty救援机制

news2024/11/24 14:15:16

RescueParty机制正是在这个背景下诞生的,当它注意到系统或系统核心组件陷入循环崩溃状态时,就会根据崩溃的程度执行不同的救援行动,以期望让设备恢复到正常使用的状态。

开机后会自动重启,进入Recovery界面。经查找,是由于有一个进程phone进程在系统开机的时候,不断崩溃,触发了RescueParty机制,

	Line 529269: E07D549  02-01 22:48:35.910   856   856 I am_crash: [15147,0,com.android.phone,684211789,java.lang.OutOfMemoryError,NULL,Parcel.java,-2,0]
	Line 531948: E07DDD3  02-01 22:49:10.425   856  3019 I am_crash: [25787,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 533159: E07E272  02-01 22:49:13.280   856  2861 I am_crash: [25881,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 534403: E07E722  02-01 22:49:16.198   856  5586 I am_crash: [25969,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 535604: E07EBA7  02-01 22:49:18.967   856  1642 I am_crash: [26055,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 536730: E07EFF1  02-01 22:49:21.790   856  2295 I am_crash: [26149,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 537881: E07F454  02-01 22:49:24.643   856  3012 I am_crash: [26231,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 539010: E07F8A1  02-01 22:49:27.549   856  4223 I am_crash: [26315,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 540549: E07FE88  02-01 22:49:30.200   856  1642 I am_crash: [26396,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 541649: E0802B8  02-01 22:49:33.040   856  1718 I am_crash: [26491,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 542844: E080747  02-01 22:49:35.740   856  2344 I am_crash: [26576,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 544058: E080BD9  02-01 22:49:38.501   856  2889 I am_crash: [26677,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 545226: E08104C  02-01 22:49:41.248   856   933 I am_crash: [26814,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 546330: E081480  02-01 22:49:44.017   856  6041 I am_crash: [26960,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 547501: E0818D7  02-01 22:49:46.825   856  3986 I am_crash: [27047,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 548666: E081D28  02-01 22:49:49.657   856  1718 I am_crash: [27127,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 549841: E082183  02-01 22:49:52.626   856  2861 I am_crash: [27210,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 550922: E082590  02-01 22:49:55.352   856  6035 I am_crash: [27291,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 552014: E0829B8  02-01 22:49:58.081   856  2920 I am_crash: [27401,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 553516: E082F7A  02-01 22:50:00.633   856  1719 I am_crash: [27513,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 554659: E0833D5  02-01 22:50:03.484   856  4600 I am_crash: [27600,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 555805: E083833  02-01 22:50:06.021   856  5825 I am_crash: [27684,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 556963: E083C8D  02-01 22:50:08.703   856  1718 I am_crash: [27763,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
	Line 558157: E0840FB  02-01 22:50:11.451   856  3017 I am_crash: [27846,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]

代码基于安卓 13

0.crash 触发的流程

M086FC7  02-01 22:50:42.202 28772 28772 D AndroidRuntime: Shutting down VM
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: FATAL EXCEPTION: main
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: Process: com.android.phone, PID: 28772
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: java.lang.RuntimeException: Error receiving broadcast Intent { act=android.intent.action.SIM_STATE_CHANGED flg=0x15000010 (has extras) } in com.android.internal.telephony.SimStateTracker$1@c995e01
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0(LoadedApk.java:1819)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at android.app.LoadedApk$ReceiverDispatcher$Args.$r8$lambda$gDuJqgxY6Zb-ifyeubKeivTLAwk(Unknown Source:0)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at android.app.LoadedApk$ReceiverDispatcher$Args$$ExternalSyntheticLambda0.run(Unknown Source:2)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at android.os.Handler.handleCallback(Handler.java:958)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:99)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at android.os.Looper.loopOnce(Looper.java:205)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at android.os.Looper.loop(Looper.java:294)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:8492)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:640)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1026)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: Caused by: java.util.NoSuchElementException: No value present
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at java.util.Optional.get(Optional.java:144)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at com.android.internal.telephony.SimStateTracker.isUiccEnable(SimStateTracker.java:318)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at com.android.internal.telephony.SimStateTracker.onSimStateChanged(SimStateTracker.java:252)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at com.android.internal.telephony.SimStateTracker.-$$Nest$monSimStateChanged(Unknown Source:0)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at com.android.internal.telephony.SimStateTracker$1.onReceive(SimStateTracker.java:190)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	at android.app.LoadedApk$ReceiverDispatcher$Args.lambda$getRunnable$0(LoadedApk.java:1811)
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: 	... 10 more
E086FC9  02-01 22:50:42.206   856  3017 I am_crash: [28772,0,com.android.phone,684211789,java.util.NoSuchElementException,No value present,Optional.java,144,0]
S086FCA  02-01 22:50:42.211   856  3017 D ActivityManager: crashInfo.exceptionClassName = java.util.NoSuchElementException , crashInfo.exceptionMessage = No value present, crashInfo.throwFileName = Optional.java,crashInfo.throwLineNumber = 144

 AndroidRuntime: Shutting down VM 的打印是在进程退出的时候打印的。

在app 进程启动的时候,会走 AndroidRuntime.start 方法,然后死循环执行loop handler 方法。在进程退出的时候,去 free

/frameworks/base/core/jni/AndroidRuntime.cpp

1193  void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
1194  {
1195      ALOGD(">>>>>> START %s uid %d <<<<<<\n",
1196              className != NULL ? className : "(unknown)", getuid());
1197  
1198      static const String8 startSystemServer("start-system-server");
1199      // Whether this is the primary zygote, meaning the zygote which will fork system server.
1200      bool primary_zygote = false;

。。。。。。
1290      char* slashClassName = toSlashClassName(className != NULL ? className : "");
1291      jclass startClass = env->FindClass(slashClassName);
1292      if (startClass == NULL) {
1293          ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);
1294          /* keep going */
1295      } else {
1296          jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
1297              "([Ljava/lang/String;)V");
1298          if (startMeth == NULL) {
1299              ALOGE("JavaVM unable to find main() in '%s'\n", className);
1300              /* keep going */
1301          } else {

// 执行main 方法
1302              env->CallStaticVoidMethod(startClass, startMeth, strArray);
1303  
1304  #if 0
1305              if (env->ExceptionCheck())
1306                  threadExitUncaughtException(env);
1307  #endif
1308          }
1309      }
1310      free(slashClassName);
1311  

// 打印下列的log
1312      ALOGD("Shutting down VM\n");

在执行main 方法的时候,会去监听 app crash 的报错的信息

/frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

339      @UnsupportedAppUsage
340      public static final void main(String[] argv) {
341          preForkInit();
342          if (argv.length == 2 && argv[1].equals("application")) {
343              if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application");
344              redirectLogStreams();
345          } else {
346              if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting tool");
347          }
348  
349          commonInit();

-------------
221      @UnsupportedAppUsage
222      protected static final void commonInit() {
223          if (DEBUG) Slog.d(TAG, "Entered RuntimeInit!");
224  
225          /*
226           * set handlers; these apply to all threads in the VM. Apps can replace
227           * the default handler, but not the pre handler.
228           */

// 创建  LoggingHandler 对象
229          LoggingHandler loggingHandler = new LoggingHandler();
230          RuntimeHooks.setUncaughtExceptionPreHandler(loggingHandler);
// 设置捕获异常的handler类为 内部类 KillApplicationHandler:setDefaultUncaughtExceptionHandler
231          Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
232  
233          /*

// 设置捕获异常的handler类为 内部类 KillApplicationHandler:setDefaultUncaughtExceptionHandler

/libcore/ojluni/src/main/java/java/lang/Thread.java

2193      public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
2194          // Android-removed: SecurityManager stubbed out on Android.
2195          /*
2196          SecurityManager sm = System.getSecurityManager();
2197          if (sm != null) {
2198              sm.checkPermission(
2199                  new RuntimePermission("setDefaultUncaughtExceptionHandler")
2200                      );
2201          }
2202          */
2203  
2204           defaultUncaughtExceptionHandler = eh;
2205       }

===========
2215      public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
2216          return defaultUncaughtExceptionHandler;
2217      }

==========
// 回调 uncaughtException 在如下,调用 dispatchUncaughtException 方法

2293      // Android-changed: Make dispatchUncaughtException() public, for use by tests.
2294      public final void dispatchUncaughtException(Throwable e) {
2295          // BEGIN Android-added: uncaughtExceptionPreHandler for use by platform.
2296          Thread.UncaughtExceptionHandler initialUeh =
2297                  Thread.getUncaughtExceptionPreHandler();
2298          if (initialUeh != null) {
2299              try {
2300                  initialUeh.uncaughtException(this, e);
2301              } catch (RuntimeException | Error ignored) {
2302                  // Throwables thrown by the initial handler are ignored
2303              }
2304          }
2305          // END Android-added: uncaughtExceptionPreHandler for use by platform.
2306          getUncaughtExceptionHandler().uncaughtException(this, e);
2307      }
2308  

在 art 虚拟机 进程退出的时候,会去抛出执行错误的异常

/art/runtime/thread.cc

// 进程回收的时候调用
2500  void Thread::Destroy() {
2501    Thread* self = this;
2502    DCHECK_EQ(self, Thread::Current());
2503  
2504    if (tlsPtr_.jni_env != nullptr) {
2505      {
2506        ScopedObjectAccess soa(self);
2507        MonitorExitVisitor visitor(self);
2508        // On thread detach, all monitors entered with JNI MonitorEnter are automatically exited.
2509        tlsPtr_.jni_env->monitors_.VisitRoots(&visitor, RootInfo(kRootVMInternal));
2510      }
2511      // Release locally held global references which releasing may require the mutator lock.
2512      if (tlsPtr_.jpeer != nullptr) {
2513        // If pthread_create fails we don't have a jni env here.
2514        tlsPtr_.jni_env->DeleteGlobalRef(tlsPtr_.jpeer);
2515        tlsPtr_.jpeer = nullptr;
2516      }
2517      if (tlsPtr_.class_loader_override != nullptr) {
2518        tlsPtr_.jni_env->DeleteGlobalRef(tlsPtr_.class_loader_override);
2519        tlsPtr_.class_loader_override = nullptr;
2520      }
2521    }
2522  
2523    if (tlsPtr_.opeer != nullptr) {
2524      ScopedObjectAccess soa(self);
2525      // We may need to call user-supplied managed code, do this before final clean-up.

// 执行下列方法:HandleUncaughtExceptions
2526      HandleUncaughtExceptions(soa);

// 执行下列方法:HandleUncaughtExceptions

2612  void Thread::HandleUncaughtExceptions(ScopedObjectAccessAlreadyRunnable& soa) {
2613    if (!IsExceptionPending()) {
2614      return;
2615    }
2616    ScopedLocalRef<jobject> peer(tlsPtr_.jni_env, soa.AddLocalReference<jobject>(tlsPtr_.opeer));
2617    ScopedThreadStateChange tsc(this, ThreadState::kNative);
2618  
2619    // Get and clear the exception.

// 获取到抛出异常的信息
2620    ScopedLocalRef<jthrowable> exception(tlsPtr_.jni_env, tlsPtr_.jni_env->ExceptionOccurred());
2621    tlsPtr_.jni_env->ExceptionClear();
2622  
2623    // Call the Thread instance's dispatchUncaughtException(Throwable)
2624    tlsPtr_.jni_env->CallVoidMethod(peer.get(),

// 调用方法:java_lang_Thread_dispatchUncaughtException
2625        WellKnownClasses::java_lang_Thread_dispatchUncaughtException,
2626        exception.get());
2627  
2628    // If the dispatchUncaughtException threw, clear that exception too.
2629    tlsPtr_.jni_env->ExceptionClear();
2630  }

// 调用方法:java_lang_Thread_dispatchUncaughtException,分发异常信息

/art/runtime/well_known_classes.cc

// 调用 Thread 的 dispatchUncaughtException 方法

421    java_lang_Thread_dispatchUncaughtException = CacheMethod(env, java_lang_Thread, false, "dispatchUncaughtException", "(Ljava/lang/Throwable;)V");

综上从虚拟机调用了  Thread 的 dispatchUncaughtException 方法

回到 RuntimeInit.java

/frameworks/base/core/java/com/android/internal/os/RuntimeInit.java

135          public KillApplicationHandler(LoggingHandler loggingHandler) {
136              this.mLoggingHandler = Objects.requireNonNull(loggingHandler);
137          }
138  

// 执行 uncaughtException 方法
139          @Override
140          public void uncaughtException(Thread t, Throwable e) {
141              try {

// 1)先执行 ensureLogging 打印crash log 方法
142                  ensureLogging(t, e);
143  
144                  // Don't re-enter -- avoid infinite loops if crash-reporting crashes.
145                  if (mCrashing) return;
146                  mCrashing = true;

151                  if (ActivityThread.currentActivityThread() != null) {
152                      ActivityThread.currentActivityThread().stopProfiling();
153                  }
154  
155                  // Bring up crash dialog, wait for it to be dismissed

// 2)ams 去执行app crash 的方法:handleApplicationCrash
156                  ActivityManager.getService().handleApplicationCrash(
157                          mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
158              } catch (Throwable t2) {
159                  if (t2 instanceof DeadObjectException) {
160                      // System process is dead; ignore
161                  } else {
162                      try {
163                          Clog_e(TAG, "Error reporting crash", t2);
164                      } catch (Throwable t3) {
165                          // Even Clog_e() fails!  Oh well.
166                      }
167                  }
168              } finally {

// 3)最后杀掉自己的进程:killProcess
169                  // Try everything to make sure this process goes away.
170                  Process.killProcess(Process.myPid());
171                  System.exit(10);
172              }
173          }

// 1)先执行 ensureLogging 打印crash log 方法

192          private void ensureLogging(Thread t, Throwable e) {
193              if (!mLoggingHandler.mTriggered) {
194                  try {
// mLoggingHandler 是对象 LoggingHandler ,执行 uncaughtException 方法
195                      mLoggingHandler.uncaughtException(t, e);
196                  } catch (Throwable loggingThrowable) {
197                      // Ignored.
198                  }
199              }
200          }

============
93      private static class LoggingHandler implements Thread.UncaughtExceptionHandler {
94          public volatile boolean mTriggered = false;
95  
96          @Override
97          public void uncaughtException(Thread t, Throwable e) {
98              mTriggered = true;
99  
100              // Don't re-enter if KillApplicationHandler has already run
101              if (mCrashing) return;
102  
103              // mApplicationObject is null for non-zygote java programs (e.g. "am")
104              // There are also apps running with the system UID. We don't want the
105              // first clause in either of these two cases, only for system_server.

// 如果是系统进程的话
106              if (mApplicationObject == null && (Process.SYSTEM_UID == Process.myUid())) {
107                  Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
108              } else {

// app 进程走 logUncaught
109                  logUncaught(t.getName(), ActivityThread.currentProcessName(), Process.myPid(), e);
110              }
111          }
112      }

// app 进程走 logUncaught

// 打印线程名字,进程名,pid 进程号,e 抛出的异常
75      public static void logUncaught(String threadName, String processName, int pid, Throwable e) {
76          StringBuilder message = new StringBuilder();
77          // The "FATAL EXCEPTION" string is still used on Android even though
78          // apps can set a custom UncaughtExceptionHandler that renders uncaught
79          // exceptions non-fatal.
80          message.append("FATAL EXCEPTION: ").append(threadName).append("\n");
81          if (processName != null) {
82              message.append("Process: ").append(processName).append(", ");
83          }
84          message.append("PID: ").append(pid);
85          Clog_e(TAG, message.toString(), e);
86      }

----------
71      private static int Clog_e(String tag, String msg, Throwable tr) {
72          return Log.printlns(Log.LOG_ID_CRASH, Log.ERROR, tag, msg, tr);
73      }

// 主进程crash,进程名为:com.android.phone,进程号为:28772

C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: FATAL EXCEPTION: main
C086FC8  02-01 22:50:42.203 28772 28772 E AndroidRuntime: Process: com.android.phone, PID: 28772

// 2)ams 去执行app crash 的方法:handleApplicationCrash

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

8369      public void handleApplicationCrash(IBinder app,
8370              ApplicationErrorReport.ParcelableCrashInfo crashInfo) {
8371          ProcessRecord r = findAppProcess(app, "Crash");
8372          final String processName = app == null ? "system_server"
8373                  : (r == null ? "unknown" : r.processName);
8374  
8375          handleApplicationCrashInner("crash", r, processName, crashInfo);
8376      }

============
// 执行 handleApplicationCrashInner 方法

// 会打印下列log

S086FD5  02-01 22:50:42.220   856  3017 W ActivityManager: Process com.android.phone has crashed too many times, killing! Reason: crashed quickly
E086FD6  02-01 22:50:42.221   856  3017 I am_process_crashed_too_much: [0,com.android.phone,1001]

// 3)最后杀掉自己的进程:killProcess:发送signal 9去杀掉进程

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

585      public static final int SIGNAL_KILL = 9;

1302      public static final void killProcess(int pid) {

// 发送signal 9去杀掉进程
1303          sendSignal(pid, SIGNAL_KILL);
1304      }

1318      public static final native void sendSignal(int pid, int signal);

1. 救援的级别

    //什么也不做
    static final int LEVEL_NONE = 0;
    //主要针对非系统进程的属性设置进行重置
    static final int LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS = 1;
    //针对非系统进程属性,来自系统默认的属性重置,其他删除
    static final int LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES = 2;
    //所有进程系统默认的属性重置,其他删除
    static final int LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS = 3;
    //重启设备
    static final int LEVEL_WARM_REBOOT = 4;
    //尝试恢复出厂设置
    static final int LEVEL_FACTORY_RESET = 5;

2. 触发场景:

(1)永久性系统应用在 30 秒内崩溃 5 次以上调整一次级别。(Android 12 默认为60秒内5次)

(2)system_server 在 5 分钟内重启 5 次以上调整一次级别。(Android 12 为10分钟内5次)

当检测到上述某种情况时,救援程序会将其上报给下一救援级别、处理与该级别相关联的任务,并让设备继续运行,看看能否恢复。清除或重置内容的程度随级别而增加。最高级别会提示用户将设备恢复出厂设置。
 

(1)永久性系统应用在 60 秒内崩溃 5 次以上调整一次级别。(Android 12 默认为60秒内5次)

比如phone 进程短时间crash 多次

ResucParty机制 - 掘金

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

575      private void crashApplicationInner(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo,
576              int callingPid, int callingUid) {
577          long timeMillis = System.currentTimeMillis();
578          String shortMsg = crashInfo.exceptionClassName;
579          String longMsg = crashInfo.exceptionMessage;
580          String stackTrace = crashInfo.stackTrace;
581          if (shortMsg != null && longMsg != null) {
582              longMsg = shortMsg + ": " + longMsg;
583          } else if (shortMsg != null) {
584              longMsg = shortMsg;
585          }
586  
587          if (r != null) {
588              mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode(),
589                      PackageWatchdog.FAILURE_REASON_APP_CRASH);
590  
591              synchronized (mService) {
592                  mService.mProcessList.noteAppKill(r, (crashInfo != null
593                            && "Native crash".equals(crashInfo.exceptionClassName))
594                            ? ApplicationExitInfo.REASON_CRASH_NATIVE
595                            : ApplicationExitInfo.REASON_CRASH,
596                            ApplicationExitInfo.SUBREASON_UNKNOWN,
597                          "crash");
598              }
599          }
600  
601          final int relaunchReason = r != null

其中:r.getPackageListWithVersionCode 可以通过dump 获取到

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

141      List<VersionedPackage> getPackageListWithVersionCode() {
142          synchronized (this) {
143              int size = mPkgList.size();
144              if (size == 0) {
145                  return null;
146              }
147              List<VersionedPackage> list = new ArrayList<>();
148              for (int i = 0; i < size; i++) {
149                  list.add(new VersionedPackage(mPkgList.keyAt(i), mPkgList.valueAt(i).appVersion));
150              }
151              return list;
152          }
153      }
154  
155      void dump(PrintWriter pw, String prefix) {
156          synchronized (this) {
157              pw.print(prefix); pw.print("packageList={");
158              for (int i = 0, size = mPkgList.size(); i < size; i++) {
159                  if (i > 0) pw.print(", ");
160                  pw.print(mPkgList.keyAt(i));
161              }
162              pw.println("}");
163          }
164      }
165  }
166  

打印 packageList 输出信息是该进程所有的包名,使用命令为:adb shell dumpsys activity processes 

 packageList={com.android.providers.telephony, com.sprd.omacp, com.android.ons, com.android.stk, com.android.unisoc.telephony.server, com.spreadtrum.ims, com.unisoc.phone, com.android.phone, com.sprd.ImsConnectionManager}

调用 PackageWatchdog 的  onPackageFailure 方法

/frameworks/base/services/core/java/com/android/server/PackageWatchdog.java

391      public void onPackageFailure(List<VersionedPackage> packages,
392              @FailureReasons int failureReason) {
393          if (packages == null) {
394              Slog.w(TAG, "Could not resolve a list of failing packages");
395              return;
396          }
397          mLongTaskHandler.post(() -> {
398              synchronized (mLock) {
399                  if (mAllObservers.isEmpty()) {
400                      return;
401                  }

// fail 原因是 FAILURE_REASON_APP_CRASH,不满足下列条件,走else 逻辑
402                  boolean requiresImmediateAction = (failureReason == FAILURE_REASON_NATIVE_CRASH
403                          || failureReason == FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
404                  if (requiresImmediateAction) {
405                      handleFailureImmediately(packages, failureReason);
406                  } else {

// 走下列条件:
// 如上分析,packages ,phone 进程有多个包名。遍历所有的包名
407                      for (int pIndex = 0; pIndex < packages.size(); pIndex++) {
408                          VersionedPackage versionedPackage = packages.get(pIndex);
409                          // Observer that will receive failure for versionedPackage
410                          PackageHealthObserver currentObserverToNotify = null;
411                          int currentObserverImpact = Integer.MAX_VALUE;
412                          MonitoredPackage currentMonitoredPackage = null;
413  
414                          // Find observer with least user impact

// 遍历所有的观察者,调用了方法 registerHealthObserver
415                          for (int oIndex = 0; oIndex < mAllObservers.size(); oIndex++) {
416                              ObserverInternal observer = mAllObservers.valueAt(oIndex);
417                              PackageHealthObserver registeredObserver = observer.registeredObserver;

// 1) 调用 observer 为 ObserverInternal 的 onPackageFailureLocked 方法
418                              if (registeredObserver != null
419                                      && observer.onPackageFailureLocked(
420                                      versionedPackage.getPackageName())) {
421                                  MonitoredPackage p = observer.getMonitoredPackage(
422                                          versionedPackage.getPackageName());
423                                  int mitigationCount = 1;
424                                  if (p != null) {
// 获取到 getMitigationCountLocked 的值为0,所以 mitigationCount 的值为 1
425                                      mitigationCount = p.getMitigationCountLocked() + 1;
426                                  }

// 2)调用RescueParty.java 的方法onHealthCheckFailed
// 该方法分析的结果是返回 1 
427                                  int impact = registeredObserver.onHealthCheckFailed(
428                                          versionedPackage, failureReason, mitigationCount);
429                                  if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
430                                          && impact < currentObserverImpact) {
431                                      currentObserverToNotify = registeredObserver;
// 重新设置下 currentObserverImpact 为 1
432                                      currentObserverImpact = impact;
// 缓存当前的 MonitoredPackage
433                                      currentMonitoredPackage = p;
434                                  }
435                              }
436                          }
437  
438                          // Execute action with least user impact
439                          if (currentObserverToNotify != null) {
440                              int mitigationCount = 1;

// noteMitigationCallLocked 通知增加 mitigationCount 的值为 1
441                              if (currentMonitoredPackage != null) {
442                                  currentMonitoredPackage.noteMitigationCallLocked();
443                                  mitigationCount =
444                                          currentMonitoredPackage.getMitigationCountLocked();
445                              }
// mitigationCount  的值为 1
// 3)调用RescueParty.java 的方法execute,mitigationCount 的值为 1
446                              currentObserverToNotify.execute(versionedPackage,
447                                      failureReason, mitigationCount);
448                          }
449                      }
450                  }
451              }
452          });
453      }

============
// registerHealthObserver 方法,在 
279      public void registerHealthObserver(PackageHealthObserver observer) {
280          synchronized (mLock) {
281              ObserverInternal internalObserver = mAllObservers.get(observer.getName());
282              if (internalObserver != null) {
283                  internalObserver.registeredObserver = observer;
284              } else {

// 在 PackageWatchdog.getInstance(context).registerHealthObserver(
126                  RescuePartyObserver.getInstance(context));
// PackageWatchdog 设置 registeredObserver  为 RescuePartyObserver
285                  internalObserver = new ObserverInternal(observer.getName(), new ArrayList<>());
286                  internalObserver.registeredObserver = observer;
287                  mAllObservers.put(observer.getName(), internalObserver);
288                  syncState("added new observer");
289              }
290          }
291      }

// 1) 调用 observer 为 ObserverInternal 的 onPackageFailureLocked 方法

1236          @GuardedBy("mLock")
1237          public boolean onPackageFailureLocked(String packageName) {

// RescueParty.java 返回的 isPersistent 是为true
1238              if (getMonitoredPackage(packageName) == null && registeredObserver.isPersistent()
// mayObservePackage方法会判断该app 是否是常驻进程
1239                      && registeredObserver.mayObservePackage(packageName)) {

// 接下来调用 PackageWatchdog.newMonitoredPackage
1240                  putMonitoredPackage(sPackageWatchdog.newMonitoredPackage(
1241                          packageName, DEFAULT_OBSERVING_DURATION_MS, false));
1242              }
1243              MonitoredPackage p = getMonitoredPackage(packageName);
1244              if (p != null) {
// 然后调用 MonitoredPackage 的 onFailureLocked 方法
1245                  return p.onFailureLocked();
1246              }
1247              return false;
1248          }
1249  

// mayObservePackage方法会判断该app 是否是常驻进程

/frameworks/base/services/core/java/com/android/server/RescueParty.java

652          @Override
653          public boolean mayObservePackage(String packageName) {
654              PackageManager pm = mContext.getPackageManager();
655              try {
656                  // A package is a module if this is non-null
657                  if (pm.getModuleInfo(packageName, 0) != null) {
658                      return true;
659                  }
660              } catch (PackageManager.NameNotFoundException ignore) {
661              }
662  

// 判断是常驻进程才会返回为 true
663              return isPersistentSystemApp(packageName);
664          }

============
702          private boolean isPersistentSystemApp(@NonNull String packageName) {
703              PackageManager pm = mContext.getPackageManager();
704              try {
705                  ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
706                  return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
707              } catch (PackageManager.NameNotFoundException e) {
708                  return false;
709              }
710          }

// 接下来调用 PackageWatchdog.newMonitoredPackage

// 观察的时间为 2 分钟, 为 durationMs
122      static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2);

1360      MonitoredPackage newMonitoredPackage(
1361              String name, long durationMs, boolean hasPassedHealthCheck) {
1362          return newMonitoredPackage(name, durationMs, Long.MAX_VALUE, hasPassedHealthCheck,
1363                  new LongArrayQueue());
1364      }
1365  
1366      MonitoredPackage newMonitoredPackage(String name, long durationMs, long healthCheckDurationMs,
1367              boolean hasPassedHealthCheck, LongArrayQueue mitigationCalls) {

// 创建了 MonitoredPackage 对象
1368          return new MonitoredPackage(name, durationMs, healthCheckDurationMs,
1369                  hasPassedHealthCheck, mitigationCalls);
1370      }

==========
// MonitoredPackage 构造函数
1421          MonitoredPackage(String packageName, long durationMs,
1422                  long healthCheckDurationMs, boolean hasPassedHealthCheck,
1423                  LongArrayQueue mitigationCalls) {
1424              mPackageName = packageName;
1425              mDurationMs = durationMs;
1426              mHealthCheckDurationMs = healthCheckDurationMs;
1427              mHasPassedHealthCheck = hasPassedHealthCheck;
1428              mMitigationCalls = mitigationCalls;
1429              updateHealthCheckStateLocked();
1430          }

// 然后调用 MonitoredPackage 的 onFailureLocked 方法

1450          @GuardedBy("mLock")
1451          public boolean onFailureLocked() {
1452              // Sliding window algorithm: find out if there exists a window containing failures >=
1453              // mTriggerFailureCount.

// 首先获取当前的时间
1454              final long now = mSystemClock.uptimeMillis();
/ 首先将当前的时间保存到 mFailureHistory
1455              mFailureHistory.addLast(now);
// mTriggerFailureDurationMs  的值是为 1 分钟的
// 如果下一次产生crash 的时间大于 1 分钟,则移除
1456              while (now - mFailureHistory.peekFirst() > mTriggerFailureDurationMs) {
1457                  // Prune values falling out of the window
1458                  mFailureHistory.removeFirst();
1459              }

// 产生fail 的次数为 5 次。
// 如果产生crash 的次数超过 5 次的话,则设置failed 为 true,返回值也是为true 的
1460              boolean failed = mFailureHistory.size() >= mTriggerFailureCount;
1461              if (failed) {
1462                  mFailureHistory.clear();
1463              }
1464              return failed;
1465          }

===========
197      private int mTriggerFailureDurationMs = DEFAULT_TRIGGER_FAILURE_DURATION_MS;
116      static final int DEFAULT_TRIGGER_FAILURE_DURATION_MS =
117              (int) TimeUnit.MINUTES.toMillis(1);

------
198      @GuardedBy("mLock")
199      private int mTriggerFailureCount = DEFAULT_TRIGGER_FAILURE_COUNT;
120      static final int DEFAULT_TRIGGER_FAILURE_COUNT = 5;

418                              if (registeredObserver != null
419                                      && observer.onPackageFailureLocked(
420                                      versionedPackage.getPackageName())) {

综上,上述代码判断条件为 true,需要满足  该常驻进程1 分钟内 crash  5 次以上。

// 2)调用RescueParty.java 的方法onHealthCheckFailed

/frameworks/base/services/core/java/com/android/server/RescueParty.java

617          @Override
618          public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
619                  @FailureReasons int failureReason, int mitigationCount) {

// isDisabled 有一些配置看是否设置进入到recovery
// failureReason  的值为 FAILURE_REASON_APP_CRASH
620              if (!isDisabled() && (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
621                      || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING)) {
// mayPerformFactoryReset 返回的值为 true
622                  return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount,
623                          mayPerformFactoryReset(failedPackage)));
624              } else {
625                  return PackageHealthObserverImpact.USER_IMPACT_NONE;
626              }
627          }

=============
// 获取到救援模式的级别,当前的级别是为 1 的,所以进入到 LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS
346      private static int getRescueLevel(int mitigationCount, boolean mayPerformFactoryReset) {
347          if (mitigationCount == 1) {
348              return LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
349          } else if (mitigationCount == 2) {
350              return LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES;
351          } else if (mitigationCount == 3) {
352              return LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS;
353          } else if (mitigationCount == 4) {
354              return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_WARM_REBOOT);
355          } else if (mitigationCount >= 5) {
356              return Math.min(getMaxRescueLevel(mayPerformFactoryReset), LEVEL_FACTORY_RESET);
357          } else {
358              Slog.w(TAG, "Expected positive mitigation count, was " + mitigationCount);
359              return LEVEL_NONE;
360          }
361      }

============
// 接着执行mapRescueLevelToUserImpact
// 返回的值为 int USER_IMPACT_LOW = 1;

477      private static int mapRescueLevelToUserImpact(int rescueLevel) {
478          switch(rescueLevel) {
479              case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
480              case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
481                  return PackageHealthObserverImpact.USER_IMPACT_LOW;
482              case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
483              case LEVEL_WARM_REBOOT:
484              case LEVEL_FACTORY_RESET:
485                  return PackageHealthObserverImpact.USER_IMPACT_HIGH;
486              default:
487                  return PackageHealthObserverImpact.USER_IMPACT_NONE;
488          }
489      }

// 3)调用RescueParty.java 的方法execute,mitigationCount 的值为 1

/frameworks/base/services/core/java/com/android/server/RescueParty.java

629          @Override
630          public boolean execute(@Nullable VersionedPackage failedPackage,
631                  @FailureReasons int failureReason, int mitigationCount) {
632              if (isDisabled()) {
633                  return false;
634              }
635              if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
636                      || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
// 获取到的level 为 LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS;
637                  final int level = getRescueLevel(mitigationCount,
638                          mayPerformFactoryReset(failedPackage));
639                  executeRescueLevel(mContext,
640                          failedPackage == null ? null : failedPackage.getPackageName(), level);
641                  return true;
642              } else {
643                  return false;
644              }
645          }

===========
363      private static void executeRescueLevel(Context context, @Nullable String failedPackage,
364              int level) {

// 会打印下列的lg
365          Slog.w(TAG, "Attempting rescue level " + levelToString(level));
366          try {
// 执行下列方法 executeRescueLevelInternal:level为 LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS
367              executeRescueLevelInternal(context, level, failedPackage);
// 会打印下列的 event log
368              EventLogTags.writeRescueSuccess(level);
369              String successMsg = "Finished rescue level " + levelToString(level);
370              if (!TextUtils.isEmpty(failedPackage)) {
371                  successMsg += " for package " + failedPackage;
372              }
373              logCriticalInfo(Log.DEBUG, successMsg);
374          } catch (Throwable t) {
375              logRescueException(level, failedPackage, t);
376          }
377      }

// 执行下列方法 executeRescueLevelInternal:level为 LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS

379      private static void executeRescueLevelInternal(Context context, int level, @Nullable
380              String failedPackage) throws Exception {
381          FrameworkStatsLog.write(FrameworkStatsLog.RESCUE_PARTY_RESET_REPORTED, level);
382          // Try our best to reset all settings possible, and once finished
383          // rethrow any exception that we encountered
384          Exception res = null;
385          Runnable runnable;
386          Thread thread;
387          switch (level) {
388              case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
389                  try {
390                      resetAllSettingsIfNecessary(context, Settings.RESET_MODE_UNTRUSTED_DEFAULTS,
391                              level);
392                  } catch (Exception e) {
393                      res = e;
394                  }
395                  try {
396                      resetDeviceConfig(context, /*isScoped=*/true, failedPackage);
397                  } catch (Exception e) {
398                      res = e;
399                  }
400                  break;

---------
427              case LEVEL_WARM_REBOOT:
428                  // Request the reboot from a separate thread to avoid deadlock on PackageWatchdog
429                  // when device shutting down.
430                  SystemProperties.set(PROP_ATTEMPTING_REBOOT, "true");
431                  runnable = () -> {
432                      try {

// 如果是 LEVEL_WARM_REBOOT,则会去调用pm 的重启:reboot
433                          PowerManager pm = context.getSystemService(PowerManager.class);
434                          if (pm != null) {
435                              pm.reboot(TAG);
436                          }
437                      } catch (Throwable t) {
438                          logRescueException(level, failedPackage, t);
439                      }
440                  };
441                  thread = new Thread(runnable);
442                  thread.start();
443                  break;

(2)system_server 在 5 分钟内重启 5 次以上调整一次级别。(Android 12 为10分钟内5次)

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

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

相关文章

软考中级--数据库系统工程师备考建议和考试注意事项

相关资料&#xff1a;《数据库系统工程师》15-21年真题、考试说明 今日软考出分&#xff0c;顺利通过。 备注&#xff1a;本人是计算机专业学生&#xff0c;之前系统学习过408和数据库理论&#xff0c;以下仅为本人的视角&#xff0c;仅供参考。 1. 备考规划 1.1 备考资料 《…

Javaweb配置tomcat

Tomcat 9版本链接 链接&#xff1a;https://pan.baidu.com/s/1u-eDur5KlqlXM_IM50Ahtg?pwd1njm 提取码&#xff1a;1njm 1、打开idea&#xff0c;创建maven项目 2023版IDEA 2、 目录结构 ps: 如果结果不完整,选中main右键 新建对应的文件夹 3、 web项目设置Tomcat(部署项目…

SelfReg-UNet:解决UNet语义损失,增强特征一致性与减少冗余的优化模型

SelfReg-UNet&#xff1a;解决UNet语义损失&#xff0c;增强特征一致性与减少冗余的优化模型 提出背景拆解类比&#xff1a;整理书架语义一致性正则化内部特征蒸馏为什么 UNet 会有语义损失&#xff1f; 提出背景 论文&#xff1a;https://arxiv.org/pdf/2406.14896 代码&…

YOLOv8关键点pose训练自己的数据集

这里写自定义目录标题 YOLOv8关键点pose训练自己的数据集一、项目代码下载二、制作自己的关键点pose数据集2.1 标注(非常重要)2.1.1 标注软件2.1.2 标注注意事项a.多类别检测框b.单类别检测框2.2 格式转换(非常重要)2.3 数据集划分三、YOLOv8-pose训练关键点数据集3.1 训练…

七天速通javaSE:第三天 程序控制结构:练习题

文章目录 前言一、基础1.计算从0~100之间奇数之和和偶数之和2. 用for循环输出0~1000之间能被5整除的数&#xff0c;每行输出三个 二、进阶1. 九九乘法表2.等边三角形 前言 本文主要讲解三种基本程序控制结构的练习题&#xff0c;以期熟练掌握顺序、选择、循环三种基本结构 一、…

Go 语言学习笔记之通道 Channel

Go 语言学习笔记之通道 Channel 大家好&#xff0c;我是码农先森。 概念 Go 语言中的通道&#xff08;channel&#xff09;是用来在 Go 协程之间传递数据的一种通信机制。 通道可以避免多个协程直接共享内存&#xff0c;避免数据竞争和锁的使用&#xff0c;从而简化了并发程…

前端开发流程与技术选型

目录 一、简介 二、前端职责 三、开发步骤 四、技术选型 五、页面展示 一、简介 做一个网站时&#xff0c;能看到的一切都是前端程序员的工作&#xff0c;负责网页或者app的结构、样式、用户操作网站时的事件逻辑&#xff08;比如点击一个按钮&#xff09;。 二、前端职…

鸿蒙开发系统基础能力:【@ohos.inputMethodEngine (输入法服务)】

输入法服务 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import inputMethodEngine from ohos.inputMethodEngine;inputMethodEngine 常量值。 系统能力&#xff1a;以下各项对应…

TCP: 传输控制协议

TCP: 传输控制协议 TCP的服务TCP 的首部小结 本系列文章旨在巩固网络编程理论知识&#xff0c;后续将结合实际开展深入理解的文章。 TCP的服务 T C P和U D P都使用相同的网络层&#xff08;I P&#xff09;&#xff0c;T C P却向应用层提供与U D P完全不同的服务。 T C P提供一…

GPOPS-II教程(2): 可复用火箭再入大气层最优轨迹规划问题

问题描述 考虑一类可复用火箭再入大气层最优轨迹规划问题&#xff0c;其动力学方程为 { r ˙ v sin ⁡ γ , θ ˙ v cos ⁡ γ sin ⁡ ψ r cos ⁡ ϕ , ϕ ˙ v cos ⁡ γ cos ⁡ ψ r , v ˙ − F d m − F g sin ⁡ γ , γ ˙ F l cos ⁡ σ m v − ( F g v − v r …

malloc和new的本质区别

目录 一、结论 二、示例 1.实现类T 2.用malloc分配类T的内存空间 3.用new分配类T的内存空间 一、结论 malloc 和 new 都是用于在运行时动态分配内存的机制。但它们之间存在一些本质的区别&#xff0c;主要是在使用方面&#xff0c;现在我们直接说结论&#xff0c;然后在通过…

日光模拟器对显示器光干涉影响

太阳光模拟器应用领域 抬头显示器TFT日光照射仿真太阳光模拟器LED显示器阳光耐候老化测试仪器中的光照实验抬头显示器TFT日光照射仿真太阳光模拟器全光谱阳光太阳辐射环境模拟系统中的光LCD和OLED显示器强光试验太阳光模拟器日光模拟器的光谱匹配度测量方法新型LED太阳模拟器光…

Android音频系统

最近在做UAC的项目&#xff0c;大概就是接收内核UAC的事件&#xff0c;也就是声音相关事件。然后就是pcm_read和AudioTrackr->write之间互传。感觉略微有点奇怪&#xff0c;所以简单总结一下。 1 UAC的简要流程 open_netlink_socket 打开内核窗口&#xff0c;类似于ioctl。…

Verilog的逻辑系统及数据类型(一):四值逻辑系统

目录 1. Verilog采用的四值逻辑系统2.主要数据类型2.1 net&#xff08;线网&#xff09;2.2 寄存器类 &#xff08;register)2.3 Verilog中net和register声明语法2.3.1 net声明2.3.2 寄存器声明 2.4 选择正确的数据类型2.5 选择数据类型时常犯的错误2.5.1 信号类型确定方法总结…

使用 Spring Boot 3.x 与图形学技术,添加电子印章防伪特征

使用 Spring Boot 3.x 与图形学技术,添加电子印章防伪特征 在电子办公和无纸化办公日益普及的今天,电子印章的使用越来越广泛。然而,如何确保电子印章的安全性和防伪能力成为了一个亟待解决的问题。本文将通过 Spring Boot 3.x 和图形学技术,深入探讨如何为电子印章添加防…

速卖通自养号测评:安全高效的推广手段

在速卖通平台上&#xff0c;卖家们常常寻求各种方法来提升商品的曝光、转化率和店铺权重。其中&#xff0c;自养号测评作为一种低成本、高回报的推广方式&#xff0c;备受关注。然而&#xff0c;若操作不当&#xff0c;也可能带来风险。以下是如何安全有效地进行自养号测评的指…

label studio数据标注平台的自动化标注使用

&#xff08;作者&#xff1a;陈玓玏&#xff09; 开源项目&#xff0c;欢迎star哦&#xff0c;https://github.com/tencentmusic/cube-studio 做图文音项目过程中&#xff0c;我们通常会需要进行数据标注。label studio是一个比较好上手的标注平台&#xff0c;可以直接搜…

MAB规范(3):Chapter6 Glossary 术语表

第6章 - 术语表 此章不做过多的批注&#xff0c;都是些简单的术语解释。

【算法学习】判断点在多边形内外的算法以及确定内外两点连线与边界的交点

1.前言&#xff1a; 在GIS开发中&#xff0c;经常会遇到确定一个坐标点是否在一块区域的内部这一问题。 如果这个问题不是一个单纯的数学问题&#xff0c;例如&#xff1a;在判断DEM、二维图像像素点、3D点云点等含有自身特征信息的这些点是否在一个区域范围内部的时候&#x…

Java三层框架的解析

引言&#xff1a;欢迎各位点击收看本篇博客&#xff0c;在历经很多的艰辛&#xff0c;我也是成功由小白浅浅进入了入门行列&#xff0c;也是收货到很多的知识&#xff0c;每次看黑马的JavaWeb课程视频&#xff0c;才使一个小菜鸡见识到了Java前后端是如何进行交互访问的&#x…