Android14应用启动流程(源码+Trace)

1.简介

应用启动过程快的都不需要一秒钟,但这整个过程的执行是比较复杂的,无论是对手机厂商、应用开发来说启动速度也是核心用户体验指标之一,本文采用Android14源码与perfetto工具进行解析。

源码参考地址:Search

trace分析工具:Perfetto UI

2. Input事件处理流程

Input 是Android系统最常见的事件驱动之一,用户的点击、滑动、长按等操作,都属于 input 事件驱动,其中跑在 SystemServer进程的两个 native 循环线程InputReader 和 InputDispatcher就是input的核心,负责读取和分发 Input 事件。整个处理过程大致流程如下:

  1. InputReader负责从EventHub里面把Input事件读取出来,后放入 inboundqueue,即“iq”队列,然后交给 InputDispatcher 进行事件分发;
  2. InputDispatcher在拿到 InputReader获取的事件之后,对事件进行包装后放入 outboundqueueue,即“oq”队列,寻找并分发到各个目标窗口App的事件;
  3. WaitQueue队列“wq“里面记录的是已经派发给 App的事件,但是 App还在处理没有返回处理成功的事件;
  4. PendingInputEventQueue队列“aq”中记录的是应用需要处理的Input事件,这里可以看到input事件已经传递到了应用进程;
  5. 在perfettotrace 中,inputreader.inputdispatcher.iq队列,oq队列,wq队列,wq队列都在system_server进程之中;每一个应用都有自己的aq队列。
  6. deliverInputEvent 标识 App UI Thread 被 Input 事件唤醒;App 响应处理Input 事件,内部会在其界面View树中传递处理。
  7. InputReader从trace里可以看到Input按下去的时候AppLaunch_dispatchPtr:Down和Input抬起的时候AppLaunch_dispatchPtr:Up,分析启动流程的时候可以从AppLaunch_dispatchPtr:Up开始

从trace上分析如下:

3. 应用进程的创建与启动

3.1 Pause桌面应用

这个过程可以先看一下trace的整体表现,然后再看对应的源码流程

3.1.1 trace分析如下:

3.1.2 源码分析如下:

launcher进程接收到input触控事件后调用binder调用框架AMS的的startActivity接口启动应用

相关简化代码如下:

/frameworks/base/services/core/java/com/android/server/wm/ActivityStarter.java

private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,
        IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
        int startFlags, ActivityOptions options, Task inTask,
        TaskFragment inTaskFragment, @BalCode int balCode,
        NeededUriGrants intentGrants, int realCallingUid) {
    int result = START_CANCELED;
    final Task startedActivityRootTask;

    // Create a transition now to record the original intent of actions taken within
    // startActivityInner. Otherwise, logic in startActivityInner could start a different
    // transition based on a sub-action.
    // Only do the create here (and defer requestStart) since startActivityInner might abort.
    ....
        try {
        //添加"startActivityInner"tag
            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
            // 执行startActivityInner启动应用的逻辑
            result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
                    startFlags, options, inTask, inTaskFragment, balCode,
                    intentGrants, realCallingUid);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
            startedActivityRootTask = handleStartResult(r, options, result, newTransition,
                    remoteTransition);
        }
    }
....

    return result;
}

在启动app前,需要检查当前前台resume状态的activity,一般为launcher应用,所以第一步需要让launcher的 activity 进入pause状态。相关简化代码逻辑如下:

/frameworks/base/services/core/java/com/android/server/wm/TaskFragment.java

final boolean resumeTopActivity(ActivityRecord prev, ActivityOptions options,
        boolean deferPause) {
    boolean pausing = !deferPause && taskDisplayArea.pauseBackTasks(next);
    // mResumedActivity不为null,说明当前存在处于resume状态的Activity且不是新需要启动的应用
    if (mResumedActivity != null) {
        ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Pausing %s", mResumedActivity);
        // 执行startPausing通知桌面应用进入paused状态
        pausing |= startPausing(mTaskSupervisor.mUserLeaving, false /* uiSleeping */,
                next, "resumeTopActivity");
    }
}


boolean startPausing(boolean userLeaving, boolean uiSleeping, ActivityRecord resuming,
        String reason) {
......
            schedulePauseActivity(prev, userLeaving, pauseImmediately,
                    false /* autoEnteringPip */, reason);
......
    }


void schedulePauseActivity(ActivityRecord prev, boolean userLeaving,
        boolean pauseImmediately, boolean autoEnteringPip, String reason) {
    ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
    try {
.....
        // 相关执行动作封装事务,binder通知mResumedActivity也就是桌面执行pause动作
        mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
                prev.token, PauseActivityItem.obtain(prev.finishing, userLeaving,
                        prev.configChangeFlags, pauseImmediately, autoEnteringPip));
    } catch (Exception e) {
        // Ignore exception, if process died other code will cleanup.
....
    }
}

桌面应用进程这边执行收到pause消息后执行Activity的onPause生命周期,并在执行完成后,会binder调用AMS的activityPaused接口通知系统执行完activity的pause动作,相关代码如下:

/frameworks/base/core/java/android/app/servertransaction/PauseActivityItem.java

@Override
public void postExecute(ClientTransactionHandler client, IBinder token,
        PendingTransactionActions pendingActions) {
    if (mDontReport) {
        return;
    }
    // TODO(lifecycler): Use interface callback instead of actual implementation.
    ActivityClient.getInstance().activityPaused(token);
}

AMS这边收到应用的activityPaused调用后,继续执行启动应用的逻辑,判断需要启动的应用Activity所在的进程不存在,所以接下来需要先startProcessAsync创建应用进程,相关简化代码如下:

/frameworks/base/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java

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);
....
    // 1.如果wpc不为null且hasThread表示应用Activity所属进程存在,直接realStartActivityLocked启动Activity
    if (wpc != null && wpc.hasThread()) {
        try {
            realStartActivityLocked(r, wpc, andResume, checkConfig);
            return;
        }
.......
    final boolean isTop = andResume && r.isTopRunningActivity();
    mService.startProcessAsync(r, knownToBeDead, isTop,
            isTop ? HostingRecord.HOSTING_TYPE_TOP_ACTIVITY
                    : HostingRecord.HOSTING_TYPE_ACTIVITY);
}

3.2 创建应用进程

在桌面点击图标启动一个应用的组件如Activity时,如果Activity所在的进程不存在,就会创建并启动进程。Android系统中一般应用进程的创建都是统一由zygote进程fork创建的,AMS在需要创建应用进程时,会通过socket连接并通知到到zygote进程在开机阶段就创建好的socket服务端,然后由zygote进程fork创建出应用进程。整体架构如下图所示:

应用进程创建流程图.png

3.2.1 AMS 发送socket请求

3.2.1.1 源码分析如下:
ActivityManagerService.java

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

@GuardedBy("this")
 final ProcessRecord startProcessLocked(...) {
     return mProcessList.startProcessLocked(...);
}
ProcessList.java

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

private Process.ProcessStartResult startProcess(HostingRecord hostingRecord, String entryPoint,
         ProcessRecord app, int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags,
         int mountExternal, String seInfo, String requiredAbi, String instructionSet,
         String invokeWith, long startTime) {
     try {
         // 原生标识应用进程创建所加的systrace tag
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Start proc: " +
                 app.processName);
         ...
         // 调用Process的start方法创建进程
         startResult = Process.start(...);
         ...
     } finally {
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
}
Process.java

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

public static ProcessStartResult start(...) {
    // 调用ZygoteProcess的start函数
    return ZYGOTE_PROCESS.start(...);
}
ZygoteProcess.java

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

public final Process.ProcessStartResult start(...){
    try {
        return startViaZygote(...);
    } catch (ZygoteStartFailedEx ex) {
       ...
    }
}

private Process.ProcessStartResult startViaZygote(...){
    ArrayList<String> argsForZygote = new ArrayList<String>();
    ...
//在ZygoteProcess#startViaZygote中,最后创建应用进程的逻辑:
1. openZygoteSocketIfNeeded函数中打开本地socket客户端连接到zygote进程的socket服务端;
2. zygoteSendArgsAndGetResult发送socket请求参数,带上了创建的应用进程参数信息;
3. return返回的数据结构ProcessStartResult中会有新创建的进程的pid字段。
    return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
}
3.2.1.2 trace分析如下:

3.2.2 Zygote 处理socket请求

其实早在系统开机阶段,zygote进程创建时,就会在ZygoteInit#main入口函数中创建服务端socket,并预加载系统资源和框架类(加速应用进程启动速度)

3.2.2.1 源码分析如下:

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

public static void main(String[] argv) {
       ZygoteServer zygoteServer = null;
        ...
       try {
           ...
           // 1.preload提前加载框架通用类和系统资源到进程,加速进程启动
           preload(bootTimingsTraceLog);
           ...
           // 2.创建zygote进程的socket server服务端对象
           zygoteServer = new ZygoteServer(isPrimaryZygote);
           ...
           // 3.进入死循环,等待AMS发请求过来
           caller = zygoteServer.runSelectLoop(abiList);
       } catch (Throwable ex) {
           ...
       } finally {
           ...
       }
       ...
   }

继续往下看ZygoteServer#runSelectLoop如何监听并处理AMS客户端的请求:

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

 Runnable runSelectLoop(String abiList) {
     // 进入死循环监听
     while (true) {
        while (--pollIndex >= 0) {
           if (pollIndex == 0) {
             ...
           } else if (pollIndex < usapPoolEventFDIndex) {
             // Session socket accepted from the Zygote server socket
             // 得到一个请求连接封装对象ZygoteConnection
             ZygoteConnection connection = peers.get(pollIndex);
             // processCommand函数中处理AMS客户端请求
             final Runnable command = connection.processCommand(this, multipleForksOK);
           }
        }
     }
 }

继续往下看ZygoteConnection#processCommand如何监听并处理AMS客户端的请求:

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

 Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) {
         ...
         // 1.fork创建应用子进程
         pid = Zygote.forkAndSpecialize(...);
         try {
             if (pid == 0) {
                 ...
                 // 2.pid为0,当前处于新创建的子应用进程中,处理请求参数
                 return handleChildProc(parsedArgs, childPipeFd, parsedArgs.mStartChildZygote);
             } else {
                 ...
                 handleParentProc(pid, serverPipeFd);
             }
          } finally {
             ...
          }
 }

   private Runnable handleChildProc(ZygoteArguments parsedArgs,
            FileDescriptor pipeFd, boolean isZygote) {
        ...
        // 关闭从父进程zygote继承过来的ZygoteServer服务端地址
        closeSocket();
        ...
        if (parsedArgs.mInvokeWith != null) {
           ...
        } else {
            if (!isZygote) {
                // 继续进入ZygoteInit#zygoteInit继续完成子应用进程的相关初始化工作
                return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
                        parsedArgs.mDisabledCompatChanges,
                        parsedArgs.mRemainingArgs, null /* classLoader */);
            } else {
                ...
            }
        }
    }

3.2.3 应用进程初始化

接上一节中的分析,zygote进程监听接收AMS的请求,fork创建子应用进程,然后pid为0时进入子进程空间,然后在 ZygoteInit#zygoteInit中完成进程的初始化动作

3.2.3.1 源码分析如下:

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

public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,
            String[] argv, ClassLoader classLoader) {
        ...
        // 原生添加名为“ZygoteInit ”的systrace tag以标识进程初始化流程
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
        RuntimeInit.redirectLogStreams();
        // 1.RuntimeInit#commonInit中设置应用进程默认的java异常处理机制
        RuntimeInit.commonInit();
        // 2.ZygoteInit#nativeZygoteInit函数中JNI调用启动进程的binder线程池
        ZygoteInit.nativeZygoteInit();
        // 3.RuntimeInit#applicationInit中反射创建ActivityThread对象并调用其“main”入口方法
        return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,
                classLoader);
 }

我们继续看RuntimeInit#applicationInit简化的代码流程:

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

 protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges,
            String[] argv, ClassLoader classLoader) {
        ...
        // 结束“ZygoteInit ”的systrace tag
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        // Remaining arguments are passed to the start class's static main
        return findStaticMain(args.startClass, args.startArgs, classLoader);
  }
  
  protected static Runnable findStaticMain(String className, String[] argv,
            ClassLoader classLoader) {
        Class<?> cl;
        try {
            // 1.反射加载创建ActivityThread类对象
            cl = Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException ex) {
            ...
        }
        Method m;
        try {
            // 2.反射调用其main方法
            m = cl.getMethod("main", new Class[] { String[].class });
        } catch (NoSuchMethodException ex) {
            ...
        } catch (SecurityException ex) {
            ...
        }
        ...
        // 3.触发执行以上逻辑
        return new MethodAndArgsCaller(m, argv);
    }

我们继续往下看ActivityThread的main函数中又干了什么:

frameworks/base/core/java/android/app/ActivityThread.java

public static void main(String[] args) {
     // 原生添加的标识进程ActivityThread初始化过程的systrace tag,名为“ActivityThreadMain”
     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
     ...
     // 1.创建并启动主线程的loop消息循环
     Looper.prepareMainLooper();
     ...
     // 2.attachApplication注册到系统AMS中
     ActivityThread thread = new ActivityThread();
     thread.attach(false, startSeq);
     ...
     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     Looper.loop();
     ...
}

private void attach(boolean system, long startSeq) {
    ...
    if (!system) {
       ...
       final IActivityManager mgr = ActivityManager.getService();
       try {
          // 通过binder调用AMS的attachApplication接口将自己注册到AMS中
          mgr.attachApplication(mAppThread, startSeq);
       } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
       }
    }
}

可以看到进程ActivityThread#main函数初始化的主要逻辑是:

  1. 创建并启动主线程的loop消息循环;
  2. 通过binder调用AMSattachApplication接口将自己attach注册到AMS中。

主线程初始化完成后,主线程就有了完整的 LooperMessageQueueHandler,此时 ActivityThreadHandler 就可以开始处理 Message,包括 ApplicationActivityContentProviderServiceBroadcast 等组件的生命周期函数,都会以 Message 的形式,在主线程按照顺序处理,这就是 App 主线程的初始化和运行原理。

主线程初始化完成后,主线程就进入阻塞状态,等待 Message,一旦有 Message 发过来,主线程就会被唤醒,处理 Message,处理完成之后,如果没有其他的 Message 需要处理,那么主线程就会进入休眠阻塞状态继续等待。可以说Android系统的运行是受消息机制驱动的,而整个消息机制是由上面所说的四个关键角色相互配合实现的(HandlerLooperMessageQueueMessage):

  1. Handler : Handler 主要是用来处理 Message,应用可以在任何线程创建 Handler,只要在创建的时候指定对应的 Looper 即可,如果不指定,默认是在当前 Thread 对应的 Looper。
  2. Looper : Looper 可以看成是一个循环器, loop 方法开启后,不断地从 MessageQueue 中获取 Message,对 Message 进行 Delivery 和 Dispatch,最终发给对应的 Handler 去处理。
  3. **MessageQueue**:MessageQueue 就是一个 Message 管理器,队列中是 Message,在没有 Message 的时候,MessageQueue 借助 Linux ePoll机制,阻塞休眠等待,直到有 Message 进入队列将其唤醒
  4. **Message**:Message 是传递消息的对象,其内部包含了要传递的内容,最常用的包括 what、arg、callback 等。
3.2.3.2 trace分析如下:

4. 应用Application和Activity组件创建与初始化

4.1 Application的创建与初始化

应用进程启动初始化执行ActivityThread#main函数过程中,在开启主线程loop消息循环之前,会通过Binder调用系统核心服务AMS的attachApplication接口将自己注册到AMS中。下面我们接着这个流程往下看,我们先从systrace上看看AMS服务的attachApplication接口是如何处理应用进程的attach注册请求的:

trace分析如下:

attachApplication.png

我们继续来看相关代码的简化流程:

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

@GuardedBy("this")
private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
            int pid, int callingUid, long startSeq) {
     ...
     if (app.isolatedEntryPoint != null) {
           ...
     } else if (instr2 != null) {
           // 1.通过oneway异步类型的binder调用应用进程ActivityThread#IApplicationThread#bindApplication接口
           thread.bindApplication(...);
     } else {
           thread.bindApplication(...);
     }
     ...
     // See if the top visible activity is waiting to run in this process...
     if (normalMode) {
          try {
            // 2.继续执行启动应用Activity的流程
            didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
          } catch (Exception e) {
                Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
                badApp = true;
          }
      }
}

/*frameworks/base/core/java/android/app/ActivityThread.java*/
private class ApplicationThread extends IApplicationThread.Stub {
      @Override
      public final void bindApplication(...) {
            ...
            AppBindData data = new AppBindData();
            data.processName = processName;
            data.appInfo = appInfo;
            ...
            // 向应用进程主线程Handler发送BIND_APPLICATION消息,触发在应用主线程执行handleBindApplication初始化动作
            sendMessage(H.BIND_APPLICATION, data);
      }
      ...
}

class H extends Handler {
      ...
      public void handleMessage(Message msg) {
           switch (msg.what) {
                case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    // 在应用主线程执行handleBindApplication初始化动作
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                    ...
           }
      }
      ...
}

@UnsupportedAppUsage
private void handleBindApplication(AppBindData data) {
    ...
}

从上面的代码流程可以看出:AMS服务在执行应用的attachApplication注册请求过程中,会通过oneway类型的binder调用应用进程ActivityThread#IApplicationThreadbindApplication接口,而bindApplication接口函数实现中又会通过往应用主线程消息队列post BIND_APPLICATION消息触发执行handleBindApplication初始化函数,从systrace看如下图所示:

结合代码看看handleBindApplication的关键流程:

frameworks/base/core/java/android/app/ActivityThread.java

@UnsupportedAppUsage
private void handleBindApplication(AppBindData data) {
    ...
    // 1.创建应用的LoadedApk对象
    data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
    ...
    // 2.创建应用Application的Context、触发Art虚拟机加载应用APK的Dex文件到内存中,并加载应用APK的Resource资源
    final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
    ...
    // 3.调用LoadedApk的makeApplication函数,实现创建应用的Application对象
    app = data.info.makeApplicationInner(data.restrictedBackupMode, null);
    ...
    // 4.执行应用Application#onCreate生命周期函数
    mInstrumentation.onCreate(data.instrumentationArgs);
    ...
}

在ActivityThread#**handleBindApplication初始化过程中在应用主线程中主要完成如下几件事件:

  1. 根据框架传入的ApplicationInfo信息创建应用APK对应的LoadedApk对象;
  2. 创建应用Application的Context对象;
  3. 创建类加载器ClassLoader对象并触发Art虚拟机执行OpenDexFilesFromOat动作加载应用APKDex文件
  4. 通过LoadedApk加载应用APKResource资源
  5. 调用LoadedApk的makeApplication函数,创建应用的Application对象;
  6. 执行应用Application#onCreate生命周期函数(APP应用开发者能控制的第一行代码);

下面我们结合代码重点看看APK Dex文件的加载和Resource资源的加载流程。

4.1.1 应用APK的Dex文件加载

ContextImpl.java

frameworks/base/core/java/android/app/ContextImpl.java

static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
        String opPackageName) {
    if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
    // 1.创建应用Application的Context对象
    ContextImpl context = new ContextImpl(null, mainThread, packageInfo,
        ContextParams.EMPTY, null, null, null, null, null, 0, null, opPackageName);
    // 2.触发加载APK的DEX文件和Resource资源
    context.setResources(packageInfo.getResources());
    context.mContextType = isSystemOrSystemUI(context) ? CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI
            : CONTEXT_TYPE_NON_UI;
    return context;
}



LoadedApk.java

frameworks/base/core/java/android/app/LoadedApk.java


@UnsupportedAppUsage
public Resources getResources() {
     if (mResources == null) {
         ...
    // 加载APK的Resource资源
    mResources = ResourcesManager.getInstance().getResources(null, mResDir,
            splitPaths, mLegacyOverlayDirs, mOverlayPaths,
            mApplicationInfo.sharedLibraryFiles, null, null, getCompatibilityInfo(),
            getClassLoader()//触发加载APK的DEX文件, null);
}
return mResources;

@UnsupportedAppUsage
public ClassLoader getClassLoader() {
    synchronized (mLock) {
        if (mClassLoader == null) {
            createOrUpdateClassLoaderLocked(null /*addedPaths*/);
        }
        return mClassLoader;
    }
}
private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
      if (!mIncludeCode) {
          if (mDefaultClassLoader == null) {
              StrictMode.ThreadPolicy oldPolicy = allowThreadDiskReads();
              //创建默认的mDefaultClassLoader对象,触发art虚拟机加载dex文件
              mDefaultClassLoader = ApplicationLoaders.getDefault().getClassLoader(
                      "" /* codePath */, mApplicationInfo.targetSdkVersion, isBundledApp,
                      librarySearchPath, libraryPermittedPath, mBaseClassLoader,
                      null /* classLoaderName */);
              setThreadPolicy(oldPolicy);
              mAppComponentFactory = AppComponentFactory.DEFAULT;
          }
        }
     ...
     if (mClassLoader == null) {
         // 赋值给mClassLoader对象
         mClassLoader = mAppComponentFactory.instantiateClassLoader(mDefaultClassLoader,
                    new ApplicationInfo(mApplicationInfo));
     }
}


ApplicationLoaders.java

frameworks/base/core/java/android/app/ResourcesManager.java

ClassLoader getClassLoaderWithSharedLibraries(...) {
    // For normal usage the cache key used is the same as the zip path.
     return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
                      libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries,
                      nativeSharedLibraries, sharedLibrariesLoadedAfterApp);
                      }

private ClassLoader getClassLoader(String zip, ...) {
        ...
        synchronized (mLoaders) {
            ...
            if (parent == baseParent) {
                ...
                // 1.创建BootClassLoader加载系统框架类,并增加相应的systrace tag
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
                ClassLoader classloader = ClassLoaderFactory.createClassLoader(
                        zip,  librarySearchPath, libraryPermittedPath, parent,
                        targetSdkVersion, isBundled, classLoaderName, sharedLibraries,
                        nativeSharedLibraries, sharedLibrariesLoadedAfterApp);
                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                ...
                return classloader;
            }
            // 2.创建PathClassLoader加载应用APK的Dex类,并增加相应的systrace tag
            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
            ClassLoader loader = ClassLoaderFactory.createClassLoader(
                    zip, null, parent, classLoaderName, sharedLibraries,
                    null /*sharedLibrariesLoadedAfterApp*/);
            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
            return loader;
        }
}
ClassLoaderFactory.java

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

public static ClassLoader createClassLoader(...) {
        
        ClassLoader[] arrayOfSharedLibraries = (sharedLibraries == null)
                ? null
                : sharedLibraries.toArray(new ClassLoader[sharedLibraries.size()]);
        if (isPathClassLoaderName(classloaderName)) {
            return new PathClassLoader(dexPath, librarySearchPath, parent, arrayOfSharedLibraries);
        }
        ...
}
public static ClassLoader createClassLoader(String dexPath,
        String librarySearchPath, ClassLoader parent, String classloaderName,
        List<ClassLoader> sharedLibraries, List<ClassLoader> sharedLibrariesLoadedAfter) {
    // 通过new的方式创建ClassLoader对象,最终会触发art虚拟机加载APK的dex文件
    ClassLoader[] arrayOfSharedLibraries = (sharedLibraries == null)
            ? null
            : sharedLibraries.toArray(new ClassLoader[sharedLibraries.size()]);
    ClassLoader[] arrayOfSharedLibrariesLoadedAfterApp = (sharedLibrariesLoadedAfter == null)
            ? null
            : sharedLibrariesLoadedAfter.toArray(
                    new ClassLoader[sharedLibrariesLoadedAfter.size()]);
    if (isPathClassLoaderName(classloaderName)) {
        return new PathClassLoader(dexPath, librarySearchPath, parent, arrayOfSharedLibraries,
                arrayOfSharedLibrariesLoadedAfterApp);
.....
 }

从以上代码可以看出:在创建Application的Context对象后会立马尝试去加载APK的Resource资源,而在这之前需要通过LoadedApk去创建类加载器ClassLoader对象,而这个过程最终就会触发Art虚拟机加载应用APK的dex文件,从systrace上看如下图所示:

OpenDexFilesFromOat.png

4.1.2 应用APK的Resource资源加载

ResourcesManager.java

frameworks/base/core/java/android/app/ResourcesManager.java

public @Nullable Resources getResources(...) {
      try {
          // 原生Resource资源加载的systrace tag
          Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
          ...
             resources = createResources(key, classLoader, assetsSupplier);
         }
         return resources;
      } finally {
          Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
      }
}

private @Nullable Resources createResources(...) {
      synchronized (this) {
            ...
            // 执行创建Resources资源对象
            ResourcesImpl resourcesImpl = findOrCreateResourcesImplForKeyLocked(key, apkSupplier);
            if (resourcesImpl == null) {
                return null;
            }
            ...
     }
}

private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked(
            @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) {
      ...
      impl = createResourcesImpl(key, apkSupplier);
      ...
}

private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
            @Nullable ApkAssetsSupplier apkSupplier) {
        ...
        // 创建AssetManager对象,真正实现的APK文件加载解析动作
        final AssetManager assets = createAssetManager(key, apkSupplier);
        ...
}

private @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key,
            @Nullable ApkAssetsSupplier apkSupplier) {
        ...
        for (int i = 0, n = apkKeys.size(); i < n; i++) {
            final ApkKey apkKey = apkKeys.get(i);
            try {
                // 通过loadApkAssets实现应用APK文件的加载
                builder.addApkAssets(
                        (apkSupplier != null) ? apkSupplier.load(apkKey) : loadApkAssets(apkKey));
            } catch (IOException e) {
                ...
            }
        }
        ...   
}

private @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException {
        ...
        if (key.overlay) {
            ...
        } else {
            // 通过ApkAssets从APK文件所在的路径去加载
            apkAssets = ApkAssets.loadFromPath(key.path, flags);
        }
        ...
    }
ApkAssets.java

frameworks/base/core/java/android/content/res/ApkAssets.java

public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags)
            throws IOException {
        return new ApkAssets(FORMAT_APK, path, flags, null /* assets */);
}

private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
            @Nullable AssetsProvider assets) throws IOException {
        ...
        // 通过JNI调用Native层的系统system/lib/libandroidfw.so库中的相关C函数实现对APK文件压缩包的解析与加载
        mNativePtr = nativeLoad(format, path, flags, assets);
        ...
}

从以上代码可以看出:系统对于应用APK文件资源的加载过程其实就是创建应用进程中的Resources资源对象的过程,其中真正实现APK资源文件的I/O解析作,最终是借助于AssetManager中通过JNI调用系统Native层的相关C函数实现。整个过程从systrace上看如下图所示:

getResources.png

4.2 Activity的创建与初始化

AMS在收到应用进程的attachApplication注册请求后,先通过oneway类型的binder调用应用及进程的IApplicationThread#bindApplication接口,触发应用进程在主线程执行handleBindeApplication初始化操作,然后继续执行启动应用Activity的操作,下面我们来看看系统是如何启动创建应用Activity的,简化代码流程如下:

ActivityManagerService.java

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

@GuardedBy("this")
private boolean attachApplicationLocked(...) {
     ...
     if (app.isolatedEntryPoint != null) {
           ...
     } else if (instr2 != null) {
           // 1.通过oneway异步类型的binder调用应用进程ActivityThread#IApplicationThread#bindApplication接口
           thread.bindApplication(...);
     } else {
           thread.bindApplication(...);
     }
     ...
     // See if the top visible activity is waiting to run in this process...
     if (normalMode) {
          try {
            // 2.继续执行启动应用Activity的流程
            didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
          } catch (Exception e) {
                Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
                badApp = true;
          }
      }
}
ActivityTaskManagerService.java

frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java

public boolean attachApplication(WindowProcessController wpc) throws RemoteException {
       synchronized (mGlobalLockWithoutBoost) {
            if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
                // 原生标识attachApplication过程的systrace tag
                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "attachApplication:" + wpc.mName);
            }
            try {
                return mRootWindowContainer.attachApplication(wpc);
            } finally {
                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
            }
       }
}
RootWindowContainer.java

frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java

boolean attachApplication(WindowProcessController app) throws RemoteException {
    try {
        return mAttachApplicationHelper.process(app);
    } finally {
        mAttachApplicationHelper.reset();
    }
}
......
    private class AttachApplicationHelper implements Consumer<Task>, Predicate<ActivityRecord> {
        private boolean mHasActivityStarted;
        private RemoteException mRemoteException;
        private WindowProcessController mApp;
        private ActivityRecord mTop;
            try {
                // realStartActivityLocked真正实现启动应用Activity流程
                if (mTaskSupervisor.realStartActivityLocked(r, mApp,
                        mTop == r && r.getTask().canBeResumed(r) /* andResume */,
                        true /* checkConfig */)) {
                    mHasActivityStarted = true;
                }
            } catch (RemoteException e) {
                Slog.w(TAG, "Exception in new application when starting activity " + mTop, e);
                mRemoteException = e;
                return true;
            }
            return false;
        }
    }
}
ActivityTaskSupervisor.java

 /frameworks/base/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java  


boolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc,
            boolean andResume, boolean checkConfig) throws RemoteException {
         ...
        // 1.先通过LaunchActivityItem封装Binder通知应用进程执行Launch Activity动作       
         clientTransaction.addCallback(LaunchActivityItem.obtain(...);
         // Set desired final state.
         final ActivityLifecycleItem lifecycleItem;
         if (andResume) {
                // 2.再通过ResumeActivityItem封装Binder通知应用进程执行Launch Resume动作        
                lifecycleItem = ResumeActivityItem.obtain(dc.isNextTransitionForward());
         }
         ...
         clientTransaction.setLifecycleStateRequest(lifecycleItem);
         // 执行以上封装的Binder调用
         mService.getLifecycleManager().scheduleTransaction(clientTransaction);
         ...
}

从以上代码分析可以看到,框架system_server进程最终是通过ActivityTaskSupervisor#realStartActivityLocked函数中,通过LaunchActivityItem和ResumeActivityItem两个类的封装,依次实现binder调用通知应用进程这边执行Activity的Launch和Resume动作的,我们继续往下看相关代码流程:

4.2.1 Activity Create

LaunchActivityItem.java

frameworks/base/core/java/android/app/servertransaction/LaunchActivityItem.java

public void execute(ClientTransactionHandler client, IBinder token,
        PendingTransactionActions pendingActions) {
     // 原生标识Activity Launch的systrace tag
    Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
    ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
            mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState,
             mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
             client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
             mTaskFragmentToken);
    // 调用到ActivityThread的handleLaunchActivity函数在主线程执行应用Activity的Launch创建动作
     client.handleLaunchActivity(r, pendingActions, mDeviceId, null /* customIntent */);
     Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
 }
ActivityThread.java

frameworks/base/core/java/android/app/ActivityThread.java

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

/**  Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        // 1.创建Activity的Context
        ContextImpl appContext = createBaseContextForActivity(r);
        try {
            //2.反射创建Activity对象
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            ...
        } catch (Exception e) {
            ...
        }
        try {
            ...
            if (activity != null) {
                ...
                // 3.执行Activity的attach动作
                activity.attach(...);
                ...
                // 4.执行应用Activity的onCreate生命周期函数,并在setContentView调用中创建DecorView对象
                mInstrumentation.callActivityOnCreate(activity, r.state);
                ...
            }
            ...
        } catch (SuperNotCalledException e) {
            ...
        }
}
Activity.java

frameworks/base/core/java/android/app/Activity.java

 @UnsupportedAppUsage
 final void attach(...) {
        ...
        // 1.创建表示应用窗口的PhoneWindow对象
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ...
        // 2.为PhoneWindow配置WindowManager
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        ...
}

从上面代码可以看出,应用进程这边在收到系统binder调用后,在主线程中创建Activiy的流程主要步骤如下

  1. 创建Activity的Context;
  2. 通过反射创建Activity对象;
  3. 执行Activity的attach动作,其中会创建应用窗口的PhoneWindow对象并设置WindowManage
  4. 执行应用ActivityonCreate生命周期函数,并在setContentView中创建窗口的DecorView对象
以上过程从trace分析如下:

ActivityStart.png

4.2.2 Activity Resume

ResumeActivityItem.java

frameworks/base/core/java/android/app/servertransaction/ResumeActivityItem.java

@Override
public void execute(ClientTransactionHandler client, ActivityClientRecord r,
        PendingTransactionActions pendingActions) {
    // 原生标识Activity Resume的systrace tag
    Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
    client.handleResumeActivity(r, true /* finalStateRequest */, mIsForward,
            mShouldSendCompatFakeFocus, "RESUME_ACTIVITY");
    Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
ActivityThread.java

frameworks/base/core/java/android/app/ActivityThread.java

@Override
public void handleResumeActivity(...){
    ...
    // 1.执行performResumeActivity流程,执行应用Activity的onResume生命周期函数
    if (!performResumeActivity(r, finalStateRequest, reason)) {
        return;
    }    ...
    if (r.window == null && !a.mFinished && willBeVisible) {
            ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    ...
                    // 2.执行WindowManager#addView动作开启视图绘制逻辑
                    wm.addView(decor, l);
                } else {
                  ...
                }
            }
     }
    ...
}
public ActivityClientRecord performResumeActivity(...) {
    ...
    // 执行应用Activity的onResume生命周期函数
    r.activity.performResume(r.startsNotResumed, reason);
    ...
}
WindowManagerGlobal.java

frameworks/base/core/java/android/view/WindowManagerGlobal.java

public void addView(...) {
     // 创建ViewRootImpl对象
     root = new ViewRootImpl(view.getContext(), display);
     ...
     try {
         // 执行ViewRootImpl的setView函数
         root.setView(view, wparams, panelParentView, userId);
     } catch (RuntimeException e) {
         ...
     } 
}

从上面代码可以看出,应用进程这边在接收到系统Binder调用请求后,在主线程中Activiy Resume的流程主要步骤如下

  1. 执行应用ActivityonResume生命周期函数;
  2. 执行WindowManager的addView动作开启视图绘制逻辑;
  3. 创建Activity的ViewRootImpl对象;
  4. 执行ViewRootImplsetView函数开启UI界面绘制动作
以上过程从trace分析如下:

activityResume.png

5. Choreographer

5.1 Choreographer的作用

Choreographer 的引入,主要是配合系统Vsync垂直同步机制,在 Android 渲染链路扮演中承上启下的角色

承上:负责接收和处理 App 的各种更新消息和回调,等到 Vsync 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等

启下:负责请求和接收 Vsync 信号。接收 Vsync 事件回调(通过 FrameDisplayEventReceiver.onVsync );请求 Vsync(FrameDisplayEventReceiver.scheduleVsync)

从上面可以看出来, Choreographer 担任的是一个工具人的角色,他之所以重要,是因为通过 Choreographer + SurfaceFlinger + Vsync + TripleBuffer 这一套从上到下的机制,保证了 Android App 可以以一个稳定的帧率运行(20fps、90fps 或者 60fps),减少帧率波动带来的不适感。

5.2 ViewRootImpl

接上一节的分析,应用主线程中在执行Activity的Resume流程的最后,会创建ViewRootImpl对象并调用其setView函数,从此并开启了应用界面UI布局与绘制的流程。

我们从ViewRootImpl的setView流程继续结合代码往下看:

ViewRootImpl.java

frameworks/base/core/java/android/view/ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
      synchronized (this) {
         if (mView == null) {
             mView = view;
         }
         ...
         // 开启绘制硬件加速,初始化RenderThread渲染线程运行环境
         enableHardwareAcceleration(attrs);
         ...
         // 1.触发绘制动作
         requestLayout();
         ...
         inputChannel = new InputChannel();
         ...
         // 2.Binder调用访问系统窗口管理服务WMS接口,实现addWindow添加注册应用窗口的操作,并传入inputChannel用于接收触控事件
         res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                 getHostVisibility(), mDisplay.getDisplayId(), userId,
                 mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
                 mTempControls, attachedFrame, compatScale);
         ...
         // 3.创建WindowInputEventReceiver对象,实现应用窗口接收触控事件
         mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                            Looper.myLooper());
         ...
         // 4.设置DecorView的mParent为ViewRootImpl
         view.assignParent(this);
         ...
      }
}

从以上代码可以看出ViewRootImpl的setView内部关键流程如下:

  1. requestLayout()通过一系列调用触发界面绘制(measure、layout、draw)动作
  2. 通过Binder调用访问系统窗口管理服务WMSaddWindow接口实现添加、注册应用窗口的操作,并传入本地创建inputChannel对象用于后续接收系统的触控事件,这一步执行完我们的View就可以显示到屏幕上了。关于WMS的内部实现流程也非常复杂,由于篇幅有限本文就不详细展开分析了。
  3. 创建WindowInputEventReceiver对象,封装实现应用窗口接收系统触控事件的逻辑;
  4. 执行view.assignParent(this),设置DecorView的mParent为ViewRootImpl。所以,虽然ViewRootImpl不是一个View,但它是所有View的顶层Parent。

我们顺着ViewRootImpl的requestLayout动作继续往下看界面绘制的流程代码:

frameworks/base/core/java/android/view/ViewRootImpl.java

public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
         // 检查当前UI绘制操作是否发生在主线程,如果发生在子线程则会抛出异常
         checkThread();
         mLayoutRequested = true;
         // 触发绘制操作
         scheduleTraversals();
    }
}

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
void scheduleTraversals() {
    if (!mTraversalScheduled) {
         ...
         // 注意此处会往主线程的MessageQueue消息队列中添加同步栏删,因为系统绘制消息属于异步消息,需要更高优先级的处理
         mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
         // 通过Choreographer往主线程消息队列添加CALLBACK_TRAVERSAL绘制类型的待执行消息,用于触发后续UI线程真正实现绘制动作
         mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
         ...
     }
}

ViewRootImpl会调用Choreographer的postCallback接口放入待执行的绘制消息后,Choreographer会先向系统申请APP 类型的vsync信号,然后等待系统vsync信号到来后,去回调到ViewRootImpl的doTraversal函数中执行真正的绘制动作(measure、layout、draw)。

我们接着ViewRootImpl的doTraversal函数的简化代码流程往下看:

frameworks/base/core/java/android/view/ViewRootImpl.java

void doTraversal() {
     if (mTraversalScheduled) {
         mTraversalScheduled = false;
         // 调用removeSyncBarrier及时移除主线程MessageQueue中的Barrier同步栏删,以避免主线程发生“假死”
         mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
         ...
         // 执行具体的绘制任务
         performTraversals();
         ...
    }
}

private void performTraversals() {
     ...
     // 1.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的measure测量操作
     windowSizeMayChange |= measureHierarchy(...);
     ...
     if (mFirst...) {
    // 2.第一次执行traversals绘制任务时,Binder调用访问系统窗口管理服务WMS的relayoutWindow接口,实现WMS计算应用窗口尺寸并向系统surfaceflinger正式申请Surface“画布”操作
         relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
     }
     ...
     // 3.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的layout测量操作
     performLayout(lp, mWidth, mHeight);
     ...
     // 4.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的draw测量操作
     performDraw();
     ...
}

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
        ...
        // 通过Binder IPC访问系统WMS服务的relayout接口,申请Surface“画布”操作
        relayoutResult = mWindowSession.relayout(mWindow, params,
                requestedWidth, requestedHeight, viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
                mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
                mTempInsets, mTempControls, mRelayoutBundle);
        ....
        if (mSurfaceControl.isValid()) {
            if (!useBLAST()) {
                // 本地Surface对象获取指向远端分配的Surface的引用
                mSurface.copyFrom(mSurfaceControl);
            } else {
               ...
            }
        }
        ...
}

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        ...
        // 原生标识View树的measure测量过程的trace tag
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            // 从mView指向的View控件树的根节点DecorView出发,遍历访问整个View树,并完成整个布局View树的测量工作
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
}

private void performDraw() {
     ...
     boolean canUseAsync = draw(fullRedrawNeeded);
     ...
}

private boolean draw(boolean fullRedrawNeeded) {
    ...
    if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
        ...
        // 如果开启并支持硬件绘制加速,则走硬件绘制的流程(从Android 4.+开始,默认情况下都是支持跟开启了硬件加速的)
        mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
    } else {
        // 否则走drawSoftware软件绘制的流程
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
                    return false;
         }
    }
}

从上面的代码流程可以看出,ViewRootImpl中负责的整个应用界面绘制的主要流程如下

  1. 从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的measure测量操作,由于篇幅所限,本文就不展开分析这块的详细流程;
  2. 界面第一次执行绘制任务时,会通过Binder IPC访问系统窗口管理服务WMS的relayout接口,实现窗口尺寸的计算并向系统申请用于本地绘制渲染的Surface“画布”的操作(具体由SurfaceFlinger负责创建应用界面对应的BufferQueueLayer对象,并通过内存共享的方式通过Binder将地址引用透过WMS回传给应用进程这边),由于篇幅所限,本文就不展开分析这块的详细流程;
  3. 从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的layout测量操作;
  4. 从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的draw测量操作,如果开启并支持硬件绘制加速(从Android 4.X开始谷歌已经默认开启硬件加速),则走GPU硬件绘制的流程,否则走CPU软件绘制的流程

5.3 以上过程从trace分析如下:

借用一张图来总结应用UI绘制的流程,如下所示:

UI绘制流程.png

6. RenderThread

目前为止,用户依然看不到屏幕上显示的应用界面内容,因为整个Android系统的显示流程除了前面讲到的UI线程的绘制外,界面还需要经过RenderThread线程的渲染处理,渲染完成后,还需要通过Binder调用“上帧”交给surfaceflinger进程中进行合成后送显才能最终显示到屏幕上。

我们将接上一节中ViewRootImpl中最后draw的流程继续往下分析开启硬件加速情况下,RenderThread渲染线程的工作流程。由于目前Android 4.X之后系统默认界面是开启硬件加速的,所以本文我们重点分析硬件加速条件下的界面渲染流程,我们先分析一下简化的代码流程:

6.1 硬件加速绘制

ViewRootImpl.java

frameworks/base/core/java/android/view/ViewRootImpl.java

private boolean draw(boolean fullRedrawNeeded) {
    ...
    if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
        ...
        // 硬件加速条件下的界面渲染流程
        mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
    } else {
        ...
    }
}

ThreadedRenderer.java

frameworks/base/core/java/android/view/ThreadedRenderer.java

void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    ...
    // 1.从DecorView根节点出发,递归遍历View控件树,记录每个View节点的绘制操作命令,完成绘制操作命令树的构建
    updateRootDisplayList(view, callbacks);
    ...
    // 2.JNI调用同步Java层构建的绘制命令树到Native层的RenderThread渲染线程,并唤醒渲染线程利用OpenGL执行渲染任务;
    int syncResult = syncAndDrawFrame(frameInfo);
    ...
}

从上面的代码可以看出,硬件加速绘制主要包括两个阶段

  1. 从DecorView根节点出发,递归遍历View控件树,记录每个View节点的drawOp绘制操作命令,完成绘制操作命令树的构建;
  2. JNI调用同步Java层构建的绘制命令树到Native层的RenderThread渲染线程,并唤醒渲染线程利用OpenGL执行渲染任务;

6.2 构建绘制命令树

我们先来看看第一阶段构建绘制命令树的代码简化流程:

6.2.1 源码分析如下:

ThreadedRenderer.java

frameworks/base/core/java/android/view/ThreadedRenderer.java

private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
        // 原生标记构建View绘制操作命令树过程的systrace tag
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
        // 递归子View的updateDisplayListIfDirty实现构建DisplayListOp
        updateViewTreeDisplayList(view);
        ...
        if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
            // 获取根View的SkiaRecordingCanvas
            RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
            try {
                ...
                // 利用canvas缓存DisplayListOp绘制命令
                canvas.drawRenderNode(view.updateDisplayListIfDirty());
                ...
            } finally {
                // 将所有DisplayListOp绘制命令填充到RootRenderNode中
                mRootNode.endRecording();
            }
        }
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

private void updateViewTreeDisplayList(View view) {
        ...
        // 从DecorView根节点出发,开始递归调用每个View树节点的updateDisplayListIfDirty函数
        view.updateDisplayListIfDirty();
        ...
}
View.java

frameworks/base/core/java/android/view/View.java

public RenderNode updateDisplayListIfDirty() {
     ...
     // 1.利用`View`对象构造时创建的`RenderNode`获取一个`SkiaRecordingCanvas`“画布”;
     final RecordingCanvas canvas = renderNode.beginRecording(width, height);
     try {
         ...
         if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
              // 如果仅仅是ViewGroup,并且自身不用绘制,直接递归子View
              dispatchDraw(canvas);
              ...
         } else {
              // 2.利用SkiaRecordingCanvas,在每个子View控件的onDraw绘制函数中调用drawLine、drawRect等绘制操作时,创建对应的DisplayListOp绘制命令,并缓存记录到其内部的SkiaDisplayList持有的DisplayListData中;
              draw(canvas);
         }
     } finally {
         // 3.将包含有`DisplayListOp`绘制命令缓存的`SkiaDisplayList`对象设置填充到`RenderNode`中;
         renderNode.endRecording();
         ...
     }
     ...
}

@CallSuper
public void draw(@NonNull Canvas canvas) {
    ...
    // draw the content(View自己实现的onDraw绘制,由应用开发者自己实现)
    onDraw(canvas);
    ...
    // draw the children
    dispatchDraw(canvas);
    ...
}
RenderNode.java

frameworks/base/graphics/java/android/graphics/RenderNode.java

public void endRecording() {
    if (mCurrentRecordingCanvas == null) {
        throw new IllegalStateException(
                "No recording in progress, forgot to call #beginRecording()?");
    }
    RecordingCanvas canvas = mCurrentRecordingCanvas;
    mCurrentRecordingCanvas = null;
    // 从SkiaRecordingCanvas中获取SkiaDisplayList对象
    canvas.finishRecording(this);
    // 将SkiaDisplayList对象填充到RenderNode中
    canvas.recycle();
}

从以上代码可以看出,构建绘制命令树的过程是从View控件树的根节点DecorView触发,递归调用每个子View节点的updateDisplayListIfDirty函数,最终完成绘制树的创建,简述流程如下

  1. 利用View对象构造时创建的RenderNode获取一个SkiaRecordingCanvas“画布”;
  2. 利用SkiaRecordingCanvas,在每个子View控件的onDraw绘制函数中调用drawLinedrawRect等绘制操作时,创建对应的DisplayListOp绘制命令,并缓存记录到其内部的SkiaDisplayList持有的DisplayListData
  3. 将包含有DisplayListOp绘制命令缓存的SkiaDisplayList对象设置填充到RenderNode中;
  4. 最后将根View的缓存DisplayListOp设置到RootRenderNode中,完成构建。

6.2.2 以上过程从trace分析如下:

构建View绘制命令树.png

6.3 执行渲染绘制任务

经过上一小节中的分析,应用在UI线程中从根节点DecorView出发,递归遍历每个子View节点,搜集其drawXXX绘制动作并转换成DisplayListOp命令,将其记录到DisplayListData并填充到RenderNode中,最终完成整个View绘制命令树的构建。从此UI线程的绘制任务就完成了。下一步UI线程将唤醒RenderThread渲染线程,触发其利用OpenGL执行界面的渲染任务,本小节中我们将重点分析这个流程。

6.3.1 源码分析如下:

HardwareRenderer.java

frameworks/base/graphics/java/android/graphics/HardwareRenderer.java

public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) {
    // JNI调用native层的相关函数
    return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length);
}
android_graphics_HardwareRenderer.cpp

frameworks/base/libs/hwui/jni/android_graphics_HardwareRenderer.cpp

static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
                                                          jlong proxyPtr, jlongArray frameInfo,
                                                          jint frameInfoSize) {
    ...
    RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
    env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
    return proxy->syncAndDrawFrame();
}

RenderProxy.cpp

frameworks/base/libs/hwui/renderthread/RenderProxy.cpp

int RenderProxy::syncAndDrawFrame() {
    // 唤醒RenderThread渲染线程,执行DrawFrame绘制任务
    return mDrawFrameTask.drawFrame();
}
DrawFrameTask.cpp

frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp

int DrawFrameTask::drawFrame() {
    ...
    postAndWait();
    ...
}

void DrawFrameTask::postAndWait() {
    AutoMutex _lock(mLock);
    // 向RenderThread渲染线程的MessageQueue消息队列放入一个待执行任务,以将其唤醒执行run函数
    mRenderThread->queue().post([this]() { run(); });
    // UI线程暂时进入wait等待状态
    mSignal.wait(mLock);
}

void DrawFrameTask::run() {
    // 原生标识一帧渲染绘制任务的systrace tag
    ATRACE_NAME("DrawFrame");
    ...
    {
        TreeInfo info(TreeInfo::MODE_FULL, *mContext);
        //1.将UI线程构建的DisplayListOp绘制命令树同步到RenderThread渲染线程
        canUnblockUiThread = syncFrameState(info);
        ...
    }
    ...
    // 同步完成后则可以唤醒UI线程
    // From this point on anything in "this" is *UNSAFE TO ACCESS*
    if (canUnblockUiThread) {
        unblockUiThread();
    }
    ...
    if (CC_LIKELY(canDrawThisFrame)) {
        // 2.执行draw渲染绘制动作
        context->draw(solelyTextureViewUpdates);
    } else {
        ...
    }
    ...
}

bool DrawFrameTask::syncFrameState(TreeInfo& info) {
    ATRACE_CALL();
    ...
    // 调用CanvasContext的prepareTree函数实现绘制命令树同步的流程
    mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
    ...
}
CanvasContext.cpp
void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued,
                                RenderNode* target) {
     ...
     for (const sp<RenderNode>& node : mRenderNodes) {
        ...
        // 递归调用各个子View对应的RenderNode执行prepareTree动作
        node->prepareTree(info);
        ...
    }
    ...
}
RenderNode.cpp

frameworks/base/libs/hwui/RenderNode.cpp


void RenderNode::prepareTree(TreeInfo& info) {
    ATRACE_CALL();
    ...
    prepareTreeImpl(observer, info, false);
    ...
}

void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
    ...
    if (info.mode == TreeInfo::MODE_FULL) {
        // 同步绘制命令树
        pushStagingDisplayListChanges(observer, info);
    }
    if (mDisplayList) {
        // 遍历调用各个子View对应的RenderNode的prepareTreeImpl
        bool isDirty = mDisplayList.prepareListAndChildren(
                observer, info, childFunctorsNeedLayer,
                [this](RenderNode* child, TreeObserver& observer, TreeInfo& info,
                       bool functorsNeedLayer) {
                    child->prepareTreeImpl(observer, info, functorsNeedLayer);
                    mHasHolePunches |= child->hasHolePunches();
                });
.....
        }.
}

void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {
    ...
    syncDisplayList(observer, &info);
    ...
}

void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {
    ...
    // 完成赋值同步DisplayList对象
    deleteDisplayList(observer, info);
    mDisplayList = std::move(mStagingDisplayList);
    ...
}
CanvasContext.cpp

/frameworks/base/libs/hwui/renderthread/CanvasContext.cpp

void CanvasContext::draw() {
    ...
    // 1.调用OpenGL库使用GPU,按照构建好的绘制命令完成界面的渲染
    drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
                                       &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
                                       mLightInfo, mRenderNodes, &(profiler()), mBufferParams);
    ...
    // 2.将前面已经绘制渲染好的图形缓冲区Binder上帧给SurfaceFlinger合成和显示
    bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult.success, windowDirty,
                                                mCurrentFrameInfo, &requireSwap);
}

从以上代码可以看出:UI线程利用RenderProxy向RenderThread线程发送一个DrawFrameTask任务请求,RenderThread被唤醒,开始渲染,大致流程如下

  1. syncFrameState中遍历View树上每一个RenderNode,执行prepareTreeImpl函数,实现同步绘制命令树的操作;
  2. 调用OpenGL库API使用GPU,按照构建好的绘制命令完成界面的渲染(具体过程,由于本文篇幅所限,暂不展开分析);
  3. 将前面已经绘制渲染好的图形缓冲区Binder上帧给SurfaceFlinger合成和显示;

6.3.2 以上过程从trace分析如下:

RenderThread实现界面渲染.png

7. SurfaceFlinger合成显示

SurfaceFlinger合成显示部分完全属于Android系统GUI中图形显示的内容,逻辑结构也比较复杂,但不属于本文介绍内容的重点。所以本小节中只是总体上介绍一下其工作原理与思想,不再详细分析源码,感兴趣的读者可以关注笔者后续的文章再来详细分析讲解。简单的说SurfaceFlinger作为系统中独立运行的一个Native进程,借用Android官网的描述,其职责就是负责接受来自多个来源的数据缓冲区,对它们进行合成,然后发送到显示设备。如下图所示:
 

SurfaceFlinger工作原理.jpg


从上图可以看出,其实SurfaceFlinger在Android系统的整个图形显示系统中是起到一个承上启下的作用

  • 对上:通过Surface与不同的应用进程建立联系,接收它们写入Surface中的绘制缓冲数据,对它们进行统一合成。
  • 对下:通过屏幕的后缓存区与屏幕建立联系,发送合成好的数据到屏幕显示设备。

图形的传递是通过Buffer作为载体,Surface是对Buffer的进一步封装,也就是说Surface内部具有多个Buffer供上层使用,如何管理这些Buffer呢?答案就是BufferQueue ,下面我们来看看BufferQueue的工作原理:

7.1 BufferQueue机制

借用一张经典的图来描述BufferQueue的工作原理:
 

BufferQueue状态转换图.jpg


BufferQueue是一个典型的生产者-消费者模型中的数据结构。在Android应用的渲染流程中,应用扮演的就是“生产者”的角色,而SurfaceFlinger扮演的则是“消费者”的角色,其配合工作的流程如下

  1. 应用进程中在开始界面的绘制渲染之前,需要通过Binder调用dequeueBuffer接口从SurfaceFlinger进程中管理的BufferQueue 中申请一张处于free状态的可用Buffer,如果此时没有可用Buffer则阻塞等待;
  2. 应用进程中拿到这张可用的Buffer之后,选择使用CPU软件绘制渲染或GPU硬件加速绘制渲染,渲染完成后再通过Binder调用queueBuffer接口将缓存数据返回给应用进程对应的BufferQueue(如果是 GPU 渲染的话,这里还有个 GPU处理的过程,所以这个 Buffer 不会马上可用,需要等 GPU 渲染完成的Fence信号),并申请sf类型的Vsync以便唤醒“消费者”SurfaceFlinger进行消费;
  3. SurfaceFlinger 在收到 Vsync 信号之后,开始准备合成,使用 acquireBuffer获取应用对应的 BufferQueue 中的 Buffer 并进行合成操作;
  4. 合成结束后,SurfaceFlinger 将通过调用 releaseBuffer将 Buffer 置为可用的free状态,返回到应用对应的 BufferQueue中。

7.2 Vsync同步机制

Vysnc垂直同步是Android在“黄油计划”中引入的一个重要机制,本质上是为了协调BufferQueue的应用生产者生成UI数据动作和SurfaceFlinger消费者的合成消费动作,避免出现画面撕裂的Tearing现象。Vysnc信号分为两种类型:

  1. app类型的Vsync:app类型的Vysnc信号由上层应用中的Choreographer根据绘制需求进行注册和接收,用于控制应用UI绘制上帧的生产节奏。根据第7小结中的分析:应用在UI线程中调用invalidate刷新界面绘制时,需要先透过Choreographer向系统申请注册app类型的Vsync信号,待Vsync信号到来后,才能往主线程的消息队列放入待绘制任务进行真正UI的绘制动作;
  2. sf类型的Vsync:sf类型的Vsync是用于控制SurfaceFlinger的合成消费节奏。应用完成界面的绘制渲染后,通过Binder调用queueBuffer接口将缓存数据返还给应用对应的BufferQueue时,会申请sf类型的Vsync,待SurfaceFlinger 在其UI线程中收到 Vsync 信号之后,便开始进行界面的合成操作。

Vsync信号的生成是参考屏幕硬件的刷新周期的,其架构如下图所示:
 

vsync.png

7.3 trace上SurfaceFlinger工作的流程如下图所示:

SurfaceFlinger处理.png

8.总结

本文结合Android 14源码和Perfetto分析了从用户手指点击桌面上的应用图标到屏幕上显示出应用主Activity界面第一帧画面的完整流程,这其中涉及了App应用、system_server框架、surfaceflinger等一系列Android系统核心模块的相互配合,有很多的细节也由于篇幅所限无法完全展开分析,感兴趣的读者可以结合AOSP源码继续深入分析。而优化应用启动打开的速度这个系统核心用户体验的指标,也是多少年来谷歌、SOC芯片厂商、ODM手机厂商以及各个应用开发者共同努力优化的方向:

  • 对于SOC芯片厂商而言:需要不断升级CPU和GPU的硬件算力;
  • 对于Android系统的维护者谷歌而言:在Android系统大版本升级过程中,不断的优化应用启动过程上的各个系统流程,比如进程创建的速度优化、Art虚拟机的引入与性能优化、View绘制流程的简化、硬件绘制加速机制的引入、系统核心AMS、WMS等核心服务的锁优化等;
  • 对于各个ODM手机厂商而言:开发识别应用启动的场景,进行针对性的CPU主频的拉升调节、触控响应速度的优化等机制;
  • 对于各个应用开发者而言:会结合自己的业务对应用启动的场景进行优化,比如尽量减少或推迟在Application、Activity生命周期函数中的初始化逻辑、去除界面布局的过度绘制、异步化的布局XML文件解析等机制。

9.参考文档

Search

Android应用启动全流程分析(源码深度剖析) - 简书

理解Android硬件加速原理的小白文 - 简书

Perfetto详细解析-CSDN博客

https://www.androidperformance.com/2021/04/24/android-systrace-smooth-in-action-1/
史上最全Android渲染机制讲解(长文源码深度剖析)https://mp.weixin.qq.com/s?__biz=MzU2MTk0ODUxOQ==&mid=2247483782&idx=1&sn=f9eae167b217c83036b3a24cd4182cd1&chksm=fc71b38ecb063a9847f4518802fc541091d7f708b112399ec39827e68a6f590249748d643747&mpshare=1&scene=1&srcid=0224RGsfWeG5GyMpxLwEhx7N&sharer_sharetime=1582507745901&sharer_shareid=2d76fc4769fc55b6ca84ec3820ba5821#rd

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于单片机多功能MP3播放器系统设计

**单片机设计介绍&#xff0c;基于单片机多功能MP3播放器系统设计 文章目录 一 概要二、功能设计设计思路 三、 软件设计原理图 五、 程序六、 文章目录 一 概要 基于单片机多功能MP3播放器系统设计是一个结合了硬件和软件设计的复杂项目。以下是对该系统设计的概要描述&#…

初识二叉树和二叉树的基本操作

目录 一、树 1.什么是树 2. 与树相关的概念 二、二叉树 1.什么是二叉树 2.二叉树特点 3.满二叉树与完全二叉树 4.二叉树性质 相关题目&#xff1a; 5.二叉树的存储 6.二叉树的遍历和基本操作 二叉树的遍历 二叉树的基本操作 一、树 1.什么是树 子树是不相交的;…

Github 2024-04-06Rust开源项目日报Top10

根据Github Trendings的统计,今日(2024-04-06统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Rust项目10HTML项目1Dart项目1RustDesk: 用Rust编写的开源远程桌面软件 创建周期:1218 天开发语言:Rust, Dart协议类型:GNU Affero General …

docker + miniconda + python 环境安装与迁移(详细版)

本文主要列出从安装dockerpython环境到迁移环境的整体步骤。windows与linux之间进行测试。 简化版可以参考&#xff1a;docker miniconda python 环境安装与迁移&#xff08;简化版&#xff09;-CSDN博客 目录 一、docker 安装和测试 二、docker中拉取miniconda&#xff…

C语言--指针终章

目录 1. sizeof和strlen的对⽐ 1.1 sizeof 1.2 strlen 1.3 sizeof 和 strlen的对⽐ 2. 数组和指针的理解——题目理解 2.1.sizeof 代码1&#xff1a; 代码2&#xff1a; 代码3&#xff1a; 代码4&#xff1a; 代码5&#xff08;二维数组&#xff09;&#xff1a; 2.2…

【蓝桥杯-单链表-网络寻路】

蓝桥杯-单链表-网络寻路 单链表基本操作操作一&#xff1a;向链表头插入一个数操作二:在第 k个插入的数后插入一个数操作三&#xff1a;删除第 k个插入的数后面的一个数&#xff1b; P8605 [蓝桥杯 2013 国 AC] 网络寻路 单链表基本操作 初始化有关操作 // head 表示头结点的…

Debian12 使用 nginx 与 php8.2 使用 Nextcloud

最近将小服务器升级了下系统&#xff0c;使用了 debian12 的版本&#xff0c;正好试试 nginx 和 php-fpm 这种方式运行 Nextcloud 这个私有云的配置。 一、基本系统及应用安装 系统&#xff1a;debian12 x86_64 位版本最小安装&#xff0c;安装后可根据自己需求安装一些工具&…

如何优化TCP?TCP的可靠传输机制是什么?

在网络世界中&#xff0c;传输层协议扮演着至关重要的角色&#xff0c;特别是TCP协议&#xff0c;以其可靠的数据传输特性而广受青睐。然而&#xff0c;随着网络的发展和数据量的激增&#xff0c;传统的TCP协议在效率方面遭遇了挑战。小编将深入分析TCP的可靠性传输机制&#x…

【C++初阶】 vector 在OJ中的使用

前言&#xff1a; &#x1f3af;个人博客&#xff1a;Dream_Chaser &#x1f388;博客专栏&#xff1a;C &#x1f4da;本篇内容&#xff1a;只出现一次的数字 和 杨辉三角 OJ 目录 一、只出现一次的数字 题目描述&#xff1a; 二、杨辉三角OJ 题目描述&#xff1a; 一、只…

vue快速入门(七)内联语句

注释很详细&#xff0c;直接上代码 上一篇 新增内容 button点击事件绑定内联语句写法与要求 源码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-wid…

phpstorm设置头部注释和自定义注释内容

先说设置位置&#xff1a; PhpStorm中文件、类、函数等注释的设置在&#xff1a;setting-》Editor-》FIle and Code Template-》Includes-》PHP Function Doc Comment下设置即可&#xff0c;其中方法的默认是这样的&#xff1a; /** ${PARAM_DOC} #if (${TYPE_HINT} ! "…

【第九篇】使用BurpSuite进行编码与解码

Burp存在一个功能&#xff0c;可以识别包含不透明数据&#xff08;例如会话令牌&#xff09;的消息。 如图&#xff1a;如果 Burp 识别所选内容的编码格式&#xff0c;它会自动解码数据。解码后的文本显示在 Inspector面板中。 在编码工具模块中&#xff0c;可对数据进行重复解…

C. MEX Game 1

本题如果我们去模拟这个算法的话会很麻烦&#xff0c;也会TLE&#xff0c;首先我们想 1&#xff0c;对于alice来说&#xff0c;先取小的&#xff0c;对于bob来说先删除alic想取的下一个小的 2&#xff0c;那如果这个数多于两个&#xff0c;那也就是说&#xff0c;alice肯定能…

电工技术学习笔记——正弦交流电路

一、正弦交流电路 1. 正弦量的向量表示法 向量表示方法&#xff1a;正弦交流电路中&#xff0c;相量表示法是一种常用的方法&#xff0c;用于描述电压、电流及其相位关系。相量表示法将正弦交流信号表示为复数&#xff0c;通过复数的运算来描述电路中各种参数的相互关系 …

C/C++预处理过程

目录 前言&#xff1a; 1. 预定义符号 2. #define定义常量 3. #define定义宏 4. 带有副作用的宏参数 5. 宏替换的规则 6. 宏和函数的对比 7. #和## 8. 命名约定 9. #undef 10. 命令行定义 11. 条件编译 12. 头文件的包含 13. 其他预处理指令 总结&#x…

IP地址:是给主机配置的,还是给网卡配置的?

在探索网络的奥秘时&#xff0c;我们经常会遇到一个看似简单但又复杂的问题&#xff1a;IP地址到底是配置在主机上&#xff0c;还是配置在网卡上&#xff1f;为什么我们通常说的是“主机IP地址”呢&#xff1f;让我们一起深入探讨。 1. 网卡与IP地址 &#x1f5a5;️&#x1f…

有同学和我说,深度学习不用特征工程,只有浅层机器学习方法采用特征工程,我说你误会了,我给你好好解释吧!!

1. 通俗解释 浅层机器学习算法&#xff08;如逻辑回归、决策树、支持向量机等&#xff09;和深度学习算法&#xff08;如神经网络&#xff09;在特征工程上的依赖性确实存在一些差异。 浅层机器学习算法的特征工程依赖性&#xff1a; 浅层算法通常需要手工选择和设计特征&…

lua学习笔记5(分支结构和循环的学习)

print("*****************分支结构和循环的学习******************") print("*****************if else语句******************") --if 条件 then end a660 b670 --单分支 if a<b thenprint(a) end --双分支 if a>b thenprint("满足条件")…

RGB三通道和灰度值的理解

本文都是来自于chatGPT的回答!!! 目录 Q1:像素具有什么属性?Q2:图像的色彩是怎么实现的?Q3:灰度值和颜色值是一个概念吗?Q4:是不是像素具有灰度值&#xff0c;也有三个颜色分量RGB&#xff1f;Q5:灰度图像是没有色彩的吗&#xff1f;Q6: 彩色图像是既具有灰度值也具有RGB三…

11_printf函数移植串口通信

printf函数移植串口通信 printf函数移植串口通信串口显示汉字乱码问题解决代码 printf函数移植串口通信 MicroLIB是Keil为嵌入式平台优化的一个精简库 --no-multibyte-chars 串口显示汉字乱码问题解决 法一 法二 代码 主函数 #include "stm32f10x.h" …