android N进程启动流程(一)(捕获输入事件、准备创建activity、焦点切换)

2023-05-16

android N进程启动流程(一)(捕获输入事件、准备创建activity、焦点切换)

1. 背景

本文主要针对event log中各处节点进行进程启动流程分析。

//此处使用的是adb指令input tap + location的方法(具体实现可以参考)
//Input.java (frameworks\base\cmds\input\src\com\android\commands\input)
//1、捕获输入事件
01-01 00:34:12.298349  8599  8599 I InputManager: PERF:injectInputEvent: MotionEvent { action=ACTION_UP...

//2、准备创建activity
01-01 00:34:12.311531  1135  3103 I am_create_task: [0,38]
01-01 00:34:12.311755  1135  3103 I am_create_activity: [0,201384592,38,test2.com.myapplication/.MainActivity,android.intent.action.MAIN...

//3、焦点切换
01-01 00:34:12.318456  1135  3103 I am_focused_stack: [0,1,0,startedActivity setFocusedActivity]
01-01 00:34:12.323919  1135  3103 I am_focused_activity: [0,test2.com.myapplication/.MainActivity,startedActivity]

//4、上一个activity的暂停,如此处是launcher桌面
01-01 00:34:12.324637  1135  3103 I am_pause_activity: [0,125629571,com.android.launcher/.Launcher]

01-01 00:34:12.342909  2699  2699 I am_on_paused_called: [0,com.android.launcher.Launcher,handlePauseActivity]

//5、进程启动
01-01 00:34:12.398299  1135  1698 I am_proc_start: [0,8610,10123,test2.com.myapplication,activity,test2.com.myapplication/.MainActivity]

//6、绑定与创建application
01-01 00:34:12.429265  1135  1697 I am_proc_bound: [0,8610,test2.com.myapplication]

//7、创建activity实例并且调用onCreate
01-01 00:34:12.439813  1135  1697 I am_restart_activity: [0,201384592,38,test2.com.myapplication/.MainActivity]

//8、调用active的resume方法
01-01 00:34:12.651913  8610  8610 I am_on_resume_called: [0,test2.com.myapplication.MainActivity,LAUNCH_ACTIVITY]

//9、界面添加完成并可见
01-01 00:34:12.795221  1135  1204 I am_activity_launch_time: [0,201384592,test2.com.myapplication/.MainActivity,417,417]

//10、上一个activity的停止
01-01 00:34:12.832973  1135  1610 I am_stop_activity: [0,125629571,com.android.launcher/.Launcher]
01-01 00:34:12.835790  2699  2699 I am_on_stop_called: [0,com.android.launcher.Launcher,handleStopActivity]

下面主要按照上述流程部分来讲解:

  • 1、捕获输入事件
  • 2、准备创建activity
  • 3、焦点切换
  • 4、上一个activity的暂停,如此处是launcher桌面
  • 5、进程启动
  • 6、绑定与创建application
  • 7、创建activity实例并且调用onCreate
  • 8、调用active的resume方法
  • 9、界面添加完成并可见
  • 10、上一个activity的停止

此处讲解的流程:
=> 在桌面(如android原生luancher:com.android.launcher) -> 点击test2测试应用的图标(test2.com.myapplication) -> test2完全显示给用户

ps:测试应用test2在该操作之前是没有进程在后台运行的。

2. 捕获输入事件

输入事件一般情况有2种: 一种是从触摸屏点击屏幕;一种是自动化软件模拟点击事件。

捕获输入事件流程图
图2.1 捕获输入事件流程图

2.1 触摸点击屏幕

当用户手动点击的时候,会输出类似如下日志,如果没有可以自行添加:

01-01 22:31:24.065   868  1085 D InputReader: PERF:AMOTION_EVENT_ACTION_POINTER_UP:Up

代码位置:
InputReader.cpp (frameworks\native\services\inputflinger)

    //读取输入事件,分发点击事件给上层
    void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
	//...
	// Dispatch pointer up events.
	while (!upIdBits.isEmpty()) {
	    uint32_t upId = upIdBits.clearFirstMarkedBit();
	    {
	        //输出点击抬起的log,这段log原生是没有的
	        ALOGD("PERF:AMOTION_EVENT_ACTION_POINTER_UP:Up");

	        //分发action up的事件
	        dispatchMotion(when, policyFlags, mSource,
	                AMOTION_EVENT_ACTION_POINTER_UP, 0, 0, metaState, buttonState, 0,
	        //...
	    }
	}
	    
	// Dispatch pointer down events using the new pointer locations.
	while (!downIdBits.isEmpty()) {
	//...
	    if (dispatchedIdBits.count() == 1) {
	        // First pointer is going down.  Set down time.
	        mDownTime = when;
	        //输出点击按下的log,这段log原生是没有的
	        ALOGD("PERF:AMOTION_EVENT_ACTION_POINTER_DOWN:Down");
	    }
	    //分发action down的事件
	    dispatchMotion(when, policyFlags, mSource,
	        AMOTION_EVENT_ACTION_POINTER_DOWN, 0, 0, metaState, buttonState, 0,
	//...

一般响应时间从点击抬起开始算时间(一般认为用户手抬起了,就认为用户的操作已经表述清楚,需要尽快处理)

2.2 自动化模拟点击事件

此处方法介绍最常见的2种:

  1. 使用Instrumentation(代理控制框架)
  2. 使用adb指令:input tap

两种达到的效果是一致的,至于开始的地方可以添加自己想要的日志信息。

1、使用Instrumentationm模拟点击事件

    //创建Instrumentation实例
    Instrumentation inst = new Instrumentation();
    //点击事件开始时间为当前时间
    long now = SystemClock.uptimeMillis();
    //发送点击按下事件
    inst.sendPointerSync(MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, mx, my, 0));
    now = SystemClock.uptimeMillis();
    //发送点击抬起事件
    inst.sendPointerSync(MotionEvent.obtain(now + 10, now + 10, MotionEvent.ACTION_UP, mx, my, 0));

上述是模拟点击事件的方法,通过这个方法其实很多应用都可以实现自动化测试,这个方法是很基础的,如有需要大家可以自行拓展。

可以添加输出日志的地方:
// frameworks/base/core/java/android/hardware/input/InputManager.java

    public boolean injectInputEvent(InputEvent event, int mode) {
        //...
        //所有模拟的点击分发事件都是通过这个地方运行,所以方法1、2都可以在这里添加日志
        Log.i(TAG, "PERF:injectInputEvent: " + event);
        return mIm.injectInputEvent(event, mode);
        //...
    }

2、使用adb指令:input tap

//mx代表的是横向x轴的位置,my代表的是纵向y轴的位置
adb shell input tap mx my

至于日志LOG输出的地方可以参考上面第一种方法中的injectInputEvent函数

3. 准备创建activity

Launcher响应打开事件(如点击一个应用图标)会启动应用,会调用Activity或者ContextImpl的startActivity启动应用。

准备创建activity流程图
图3.1 准备创建activity流程图

3.1 startActivity(ContextImpl.java)

ContextImpl.java的startActivity,应用一般都会有context实例,启动应用最简单的办法是startActivity+传递一个intent

    //startActivity启动应用这个api大家应该不陌生
    public void startActivity(Intent intent) {
        startActivity(intent, null);
    }

    public void startActivity(Intent intent, Bundle options) {
        //通过获取Instrumentation代理来启动应用,这里主要是为了规范化,都通过Instrumentation去中转
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }

3.2 execStartActivity(Instrumentation.java)

Instrumentation.java其是一个工具类,可以用来启动应用

    //execStartActivity执行启动活动对象Activity的操作
    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
            //...
            //中转AMS,具体实现是在AMS的startActivity
            int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
      }

3.3 startActivity(ActivityManagerService.java)

ActivityManagerService.java这里最后会走到ActivityStarter的 startActivityMayWait函数,才是真正的启动应用的地方

    public final int startActivity(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
        //android是支持多用户的,故此处还需要传入用户组的ID
        return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
                resultWho, requestCode, startFlags, profilerInfo, bOptions,
                UserHandle.getCallingUserId());
    }

    public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
            Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
            int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {
        // ...
        // 最后走的地方是ActivityStarter应用启动器的startActivityMayWait,
        // 此处传递的WaitResult == null,代表启动完成之后无需返回启动时间的结果
        return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
                resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
                profilerInfo, null, null, bOptions, false, userId, null, null);
    }

3.4 startActivityMayWait(ActivityStarter.java)

ActivityStarter活动对象启动器

    final int startActivityMayWait(IApplicationThread caller, int callingUid,
        //...
        //通过PMS解析意图
        ResolveInfo rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId);
        //...
        //获取该意图的活动对象Activity的Info信息,里面有需要启动应用的信息如包名test2
        ActivityInfo aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags, profilerInfo);
        //这里才是真正启动应用的地方
        int res = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,
        //...
        //outResult输出activity的启动结果,一般自动化测试的中使用
            if (outResult != null) {
                    //...
                    do {
                        try {
                            //只有等到mService.notify()或者mService.notifyAll()此处才会继续执行下去
                            mService.wait();
                        } catch (InterruptedException e) {
                        }
                    } while (outResult.result != START_TASK_TO_FRONT
                            && !outResult.timeout && outResult.who == null);
                    //...
            }
        ...
    }

启动活动对象之前会先解析意图,和activity的Info,使用AMS的startActivity传递的outResult参数一般都是null,故不会等待结果。

ps:自动化测试中使用的是AMS的startActivityAndWait,里面传递的WaitResult对象不为null,此时会等到界面绘制完成才会返回结果

3.5 startActivityLocked

    final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
        //...
        //新建需要启动活动对象activity(如test2)的ActivityRecord
        ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
                intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
                requestCode, componentSpecified, voiceSession != null, mSupervisor, container,
                options, sourceRecord);
        //...
        //看看之前是否还有未启动的活动对象
        doPendingActivityLaunchesLocked(false);
        //...
        //这里是真正起到进程的地方,倒数第三个参数doResume=true
        err = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor, 
                startFlags, true, options, inTask);

3.6 startActivityUnchecked

  1. 此处会启动新的ActivityStack,输出am_create_task
  2. 输出am_create_activity,准备创建activity
  3. 设置焦点、焦点切换
  4. 上一个activity的onpause
    private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask) {
        //...
        //初始化一些参数,如mUserLeaving==true就是在这里设置的
        setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                voiceInteractor);

        //...
        if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
                && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
            newTask = true;
            //这里会进来,启动新的ActivityStack  mTargetStack
            setTaskFromReuseOrCreateNewTask(taskToAffiliate);
            //...
        }

        if (newTask) {
            //属于新的task,会在event log中输出am_create_task
            EventLog.writeEvent(
                    EventLogTags.AM_CREATE_TASK, mStartActivity.userId, mStartActivity.task.taskId);
        }
        //event log中输出am_create_activity,开始启动应用
        ActivityStack.logStartActivity(
                EventLogTags.AM_CREATE_ACTIVITY, mStartActivity, mStartActivity.task);

        //...
        //stack中的startActivityLocked,此处设置mStartActivity.task(TaskRecord)的
        //topRunningActivityLocked为test2
        mTargetStack.startActivityLocked(mStartActivity, newTask, mKeepCurTransition, mOptions);

        //mDoResume == true,会进入此处
        if (mDoResume) {
            //mLaunchTaskBehind没有设置过就是false,也就是会进入setFocusedActivityLocked
            if (!mLaunchTaskBehind) {
                //设置焦点,此处消耗大概6ms左右
                mService.setFocusedActivityLocked(mStartActivity, "startedActivity");
            }
            //TaskRecord最顶点的activity,如test2
            final ActivityRecord topTaskActivity = mStartActivity.task.topRunningActivityLocked();
            //...
            //一般情况都是走的这里,进行上一个activity的onpause(如launcher)
            mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity,
                    mOptions);
            //...
    }

4. 焦点切换

接着章节3.6的startActivityUnchecked中会调用setFocusedActivityLocked(ActivityManagerService.java),
此处会进行焦点切换。

焦点切换流程图
图4.1 焦点切换流程图

4.1 setFocusedActivityLocked(ActivityManagerService.java)

里面包括3个部分

  1. moveActivityStackToFront移动堆栈到顶端
  2. setFocusedApp在WMS中设置focus的app,属于窗体焦点变换
  3. event log中输出am_focused_activity,代表已经在AMS中设置了focus的对象
    boolean setFocusedActivityLocked(ActivityRecord r, String reason) {
        //...
        //设置AMS中foucs的活动对象
        mFocusedActivity = r;
        //...
        if (mStackSupervisor.moveActivityStackToFront(r, reason + " setFocusedActivity")) {
            //moveActivityStackToFront是true才会进来
            mWindowManager.setFocusedApp(r.appToken, true);
        }
        //...
        //此处是eventlog的am_focused_activity打印
        EventLogTags.writeAmFocusedActivity(
                mFocusedActivity == null ? -1 : mFocusedActivity.userId,
                mFocusedActivity == null ? "NULL" : mFocusedActivity.shortComponentName,
                reason);
        //...
        return true;
    }

4.2 moveActivityStackToFront(ActivityStackSupervisor.java)

移动堆栈,此处会将test2移动到堆栈顶端:

  1. mStackSupervisor.setFocusStackUnchecked,event log中输出am_focused_stack,代表堆栈的焦点已经发生变化
  2. insertTaskAtTop,将test2的stack移动到最顶端
  3. moveTaskToTop插入WMS窗体相关的task的顶端
    // moveActivityStackToFront(ActivityStackSupervisor.java)
    boolean moveActivityStackToFront(ActivityRecord r, String reason) {
        …
        stack.moveToFront(reason, task);
        return true;
    }

    // moveToFront(ActivityStack.java)
    void moveToFront(String reason, TaskRecord task) {
        //...
        //此处判断是不是主的显示设备(注意这里不是指桌面),一般都是true
        if (isOnHomeDisplay()) {
            //此处会设置mFocusedStack为当前需要启动的activity,如test2.com.myapplication, 
            //getFocusedStack时就会是test2
            mStackSupervisor.setFocusStackUnchecked(reason, this);
        }
        if (task != null) {
            //此处是修改activity的task,会将test2放在mTaskHistory中,
            //当调用topRunningActivityLocked(ActivityStack)判断顶端activity的时候就会返回test2
            insertTaskAtTop(task, null);
        }

        if (task != null) {
            //此处是修改WMS中的task,对应于wm_task_moved: [44,1,2],
            //44代表的是移动task,1代表移动到顶端,2代表移动到task的哪个位置
            //顶端的话最后一个参数是mTasks.size()(也就是上面的2)
            //stack是栈,后进先出,所以放的位置是List最后面
            mWindowManager.moveTaskToTop(task.taskId);
        }
    }

    // setFocusStackUnchecked(ActivityStackSupervisor.java)
    void setFocusStackUnchecked(String reason, ActivityStack focusCandidate) {
        //...
        //如果需要设置的焦点focusCandidate和之前focus的不一样,会进入此处
        if (focusCandidate != mFocusedStack) {
            //更新堆栈焦点状态
            mLastFocusedStack = mFocusedStack;
            mFocusedStack = focusCandidate;
            //此处是eventlog的am_focused_stack打印
            EventLogTags.writeAmFocusedStack(
                    mCurrentUser, mFocusedStack == null ? -1 : mFocusedStack.getStackId(),
                    mLastFocusedStack == null ? -1 : mLastFocusedStack.getStackId(), reason);
        }
        //...
    }

4.3 setFocusedApp(WindowManagerService.java)

setFocusedApp用户更新当前WMS focus的窗体

    //moveFocusNow上面章节4.1传递进来的就是true
    public void setFocusedApp(IBinder token, boolean moveFocusNow) {
        //...
        //newFocus是test2,mFocusedApp是launcher,故changed==true
        final boolean changed = mFocusedApp != newFocus;
        //...
        //moveFocusNow==true, changed==true,故这里是会进来的
        if (moveFocusNow && changed) {
            //更新窗体显示
            updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
        //...
    }

updateFocusedWindowLocked会先遍历查找最新的焦点,如果窗体焦点有变化,则进行更新。
至于遍历查找窗体焦点是在computeFocusedWindowLocked完成的
此处会输出Changing focus from Window*** to null

    boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
        //会先调用查找最新的焦点窗口,如果返回null代表当前没有窗口聚焦
        WindowState newFocus = computeFocusedWindowLocked();
        //当焦点有变化的时候会进入此处
        if (mCurrentFocus != newFocus) {
            //...
            //如果需要焦点变化的日志输出,可以打开下面的静态变量
            if (DEBUG_FOCUS_LIGHT || localLOGV) Slog.v(TAG_WM, "Changing focus from " +
                    mCurrentFocus + " to " + newFocus + " Callers=" + Debug.getCallers(4));
            //更新焦点
            final WindowState oldFocus = mCurrentFocus;
            mCurrentFocus = newFocus;
            //...

            if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
                //更新input foucus的焦点,这个是在界面focus的时候做的
                mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows);
            }

            //...
        }
        return false;
    }

computeFocusedWindowLocked这个函数是会遍历当前显示的设备(可能有多个显示设备),返回当前系统焦点所在的窗体

    private WindowState computeFocusedWindowLocked() {
        final int displayCount = mDisplayContents.size();
        //遍历当前显示的设备
        for (int i = 0; i < displayCount; i++) {
            final DisplayContent displayContent = mDisplayContents.valueAt(i);
            //查找每一个显示设备是否有窗体占用着焦点
            WindowState win = findFocusedWindowLocked(displayContent);
            if (win != null) {
                return win;
            }
        }
        return null;
    }

findFocusedWindowLocked查找当前需要focus的窗体,如果之前有focus,而且需要切换到别的focus的activity窗体的时候,会把上一个launcher的窗体的焦点先切换成null

    WindowState findFocusedWindowLocked(DisplayContent displayContent) {
        final WindowList windows = displayContent.getWindowList();
        for (int i = windows.size() - 1; i >= 0; i--) {
            final WindowState win = windows.get(i);

            //如果窗体可以接受按键响应
            if (!win.canReceiveKeys()) {
                continue;
            }
            //...
            //当前接收按键相应的窗体仍是launcher(test2进程都还未启动)
            AppWindowToken wtoken = win.mAppToken;
            //...
            //当便遍历到token与mFocusedApp相等(test2)的时候,而且该window是允许聚焦
            if (mFocusedApp == token && token.windowsAreFocusable()) {
                //那我们就会把之前的焦点更换,就是把launcher焦点切换成null
                return null;
            }
            //...
    }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android N进程启动流程(一)(捕获输入事件、准备创建activity、焦点切换) 的相关文章

  • 解决虚拟机安装64位系统“此主机支持 Intel VT-x,但 Intel VT-x 处于禁用状态”的问题

    在Intel i5 4460的主机上安装Ubuntu 14 04 xff08 64位 xff09 xff0c 虚拟机使用的是Vmware 10 0 0 build 1295980 在新建好虚拟机 xff0c 运行时候就出现了VMware W
  • 【CentOS7】yum安装时出现错误Errno 14 Couldn't resolve host的解决办法

    在安装python sphinx时出现Errno 14 Couldn 39 t resolve host xff0c 什么东东 xff1f root 64 localhost jansson 1 2 yum install python s
  • SAS (Serial Attached SCSI) 技术详解

    xff08 一 xff09 什么是SAS SAS xff08 Serial Attached SCSI xff09 即串行SCSI技术 xff0c 是一种磁盘连接技术 xff0c 它综合了并行SCSI和串行连接技术 xff08 如FC SS
  • mdadm命令解析

    mdadm命令解析 一 在linux系统中目前以MD Multiple Devices 虚拟块设备的方式实现软件RAID 利用多个底层的块设备虚拟出一个新的虚拟设备 并且利用条带化 stripping 技术将数据块均匀分布到多个磁盘上来提高
  • fio使用指南

    这个文档是对fio 2 0 9 HOWTO文档的翻译 xff0c fio的参数太多了 xff0c 翻译这个文档时并没有测试每一个参数的功能和使用方法 xff0c 只有少量参数做了试验 xff0c 大部分的参数采用的是根据字面翻译或是个人理解
  • CentOS系统安装VNC详细步骤

    下面是总结的详细配置步骤 xff0c 分享给大家 一 VNC 远程控制 CentOS 系统 1 查看 CentOS 系统中是否有安装 vnc xff08 默认安装 xff09 输入命令 xff1a rpm q vnc vnc server
  • 怎么看电脑CPU是几核?

    对硬件的东西知之甚少 xff0c 保存在此 xff0c 以便以后查阅 方法一 鼠标右键桌面最下方的任务栏 任务管理器 性能 查看cpu使用记录 xff0c 有几个窗口就是几核心cpu xff1b 方法二 右键 我的电脑 属性 硬件 设备管理
  • linux批量远程控制

    前提 xff1a 勉ssh秘钥登陆 使用pdssh 1 配置主机列表 web list root 64 192 168 1 2 22 root 64 192 168 1 3 182 22 root 64 192 168 1 4 181 22
  • DataBinding详解

    一 开启DataBinding 在build gradle文件添加 android dataBinding enabled true 二 生成DataBinding布局 1 光标在布局文件的根布局 gt 点击Alt 43 Enter gt
  • python批量删除txt文件中指定行

    应用场景 xff1a 在深度学习项目中 xff0c 常常会处理各种数据集 比如已经标注好的数据标签有三类 xff1a 人形 汽车 猫 xff0c 有一个新项目 xff0c 只需要识别人形 xff0c 那就需要把这个数据集进行处理 xff0c
  • Python.循环

    一 循环结构 xff1a 是程序控制流程的三大结构之一 xff08 三大手段 方法之一 xff09 通过指定的条件将循环体进行有限次或无限次 xff08 死循环 xff09 地重复运行 在Python中主要用到while和for函数实现 二
  • bind详细学习

    DNS DNS xff1a Domain Name Service 应用层协议 xff08 C S 53 udp 53 tcp xff09 域名 分类 xff1a 最多可以有127级域名 根域一级域名 xff1a Top Level Dom
  • C++ 指针常量、常量指针和常指针常量

    1 指针常量 如果在定义指针变量时候 xff0c 指针变量前用const修饰 xff0c 被定义的指针变量就变成了一个指针类型的常变量 xff0c 指针类型的常变量简称为指针常量 格式如下 数据类型 const 指针变量 61 变量名 xf
  • Jetpack初尝试 NavController,LiveData,DataBing,ViewModel,Paging

    文章目录 插件配置NavController 使用1 创建xml2 创建Activity3 res 创建navigation nav garden和说明流程 ViewModel 负责页面的数据LiveData onChangedObserv
  • 移动固态硬盘删除分区(包括EFI分区)

    新换电脑原始的固态硬盘大小是500G xff0c 担心不够用 xff0c 但电脑只有一个放置固态硬盘的位置 xff0c 所以打算将原装的500G固态换成1T固态 xff0c 原始500G固态改成移动固态硬盘 原始500G固态一共有4个分区
  • Linux-OneNote的安装和使用

    P3X OneNote是Linux的非官方应用程序 xff0c 允许用户直接从Linux平台创建和共享笔记 安装 第一种方法 xff1a Snap安装 此部分转载自链接 在Linux操作系统上使用非官方版OneNote最快捷 最简单的方法是
  • Visual Studio运行控制台程序一闪而退的解决方法!

    初学者在使用Visual Studio各个版本时 xff0c 在进行调试运行时 xff0c 会发现控制台总是一闪即退 xff0c 输出结果的窗口无法保持打开状态 xff01 其实问题是你执行时按的是F5还是Ctrl 43 F5 xff0c
  • C# Microsoft.ClearScript.V8脚本使用

    1 ClearScript支持的功能和适用场景 微软的 net是非常强大和灵活的 xff0c 除了C 体系脚本扩展 xff0c 也支持其他流行的脚本扩展 xff0c Microsoft ClearScript V8就是一个 NET绑定到Go
  • WSL2中使用systemctl报错Failed to connect to bus: Host is down

    问题截图 xff1a System has not been booted with systemd as init system PID 1 Can t operate Failed to connect to bus Host is d
  • dom4j的Element

    1 Element其中的一个Element为dom4j 创建Element xff0c 通过DocumentHelper createElement xff08 name xff09 如 xff1a Element result 61 Do

随机推荐