Android 11 Activity启动流程分析

2023-11-16

Android 11 Activity启动流程分析

本片文章是基于Android 11版本来分析应用Activity的启动的,Activity是Android四大组件中最重要的一个,因为我们所有的页面基本上都是基于Activity开发的,所以了解Activity是怎么启动的是很有必要的,接下来我们就来分析一下Activity的启动流程。

Activty启动调用时序图

在这里插入图片描述

下面我们就来根据这张时序图来具体看一下代码中的调用流程
我们一般在开发中会用下面的方法来启动一个Activity,那我们分析源码的如果其实就是这里

startActivity(intent);

点击上面方法会跳转到Activity的startActivity方法中代码如下:

public void startActivity(Intent intent) {
       this.startActivity(intent, null);
   } 

这个方法最终会调用到Activity的startActivityForResult()方法中,接下来我们看一下startActivityForResult()方法干了什么事情。


public void startActivityForResult(
            String who, Intent intent, int requestCode, @Nullable Bundle options) {
         ...
         //1
        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
                this, mMainThread.getApplicationThread(), mToken, who,
                intent, requestCode, options);
        ...
    }

从注释1中可以看出这个方法又调用了Instrumentation的execStartActivity()方法,接下来我们就来看一下execStartActivity方法内部是怎么实现的:

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
            
          //省略ActivityMonitor相关代码
        try {
            intent.migrateExtraStreamToClipData(who);
            intent.prepareToLeaveProcess(who);
            //1
            int result = ActivityTaskManager.getService().startActivity(whoThread,
                    who.getBasePackageName(), who.getAttributionTag(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()), token,
                    target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }

关键部分在注释1处,这个调用了ActivityTaskManager的getservice方法,这个方法其实获取的是IActivityTaskManager这个AIDL接口,具体的实现类是ActivityTaskManagerService,接下来我们看一下ActivityTaskManagerService中的startActivity的实现,代码如下:

 @Override
    public final int startActivity(IApplicationThread caller, String callingPackage,
            String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo,
            String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo,
            Bundle bOptions) {
                //1
        return startActivityAsUser(caller, callingPackage, callingFeatureId, intent, resolvedType,
                resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions,
                UserHandle.getCallingUserId());
    }

从注释1中可以看出这个调用了自己的startActivityAsUser方法,这里有两个重载的方法,我们主要看第二个startActivityAsUser()。代码如下:

 private int startActivityAsUser(IApplicationThread caller, String callingPackage,
            @Nullable String callingFeatureId, Intent intent, String resolvedType,
            IBinder resultTo, String resultWho, int requestCode, int startFlags,
            ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {
        assertPackageMatchesCallingUid(callingPackage);
        enforceNotIsolatedCaller("startActivityAsUser");

        userId = getActivityStartController().checkTargetUser(userId, validateIncomingUser,
                Binder.getCallingPid(), Binder.getCallingUid(), "startActivityAsUser");

        // TODO: Switch to user app stacks here.
        //1
        return getActivityStartController().obtainStarter(intent, "startActivityAsUser")
                .setCaller(caller)
                .setCallingPackage(callingPackage)
                .setCallingFeatureId(callingFeatureId)
                .setResolvedType(resolvedType)
                .setResultTo(resultTo)
                .setResultWho(resultWho)
                .setRequestCode(requestCode)
                .setStartFlags(startFlags)
                .setProfilerInfo(profilerInfo)
                .setActivityOptions(bOptions)
                .setUserId(userId)
                .execute();

    }

这段代码关键的部分是注释1处,这个里构建了一个ActivityStarter并设置了一些配置,最终执行了ActivityStarter的execute()方法,接下来我们就看一下execute方法的具体实现。代码如下:

int execute() {
        try {
            // Refuse possible leaked file descriptors
            if (mRequest.intent != null && mRequest.intent.hasFileDescriptors()) {
                throw new IllegalArgumentException("File descriptors passed in Intent");
            }

                 ...
                 //1
                res = executeRequest(mRequest);

                ...
                return getExternalResult(mRequest.waitResult == null ? res
                        : waitForResult(res, mLastStartActivityRecord));
            }
        } finally {
            onExecutionComplete();
        }
    }

这个方法比较长,我们只看关键部分其他部分这里就删除掉了,我们主要看一下注释1部分。其实这个的mRequest是在我们上面构建ActivityStarter的时候就会被创建,并且上面的对ActivityStarter的一些参数设置其实是设置给了Request这个内部类。最终回调用ActivityStarter的executeRequest方法。接下我们看一下executeRequest方法内部是怎么实现的。代码如下:

 private int executeRequest(Request request) {
        //注释1
        final ActivityRecord r = new ActivityRecord(mService, callerApp, callingPid, callingUid,
                callingPackage, callingFeatureId, intent, resolvedType, aInfo,
                mService.getGlobalConfiguration(), resultRecord, resultWho, requestCode,
                request.componentSpecified, voiceSession != null, mSupervisor, checkedOptions,
                sourceRecord);
        mLastStartActivityRecord = r;

        if (r.appTimeTracker == null && sourceRecord != null) {
            // If the caller didn't specify an explicit time tracker, we want to continue
            // tracking under any it has.
            r.appTimeTracker = sourceRecord.appTimeTracker;
        }

        final ActivityStack stack = mRootWindowContainer.getTopDisplayFocusedStack();

        // If we are starting an activity that is not from the same uid as the currently resumed
        // one, check whether app switches are allowed.
        if (voiceSession == null && stack != null && (stack.getResumedActivity() == null
                || stack.getResumedActivity().info.applicationInfo.uid != realCallingUid)) {
            if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid,
                    realCallingPid, realCallingUid, "Activity start")) {
                if (!(restrictedBgActivity && handleBackgroundActivityAbort(r))) {
                    mController.addPendingActivityLaunch(new PendingActivityLaunch(r,
                            sourceRecord, startFlags, stack, callerApp, intentGrants));
                }
                ActivityOptions.abort(checkedOptions);
                return ActivityManager.START_SWITCHES_CANCELED;
            }
        }

        mService.onStartActivitySetDidAppSwitch();
        mController.doPendingActivityLaunches(false);
        //2
        mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,
                request.voiceInteractor, startFlags, true /* doResume */, checkedOptions, inTask,
                restrictedBgActivity, intentGrants);

        if (request.outActivity != null) {
            request.outActivity[0] = mLastStartActivityRecord;
        }

        return mLastStartActivityResult;
    }

这个方法也比较长这里我们省略了一下状态判断和参数赋值的逻辑,我们主要看注释1部分,这里构建了一个ActivityRecord,这个应该是我们比较熟悉的类了其实这个类就是对Activity的一些信息的封装类,代表着返回栈的一条记录。关键部分是注释2处,这里可以看到它调用的自己的startActivityUnchecked方法,接下来我们看一下startActivityUnchecked方法的具体实现代码如下:

 private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
                IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
                int startFlags, boolean doResume, ActivityOptions options, Task inTask,
                boolean restrictedBgActivity, NeededUriGrants intentGrants) {
             //1
            result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
                    startFlags, doResume, options, inTask, restrictedBgActivity, intentGrants);
        } finally {
        ...
        }
        return result;
    }

这个方法在注释1处调用了自己的startActivityInner()方法,代码如下:

   int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,
            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
            int startFlags, boolean doResume, ActivityOptions options, Task inTask,
            boolean restrictedBgActivity, NeededUriGrants intentGrants) {
        setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,
                voiceInteractor, restrictedBgActivity);
            ...
                //1
                mRootWindowContainer.resumeFocusedStacksTopActivities(
                        mTargetStack, mStartActivity, mOptions);
            ...
        return START_SUCCESS;
    }

这个方法的作用主要是,启动一个活动,并确定该活动是否应该添加到现有活动的顶部,省略了一些关于任务栈的判断逻辑,关键部分是注释1处,可以看出它调用了RootWindowContainer这个类的resumeFocusedStacksTopActivities方法。这个方法的代码如下:

boolean resumeFocusedStacksTopActivities(
            ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions) {
            ...
            if (!resumedOnDisplay) {
                // In cases when there are no valid activities (e.g. device just booted or launcher
                // crashed) it's possible that nothing was resumed on a display. Requesting resume
                // of top activity in focused stack explicitly will make sure that at least home
                // activity is started and resumed, and no recursion occurs.
                final ActivityStack focusedStack = display.getFocusedStack();
                if (focusedStack != null) {
                    //1
                    result |= focusedStack.resumeTopActivityUncheckedLocked(target, targetOptions);
                } else if (targetStack == null) {
                    result |= resumeHomeActivity(null /* prev */, "no-focusable-task",
                            display.getDefaultTaskDisplayArea());
                }
            }
        }

        return result;
    }

这段代码终于看到了我们熟悉的类ActivityStack,关键代码是注释1部分,这里调用了ActivityStack的
resumeTopActivityUncheckedLocked()方法,这个方法的代码如下:

    boolean resumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options) {
            ...
            // Protect against recursion.
            mInResumeTopActivity = true;
            //1
            result = resumeTopActivityInnerLocked(prev, options);
            ...
        return result;
    }

这个方法的关键部分是注释1,这里它又调用了自己的resumeTopActivityInnerLocked()方法,这个方法的代码如下:

  private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
        if (!mAtmService.isBooting() && !mAtmService.isBooted()) {
            // Not ready yet!
            return false;
        }
        ...
        ActivityRecord next = topRunningActivity(true /* focusableOnly */);
        ...
           //1
            mStackSupervisor.startSpecificActivity(next, true, true);
        }

        return true;
    }

这个代码也很长,主要住一些ActivityRecord类状态的判断和更新,关键部分是最后注释1的部分,这里会调用ActivityStackSupervisor的startSpecificActivity()方法,这个方法代码如下:

 void startSpecificActivity(ActivityRecord r, boolean andResume, boolean checkConfig) {
        // Is this activity's application already running?
        final WindowProcessController wpc =
                mService.getProcessController(r.processName, r.info.applicationInfo.uid);

        boolean knownToBeDead = false;
        if (wpc != null && wpc.hasThread()) {
            try {
                //1
                realStartActivityLocked(r, wpc, andResume, checkConfig);
                return;
            } catch (RemoteException e) {
                Slog.w(TAG, "Exception when starting activity "
                        + r.intent.getComponent().flattenToShortString(), e);
            }
            ...
        }
    }

这里关键部分是注释1处,这里调用了自己的realStartActivityLocked()方法,这里从名字可以看出是真正的启动Activity了,这个方法的代码如下:

  boolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc,
            boolean andResume, boolean checkConfig) throws RemoteException {

                   ...
                  //1 
                // Create activity launch transaction.
                final ClientTransaction clientTransaction = ClientTransaction.obtain(
                        proc.getThread(), r.appToken);

                final DisplayContent dc = r.getDisplay().mDisplayContent;
               //2
                 clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
                        System.identityHashCode(r), r.info,
                        // TODO: Have this take the merged configuration instead of separate global
                        // and override configs.
                        mergedConfiguration.getGlobalConfiguration(),
                        mergedConfiguration.getOverrideConfiguration(), r.compat,
                        r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(),
                        r.getSavedState(), r.getPersistentSavedState(), results, newIntents,
                        dc.isNextTransitionForward(), proc.createProfilerInfoIfNeeded(),
                        r.assistToken, r.createFixedRotationAdjustmentsIfNeeded()));

                // Set desired final state.
                final ActivityLifecycleItem lifecycleItem;
                if (andResume) {
                    lifecycleItem = ResumeActivityItem.obtain(dc.isNextTransitionForward());
                } else {
                    lifecycleItem = PauseActivityItem.obtain();
                }
                clientTransaction.setLifecycleStateRequest(lifecycleItem);

                // Schedule transaction.
                //3
                mService.getLifecycleManager().scheduleTransaction(clientTransaction);

              ...

        return true;
    }

这一段代码注释1处创建ClientTransaction对象,然后在注释2处通过clientTransaction.addCallback添加一个LaunchActivityItem对象,(LaunchActivityItem.obtain方法返回的是一个LaunchActivityItem对象)此处尤为重要,因为下面的流程会回调到这LaunchActivityItem这个类中。
然后调用ClientLifecycleManager的scheduleTransaction方法把当前的是事务进行提交。接下来看一下ClientLifecycleManager的scheduleTransaction的具体实现,代码如下:

    void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
        final IApplicationThread client = transaction.getClient();
        transaction.schedule();
       ...
    }

这里调用了ClientTransaction的schedule()方法,schedule方法的代码如下:

    public void schedule() throws RemoteException {
        mClient.scheduleTransaction(this);
    }

这里调用了mClient的scheduleTransaction()方法,那mClient是谁呢?其实mClient其实就是
IApplicationThread这个AIDL接口,具体的实现类其实是ActivityThread的内部类ApplicationThread,
从这个类继承自IApplicationThread.Stub我们就可以看出IApplicationThread是一个AIDL接口。那接下来我们看一下ApplicationThread的scheduleTransaction方法。代码如下:

        public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
            ActivityThread.this.scheduleTransaction(transaction);
        }

这个方法调用了ActivityThread的scheduleTransaction()方法,但是我们在ActivityThread并没有找到这个方法,其实这个方法在ActivityThread的父类ClientTransactionHandler 事务处理器中。接下类我们看一下ClientTransactionHandler中的scheduleTransaction方法。代码如下:

 void scheduleTransaction(ClientTransaction transaction) {
        transaction.preExecute(this);
        sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
    }

这里我们可以看到它是通过调用ActivityThread的sendMessage方法,给handler的子类H发了一个what为 EXECUTE_TRANSACTION obj为ClientTransaction的消息接下来我们在H类的handleMessage方法中可以看到调用了TransactionExecutor事务执行器的execute()方法并把ClientTransaction 作为参数,execute方法代码如下:

    public void execute(ClientTransaction transaction) {
        if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Start resolving transaction");
        ...
        //1
        executeCallbacks(transaction);
        //2
        executeLifecycleState(transaction);
        ...
    }

这个方法主要关键部分是注释1和注释2,注释2是Activity声明周期的,我们主要还是来看注释1处,这里调用自己的executeCallbacks方法,具体代码如下:

    public void executeCallbacks(ClientTransaction transaction) {
        final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
         ...
        for (int i = 0; i < size; ++i) {
            final ClientTransactionItem item = callbacks.get(i);
             ...
             //1
            item.execute(mTransactionHandler, token, mPendingActions);
            item.postExecute(mTransactionHandler, token, mPendingActions);
           ...
        }
    }

从注释1可以看到这里调用ClientTransactionItem的execute方法,这就是我为什么在上面分析realStartActivityLocked方法是说,ClientTransactionItem这个类很重要,就体现在这里。我们发现这个类是一个抽象类,具体的实现类其实就是我们上面realStartActivityLocked中注释2中构建的LaunchActivityItem,所以我们看一下LaunchActivityItem的execute方法。

    public void execute(ClientTransactionHandler client, IBinder token,
            PendingTransactionActions pendingActions) {
        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
        ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
                mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
                mPendingResults, mPendingNewIntents, mIsForward,
                mProfilerInfo, client, mAssistToken, mFixedRotationAdjustments);
           //1
         client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
    }

在这里我们终于看到我们熟悉的东西了handleLaunchActivity方法(注释1),那这个client是谁呢?通过上面的分析我们可以知道这个client其实就是ActivityThread这个类,ActivityThread继承自ClientTransactionHandler。接下来我们看一下ActivityThread的handleLaunchActivity方法的具体实现。代码如下:

    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
        ...
        final Activity a = performLaunchActivity(r, customIntent);

          ....

        return a;
    }

这个方法内部调用了自己的performLaunchActivity()方法。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...

        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            //1
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }

        try {
            Application app = r.packageInfo.makeApplication(false, mInstrumentation);

            if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
            if (localLOGV) Slog.v(
                    TAG, r + ": app=" + app
                    + ", appName=" + app.getPackageName()
                    + ", pkg=" + r.packageInfo.getPackageName()
                    + ", comp=" + r.intent.getComponent().toShortString()
                    + ", dir=" + r.packageInfo.getAppDir());

                ...
                // Activity resources must be initialized with the same loaders as the
                // application context.
                appContext.getResources().addLoaders(
                        app.getResources().getLoaders().toArray(new ResourcesLoader[0]));

                appContext.setOuterContext(activity);
                //2
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);
                ...
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    //3
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
             ...

        return activity;
    }

在注释1处我们可以发现它会创建Activity实例,然后调用还创建了appContext的然后在注释2处调用Activity的attach方法来绑定appContent,接下来在注3处调用了Instrumentation这个类的callActivityOnCreate()方法。

    public void callActivityOnCreate(Activity activity, Bundle icicle) {
        prePerformCreate(activity);
        activity.performCreate(icicle);
        postPerformCreate(activity);
    }

这里调用了Activity的performCreate方法,具体实现代码如下:

    final void performCreate(Bundle icicle, PersistableBundle persistentState) {
        ...
        if (persistentState != null) {
            onCreate(icicle, persistentState);
        } else {
            onCreate(icicle);
        }
         ...
    }

到这里就会回调到我们创建Activity复写的onCreate方法了。到这里整个Activity的启动流程也就分析完了。从文章最上面的调用时序图可以看出Activity的启动流程还是比较复杂的,涉及的类也比较多,每个方法的逻辑也是非常复杂我们千万不要抠细节要不然就会迷失在代码的海洋中无法自拔。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android 11 Activity启动流程分析 的相关文章

随机推荐

  • Oracle 数据导入*.sql 提示ORA-01950

    今天执行远程Oracle 数据库数据导入时 提示ORA 01950 超出导入文件大小限制 cmd 远程连接oracle 数据库 sqlplus root root1234 192 50 68 246 orcl 导入指定位置的 sql文件 E
  • 双向广度优先搜索(介绍)

    双向广度优先搜索 广度优先搜索遵循从初始结点开始一层层扩展直到找到目标结点的搜索规则 它只能较好地解决状态不是太多的情况 承受力很有限 如果扩展结点较多 而目标结点又处在较深层 采用前文叙述的广度搜索解题 搜索量巨大是可想而知的 往往就会出
  • http请求 405错误

    http请求 405错误 方法不被允许 Method not allowed 405错误常常伴随着POST请求 所有有人会告诉你这些 但是时候他并不能解决你的问题 所以我说一点不一样的 假如你有一个user类 里面有两个属性userName
  • nat技术简介(转载)

    NAT Network Address Translation 网络地址转换 是将IP数据报文头中的IP地址转换为另一个IP地址的过程 在实际应用中 NAT主要用于实现私有网络访问公共网络的功能 这种通过使用少量的公网IP地址代表较多的私网
  • 快速搭建你的api数据交易平台-图文开发教程

    项目背景 如果你需要开发搭建自己的api数据交易平台 并且能在平台上面进行对客户管理 接口管理 套餐管理 账单管理 充值管理 那么下面将来介绍如何使用接口大师这个框架快速进行开发 安装 PhalApi专业版的运行环境要求如下 操作系统 Wi
  • nVidia TK1 基于深度学习框架 Caffe 的物体识别

    By Toradex 胡珊逢 1 简介 深度学习目前正吸引着越来越多人的关注 相关算法框架层出不穷 例如TensorFlow Caffe Keras CNTK Torch7等等 这些算法在数据分析 聚类 识别和预测方面提供了极大的帮助 因此
  • Python爬虫-某网酒店数据

    前言 本文是该专栏的第5篇 后面会持续分享python爬虫案例干货 记得关注 本文以某网的酒店数据为例 实现根据目标城市获取酒店数据 具体思路和方法跟着笔者直接往下看正文详细内容 附带完整代码 正文 地址 aHR0cHM6Ly93d3cuY
  • 基于核概念的KCCA算法

    基于核概念的KCCA算法 1 由CCA算法过渡至KCCA算法 2 KCCA算法的原理与推导 1 由CCA算法过渡至KCCA算法 典型相关分析 CCA 算法是一种标准的统计技术 用于寻找两个最大相关的随机向量的线性投影 CCA算法是一个计算两
  • 字符串初始化赋值

    在C语言中 字符串是当做字符数组来处理的 所以字符串有两种声明方式 一种是字符数组 一种是字符指针 1 直接逐个初始化字符数组 字符数组的初始化 最容易理解的方式就是逐个字符赋给数组中各元素 char str 10 I a m h a p
  • 单片机毕设项目分享 基于stm32的智能电子秤系统 - 物联网 嵌入式 单片机

    文章目录 0 前言 1 简介 2 主要器件 3 实现效果 4 设计原理 4 1 STM32F103C8T6 4 2 HX711压力传感器 5 部分核心代码 6 最后 0 前言 这两年开始毕业设计和毕业答辩的要求和难度不断提升 传统的毕设题目
  • Linux下安装jre

    原文链接 https blog csdn net qq 34368587 article details 79559102 个人收藏教程 侵权联系我删除 现需要项目部署到Linux中 需要配置java运行环境 注 以下测试环境系统为cent
  • 我看Java虚拟机(2)---Java虚拟机内存区域详解

    虚拟机内存区域的组成 直接上图 程序计数器 对于Java方法 用来选取下一条要执行的字节码 对于本地方法 值为空 线程独有 虚拟机栈 执行Java方法 每一层都是一个栈帧 栈帧包括局部变量表 操作数栈 动态链接和方法出口等信息 线程独有 本
  • Vue使用 dhtmlx-gantt 甘特图

    使用心得和一些坑分享出来 下载 npm install dhtmlx gantt save 创建 ganttVue 组件
  • React之生命周期-setState

  • supervisor入门教程

    supervisor是什么 是一个客户端 服务器系统 允许其用户在类UNIX操作系统上控制许多进程 官方解释 简单点来讲 就是一个监控脚本运行的工具 不过他可以统一化管理 laravel的队列文档上也有相关使用方式方法 例如 定时脚本的启动
  • YUV420数据格式详解

    YUV简介 YUV格式有两大类 planar和packed 对于planar的YUV格式 先连续存储所有像素点的Y 紧接着存储所有像素点的U 随后是所有像素点的V 对于packed的YUV格式 每个像素点的Y U V是连续交叉存储的 YUV
  • 无盘游戏服务器软件,安网卫士

    2018年10月12号更新说明 请注意此版本无后台 需要注册号及收银编码的请联系客服 服务端 1 更改默认备份目录 2 当客户机无SSD施工时在BV进行显示无硬盘 3 添加游戏时 支持拖动 4 删除游戏时 取消 删除客户机文件 选项 5 取
  • gcc中-c和-o参数

    c和 o都是gcc编译器的可选参数 c表示只编译 compile 源文件但不链接 会把 c或 cc的c源程序编译成目标文件 一般是 o文件 o用于指定输出 out 文件名 不用 o的话 一般会在当前文件夹下生成默认的a out文件作为可执行
  • 次表面散射

    专题介绍 在实时渲染和离线渲染领域 对场景模型表面以及空间介质的精细化建模是增加场景真实感的重要手段 计算机图形学领域的许多科研工作者设计出一系列复杂精巧的技术理论 模拟出光线从宏观世界到微观粒子的变化规律 本期专题精选了近年来关于微表面模
  • Android 11 Activity启动流程分析

    Android 11 Activity启动流程分析 本片文章是基于Android 11版本来分析应用Activity的启动的 Activity是Android四大组件中最重要的一个 因为我们所有的页面基本上都是基于Activity开发的 所