Android Framework AMS(17)APP 异常Crash处理流程解读

news2024/11/15 23:17:50

该系列文章总纲链接:专题总纲目录 Android Framework 总纲


本章关键点总结 & 说明:

说明:本章节主要解读APP Crash处理。关注思维导图中左上侧部分即可。

本章节主要是对Android的APP Crash处理有一个基本的了解。从进程启动到UncaughtHandler处理方法的注册到UncaughtHandler方法异常处理、AMS的binderDied讣告流程分析。以便于我们更好地理解APP 异常处理的闭环流程。

1 从进程启动到UncaughtHandler处理方法的注册

我们需要对开机启动一个进程有一个了解,相关参考文章如下:

android 开机启动流程分析(11)Zygote启动分析

android 开机启动流程分析(13)Zygote的分裂

基于以上文章的内容解读,在Zygote分裂时,最终会执行到runOnce方法,关键代码如下:

//ZygoteConnection
    boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
        // ...
        //fork进程操作
        pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
                    parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
                    parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,
                    parsedArgs.appDataDir);
        //...
        try {
            if (pid == 0) {
                // 在子进程中执行
                IoUtils.closeQuietly(serverPipeFd); // 关闭服务器端文件描述符
                serverPipeFd = null; // 清除文件描述符引用
                handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr); // 处理子进程

                // 这里不应该到达,因为子进程应该要么抛出ZygoteInit.MethodAndArgsCaller异常,要么执行exec()。
                return true;
            } else {
                // 在父进程中执行...pid小于0表示失败
                IoUtils.closeQuietly(childPipeFd); // 关闭客户端文件描述符
                childPipeFd = null; // 清除文件描述符引用
                return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs); // 处理父进程
            }
        } finally {
            IoUtils.closeQuietly(childPipeFd); // 最终关闭客户端文件描述符
            IoUtils.closeQuietly(serverPipeFd); // 最终关闭服务器端文件描述符
        }
    }

在fork进程后,fokr出来的子进程会调用handleChildProc方法,我们主要关注该方法,代码实现如下:

//ZygoteConnection
    private void handleChildProc(Arguments parsedArgs,
        FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
        throws ZygoteInit.MethodAndArgsCaller {

        // 关闭socket连接
        closeSocket();
        // 关闭Zygote服务器的socket
        ZygoteInit.closeServerSocket();

        // 如果提供了文件描述符数组
        if (descriptors != null) {
            try {
                // 重定向标准输入、输出和错误流
                ZygoteInit.reopenStdio(descriptors[0], descriptors[1], descriptors[2]);

                // 关闭文件描述符数组中的所有文件描述符
                for (FileDescriptor fd : descriptors) {
                    IoUtils.closeQuietly(fd);
                }
                // 设置新的标准错误流
                newStderr = System.err;
            } catch (IOException ex) {
                // 记录重定向标准IO时的错误
                Log.e(TAG, "Error reopening stdio", ex);
            }
        }

        // 如果提供了nice名称(进程名),设置进程名
        if (parsedArgs.niceName != null) {
            Process.setArgV0(parsedArgs.niceName);
        }

        // 如果需要运行时初始化
        if (parsedArgs.runtimeInit) {
            // 如果提供了invokeWith(启动命令),使用WrapperInit启动应用程序
            if (parsedArgs.invokeWith != null) {
                WrapperInit.execApplication(parsedArgs.invokeWith,
                        parsedArgs.niceName, parsedArgs.targetSdkVersion,
                        pipeFd, parsedArgs.remainingArgs);
            } else {
                // 否则,使用RuntimeInit启动应用程序
                RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
                        parsedArgs.remainingArgs, null /* classLoader */);
            }
        } else {
            // 如果不需要运行时初始化,直接启动指定的类和主方法
            String className;
            try {
                // 获取类名
                className = parsedArgs.remainingArgs[0];
            } catch (ArrayIndexOutOfBoundsException ex) {
                // 如果类名参数缺失,记录错误并退出
                logAndPrintError(newStderr,
                        "Missing required class name argument", null);
                return;
            }

            // 准备主方法的参数
            String[] mainArgs = new String[parsedArgs.remainingArgs.length - 1];
            System.arraycopy(parsedArgs.remainingArgs, 1,
                    mainArgs, 0, mainArgs.length);

            // 如果提供了invokeWith(启动命令),使用WrapperInit启动独立应用程序
            if (parsedArgs.invokeWith != null) {
                WrapperInit.execStandalone(parsedArgs.invokeWith,
                        parsedArgs.classpath, className, mainArgs);
            } else {
                // 否则,使用ClassLoader加载类并执行主方法
                ClassLoader cloader;
                if (parsedArgs.classpath != null) {
                    cloader = new PathClassLoader(parsedArgs.classpath,
                            ClassLoader.getSystemClassLoader());
                } else {
                    cloader = ClassLoader.getSystemClassLoader();
                }

                try {
                    ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
                } catch (RuntimeException ex) {
                    // 如果启动过程中出现异常,记录错误
                    logAndPrintError(newStderr, "Error starting.", ex);
                }
            }
        }
    }

这里如果是第一次启动APP进程,那么parsedArgs.runtimeInit的值为true,invokeWith为null,这时候会调用到RuntimeInit.zygoteInit方法,该方法实现如下:

//RuntimeInit
    public static final void zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader)
            throws ZygoteInit.MethodAndArgsCaller {
        if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");
        
        // 重定向日志流,使得应用程序的日志能够被正确地输出
        redirectLogStreams();
        
        // 执行公共初始化操作,这部分代码涉及到设置进程的基本信息,如进程名称等
        commonInit();
        
        // 调用本地方法进行Zygote初始化,这可能涉及到JNI调用,设置本地环境等
        nativeZygoteInit();

        // 执行应用程序特定的初始化操作,这部分代码会根据传入的参数和类加载器加载应用程序的类,并启动应用程序的主线程
        applicationInit(targetSdkVersion, argv, classLoader);
    }

这里因为我们主要关注未处理的异常处理,这里的commonInit关键代码实现如下:

//RuntimeInit
    private static final void commonInit() {
        Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());
        //...
        initialized = true;
    }

这里当APP抛出未处理的异常时,都是由UncaughtHandler来处理的。到此才是APP异常处理的逻辑。

注意:如果应用程序在Android中自行设定了UncaughtExceptionHandler,这将优先于系统默认的异常处理机制。因此,当应用程序发生未捕获异常时,系统框架的崩溃处理流程将不会被触发,而是执行自定义的异常处理逻辑。通过这种方式,可以实现错误日志的自动上报功能,并且在捕获异常时防止应用程序崩溃,避免显示崩溃对话框。简而言之,自定义的UncaughtExceptionHandler允许APP开发者接管异常处理,进行日志记录和稳定性维护,而不采用系统的默认崩溃响应。

2 UncaughtHandler方法异常处理解读

UncaughtHandler的代码实现如下:

//RuntimeInit
    private static class UncaughtHandler implements Thread.UncaughtExceptionHandler {
        // 处理未捕获异常的方法
        public void uncaughtException(Thread t, Throwable e) {
            try {
                // 避免重复进入——如果崩溃报告本身崩溃,避免无限循环。
                if (mCrashing) return;
                mCrashing = true;

                // 如果mApplicationObject为空,表示系统进程发生了致命异常
                if (mApplicationObject == null) {
                    Clog_e(TAG, "*** FATAL EXCEPTION IN SYSTEM PROCESS: " + t.getName(), e);
                } else {
                    // 构建错误信息
                    StringBuilder message = new StringBuilder();
                    message.append("FATAL EXCEPTION: ").append(t.getName()).append("\n");
                    final String processName = ActivityThread.currentProcessName();
                    if (processName != null) {
                        message.append("Process: ").append(processName).append(", ");
                    }
                    message.append("PID: ").append(Process.myPid());
                    // 记录致命异常信息
                    Clog_e(TAG, message.toString(), e);
                }

                // 弹出崩溃对话框,并等待它被关闭
                ActivityManagerNative.getDefault().handleApplicationCrash(
                        mApplicationObject, new ApplicationErrorReport.CrashInfo(e));
            } catch (Throwable t2) {
                //...
            } finally {
                // 尝试一切办法确保进程退出。
                Process.killProcess(Process.myPid());
                System.exit(10);
            }
        }
    }

UncaughtHandler类是Android系统中处理未捕获异常的关键组件,它确保了在应用程序发生崩溃时能够记录错误信息、弹出崩溃对话框,并最终确保进程退出。

这里我们主要关注ActivityManagerNative.getDefault().handleApplicationCrash方法,实际上就是AMS的handleApplicationCrash方法,该方法代码实现如下:

//AMS
    //关键步骤:step1
    public void handleApplicationCrash(IBinder app, ApplicationErrorReport.CrashInfo crashInfo) {
        ProcessRecord r = findAppProcess(app, "Crash");
        final String processName = app == null ? "system_server"
                : (r == null ? "unknown" : r.processName);
        handleApplicationCrashInner("crash", r, processName, crashInfo);
    }
    //关键步骤:step2
    void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
            ApplicationErrorReport.CrashInfo crashInfo) {
        addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);
        crashApplication(r, crashInfo);
    }
    //关键步骤:step3
    private void crashApplication(ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) {
        long timeMillis = System.currentTimeMillis();
        // 获取异常的类名和消息
        String shortMsg = crashInfo.exceptionClassName;
        String longMsg = crashInfo.exceptionMessage;
        // 获取异常的堆栈跟踪
        String stackTrace = crashInfo.stackTrace;
        // 构造完整的错误消息
        if (shortMsg != null && longMsg != null) {
            longMsg = shortMsg + ": " + longMsg;
        } else if (shortMsg != null) {
            longMsg = shortMsg;
        }

        AppErrorResult result = new AppErrorResult();
        synchronized (this) {
            // 如果有活动控制器,尝试通知控制器应用程序崩溃
            if (mController != null) {
                try {
                    String name = r != null ? r.processName : null;
                    int pid = r != null ? r.pid : Binder.getCallingPid();
                    int uid = r != null ? r.info.uid : Binder.getCallingUid();
                    // 通知活动控制器应用程序崩溃
                    if (!mController.appCrashed(name, pid,shortMsg, longMsg, timeMillis, crashInfo.stackTrace)) {
                        // 如果控制器处理了崩溃,不再继续
                        return;
                    }
                } catch (RemoteException e) {
                    mController = null;
                    Watchdog.getInstance().setActivityController(null);
                }
            }

            // 清除调用者身份,以便执行以下操作
            final long origId = Binder.clearCallingIdentity();
            //...
            // 尝试使应用程序崩溃
            if (r == null || !makeAppCrashingLocked(r, shortMsg, longMsg, stackTrace)) {
                Binder.restoreCallingIdentity(origId);
                return;
            }

            // 准备显示错误消息
            Message msg = Message.obtain();
            msg.what = SHOW_ERROR_MSG;
            HashMap data = new HashMap();
            data.put("result", result);
            data.put("app", r);
            msg.obj = data;
            mHandler.sendMessage(msg);
            Binder.restoreCallingIdentity(origId);
        }

        // 获取错误处理结果
        int res = result.get();
        Intent appErrorIntent = null;
        synchronized (this) {
            // 记录崩溃次数
            if (r != null && !r.isolated) {
                mProcessCrashTimes.put(r.info.processName, r.uid,SystemClock.uptimeMillis());
            }
            // 如果结果为强制退出并报告,则创建错误 appErrorIntent
            if (res == AppErrorDialog.FORCE_QUIT_AND_REPORT) {
                appErrorIntent = createAppErrorIntentLocked(r, timeMillis, crashInfo);
            }
        }

        // 如果有 appErrorIntent,尝试启动错误报告activity
        if (appErrorIntent != null) {
            try {
                mContext.startActivityAsUser(appErrorIntent, new UserHandle(r.userId));
            } catch (ActivityNotFoundException e) {
                Slog.w(TAG, "bug report receiver dissappeared", e);
            }
        }
    }

crashApplication方法是Android系统中处理应用程序崩溃的关键方法。它负责记录崩溃信息、通知系统监控服务、显示崩溃对话框,并最终确保应用程序正确地退出。这里我们关注尝试使应用崩溃的makeAppCrashingLocked方法逻辑和SHOW_ERROR_MSG发送后的消息处理逻辑。

2.1 makeAppCrashingLocked方法及相关流程解读

我们先来看使应用崩溃的makeAppCrashingLocked方法的实现,代码实现如下:

//AMS
    private boolean makeAppCrashingLocked(ProcessRecord app,
            String shortMsg, String longMsg, String stackTrace) {
        // 标记应用程序为崩溃状态
        app.crashing = true;
        // 生成进程错误报告
        app.crashingReport = generateProcessError(app,
                ActivityManager.ProcessErrorStateInfo.CRASHED, null, shortMsg, longMsg, stackTrace);
        // 启动应用程序问题处理流程
        startAppProblemLocked(app);
        // 停止所有冻结操作,以便应用程序可以响应崩溃
        app.stopFreezingAllLocked();
        // 处理应用程序崩溃的锁定逻辑
        return handleAppCrashLocked(app, shortMsg, longMsg, stackTrace);
    }

makeAppCrashingLocked方法用于在应用程序崩溃时更新其状态、生成错误报告、启动问题处理流程,并确保应用程序可以正确响应崩溃事件。这里我们关注handleAppCrashLocked方法的实现,代码如下:

//AMS
    private boolean handleAppCrashLocked(ProcessRecord app, String shortMsg, String longMsg,
        String stackTrace) {
        long now = SystemClock.uptimeMillis(); // 获取当前时间
        Long crashTime; // 上次崩溃时间
        if (!app.isolated) { // 如果进程不是隔离的
            crashTime = mProcessCrashTimes.get(app.info.processName, app.uid); // 获取该进程上次崩溃的时间
        } else {
            crashTime = null; // 隔离进程没有崩溃时间记录
        }
        // 如果进程在短时间内频繁崩溃
        if (crashTime != null && now < crashTime + ProcessList.MIN_CRASH_INTERVAL) {
            mStackSupervisor.handleAppCrashLocked(app); // 处理应用程序崩溃
            if (!app.persistent) { // 如果进程不是持久的
                if (!app.isolated) { // 如果进程不是隔离的
                    // 记录不良进程信息
                    mBadProcesses.put(app.info.processName, app.uid,
                            new BadProcessInfo(now, shortMsg, longMsg, stackTrace));
                    // 清除进程的崩溃时间记录
                    mProcessCrashTimes.remove(app.info.processName, app.uid);
                }
                app.bad = true; // 标记进程为不良
                app.removed = true; // 标记进程为已移除
                removeProcessLocked(app, false, false, "crash"); // 移除进程
                mStackSupervisor.resumeTopActivitiesLocked(); // 恢复顶部activity
                return false; // 返回false,表示进程已被处理
            }
            mStackSupervisor.resumeTopActivitiesLocked(); // 恢复顶部activity
        } else {
            mStackSupervisor.finishTopRunningActivityLocked(app); // 完成顶部运行的activity
        }

        // 更新服务记录的崩溃次数
        for (int i = app.services.size() - 1; i >= 0; i--) {
            ServiceRecord sr = app.services.valueAt(i);
            sr.crashCount++;
        }
        // 如果崩溃的进程是首页进程且不是系统应用
        if (app == mHomeProcess && activities.size() > 0
                && (mHomeProcess.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
                final ActivityRecord r = activities.get(activityNdx);
                if (r.isHomeActivity()) { // 如果是首页activity
                    try {
                        // 清除该包的首选activity设置
                        ActivityThread.getPackageManager().clearPackagePreferredActivities(r.packageName);
                    } catch (RemoteException c) {
                        //...
                    }
                }
            }
        }

        // 记录进程的崩溃时间
        if (!app.isolated) {
            mProcessCrashTimes.put(app.info.processName, app.uid, now);
        }

        // 如果进程有崩溃处理程序,发布崩溃事件
        if (app.crashHandler != null) mHandler.post(app.crashHandler);
        return true; // 返回true,表示进程崩溃事件已处理
    }

handleAppCrashLocked方法是Android系统中处理应用程序崩溃的关键方法。它负责记录崩溃时间、处理频繁崩溃的进程、更新服务记录、清除首页activity的首选设置,并发布崩溃事件。在处理应用程序崩溃时用到了ActivtyStackSupervisor的handleAppCrashLocked()方法,该方法代码实现如下:

//ActivtyStackSupervisor
    void handleAppCrashLocked(ProcessRecord app) {
        // 遍历所有显示屏幕
        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
            // 获取每个显示屏幕对应的activity堆栈列表
            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
            // 获取activity堆栈的数量
            final int numStacks = stacks.size();
            // 遍历所有activity堆栈
            for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
                // 获取特定的activity堆栈
                final ActivityStack stack = stacks.get(stackNdx);
                // 通知activity堆栈处理应用程序崩溃
                stack.handleAppCrashLocked(app);
            }
        }
    }

该方法确保了系统能够响应应用程序崩溃事件,并在所有相关的activity堆栈中更新状态。接下来看ActivityStack的handleAppCrashLocked方法,代码实现如下所示:

//ActivtyStack
    void handleAppCrashLocked(ProcessRecord app) {
        // 遍历任务历史记录,从后向前遍历以处理最近的activity
        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
            // 获取每个任务中的activity列表
            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
            // 遍历任务中的所有activity
            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
                // 获取特定的activity记录
                final ActivityRecord r = activities.get(activityNdx);
                // 如果activity属于崩溃的应用程序
                if (r.app == app) {
                    // 将activity的应用记录设置为null,表示应用已崩溃
                    r.app = null;
                    // 立即完成该activity,FINISH_IMMEDIATELY表示立即结束activity,不进行任何延迟
                    finishCurrentActivityLocked(r, FINISH_IMMEDIATELY, false);
                }
            }
        }
    }

该方法用于在应用程序崩溃时更新所有属于该应用程序的activity状态。这个方法确保了系统能够响应应用程序崩溃事件,并在所有相关的activity中更新状态。接下来看对于每个满足条件的activity的最终处理方法finishCurrentActivityLocked的实现,代码如下:

//ActivtyStack
    final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj) {
        // 如果模式是FINISH_AFTER_VISIBLE,并且活动当前是可见的
        if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) {
            // 如果活动不在停止活动中列表中,则添加到列表
            if (!mStackSupervisor.mStoppingActivities.contains(r)) {
                mStackSupervisor.mStoppingActivities.add(r);
                // 如果停止活动的数量超过3个,或者活动是任务的前台活动且任务历史记录数量小于等于1,则计划空闲
                if (mStackSupervisor.mStoppingActivities.size() > 3
                        || r.frontOfTask && mTaskHistory.size() <= 1) {
                    mStackSupervisor.scheduleIdleLocked();
                } else {
                    // 否则,检查是否准备好睡眠
                    mStackSupervisor.checkReadyForSleepLocked();
                }
            }
            // 设置活动状态为停止中
            r.state = ActivityState.STOPPING;
            // 如果需要调整OOM值,则更新
            if (oomAdj) {
                mService.updateOomAdjLocked();
            }
            return r; // 返回活动记录
        }

        // 从各种活动中列表中移除当前活动
        mStackSupervisor.mStoppingActivities.remove(r);
        mStackSupervisor.mGoingToSleepActivities.remove(r);
        mStackSupervisor.mWaitingVisibleActivities.remove(r);
        // 如果当前活动是恢复的活动,则将其设置为null
        if (mResumedActivity == r) {
            mResumedActivity = null;
        }
        // 记录活动之前的状态
        final ActivityState prevState = r.state;
        // 设置活动状态为完成中
        r.state = ActivityState.FINISHING;

        // 如果模式是FINISH_IMMEDIATELY,或者之前的状态是STOPPED或INITIALIZING
        if (mode == FINISH_IMMEDIATELY
                || prevState == ActivityState.STOPPED
                || prevState == ActivityState.INITIALIZING) {
            // 标记活动为完成
            r.makeFinishing();
            // 销毁活动
            boolean activityRemoved = destroyActivityLocked(r, true, "finish-imm");
            // 如果活动被移除,则恢复顶部活动
            if (activityRemoved) {
                mStackSupervisor.resumeTopActivitiesLocked();
            }
            return activityRemoved ? null : r; // 返回活动记录或null
        }

        // 将活动添加到完成活动中列表
        mStackSupervisor.mFinishingActivities.add(r);
        // 恢复分发键事件
        r.resumeKeyDispatchingLocked();
        // 恢复顶部活动
        mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null);
        return r; // 返回活动记录
    }

这段代码的处理逻辑是为了确保在活动完成时,系统能够正确地管理活动生命周期,同时保持用户界面的响应性和连续性。通过将活动添加到完成列表、恢复键事件分发和恢复顶部活动,系统可以确保在活动销毁后,用户界面能够立即响应用户的操作,并且不会留下任何悬空的状态。这种方法有助于提供平滑的用户体验,即使在活动发生崩溃或需要关闭的情况下。

2.2 SHOW_ERROR_MSG消息处理

发送SHOW_ERROR_MSG消息后,MainHandler会通过handlerMessage来处理,代码实现如下:

//AMS
    final class MainHandler extends Handler {
        public MainHandler(Looper looper) {
            super(looper, null, true);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SHOW_ERROR_MSG: {
                    // 从消息中获取数据
                    HashMap<String, Object> data = (HashMap<String, Object>) msg.obj;
                    // 检查设置中是否允许显示后台应用的错误对话框
                    boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
                            Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0;

                    synchronized (ActivityManagerService.this) {
                        // 从数据中获取进程记录和错误结果对象
                        ProcessRecord proc = (ProcessRecord)data.get("app");
                        AppErrorResult res = (AppErrorResult) data.get("result");

                        // 如果进程已经有错误对话框,或者结果对象为空,则直接返回
                        if (proc != null && proc.crashDialog != null) {
                            if (res != null) {
                                res.set(0);
                            }
                            return;
                        }

                        // 判断进程是否为后台进程
                        boolean isBackground = (UserHandle.getAppId(proc.uid)
                                >= Process.FIRST_APPLICATION_UID
                                && proc.pid != MY_PID);
                        for (int userId : mCurrentProfileIds) {
                            isBackground &= (proc.userId != userId);
                        }

                        // 如果设置中不允许显示后台应用的错误对话框,且进程为后台进程,则直接返回
                        if (isBackground && !showBackground) {
                            if (res != null) {
                                res.set(0);
                            }
                            return;
                        }

                        // 如果允许显示对话框,且系统未休眠,且未关闭,则显示错误对话框
                        if (mShowDialogs && !mSleeping && !mShuttingDown) {
                            Dialog d = new AppErrorDialog(mContext, ActivityManagerService.this, res, proc);
                            d.show();
                            proc.crashDialog = d; // 将对话框保存到进程记录中
                        } else {
                            if (res != null) {
                                res.set(0);
                            }
                        }
                    }
                    ensureBootCompleted(); // 确保系统启动完成
                } break;
                // ...
            }
        }
        //...
    }

这段代码处理应用程序错误对话框的显示逻辑,包括检查系统设置、判断进程是否为后台进程、创建和显示错误对话框等。这里最将异常错误相关信息呈现在用户的眼前。到这里 APP Crash的相关逻辑处理就结束了。但是因为应用退出导致它的讣告接收对象被唤醒,也就是会通知AMS,走到对应的binderDied的流程。

最后总结下,关键方法handleApplicationCrash及这条线的逻辑。为了确保当Android系统中的应用程序进程发生崩溃时,系统能够以一种可控和用户友好的方式进行响应和处理。以下是该方法设计的几个关键意义:

  • 维护系统稳定性:通过及时响应应用程序崩溃,handleApplicationCrash方法有助于防止系统级故障,确保整个系统的稳定性和可靠性。
  • 提供用户反馈:该方法通常会显示一个崩溃对话框(crash dialog),向用户报告应用程序崩溃的情况,提供用户友好的错误信息和可能的解决方案。
  • 资源回收和释放:确保崩溃的应用程序进程所占用的资源得到正确回收和释放,避免资源泄露,如内存、文件描述符和数据库连接等。
  • 应用程序重启:对于某些关键的应用程序,系统可能需要自动重启崩溃的进程,以尽量恢复服务,减少对用户体验的影响。
  • 错误报告和日志记录:收集崩溃相关的信息,如异常堆栈、错误日志等,这对于后续的错误分析、调试和修复至关重要。
  • 保护用户数据:在应用程序崩溃时,确保用户数据不受损失,或者提示用户保存工作,以防止数据丢失。
  • 系统监控和性能优化:通过监控应用程序的崩溃情况,系统可以识别潜在的性能问题和系统瓶颈,为性能优化提供数据支持。
  • 安全性考虑:崩溃处理机制还包括安全性考虑,确保应用程序崩溃不会导致系统安全漏洞或敏感信息泄露。
  • 应用程序生命周期管理:该方法有助于维护应用程序的正确生命周期,确保应用程序在崩溃后能够正确地结束或重启,符合其生命周期规范。
  • 用户体验连续性:通过适当的崩溃处理,系统可以尽量保持用户体验的连续性,尤其是在前台应用程序崩溃时,系统可以尽快恢复用户的操作环境。

综上所述,handleApplicationCrash方法的设计意义在于提供一个全面的崩溃处理机制,以保护用户利益、维护系统稳定性、优化性能,并为开发者提供必要的错误信息,以便快速定位和解决问题。

3 AMS的binderDied讣告流程分析

3.1 binderDied讣告被调用的流程

因为应用退出导致它的讣告接收对象被唤醒,也就是会走到binderDied的流程,关于具体如何让走到讣告的,可以参考文章:

android 系统核心机制binder(07)binder挂掉客户端收到通知

也就是说当APP进程挂掉时AMS会收到讣告,可是到底是什么讣告呢?这里以startProcessLocked方法启动一个新进程为例,启动相关的代码可参考文章:

Android Framework AMS(04)startActivity分析-1(am启动到ActivityThread启动)

Android Framework AMS(05)startActivity分析-2(ActivityThread启动到Activity拉起)

这里我们主要从AMS的attachApplicationLocked方法入手进行分析。代码实现如下:

    private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
        //...
        try {
            // 创建应用程序死亡接收器
            AppDeathRecipient adr = new AppDeathRecipient(
                    app, pid, thread);
            // 将死亡接收器与应用程序线程的Binder关联
            thread.asBinder().linkToDeath(adr, 0);
            // 将死亡接收器设置到应用程序记录中
            app.deathRecipient = adr;
        } catch (RemoteException e) {
            // 如果发生远程异常,重置应用程序的包列表
            app.resetPackageList(mProcessStats);
            // 重新启动应用程序进程
            startProcessLocked(app, "link fail", processName);
            return false; // 返回false,表示关联失败
        }
        return true;
    }

attachApplicationLocked方法的主要目的是确保AMS能够监控应用程序进程的生命周期。通过设置死亡接收器,当应用程序进程意外死亡时,系统能够及时响应并采取相应的措施,如重启进程或清理资源。这里的讣告类型是AppDeathRecipient,因此接下来我们继续分析AppDeathRecipient中的binderDied相关流程。

3.2讣告处理流程

AppDeathRecipient(包含binderDied)的完整代码如下所示:

//AMS
    private final class AppDeathRecipient implements IBinder.DeathRecipient {
        final ProcessRecord mApp;
        final int mPid;
        final IApplicationThread mAppThread;

        AppDeathRecipient(ProcessRecord app, int pid,
                IApplicationThread thread) {
            mApp = app;
            mPid = pid;
            mAppThread = thread;
        }

        //关键方法
        @Override
        public void binderDied() {
            synchronized(ActivityManagerService.this) {
                appDiedLocked(mApp, mPid, mAppThread);
            }
        }
    }

当APP进程挂掉时会调用到binderDied的方法,binderDied实现很简单,就是调用appDiedLocked方法,基于这个方法继续分析,该方法代码实现如下:

//AMS
    final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread) {
        // 同步mPidsSelfLocked,确保mPidsSelfLocked的一致性
        synchronized (mPidsSelfLocked) {
            // 获取mPidsSelfLocked中对应pid的ProcessRecord
            ProcessRecord curProc = mPidsSelfLocked.get(pid);
            // 如果当前pid对应的ProcessRecord不是我们关心的app,直接返回
            if (curProc != app) {
                return;
            }
        }

        // 获取活跃的电池统计服务
        BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
        // 同步stats,确保电池统计的一致性
        synchronized (stats) {
            // 记录进程死亡事件
            stats.noteProcessDiedLocked(app.info.uid, pid);
        }

        // 如果app还没有被标记为已杀死
        if (!app.killed) {
            // 静默杀死进程
            Process.killProcessQuiet(pid);
            // 杀死进程组
            Process.killProcessGroup(app.info.uid, pid);
            // 标记app为已杀死
            app.killed = true;
        }

        // 如果app的pid等于传入的pid,并且app的thread不为空,且thread的binder与传入的thread相同
        if (app.pid == pid && app.thread != null && app.thread.asBinder() == thread.asBinder()) {
            // 如果app没有instrumentation类,则需要进行低内存报告
            boolean doLowMem = app.instrumentationClass == null;
            // 是否需要更新OOM调整值
            boolean doOomAdj = doLowMem;
            // 如果app不是被ActivityManager杀死的
            if (!app.killedByAm) {
                // 允许降低内存级别
                mAllowLowerMemLevel = true;
            } else {
                // 不允许降低内存级别
                mAllowLowerMemLevel = false;
                // 不需要进行低内存报告
                doLowMem = false;
            }
            // 处理app死亡
            handleAppDiedLocked(app, false, true);

            // 如果需要更新OOM调整值,则更新
            if (doOomAdj) {
                updateOomAdjLocked();
            }
            // 如果需要进行低内存报告,则进行
            if (doLowMem) {
                doLowMemReportIfNeededLocked(app);
            }
        }
        //...
    }

appDiedLocked方法是Android系统中处理应用程序进程死亡的关键方法。它负责更新电池统计、杀死进程、调整OOM调整值和进行低内存报告。这里我们关注handleAppDiedLocked这个关键方法,代码实现如下:

private final void handleAppDiedLocked(ProcessRecord app, boolean restarting, boolean allowRestart) {
        int pid = app.pid; // 获取进程ID
        // 清理应用程序记录,返回是否保留该进程记录
        boolean kept = cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);
        // 如果不保留且不是重启中
        if (!kept && !restarting) {
            // 从LRU列表中移除进程
            removeLruProcessLocked(app);
            // 如果进程ID大于0,则从进程列表中移除
            if (pid > 0) {
                ProcessList.remove(pid);
            }
        }

        // 如果当前进程是正在被分析的进程
        if (mProfileProc == app) {
            // 清除分析器
            clearProfilerLocked();
        }

        // 处理应用程序死亡,返回是否有可见activity
        boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app);
        // 清除应用程序的所有activity
        app.activities.clear();
        // 如果进程有测试框架类
        if (app.instrumentationClass != null) {
            // 创建一个Bundle,用于传递测试结果
            Bundle info = new Bundle();
            info.putString("shortMsg", "Process crashed.");
            // 完成测试框架
            finishInstrumentationLocked(app, Activity.RESULT_CANCELED, info);
        }

        // 如果不是重启中
        if (!restarting) {
            // 尝试恢复顶部activity
            if (!mStackSupervisor.resumeTopActivitiesLocked()) {
                // 如果有可见activity,确保activity可见
                if (hasVisibleActivities) {
                    mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
                }
            }
        }
    }

handleAppDiedLocked方法负责清理应用程序记录、移除进程、清除分析器、处理应用程序死亡、清除应用程序的所有活动、完成测试仪器以及恢复顶部活动。通过这种方式,Android系统能够响应应用程序进程的死亡事件,并进行适当的资源回收和状态更新。

这里我们主要关注cleanUpApplicationRecordLocked方法,它负责清理应用程序记录、移除进程、清理对话框、重置状态、杀死服务、移除ContentProvider、跳过广播接收者等,可以说涉及4大组件的收为处理工作。代码实现如下:

//AMS
    private final boolean cleanUpApplicationRecordLocked(ProcessRecord app,
            boolean restarting, boolean allowRestart, int index) {
        // 如果提供了有效的索引,从LRU列表和进程列表中移除进程
        if (index >= 0) {
            removeLruProcessLocked(app);
            ProcessList.remove(app.pid);
        }

        // 从待GC列表和待PSS列表中移除进程
        mProcessesToGc.remove(app);
        mPendingPssProcesses.remove(app);

        // 关闭所有打开的对话框
        if (app.crashDialog != null && !app.forceCrashReport) {
            app.crashDialog.dismiss();
            app.crashDialog = null;
        }
        if (app.anrDialog != null) {
            app.anrDialog.dismiss();
            app.anrDialog = null;
        }
        if (app.waitDialog != null) {
            app.waitDialog.dismiss();
            app.waitDialog = null;
        }

        // 重置进程状态
        app.crashing = false;
        app.notResponding = false;

        // 重置进程包列表
        app.resetPackageList(mProcessStats);
        // 移除死亡接收者
        app.unlinkDeathRecipient();
        // 使进程变为非活动状态
        app.makeInactive(mProcessStats);
        // 清理进程等待杀死和强制前台的引用
        app.waitingToKill = null;
        app.forcingToForeground = null;
        // 更新进程前台状态
        updateProcessForegroundLocked(app, false, false);
        app.foregroundActivities = false;
        app.hasShownUi = false;
        app.treatLikeActivity = false;
        app.hasAboveClient = false;
        app.hasClientActivities = false;

        // 杀死进程中的服务
        mServices.killServicesLocked(app, allowRestart);

        boolean restart = false;

        // 移除发布的ContentProvider
        for (int i = app.pubProviders.size() - 1; i >= 0; i--) {
            ContentProviderRecord cpr = app.pubProviders.valueAt(i);
            final boolean always = app.bad || !allowRestart;
            // 如果需要重启ContentProvider或进程已坏
            if (removeDyingProviderLocked(app, cpr, always) || always) {
                restart = true;
            }

            cpr.provider = null;
            cpr.proc = null;
        }
        app.pubProviders.clear();
        if (checkAppInLaunchingProvidersLocked(app, false)) {
            restart = true;
        }

        // 清理连接的ContentProvider
        if (!app.conProviders.isEmpty()) {
            for (int i = 0; i < app.conProviders.size(); i++) {
                ContentProviderConnection conn = app.conProviders.get(i);
                conn.provider.connections.remove(conn);
                stopAssociationLocked(app.uid, app.processName, conn.provider.uid,
                        conn.provider.name);
            }
            app.conProviders.clear();
        }

        // 跳过当前的广播接收者
        skipCurrentReceiverLocked(app);
        // 移除所有的广播接收者
        for (int i = app.receivers.size() - 1; i >= 0; i--) {
            removeReceiverLocked(app.receivers.valueAt(i));
        }
        app.receivers.clear();
        // 如果备份目标进程死亡,通知备份管理器
        if (mBackupTarget != null && app.pid == mBackupTarget.app.pid) {
            try {
                IBackupManager bm = IBackupManager.Stub.asInterface(
                        ServiceManager.getService(Context.BACKUP_SERVICE));
                bm.agentDisconnected(app.info.packageName);
            } catch (RemoteException e) {
                // ...
            }
        }

        // 处理挂起的进程变化
        for (int i = mPendingProcessChanges.size() - 1; i >= 0; i--) {
            ProcessChangeItem item = mPendingProcessChanges.get(i);
            if (item.pid == app.pid) {
                mPendingProcessChanges.remove(i);
                mAvailProcessChanges.add(item);
            }
        }
        // 发送进程死亡的消息
        mHandler.obtainMessage(DISPATCH_PROCESS_DIED, app.pid, app.info.uid, null).sendToTarget();
        // 如果进程正在重启,返回false
        if (restarting) {
            return false;
        }

        // 清理持久进程和隔离进程的记录
        if (!app.persistent || app.isolated) {
            mProcessNames.remove(app.processName, app.uid);
            mIsolatedProcesses.remove(app.uid);
            if (mHeavyWeightProcess == app) {
                mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG,
                        mHeavyWeightProcess.userId, 0));
                mHeavyWeightProcess = null;
            }
        } else if (!app.removed) {
            // 如果持久进程没有在启动列表中,添加到启动列表并标记需要重启
            if (mPersistentStartingProcesses.indexOf(app) < 0) {
                mPersistentStartingProcesses.add(app);
                restart = true;
            }
        }
        mProcessesOnHold.remove(app);

        // 清理首页进程和前一个进程的记录
        if (app == mHomeProcess) {
            mHomeProcess = null;
        }
        if (app == mPreviousProcess) {
            mPreviousProcess = null;
        }

        // 如果需要重启且进程不是隔离的,重启进程
        if (restart && !app.isolated) {
            if (index < 0) {
                ProcessList.remove(app.pid);
            }
            mProcessNames.put(app.processName, app.uid, app);
            startProcessLocked(app, "restart", app.processName);
            return true;
        } else if (app.pid > 0 && app.pid != MY_PID) {
            // 从mPidsSelfLocked中移除进程
            boolean removed;
            synchronized (mPidsSelfLocked) {
                mPidsSelfLocked.remove(app.pid);
                mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
            }
            // 通知电池统计服务进程结束
            mBatteryStatsService.noteProcessFinish(app.processName, app.info.uid);
            if (app.isolated) {
                mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid);
            }
            app.setPid(0);
        }
        return false;
    }

cleanUpApplicationRecordLocked方法的设计目的是为了在应用程序进程死亡时,对Android系统中的四大组件(Activity、Service、ContentProvider、BroadcastReceiver)以及其它相关资源进行统一的管理和清理。以下是该方法对四大组件的处理方式:

对Activity的处理:

  • 清理Activity记录:当应用程序进程死亡时,该方法会清理所有与该进程相关联的Activity记录,包括从ActivityStack中移除这些Activity。
  • 状态更新:更新Activity的状态,将它们标记为已销毁(ActivityState.DESTROYED),确保系统不会尝试与这些Activity进行交互。
  • 移除历史记录:从各种Activity历史记录列表中移除相关Activity,如mLRUActivitiesmStoppingActivities等。

对Service的处理:

  • 杀死服务:该方法会杀死与该应用程序进程相关联的所有服务。
  • 清理服务连接:清理与服务的所有连接,包括移除服务的绑定和停止服务。

对ContentProvider的处理:

  • 移除ContentProvider:移除应用程序进程发布的所有ContentProvider,并清理相关记录。
  • 清理连接的ContentProvider:清理应用程序进程连接的所有ContentProvider。

对BroadcastReceiver的处理:

  • 跳过当前的广播接收者:如果进程中有正在处理的广播接收者,该方法会跳过当前的广播接收者。
  • 移除广播接收者:从广播接收者列表中移除所有与该应用程序进程相关联的BroadcastReceiver。

除了4大组件的处理还有一些其他处理,总结如下:

  • 资源和内存管理:清理与应用程序进程相关的所有资源,包括文件描述符、网络连接等,以确保资源不会泄露。
  • 进程重启决策:根据应用程序的配置和系统策略,决定是否需要重启该应用程序进程。
  • 用户体验维护:通过清理和可能的重启操作,最小化应用程序崩溃对用户体验的影响。

总结来说,cleanUpApplicationRecordLocked方法的设计目的是确保在应用程序进程死亡时,系统能够统一管理和清理与该进程相关的所有组件和资源,以维护系统的稳定性和响应性。

最后总结下appDiedLocked方法这条线的逻辑。该方法的主要设计目的在于确保当一个应用程序进程意外崩溃或被终止时,Android系统能够进行适当的清理和后续处理。以下是该方法实现的几个关键目标:

  • 进程状态同步:确保系统准确记录进程的死亡状态,更新进程的生命周期状态,以便系统不再将资源或事件分配给已不存在的进程。
  • 资源回收:释放与已死亡进程相关联的所有资源,包括内存、文件描述符、数据库连接等,防止资源泄露。
  • 用户体验保护:通过重启崩溃的应用程序进程(如果配置允许)或清理用户界面,确保用户不会遇到应用程序挂起或无响应的情况。
  • 系统稳定性维护:及时处理进程死亡事件,避免系统其他部分因等待已不存在的进程响应而出现稳定性问题。
  • 服务和内容提供者管理:清理进程中运行的所有服务和内容提供者,确保数据一致性,并释放相关资源。
  • 广播接收器处理:管理与进程相关的广播接收器,确保系统不会向已死亡的进程发送广播,避免潜在的系统崩溃。
  • 重启策略执行:对于前台进程或关键服务,根据需要执行重启策略,以保证应用程序的连续性和服务的可用性。
  • 系统监控和错误报告:收集崩溃进程的信息,为系统监控、错误报告和后续的调试提供数据支持。
  • 用户界面更新:如果进程包含用户可见的活动,确保用户界面得到更新,如显示崩溃对话框或重启活动。
  • 系统性能优化:通过清理不再需要的进程记录和资源,优化系统性能,提高剩余进程的响应速度和效率。

appDiedLocked方法的设计目的是在应用程序进程死亡时,进行系统级的清理和恢复操作,以保护用户体验,维护系统稳定性,并确保资源得到合理管理。

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

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

相关文章

javaWeb小白项目--学生宿舍管理系统

目录 一、检查并关闭占用端口的进程 二、修改 Tomcat 的端口配置 三、重新启动 Tomcat 一、javaw.exe的作用 二、结束javaw.exe任务的影响 三、如何判断是否可以结束 结尾&#xff1a; 这个错误提示表明在本地启动 Tomcat v9.0 服务器时遇到了问题&#xff0c;原因是所需…

深度学习在边缘检测中的应用及代码分析

摘要&#xff1a; 本文深入探讨了深度学习在边缘检测领域的应用。首先介绍了边缘检测的基本概念和传统方法的局限性&#xff0c;然后详细阐述了基于深度学习的边缘检测模型&#xff0c;包括其网络结构、训练方法和优势。文中分析了不同的深度学习架构在边缘检测中的性能表现&am…

SpringBoot(十七)创建多模块Springboot项目

在gitee上查找资料的时候,发现有不少Springboot项目里边都是嵌套了多个Springboot项目的。这个玩意好,在协作开发的时候,将项目分成多个模块,有多个团队协作开发,模块间定义标准化通信接口进行数据交互即可。 这个好这个。我之前创建的博客项目是单模块的SpringBoot项目,…

STM32WB55RG开发(2)----STM32CubeProgrammer烧录

STM32WB55RG开发----2.STM32CubeProgrammer烧录 概述硬件准备视频教学样品申请源码下载参考程序自举模式UART烧录USB烧录 概述 STM32CubeProgrammer (STM32CubeProg) 是一款用于编程STM32产品的全功能多操作系统软件工具。 它提供了一个易用高效的环境&#xff0c;通过调试接口…

使用Java爬虫获取商品订单详情:从API到数据存储

在电子商务日益发展的今天&#xff0c;获取商品订单详情成为了许多开发者和数据分析师的需求。无论是为了分析用户行为&#xff0c;还是为了优化库存管理&#xff0c;订单数据的获取都是至关重要的。本文将详细介绍如何使用Java编写爬虫&#xff0c;通过API获取商品订单详情&am…

高性能分布式缓存Redis-分布式锁与布隆过滤器

一、分布式锁 我们先来看一下本地锁 在并发编程中&#xff0c;我们通过锁&#xff0c;来避免由于竞争而造成的数据不一致问题。通常&#xff0c;我们以 synchronized 、Lock 来使用它&#xff08;单机情况&#xff09; 来看这段代码 Autowired RedisTemplate<String,Str…

SpringSecurity+jwt+captcha登录认证授权总结

SpringSecurityjwtcaptcha登录认证授权总结 版本信息&#xff1a; springboot 3.2.0、springSecurity 6.2.0、mybatis-plus 3.5.5 认证授权思路和流程&#xff1a; 未携带token&#xff0c;访问登录接口&#xff1a; 1、用户登录携带账号密码 2、请求到达自定义Filter&am…

从社交媒体到元宇宙:Facebook未来发展新方向

Facebook&#xff0c;作为全球最大的社交媒体平台之一&#xff0c;已经从最初的简单互动工具发展成为一个跨越多个领域的科技巨头。无论是连接人与人之间的社交纽带&#xff0c;还是利用大数据、人工智能等技术为用户提供个性化的体验&#xff0c;Facebook一直引领着社交网络的…

javascript用来干嘛的?赋予网站灵魂的语言

javascript用来干嘛的&#xff1f;赋予网站灵魂的语言 在互联网世界中&#xff0c;你所浏览的每一个网页&#xff0c;背后都有一群默默工作的代码在支撑着。而其中&#xff0c;JavaScript就像是一位技艺精湛的魔术师&#xff0c;它赋予了网页生命力&#xff0c;让原本静态的页…

Wordpress常用配置,包括看板娘跨域等

一个Wordpress的博客已经搭建完成了&#xff0c;那么为了让它看起来更有人间烟火气一点&#xff0c;有一些常用的初始配置&#xff0c;这里整理一下。 修改页脚 页脚这里默认会显示Powered by Wordpress&#xff0c;还有一个原因是这里要加上备案信息。在主题里找到页脚&…

The Internals of PostgreSQL 翻译版 持续更新...

为了方便自己快速学习&#xff0c;整理了翻译版本&#xff0c;目前翻译的还不完善&#xff0c;后续会边学习边完善。 文档用于自己快速参考&#xff0c;会持续修正&#xff0c;能力有限,无法确保正确!!! 《The Internals of PostgreSQL 》 不是 《 PostgreSQL14 Internals 》…

FlinkPipelineComposer 详解

FlinkPipelineComposer 详解 原文 背景 在flink-cdc 3.0中引入了pipeline机制&#xff0c;提供了除Datastream api/flink sql以外的一种方式定义flink 任务 通过提供一个yaml文件&#xff0c;描述source sink transform等主要信息 由FlinkPipelineComposer解析&#xff0c…

MybatisPlus知识

mybatis与mybatisplus的区别&#xff1a; mybatisplus顾名思义时mybatis的升级版&#xff0c;提供了更多的API和方法&#xff0c;是基于mybatis框架基础上的升级&#xff0c;更加方便开发。mybatisplus继承BaseMapper接口并调用其中提供的方法来操作数据库&#xff0c;不需要再…

利用飞书多维表格自动发布版本

文章目录 背景尝试1&#xff0c;轮询尝试2&#xff0c;长连接 背景 博主所在的部门比较奇特&#xff0c;每个车型每周都需要发版&#xff0c;所以实际上一周会发布好几个版本。经过之前使用流水线自动发版改造之后&#xff0c;发版的成本已经大大降低了&#xff0c;具体参考&a…

Qwen2-VL:发票数据提取、视频聊天和使用 PDF 的多模态 RAG 的实践指南

概述 随着人工智能技术的迅猛发展&#xff0c;多模态模型在各类应用场景中展现出强大的潜力和广泛的适用性。Qwen2-VL 作为最新一代的多模态大模型&#xff0c;融合了视觉与语言处理能力&#xff0c;旨在提升复杂任务的执行效率和准确性。本指南聚焦于 Qwen2-VL 在三个关键领域…

蓝桥杯每日真题 - 第7天

题目&#xff1a;&#xff08;爬山&#xff09; 题目描述&#xff08;X届 C&C B组X题&#xff09; 解题思路&#xff1a; 前缀和构造&#xff1a;为了高效地计算子数组的和&#xff0c;我们可以先构造前缀和数组 a&#xff0c;其中 a[i] 表示从第 1 个元素到第 i 个元素的…

家政服务小程序,家政行业数字化发展下的优势

今年以来&#xff0c;家政市场需求持续增长&#xff0c;市场规模达到了万亿级别&#xff0c;家政服务行业成为了热门行业之一&#xff01; 家政服务种类目前逐渐呈现了多样化&#xff0c;月嫂、保姆、做饭保洁、收纳、维修等家政种类不断出现&#xff0c;满足了居民日益增长的…

蓝桥杯每日真题 - 第12天

题目&#xff1a;&#xff08;数三角&#xff09; 题目描述&#xff08;14届 C&C B组E题&#xff09; 解题思路&#xff1a; 给定 n 个点的坐标&#xff0c;计算其中可以组成 等腰三角形 的三点组合数量。 核心条件&#xff1a;等腰三角形的定义是三角形的三条边中至少有…

Linux系统下svn新建目录

Linux安装svn自行查找 新建目录 新建一个自定义库的文件夹&#xff1a;mkdir security 使用svnadmin命令在新创建的目录中创建一个新的SVN版本库。例如&#xff1a; svnadmin create security 执行完成以上命令就会生成默认配置文件 通过pwd命令查找当前目录路径 路径&…

SpringCloud基础 入门级 学习SpringCloud 超详细(简单通俗易懂)

Spring Cloud 基础入门级学习 超详细&#xff08;简单通俗易懂&#xff09; 一、SpringCloud核心组件第一代&#xff1a;SpringCloud Netflix组件第二代&#xff1a;SpringCloud Alibaba组件SpringCloud原生组件 二、SpringCloud体系架构图三、理解分布式与集群分布式集群 四、…