UI的绘制流程

news2024/9/29 21:18:20

1.App的启动流程
每个App都是一个独立的进程,当一个app启动的时候,当前进程也被启动,在Android中有一个类ActivityThread,就是进程的初始类,其中main方法就是整个app的入口。ActivityThread并不是一个线程,它是一个handler类,继承ClientTransactionHandler。

public final class ActivityThread extends ClientTransactionHandler
//继承ClientTransactionHandler类
public abstract class ClientTransactionHandler {
...
}
public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

    // Install selective syscall interception
    AndroidOs.install();

    // CloseGuard defaults to true and can be quite spammy.  We
    // disable it here, but selectively enable it later (via
    // StrictMode) on debug builds, but using DropBox, not logs.
    CloseGuard.setEnabled(false);

    Environment.initForCurrentUser();

    // Make sure TrustedCertificateStore looks in the right place for CA certificates
    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
    TrustedCertificateStore.setDefaultUserDirectory(configDir);

    // Call per-process mainline module initialization.
    initializeMainlineModules();

    Process.setArgV0("<pre-initialized>");

    Looper.prepareMainLooper();

    // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
    // It will be in the format "seq=114"
    long startSeq = 0;
    if (args != null) {
        for (int i = args.length - 1; i >= 0; --i) {
            if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                startSeq = Long.parseLong(
                        args[i].substring(PROC_START_SEQ_IDENT.length()));
            }
        }
    }
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

每个app进程都有一个主线程,main方法就是通过主线程调用然后执行其中的代码,我们看到main方法中其实内容并不多,大部分是一些初始化的操作,那么app的启动是怎么做到的呢?

其实像App的启动、Activity的启动等,都是依赖系统服务完成,也就是说,ActivityThead是要和系统服务打交道,然后做启动操作,那么ActivityThead是如何和系统服务通信的呢?

1.1 ApplicationThread
在ActivityThead中有一个成员变量ApplicationThread,ApplicationThread就是ActivityThread与ActivityManagerService通信的桥梁,ActivityThread接收ActivityManagerService的命令做对应的操作。
在这里插入图片描述
当ActivityThead想要启动MainActivity时,会先通过ApplicationThread向AMS请求,AMS中存在ApplicationThread的代理,通过ApplicationThread回调给到ActivityThread做响应的处理操作,熟悉aidl流程的话这个也不难理解。
1.2 Instrumentation
如果知道Activity启动流程的话,对于这个类也不会陌生,这个类就是用于真正管理Application和Activity声明周期的类。

1.3 ActivityThread
当主线程执行main方法时,其实ActivityThread并没有创建,当执行到下面两行时,ActivityThread才真正创建完成。

ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);

public static void main(String[] args) {
        ...
        initializeMainlineModules();
        Process.setArgV0("<pre-initialized>");
        //初始化handler中的looper对象
        Looper.prepareMainLooper();
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        //创建一个activityThread,main是静态方法,调用时activityThread并没有创建
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

//位于Looper.java中
public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //将主线程赋值给sMainLooper,这就是为什么通过Looper.getMainLooper获取到主线程的原因
            sMainLooper = myLooper();
        }
    }

我们可以看到,在main方法中初始化了MainLooper,这个MainLooper会一直从主线程的MessageQueue中取出消息处理消息,保证程序的运行。所以ActivityThread是一个handler,不停的接收AMS或者PMS发来的信息,进行操作处理。ActivityThread操作的线程可以理解成主线程,也就是我们的UI线程。

所以,当我们的app启动之后,第一步就是创建Application,当然这不是在ActivityThread中直接new Application这么简单,而是需要ActivityThread与AMS通信,获取Application的信息,从而创建Application。

所以接下来,看下attach的源码:

private void attach(boolean system, long startSeq) {
    sCurrentActivityThread = this;
    mConfigurationController = new ConfigurationController(this);
    mSystemThread = system;
    if (!system) {
        RuntimeInit.setApplicationObject(mAppThread.asBinder());
        //关键点①
        final IActivityManager mgr = ActivityManager.getService();
        try {
            mgr.attachApplication(mAppThread, startSeq);
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        // Watch for getting close to heap limit.
        BinderInternal.addGcWatcher(new Runnable() {
            @Override public void run() {
                if (!mSomeActivitiesChanged) {
                    return;
                }
                Runtime runtime = Runtime.getRuntime();
                long dalvikMax = runtime.maxMemory();
                long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
                if (dalvikUsed > ((3*dalvikMax)/4)) {
                    if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
                            + " total=" + (runtime.totalMemory()/1024)
                            + " used=" + (dalvikUsed/1024));
                    mSomeActivitiesChanged = false;
                    try {
                        ActivityTaskManager.getService().releaseSomeActivities(mAppThread);
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }
                }
            }
        });
    } else {
        // Don't set application object here -- if the system crashes,
        // we can't display an alert, we just want to die die die.
        android.ddm.DdmHandleAppName.setAppName("system_process",
                UserHandle.myUserId());
        try {
            mInstrumentation = new Instrumentation();
            mInstrumentation.basicInit(this);
            ContextImpl context = ContextImpl.createAppContext(
                    this, getSystemContext().mPackageInfo);
            mInitialApplication = context.mPackageInfo.makeApplication(true, null);
            mInitialApplication.onCreate();
        } catch (Exception e) {
            throw new RuntimeException(
                    "Unable to instantiate Application():" + e.toString(), e);
        }
    }

    ViewRootImpl.ConfigChangedCallback configChangedCallback = (Configuration globalConfig) -> {
        synchronized (mResourcesManager) {
            // TODO (b/135719017): Temporary log for debugging IME service.
            if (Build.IS_DEBUGGABLE && mHasImeComponent) {
                Log.d(TAG, "ViewRootImpl.ConfigChangedCallback for IME, "
                        + "config=" + globalConfig);
            }

            // We need to apply this change to the resources immediately, because upon returning
            // the view hierarchy will be informed about it.
            if (mResourcesManager.applyConfigurationToResources(globalConfig,
                    null /* compat */,
                    mInitialApplication.getResources().getDisplayAdjustments())) {
                mConfigurationController.updateLocaleListFromAppContext(
                        mInitialApplication.getApplicationContext());

                // This actually changed the resources! Tell everyone about it.
                final Configuration updatedConfig =
                        mConfigurationController.updatePendingConfiguration(globalConfig);
                if (updatedConfig != null) {
                    sendMessage(H.CONFIGURATION_CHANGED, globalConfig);
                    mPendingConfiguration = updatedConfig;
                }
            }
        }
    };
    ViewRootImpl.addConfigCallback(configChangedCallback);
}

关键点①:获取AMS的代理对象

public static IActivityManager getService() {
    return IActivityManagerSingleton.get();
}
@UnsupportedAppUsage
private static final Singleton<IActivityManager> IActivityManagerSingleton =
        new Singleton<IActivityManager>() {
            @Override
            protected IActivityManager create() {
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                final IActivityManager am = IActivityManager.Stub.asInterface(b);
                return am;
            }
        };

我们可以看到,getService方法就是从ServiceManager中获取AMS,最终拿到的是AMS的代理对象(跨进程),调用attachApplication方法,传入了ApplicationThread对象,那么我们可以看一下AMS中的attachApplication方法。

//位于ActivityManagerService.java中

public final void attachApplication(IApplicationThread thread, long startSeq) {
    if (thread == null) {
        throw new SecurityException("Invalid application interface");
    }
    synchronized (this) 
    //获取当前进程的一些信息
        int callingPid = Binder.getCallingPid();
        final int callingUid = Binder.getCallingUid();
        final long origId = Binder.clearCallingIdentity();
        attachApplicationLocked(thread, callingPid, callingUid, startSeq);
        Binder.restoreCallingIdentity(origId);
    }
}

在AMS的attachApplication当中,会调用attachApplicationLocked方法,首先会初始化当前 进程的信息,记录在ProcessRecord类中,然后调用ApplicationThread的bindApplication,将进程的信息回调到ActivityThread。

# ActivityManagerService # attachApplicationLocked

private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
            int pid, int callingUid, long startSeq) {
    ProcessRecord app;
    ... //初始化进程信息
        final ProviderInfoList providerList = ProviderInfoList.fromList(providers);
        if (app.getIsolatedEntryPoint() != null) {
            // This is an isolated process which should just call an entry point instead of
            // being bound to an application.
            thread.runIsolatedEntryPoint(
                    app.getIsolatedEntryPoint(), app.getIsolatedEntryPointArgs());
        } else if (instr2 != null) {
            thread.bindApplication(processName, appInfo, providerList,
                    instr2.mClass,
                    profilerInfo, instr2.mArguments,
                    instr2.mWatcher,
                    instr2.mUiAutomationConnection, testMode,
                    mBinderTransactionTrackingEnabled, enableTrackAllocation,
                    isRestrictedBackupMode || !normalMode, app.isPersistent(),
                    new Configuration(app.getWindowProcessController().getConfiguration()),
                    app.getCompat(), getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked(),
                    buildSerial, autofillOptions, contentCaptureOptions,
                    app.getDisabledCompatChanges(), serializedSystemFontMap);
        } else {
            thread.bindApplication(processName, appInfo, providerList, null, profilerInfo,
                    null, null, null, testMode,
                    mBinderTransactionTrackingEnabled, enableTrackAllocation,
                    isRestrictedBackupMode || !normalMode, app.isPersistent(),
                    new Configuration(app.getWindowProcessController().getConfiguration()),
                    app.getCompat(), getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked(),
                    buildSerial, autofillOptions, contentCaptureOptions,
                    app.getDisabledCompatChanges(), serializedSystemFontMap);
        }
        if (profilerInfo != null) {
            profilerInfo.closeFd();
            profilerInfo = null;
        }

    return true;
}

bindApplication的关键就是最后一行,发送BIND_APPLICATION消息到MessageQueue

//位于ActivityThread.java中
 public final void bindApplication(String processName, ApplicationInfo appInfo,
        ProviderInfoList providerList, ComponentName instrumentationName,
        ProfilerInfo profilerInfo, Bundle instrumentationArgs,
        IInstrumentationWatcher instrumentationWatcher,
        IUiAutomationConnection instrumentationUiConnection, int debugMode,
        boolean enableBinderTracking, boolean trackAllocation,
        boolean isRestrictedBackupMode, boolean persistent, Configuration config,
        CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
        String buildSerial, AutofillOptions autofillOptions,
        ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges,
        SharedMemory serializedSystemFontMap) {
        ...
    AppBindData data = new AppBindData();
    data.processName = processName;
    data.appInfo = appInfo;
    data.providers = providerList.getList();
    data.instrumentationName = instrumentationName;
    data.instrumentationArgs = instrumentationArgs;
    data.instrumentationWatcher = instrumentationWatcher;
    data.instrumentationUiAutomationConnection = instrumentationUiConnection;
    data.debugMode = debugMode;
    data.enableBinderTracking = enableBinderTracking;
    data.trackAllocation = trackAllocation;
    data.restrictedBackupMode = isRestrictedBackupMode;
    data.persistent = persistent;
    data.config = config;
    data.compatInfo = compatInfo;
    data.initProfilerInfo = profilerInfo;
    data.buildSerial = buildSerial;
    data.autofillOptions = autofillOptions;
    data.contentCaptureOptions = contentCaptureOptions;
    data.disabledCompatChanges = disabledCompatChanges;
    data.mSerializedSystemFontMap = serializedSystemFontMap;
    //代码关键
    sendMessage(H.BIND_APPLICATION, data);
}
//位于ActivityThread.java中
public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
               ...
              }
}
//位于ActivityThread.java中
private void handleBindApplication(AppBindData data) {
   ...
   try {
   //通过类加载器创建Instrumentation
                final ClassLoader cl = instrContext.getClassLoader();
                mInstrumentation = (Instrumentation)
                    cl.loadClass(data.instrumentationName.getClassName()).newInstance();
            } catch (Exception e) {
                throw new RuntimeException(
                    "Unable to instantiate instrumentation "
                    + data.instrumentationName + ": " + e.toString(), e);
            }

		Application app;
            // 创建application对象,makeApplication也是通过类加载器和反射机制来创建的
            app = data.info.makeApplication(data.restrictedBackupMode, null);

            // Propagate autofill compat state
            app.setAutofillOptions(data.autofillOptions);

            // Propagate Content Capture options
            app.setContentCaptureOptions(data.contentCaptureOptions);

            mInitialApplication = app;
            //调用application的onCreate方法
			mInstrumentation.callApplicationOnCreate(app);

至此,Application已经启动,也就意味着当前app已经启动了。
其实整个流程每个类的分工都很明朗。AMS中属于数据管理者,管理Application和activity的数据;ApplicationThread属于桥梁传输AMS中的数据到ActivityThread中;ActivityThread负责执行任务;Instrumentation是Application和activity管理工具,供ActivityThread调用。
在这里插入图片描述
2 Activity的创建流程
接着回到AMS的attachApplicationLocked方法中,在Application启动之后,调用了ActivityTaskManagerInternal的attachApplication方法

// See if the top visible activity is waiting to run in this process...
        if (normalMode) {
            try {
                didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
            } catch (Exception e) {
                Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
                badApp = true;
            }
        }

先看一下ActivityTaskManagerInternal这个类,看字面意思是Activity的任务管理类,这是一个抽象类,具体实现是LocalService

mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
//位于frameworks\base\services\core\java\com\android\server\wm\ActivityTaskManagerService.java
     @HotPath(caller = HotPath.PROCESS_CHANGE)
        @Override
        public boolean attachApplication(WindowProcessController wpc) throws RemoteException {
            synchronized (mGlobalLockWithoutBoost) {
                if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "attachApplication:" + wpc.mName);
                }
                try {
                    return mRootWindowContainer.attachApplication(wpc);
                } finally {
                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                }
            }

接着调用RootWindowContainer.attachApplication

boolean attachApplication(WindowProcessController app) throws RemoteException {
        final String processName = app.mName;
        boolean didSomething = false;
        for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
            //DisplayContent代表一块屏幕
            final DisplayContent display = getChildAt(displayNdx);
            //拿到activity的栈
            final ActivityStack stack = display.getFocusedStack();
            if (stack == null) {
                continue;
            }

            mTmpRemoteException = null;
            mTmpBoolean = false; // Set to true if an activity was started.
            //遍历ActivityStack下的所有ActivityRecord,ActivityStack便是解析apk中的xml中activity创建的,里面存放activity的信息。
            //以其为参数调用startActivityForAttachedApplicationIfNeeded方法
                //stack.topRunningActivity()表示从栈里拿到的第一个activity
            final PooledFunction c = PooledLambda.obtainFunction(
                    RootWindowContainer::startActivityForAttachedApplicationIfNeeded, this,
                    PooledLambda.__(ActivityRecord.class), app, stack.topRunningActivity());
            //遍历所有的ActivityRecord ,并执行startActivityForAttachedApplicationIfNeeded
            stack.forAllActivities(c);
            c.recycle();
            if (mTmpRemoteException != null) {
                throw mTmpRemoteException;
            }
            didSomething |= mTmpBoolean;
        }
        if (!didSomething) {
            ensureActivitiesVisible(null, 0, false /* preserve_windows */);
        }
        return didSomething;
    }

ActivityRecord 只是一个activity的数据存储类,里面有activity的各种数据,并不是一个真正的activity。

//ActivityRecord r便是每个activity的ActivityRecord 
private boolean startActivityForAttachedApplicationIfNeeded(ActivityRecord r,
            WindowProcessController app, ActivityRecord top) {
        if (r.finishing || !r.okToShowLocked() || !r.visibleIgnoringKeyguard || r.app != null
                || app.mUid != r.info.applicationInfo.uid || !app.mName.equals(r.processName)) {
            return false;
        }

        try {
            if (mStackSupervisor.realStartActivityLocked(r, app, top == r /*andResume*/,
                    true /*checkConfig*/)) {
                mTmpBoolean = true;
            }
        } 
        ...
        return false;
    }

调用realStartActivityLocked方法,

boolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc,
            boolean andResume, boolean checkConfig) throws RemoteException {
            ...
                // 创建启动activity的事务
                //proc.getThread()即activitythread中的applicationThread
                final ClientTransaction clientTransaction = ClientTransaction.obtain(
                        proc.getThread(), r.appToken);
				final DisplayContent dc = r.getDisplay().mDisplayContent;
				//给事务设置回调,后面要用到
                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()));

}

               // 提交事务
                mService.getLifecycleManager().scheduleTransaction(clientTransaction);
//位于com\android\server\wm\ClientLifecycleManager.java中
void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
        final IApplicationThread client = transaction.getClient();
        transaction.schedule();
        if (!(client instanceof Binder)) {
            // If client is not an instance of Binder - it's a remote call and at this point it is
            // safe to recycle the object. All objects used for local calls will be recycled after
            // the transaction is executed on client in ActivityThread.
            transaction.recycle();
        }
    }
//位于android\app\servertransaction\ClientTransaction.java
public void schedule() throws RemoteException {
        mClient.scheduleTransaction(this);//这里的mClient即activityThread中的ApplicationThread,this表示
    }

ActivityThread中没有scheduleTransaction方法,故调用父类ClientTransactionHandler的方法

//位于android\app\ActivityThread.java
public abstract class ClientTransactionHandler {

    // Schedule phase related logic and handlers.

    /** Prepare and schedule transaction for execution. */
    void scheduleTransaction(ClientTransaction transaction) {
        transaction.preExecute(this);
        sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);//调用sendMessage
    }
}

sendMessage发送EXECUTE_TRANSACTION后,调用到activityThread中的handleMessage中

case EXECUTE_TRANSACTION:
	 final ClientTransaction transaction = (ClientTransaction) msg.obj;
	 mTransactionExecutor.execute(transaction);
	 if (isSystem()) {
	     // Client transactions inside system process are recycled on the client side
	     // instead of ClientLifecycleManager to avoid being cleared before this
	     // message is handled.
	     transaction.recycle();
	 }
	 // TODO(lifecycler): Recycle locally scheduled transactions.
	 break;
//位于android\app\servertransaction\TransactionExecutor.java
public void execute(ClientTransaction transaction) {
   ......
   //调用事务
   executeCallbacks(transaction);

   executeLifecycleState(transaction);
   mPendingActions.clear();
   if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "End resolving transaction");
}




public void executeCallbacks(ClientTransaction transaction) {
        //获取transaction的callback
        final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
   
        final IBinder token = transaction.getActivityToken();
        ActivityClientRecord r = mTransactionHandler.getActivityClient(token);

        final ActivityLifecycleItem finalStateRequest = transaction.getLifecycleStateRequest();
        final int finalState = finalStateRequest != null ? finalStateRequest.getTargetState()
                : UNDEFINED;
        // Index of the last callback that requests some post-execution state.
        final int lastCallbackRequestingState = lastCallbackRequestingState(transaction);

        final int size = callbacks.size();
        for (int i = 0; i < size; ++i) {
            //取出一个个callback,即LaunchActivityItem,它继承ClientTransactionItem。
            final ClientTransactionItem item = callbacks.get(i);
           
            final int postExecutionState = item.getPostExecutionState();
            final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
                    item.getPostExecutionState());
            if (closestPreExecutionState != UNDEFINED) {
                cycleToPath(r, closestPreExecutionState, transaction);
            }
 			//调用LaunchActivityItem的execute
            item.execute(mTransactionHandler, token, mPendingActions);
            item.postExecute(mTransactionHandler, token, mPendingActions);
            if (r == null) {
                // Launch activity request will create an activity record.
                r = mTransactionHandler.getActivityClient(token);
            }

            if (postExecutionState != UNDEFINED && r != null) {
                // Skip the very last transition and perform it by explicit state request instead.
                final boolean shouldExcludeLastTransition =
                        i == lastCallbackRequestingState && finalState == postExecutionState;
                cycleToPath(r, postExecutionState, shouldExcludeLastTransition, transaction);
            }
        }
    }
//位于android\app\servertransaction\LaunchActivityItem.java
public void execute(ClientTransactionHandler client, IBinder token,
            PendingTransactionActions pendingActions) {
        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
        //这是activity客户端的记录类,用于activity的实例化
        ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
                mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
                mPendingResults, mPendingNewIntents, mIsForward,
                mProfilerInfo, client, mAssistToken, mFixedRotationAdjustments);
        //然后回调给activityThread,client就是activityThread
        client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
    }

最终还是回调到了ActivityThread的handleLaunchActivity方法,在这个方法中,调用了performLaunchActivity

//位于android/app/ActivityThread.java
 public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
	.....
	
    final Activity a = performLaunchActivity(r, customIntent);

    if (a != null) {
        r.createdConfig = new Configuration(mConfigurationController.getConfiguration());
        reportSizeConfigurations(r);
        if (!r.activity.mFinished && pendingActions != null) {
            pendingActions.setOldState(r.state);
            pendingActions.setRestoreInstanceState(true);
            pendingActions.setCallOnPostCreate(true);
        }
    } else {
        // If there was an error, for any reason, tell the activity manager to stop us.
        ActivityClient.getInstance().finishActivity(r.token, Activity.RESULT_CANCELED,
                null /* resultData */, Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
    }

    return a;
}

在performLaunchActivity中,是通过Instrumentation类的newActivity方法,创建了一个Activity实例,看到这里是不是跟Application的很类似.Activity创建完成之后,就需要启动Activity,启动Activity还是调用了Instrumentation的callActivityOnCreate方法

//位于android/app/ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
		Activity activity = null;
        try {
            //通过反射创建activity
            java.lang.ClassLoader cl = appContext.getClassLoader();
            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);
            }
        }
        ...
        if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
        ...
}

在这里插入图片描述
结合Application和Activity的创建启动流程:

AMS的主要职责就是初始化Application和Activity需要的数据,封装成对象,为他们的创建做准备,然后回调到ActivityThread;
ApplicationThread的主要职责就是作为ActivityThread和AMS通信桥梁;
ActivityThread的主要职责就是处理AMS发送来的请求;
Instrumentation的主要职责就是创建Application或者Activity,并启动他们。

3 UI的绘制流程

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        conn = new MyServiceConnection();
    }
    ...
 }

当Activity启动之后,会执行onCreate方法,在onCreate方法中,调用setContentView.
如下:

//位于frameworks\base\core\java\android\app\Activity.java
public void setContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().setContentView(view, params);
        initWindowDecorActionBar();
    }

public Window getWindow() {
        return mWindow;
    }
 //解释:getWindow()这里返回一个Window。

widndow.java是一个抽象类,具有唯一的实现类

//位于frameworks\base\core\java\android\view\Window.java
public abstract class Window {
	...
    public abstract void setContentView(@LayoutRes int layoutResID);
    ...
}

PhoneWindow是指整个窗口,里面有几个概念
在这里插入图片描述

1、DecorView 在一般情况下,内部会包含一个竖直的 LInearLayout,里面有上下两部分(具体情况和安卓版本和主题有关),上面是标题栏,下面是内容,内容布局有一个默认的 id: content。
2、DecorView 继承自 FrameLayout,是一个 ViewGroup。在整个 ViewTree 中, DecorView 是整个 ViewTree 的顶层 View。View 的所有事件,都先经过 DecorView,然后再传递给 View
我们在 Activity 中 通过 setContentView() 方法设置的布局,其实就是添加到了内容部分里。如果我们想获取到内容布局的话,可以通过如下方法获取:

DecorView:最顶层的View,在PhoneWindow中,可以看做整个Activity的装饰器;
mContentParent:布局容器,用于放置内容视图,要么是mDecor自身,或者mDecor的子集

ViewGroup content = (ViewGroup) findViewById(android.R.id.content);

View childAt = content.getChildAt(0);

//位于frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
    //这是窗口的顶层试图,包含所有的窗口装饰
	private DecorView mDecor;
	//布局容器
	ViewGroup mContentParent;

public void setContentView(int layoutResID) {
        // 第一次创建的时候肯定为空
        if (mContentParent == null) {
        // 初始化顶层布局
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

//
}
 # PhoneWindow # installDecor
 
 private void installDecor() {
   mForceDecorInstall = false;
   //初始化DecorView
   if (mDecor == null) {
       mDecor = generateDecor(-1);//这里面new了一个DecorView
       mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
       mDecor.setIsRootNamespace(true);
       if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
           mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
       }
   } else {
       mDecor.setWindow(this);
   }


   if (mContentParent == null) {
   		// 初始化 mContentParent
       mContentParent = generateLayout(mDecor);
       ......
   }
}

初始化DecorView很简单,就是new DecorView然后返回,如果DecorView不为空,那么就将其放到PhoneWindow中。

接下来是对mContentParent的初始化,如果mContentParent是空的,那么就调用generateLayout方法(篇幅太长了,做了删减);

一开始就获取window窗体的属性,然后做一些配置,例如是不是悬浮、是否沉浸式布局、是否有ActionBar等,然后做完属性配置之后,开始装饰DecorView

//位于com/android/internal/policy/PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.

    TypedArray a = getWindowStyle();
    mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
    int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
            & (~getForcedWindowFlags());
    if (mIsFloating) {
        setLayout(WRAP_CONTENT, WRAP_CONTENT);
        setFlags(0, flagsToUpdate);
    } else {
        setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        getAttributes().setFitInsetsSides(0);
        getAttributes().setFitInsetsTypes(0);
    }
    
    // Inflate the window decor.
    //装饰DecorView
    int layoutResource;//表示一个资源的ID,根据主题加载不同的xml
    int features = getLocalFeatures();
    //下面加载系统布局,
    if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleIconsDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_title_icons;
        }
        // XXX Remove this once action bar supports these features.
        removeFeature(FEATURE_ACTION_BAR);
        // System.out.println("Title Icons!");
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
            && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
        // Special case for a window with only a progress bar (and title).
        // XXX Need to have a no-title version of embedded windows.
        layoutResource = R.layout.screen_progress;
        // System.out.println("Progress!");
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
        // Special case for a window with a custom title.
        // If the window is floating, we need a dialog layout
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogCustomTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else {
            layoutResource = R.layout.screen_custom_title;
        }
        // XXX Remove this once action bar supports these features.
        removeFeature(FEATURE_ACTION_BAR);
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
        // If no other features and not embedded, only need a title.
        // If the window is floating, we need a dialog layout
        if (mIsFloating) {
            TypedValue res = new TypedValue();
            getContext().getTheme().resolveAttribute(
                    R.attr.dialogTitleDecorLayout, res, true);
            layoutResource = res.resourceId;
        } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
            layoutResource = a.getResourceId(
                    R.styleable.Window_windowActionBarFullscreenDecorLayout,
                    R.layout.screen_action_bar);
        } else {
            layoutResource = R.layout.screen_title;
        }
        // System.out.println("Title!");
    } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
        layoutResource = R.layout.screen_simple_overlay_action_mode;
    } else {
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!");
    }

    mDecor.startChanging();
    //将加载到的基础布局添加到mDecor里面
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    //初始化mContentParent完成
    //通过系统的content的资源ID去进行实例化这个控件,这个控件就是R.layout.screen_simple布局中的一个帧布局,id是content。int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }
    ......
    return contentParent;
}

首先声明一个layoutResource变量,这里会做多级的判断,给layoutResource赋值,一般情况下都是R.layout.screen_simple这个布局。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

当我们新建一个工程,默认的界面就是一个状态栏ActionBar和空白容器,就是这个布局,调用DecorView的onResourcesLoaded方法。

//位于com/android/internal/policy/DecorView.java
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    if (mBackdropFrameRenderer != null) {
        loadBackgroundDrawablesIfNeeded();
        mBackdropFrameRenderer.onResourcesLoaded(
                this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                getCurrentColor(mNavigationColorViewState));
    }

    mDecorCaptionView = createDecorCaptionView(inflater);
    // 把传进来的layoutResource布局进行解析并渲染
    final View root = inflater.inflate(layoutResource, null);
    if (mDecorCaptionView != null) {
        if (mDecorCaptionView.getParent() == null) {
            addView(mDecorCaptionView,
                    new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mDecorCaptionView.addView(root,
                new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
    } else {

        // Put it below the color views.
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    mContentRoot = (ViewGroup) root;
    initializeElevation();
}

在这个方法中,其实就是调用LayoutInflater的inflate方法,将这个布局加载到DecorView中;
至此mDecor的资源加载完毕。
然后findViewById拿到一个ViewGroup,这个控件就是R.layout.screen_simple布局中的一个帧布局,id是content,也就是说,mContentParent就是这个帧布局。
至此,setContentView函数中的installDecor已经完成。其实主要做了两件事:
1.mDecor初始化
2.mContentParent 初始化

public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //加载自己的布局
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

installDecor中当基础布局加载完成之后,开始加载我们自己的布局,调用LayoutInflater的inflate方法,将布局加载到mContentParent中。

# PhoneWindow 

mLayoutInflater.inflate(layoutResID, mContentParent);

//位于android/view/LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        //解析xml
        XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
//位于android/view/LayoutInflater.java
 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;
            //将自己定义的布局,加到mContentParent中
			root.addView(temp, params);
            return result;
        }
    }

总结一下,UI的加载流程:

当调用setContentView时(拿Activity为例),会调用PhoneWindow的setContentView方法,首先会进行初始化操作;

先初始化DecorView,然后初始化mContentParent;

在初始化mContentParent时,首先会窗体做一些基础配置(是否沉浸式、是否有ActionBar等),然后加载基础布局(R.layout.screen_simple)到DecorView中,通过findViewById拿到基础布局中id为content的帧布局,作为mContentParent的初始化结果;

最后,加载我们自己的布局,通过inflate方法,将自己的布局加载到mContentParent中

3 UI绘制流程
在onCreate方法中,主要做的工作就是将我们自己的写的XML布局,加载到了DecorView当中,其实这只是一个开始,还需要对布局进行测试绘制,才能展示到手机上,这个过程,是在onResume生命周期中完成。
之前在创建Activity的时候,通过handleLaunchActivity方法,所以,如果调用Activity的onResume,那么就是调用handleResumeActivity方法,这个过程也是ActivityThread跟AMS通信完成,通过ApplicationThread回调。

public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, String reason) {
   ...
   final Activity a = r.activity;
   ...
   
   if (r.window == null && !a.mFinished && willBeVisible) {
   		//获取Activity的PhoneWindow
       r.window = r.activity.getWindow();
       View decor = r.window.getDecorView();
       decor.setVisibility(View.INVISIBLE);
       //获取WindowManager对象 WindowManagerImpl
       ViewManager wm = a.getWindowManager();
       WindowManager.LayoutParams l = r.window.getAttributes();
       a.mDecor = decor;
       ......
       if (a.mVisibleFromClient) {
           if (!a.mWindowAdded) {
               a.mWindowAdded = true;
               //调用绘制
               wm.addView(decor, l);
           } else {
               a.onWindowAttributesChanged(l);
           }
       }
   } else if (!willBeVisible) {
       if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
       r.hideForNow = true;
   }
   ......
   r.nextIdle = mNewActivities;
   mNewActivities = r;
   if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
   Looper.myQueue().addIdleHandler(new Idler());
}

在handleResumeActivity中,会获取Activity的相关信息,例如拿到PhoneWindow对象、DecorView等,然后获取WindowManager对象,如果做过悬浮窗相关需求的伙伴,应该对这个很熟悉,当一个应用想要悬浮在其他应用之上时,就需要在WindowManager中添加view;

//位于android/view/WindowManagerImpl.java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

一个windowmanager对应多个activity,将每个activity的view,ViewRootImpl,wparams保存在WindowManagerGlobal中。

//位于android/view/WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        ...
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            
            try {
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
//位于android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                mAttachInfo.mDisplayState = mDisplay.getState();
                mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);

                mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
                mFallbackEventHandler.setView(view);
                mWindowAttributes.copyFrom(attrs);

                requestLayout();
                InputChannel inputChannel = null;
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    inputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    adjustLayoutParamsForCompatibility(mWindowAttributes);
                    res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mDisplayCutout, inputChannel,
                            mTempInsets, mTempControls);
                    setFrame(mTmpFrame);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    inputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }  
        }
    }

首先,调用requestLayout
requestLayout,从字面意思上来看,就是请求重新布局,也就是说每当有一个新的View添加到容器之后,都会重新测量布局。

//android/view/ViewRootImpl.java
public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

这里有个标志位mHandlingLayoutInLayoutRequest,在performLayout时会被设置为true,也就说这里会判断当前是都正在绘制测量,如果没有,核心在于scheduleTraversals方法.

# ViewRootImpl # scheduleTraversals
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

在scheduleTraversals方法中,通过Handler是启动了一个线程mTraversalRunnable,执行了doTraversal方法,然后在doTraversal方法中,执行了performTraversals方法。

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

performTraversals方法中内容太多,就不再粘贴代码,只展示核心方法流程

 // Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

performLayout(lp, mWidth, mHeight);

performDraw();

其中performMeasure,最终调用了View的measure方法测量子View的大小;performLayout、performDraw同理。
在这里插入图片描述

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

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

相关文章

Java并发编程中的JMM、3个基本属性、synchronized和volatile

1、Java内存模型JMM (Java Meemory Model) JMM规定&#xff0c;所有变量均存储在主内存中每个线程都有自己的工作内存&#xff0c;保存了该线程中用到的变量的主内存副本拷贝线程对变量的所有操作&#xff0c;必须在自己的工作内存中&#xff0c;不可直接读写主内存不同线程无法…

2023-6-29-第十一式代理模式

&#x1f37f;*★,*:.☆(&#xffe3;▽&#xffe3;)/$:*.★* &#x1f37f; &#x1f4a5;&#x1f4a5;&#x1f4a5;欢迎来到&#x1f91e;汤姆&#x1f91e;的csdn博文&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f49f;&#x1f49f;喜欢的朋友可以关注一下&#xf…

C++primer(第五版)第八章(IO库)

8.1 IO库 上表中以w开头的类型和函数是C标准库为了支持使用宽字符的语言而定义的一组类型和对象来操纵wchar_t类型的数据.(然而我没有遇到过) 8.1.1 IO对象无拷贝或赋值 IO对象不能拷贝或赋值,通常用引用方式传递和返回流,由于读写一个IO对象回改变其状态,因此传递和返回的引…

Cetos7.x连接不上网络解决办法

Cetos7.x连接不上网络解决办法 Cetos7.x连接不上网络解决办法 在VM中设置网络连接为桥接&#xff0c;修改后仍无法连接网络 ##配置centos7中ens33&#xff0c;将默认的no修改为yes 启动CentOS系统&#xff0c;并打开一个连接终端会话&#xff0c;使用root登录&#xff1b;进…

tomcat多台应该怎么能设置

一个tomcat一般能处理5000-1000的并发量但是还是远远不够我们可以设置多台来满足我们的要求 首先进入tomcat目录 配置tomcat环境变量 vim /etc/profile.d/tomcat.sh 然后刷新 source /etc/profile.d/tomcat.sh 修改tomcat1里面的配置文件 然后进入tomcat1中的启动bin程序中…

Docker安装、常见命令、安装常见容器(Mysql、Redis等)

目录 一、Docker安装 二、Docker常见命令 2.1 镜像命令 2.2 容器命令 2.3 总结 2.4 容器挂载-容器卷技术 三、Docker安装mysql容器 3.1 下载镜像文件 3.2 创建实例并启动 3.3 MySQL 配置 3.4 进入容器文件系统 四、Docker安装Redis 一、Docker安装 官网安装指引&a…

SSM框架原理畅谈之SpringMVC

SpringMVC 一、Java SE Servlet标准1.1 Servlet 接口1.2 HttpServletRequest 接口1.3 HttpServletResponse 接口1.4 Cookie 对象1.5 Filter 接口1.6 HttpSession 接口 二、SpringMVC2.1 Spring MVC核心概念2.2 DispatcherServlet2.3 DispatcherServlet.init()2.4 DispatcherSer…

第三章 搜索与图论(一)——深搜,广搜,图的存储与拓扑序列

文章目录 深度优先搜索广度优先搜索树和图的存储图的深搜 拓扑序深搜练习题842. 排列数字843. n-皇后问题 广搜练习题844. 走迷宫845. 八数码 树和图的存储与遍历练习题846. 树的重心847. 图中点的层次 拓扑序练习题848. 有向图的拓扑序列 深度优先搜索 数据结构&#xff1a; …

常见排序算法详解

文章目录 前言1. 排序算法简介2 算法效率2.1 度量一个程序执行时间两种方法2.2 时间频度2.3 时间复杂度2.4 常见的时间复杂度2.5 平均和最坏时间复杂度 3. 常见排序算法详解3.1 基数排序 (Radix Sort)(1) 算法过程(2)代码实现 3.2 冒泡排序 (Bubble Sort)(1) 算法过程(2) 代码实…

2023年7月2日leetcode每日一题打卡——125.验证回文串

一、题目描述与要求 125. 验证回文串 - 力扣&#xff08;LeetCode&#xff09; 题目描述 如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后&#xff0c;短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。 字母和数字都属于字母数字字符。 给…

学习系统编程No.28【多线程概念实战】

引言&#xff1a; 北京时间&#xff1a;2023/6/29/15:33&#xff0c;刚刚更新完博客&#xff0c;目前没什么状态&#xff0c;不好趁热打铁&#xff0c;需要去睡一会会&#xff0c;昨天睡的有点迟&#xff0c;然后忘记把7点到8点30之间的4个闹钟关掉了&#xff0c;恶心了我自己…

C语言学习(三十)---枚举、位段、联合体

这几天在往实习的地方弄东西&#xff0c;比较累&#xff0c;因此没有更新&#xff0c;在几天前我们学习了内存操作函数&#xff0c;其与之前学习的字符串操作函数相比&#xff0c;适用范围更加广泛&#xff0c;大家要注意掌握学习&#xff0c;今天我们将学习枚举、位段和联合体…

闲置BROOKSTONE Rover间谍车重生记

22年春节在家&#xff0c;哪也去不了&#xff0c;收拾出来一个多年前的玩具&#xff0c;全名叫BROOKSTONE Rover revolution&#xff0c;长这个样子。 尽管是7年前的产品了&#xff0c;科技感依旧挺足 印象中能手机控制&#xff0c;并且能语音对讲。只是网上找到的安卓版应用已…

xenomai内核解析--xenomai实时线程创建流程

版权声明&#xff1a;本文为本文为博主原创文章&#xff0c;未经同意&#xff0c;禁止转载。如有错误&#xff0c;欢迎指正&#xff0c;博客地址&#xff1a;https://blog.csdn.net/qq_22654551?typeblog 文章目录 问题概述1 libCobalt中调用非实时POSIX接口2 阶段1 linux线程…

02_jQuery与Ajax

jquery jquery的作用 他是js的库 处理html,事件,实现动画效果,方便的为网站提供AJAX交互 命名格式 .ji:体积大,用于学习和debug使用 .min.js:压缩的文件,体积小,用于线上环境使用 使用方法 必须先在页面文件中进行引用 $就是jQuery 注意: jQuery是DOM的封装 jQuery和…

Spring Boot 中的服务网关是什么,原理,如何使用

Spring Boot 中的服务网关是什么&#xff0c;原理&#xff0c;如何使用 在微服务架构中&#xff0c;服务网关是一个非常重要的组件。它可以作为所有微服务的入口&#xff0c;负责路由、负载均衡、安全性和监控等方面的功能。Spring Boot 提供了一系列的服务网关工具&#xff0…

redis-哨兵安装

解决问题 自动故障修复 1.在主从模式的基础上,在主节点添加自己的认证密码即可 2.将代码客户端地址改为哨兵地址 ------------- 主节点配置 daemonize yes port 6379 bind 0.0.0.0 requirepass 123456 save 3600 1 300 100 60 10000dir /usr/local/redis dbfilename dump.r…

Java POI (4)—— Linux环境下文件解析过程出现OOM的问题

Excel文件在进行解析的时候&#xff0c;在Windows环境中&#xff0c;没用报错&#xff0c;但是在Linux环境中&#xff0c;出现了如下的报错&#xff1a; nested exception is javalang.OutofMemoryError: Java heap space &#xff08;OOM&#xff09; 一、内存溢出和栈溢出有什…

主流特征工程平台(一)

一. 目标 对于Feature Store的能力与边界&#xff0c;每家的定义略微不同&#xff0c;《Feature Stores - A Hierarchy of Needs》&#xff09;这篇文章做了很好的总结&#xff0c;大体分为如下几个层次&#xff1a; 特征管理&#xff1a;特征抽取、处理、存储、元数据管理&am…

群晖NAS 安装 MySQL 远程访问连接

目录 1. 安装Mysql 2. 安装phpMyAdmin 3. 修改User 表 4. 本地测试连接 5. 安装cpolar 6. 配置公网访问地址 7. 固定连接公网地址 [TOC] > **转载自cpolar极点云文章&#xff1a;[群晖NAS 安装 MySQL远程访问连接](https://www.cpolar.com/blog/install-mysql-remote-…