IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Zygote 启动应用程序 -> 正文阅读

[移动开发]Zygote 启动应用程序

Zygote 进程初始化完成后,会化身为守护进程来执行启动应用程序的任务。下面是启动时序图

在这里插入图片描述

注册 Zygote 的 socket

  • ZygoteInit 的 main() 方法首先调用 registerServerSocketFromEnv() 来创建一个本地 socket,接着调用 runSelectLoop() 来进入等待 socket 连接的循环中。
  • registerServerSocketFromEnv() 方法如下
    void registerServerSocketFromEnv(String socketName) {
        if (mServerSocket == null) {
            int fileDesc;
            final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;
            try {
                String env = System.getenv(fullSocketName);
                fileDesc = Integer.parseInt(env);
            } catch (RuntimeException ex) {
                throw new RuntimeException(fullSocketName + " unset or invalid", ex);
            }

            try {
                FileDescriptor fd = new FileDescriptor();
                fd.setInt$(fileDesc);
                mServerSocket = new LocalServerSocket(fd);
                mCloseSocketFd = true;
            } catch (IOException ex) {
                throw new RuntimeException(
                        "Error binding to local socket '" + fileDesc + "'", ex);
            }
        }
    }
registerServerSocketFromEnv 通过设置环境变量 ANDROID_SOCKET_ 来获取 socket句柄。Zygote 在 init.rc 中定义 init.zygote32(或其他init.zygote32_64等) 有一句话 
socket zygote stream 660 root system
Init 进程会通过这条选项来创建一个 AF_UNIX 的 socket,并把它的句柄放到环境变量 ANDROID_SOCKET_zygote 中,这个环境变量后面的 zygote 就是选项中的名字。

得到句柄后将通过 FileDescriptor fd = new FileDescriptor();  生成一个 FileDescriptor 对象,然后在通过mServerSocket = new LocalServerSocket(fd); 生成一个本地服务 socket,它的值将保存在全局 mServerSocket 中。

请求启动应用

android 启动一个新的进程是在 ActivityManagerService 中完成的,可能会有多种原因导致系统启动一个新的进程,最终在 ActivityManagerService 中都是通过调用方法 startProcessLocked() 来完成这一过程。startProcessLocked() 准备好参数后,会调用 Process 的 start() 方法来启动应用。部分代码如下
    private ProcessStartResult startProcess(String hostingType, String entryPoint,
            ProcessRecord app, int uid, int[] gids, int runtimeFlags, int mountExternal,
            String seInfo, String requiredAbi, String instructionSet, String invokeWith,
            long startTime) {
            	// .... 其他代码省略
       		 	// 会调用  Process.start 开启应用
                startResult = Process.start(entryPoint,
                        app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                        app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                        app.info.dataDir, invokeWith,
                        new String[] {PROC_START_SEQ_IDENT + app.startSeq});

				//... 
    }
Process.start() 的第一个参数是String entryPoint,如果往上查找会发现 entryPoint 的定义为 final String entryPoint = "android.app.ActivityThread";  就是应用启动后执行的入口类。

Process.start() 实际上走的是 ZygoteProcess.start() 方法。方法如下:processClass 就是 android.app.ActivityThread
    public final Process.ProcessStartResult start(final String processClass,
                                                  final String niceName,
                                                  int uid, int gid, int[] gids,
                                                  int runtimeFlags, int mountExternal,
                                                  int targetSdkVersion,
                                                  String seInfo,
                                                  String abi,
                                                  String instructionSet,
                                                  String appDataDir,
                                                  String invokeWith,
                                                  String[] zygoteArgs) {
        try {
            return startViaZygote(processClass, niceName, uid, gid, gids,
                    runtimeFlags, mountExternal, targetSdkVersion, seInfo,
                    abi, instructionSet, appDataDir, invokeWith, false /* startChildZygote */,
                    zygoteArgs);
        } catch (ZygoteStartFailedEx ex) {
            Log.e(LOG_TAG,
                    "Starting VM process through Zygote failed");
            throw new RuntimeException(
                    "Starting VM process through Zygote failed", ex);
        }
    }
接下来通过调用 startViaZygote() 方法,将进程的启动参数保存到argsForZygote,最后调用zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);

将应用程序启动的参数发送到 Zygote 进程,startViaZygote 代码如下

private Process.ProcessStartResult startViaZygote(final String processClass,
                                                      final String niceName,
                                                      final int uid, final int gid,
                                                      final int[] gids,
                                                      int runtimeFlags, int mountExternal,
                                                      int targetSdkVersion,
                                                      String seInfo,
                                                      String abi,
                                                      String instructionSet,
                                                      String appDataDir,
                                                      String invokeWith,
                                                      boolean startChildZygote,
                                                      String[] extraArgs)
                                                      throws ZygoteStartFailedEx {
        ArrayList<String> argsForZygote = new ArrayList<String>();

        // --runtime-args, --setuid=, --setgid=,
        // and --setgroups= must go first
        argsForZygote.add("--runtime-args");
        argsForZygote.add("--setuid=" + uid);
        argsForZygote.add("--setgid=" + gid);
        argsForZygote.add("--runtime-flags=" + runtimeFlags);
        if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
            argsForZygote.add("--mount-external-default");
        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_READ) {
            argsForZygote.add("--mount-external-read");
        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_WRITE) {
            argsForZygote.add("--mount-external-write");
        }
        argsForZygote.add("--target-sdk-version=" + targetSdkVersion);

        // --setgroups is a comma-separated list
        if (gids != null && gids.length > 0) {
            StringBuilder sb = new StringBuilder();
            sb.append("--setgroups=");

            int sz = gids.length;
            for (int i = 0; i < sz; i++) {
                if (i != 0) {
                    sb.append(',');
                }
                sb.append(gids[i]);
            }

            argsForZygote.add(sb.toString());
        }

        if (niceName != null) {
            argsForZygote.add("--nice-name=" + niceName);
        }

        if (seInfo != null) {
            argsForZygote.add("--seinfo=" + seInfo);
        }

        if (instructionSet != null) {
            argsForZygote.add("--instruction-set=" + instructionSet);
        }

        if (appDataDir != null) {
            argsForZygote.add("--app-data-dir=" + appDataDir);
        }

        if (invokeWith != null) {
            argsForZygote.add("--invoke-with");
            argsForZygote.add(invokeWith);
        }

        if (startChildZygote) {
            argsForZygote.add("--start-child-zygote");
        }

        argsForZygote.add(processClass);

        if (extraArgs != null) {
            for (String arg : extraArgs) {
                argsForZygote.add(arg);
            }
        }

        synchronized(mLock) {
            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote);
        }
    }
上面代码最后调用了 zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote) 第一个参数如下调用了openZygoteSocketIfNeeded方法,建立 socket连接并且返回 ZygoteState,对于 LocalSocket 直接通过字符串作为地址进行连接就可以通信。
// 如果socket没有连接,则尝试连接socket,如果已经打开了socket则什么都不做。
private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
        Preconditions.checkState(Thread.holdsLock(mLock), "ZygoteProcess lock not held");

        if (primaryZygoteState == null || primaryZygoteState.isClosed()) {
            try {
                primaryZygoteState = ZygoteState.connect(mSocket);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to primary zygote", ioe);
            }
            maybeSetApiBlacklistExemptions(primaryZygoteState, false);
            maybeSetHiddenApiAccessLogSampleRate(primaryZygoteState);
        }
        if (primaryZygoteState.matches(abi)) {
            return primaryZygoteState;
        }

        // The primary zygote didn't match. Try the secondary.
        if (secondaryZygoteState == null || secondaryZygoteState.isClosed()) {
            try {
                secondaryZygoteState = ZygoteState.connect(mSecondarySocket);
            } catch (IOException ioe) {
                throw new ZygoteStartFailedEx("Error connecting to secondary zygote", ioe);
            }
            maybeSetApiBlacklistExemptions(secondaryZygoteState, false);
            maybeSetHiddenApiAccessLogSampleRate(secondaryZygoteState);
        }

        if (secondaryZygoteState.matches(abi)) {
            return secondaryZygoteState;
        }

        throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
    }

处理启动应用请求

 之前提过,ZygoteInit 类的 main方法最后调用了  zygoteServer.runSelectLoop(abiList); 开始了监听和接收消息的环节。
 当 (pollFds[i].revents & POLLIN) == 0 结果 为 false 代表有事件的请求到来,则调用 acceptCommandPeer() 来和客户端建立连接,然后把这个连接添加到监听数组中,等待socket的命令到来。接到命令后会处理参数,处理结束后会断开连接并且移除监听。
Runnable runSelectLoop(String abiList) {
        ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
        ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();

        fds.add(mServerSocket.getFileDescriptor());
        peers.add(null);

        while (true) {
            StructPollfd[] pollFds = new StructPollfd[fds.size()];
            for (int i = 0; i < pollFds.length; ++i) {
                pollFds[i] = new StructPollfd();
                pollFds[i].fd = fds.get(i);
                pollFds[i].events = (short) POLLIN;
            }
            try {
                Os.poll(pollFds, -1);
            } catch (ErrnoException ex) {
                throw new RuntimeException("poll failed", ex);
            }
            for (int i = pollFds.length - 1; i >= 0; --i) {
                if ((pollFds[i].revents & POLLIN) == 0) {
                    continue;
                }

                if (i == 0) {
                    ZygoteConnection newPeer = acceptCommandPeer(abiList);
                    // 添加到监听数组 等待消息到来
                    peers.add(newPeer);
                    fds.add(newPeer.getFileDesciptor());
                } else {
                    try {
                        ZygoteConnection connection = peers.get(i);
                        // processOneCommand 方法处理读取的命令 处理完后面会断开链接,并从监听列表中移除
                        final Runnable command = connection.processOneCommand(this);

                        if (mIsForkChild) {
                            // We're in the child. We should always have a command to run at this
                            // stage if processOneCommand hasn't called "exec".
                            if (command == null) {
                                throw new IllegalStateException("command == null");
                            }

                            return command;
                        } else {
                            // We're in the server - we should never have any commands to run.
                            if (command != null) {
                                throw new IllegalStateException("command != null");
                            }

                            // We don't know whether the remote side of the socket was closed or
                            // not until we attempt to read from it from processOneCommand. This shows up as
                            // a regular POLLIN event in our regular processing loop.
                            // 判断是否处理完了事件,处理完则断开链接 移除监听
                            if (connection.isClosedByPeer()) {
                                connection.closeSocket();
                                peers.remove(i);
                                fds.remove(i);
                            }
                        }
                    }
                    // 。。。
            }
        }
    }

fork() 应用进程

ZygoteConnnect 类中的 processOneCommand 负责创建子进程,首先调用args = readArgumentList(); 从 socket 中读入多个参数,参数样式为 “--setuid=1” 行与行之间用 /r /n 分割,读取完成后传入新建的 Arguments 对象 并调用parseArgs(args);解析成参数列表。

解析参数之后还会对参数进行校验和设置。
		// 检查用户是否有权利指定进程用户id,组id和所属的组,以及一些安全检查等。
		applyUidSecurityPolicy(parsedArgs, peer);
        applyInvokeWithSecurityPolicy(parsedArgs, peer);

        applyDebuggerSystemProperty(parsedArgs);
        applyInvokeWithSystemProperty(parsedArgs);
上面检测通过后,processOneCommand 会调用下面代码来 fork 子进程
 pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
                parsedArgs.runtimeFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
                parsedArgs.niceName, fdsToClose, fdsToIgnore, parsedArgs.startChildZygote,
                parsedArgs.instructionSet, parsedArgs.appDataDir);

最终会调用 native 层的 nativeForkAndSpecialize() 的函数来完成 fork 操作
native private static int nativeForkAndSpecialize(int uid, int gid, int[] gids,int runtimeFlags,
          int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
          int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir);
nativeForkAndSpecialize() 函数的主要工作:
- fork() 出子进程
- 在子进程中挂载 external storage
- 在子进程中设置用户 Id、组Id、所属进程id
- 在子进程中执行系统调用 setrlimit 来设置系统资源限制
- 在子进程中执行系统调用 capset 来设置进程的权限
- 在子进程中设置应用上下文

native 返回 Java 层后,processOneCommand 方法继续执行,如果 pid==0 则在父进程中,否则在子进程中。
			if (pid == 0) {
                // in child
                zygoteServer.setForkChild();

                zygoteServer.closeServerSocket();
                IoUtils.closeQuietly(serverPipeFd);
                serverPipeFd = null;

                return handleChildProc(parsedArgs, descriptors, childPipeFd,
                        parsedArgs.startChildZygote);
            } else {
                // In the parent. A pid < 0 indicates a failure and will be handled in
                // handleParentProc.
                IoUtils.closeQuietly(childPipeFd);
                childPipeFd = null;
                handleParentProc(pid, descriptors, serverPipeFd);
                return null;
            }

子进程初始化

上面讲了如果是在子进程中,则执行 handleChildProc() 函数。来完成子进程的初始化,代码如下
private Runnable handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
            FileDescriptor pipeFd, boolean isZygote) {
        /**
         * By the time we get here, the native code has closed the two actual Zygote
         * socket connections, and substituted /dev/null in their place.  The LocalSocket
         * objects still need to be closed properly.
         */
		// 首先关闭了 socket 连接
        closeSocket();
        if (descriptors != null) {
            try {
                Os.dup2(descriptors[0], STDIN_FILENO);
                Os.dup2(descriptors[1], STDOUT_FILENO);
                Os.dup2(descriptors[2], STDERR_FILENO);

                for (FileDescriptor fd: descriptors) {
                    IoUtils.closeQuietly(fd);
                }
            } catch (ErrnoException ex) {
                Log.e(TAG, "Error reopening stdio", ex);
            }
        }

        if (parsedArgs.niceName != null) {
            Process.setArgV0(parsedArgs.niceName);
        }

        // End of the postFork event.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        if (parsedArgs.invokeWith != null) {
            WrapperInit.execApplication(parsedArgs.invokeWith,
                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
                    VMRuntime.getCurrentInstructionSet(),
                    pipeFd, parsedArgs.remainingArgs);

            // Should not get here.
            throw new IllegalStateException("WrapperInit.execApplication unexpectedly returned");
        } else {
            if (!isZygote) {
                return ZygoteInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs,
                        null /* classLoader */);
            } else {
                return ZygoteInit.childZygoteInit(parsedArgs.targetSdkVersion,
                        parsedArgs.remainingArgs, null /* classLoader */);
            }
        }
    }
parsedArgs.invokeWith 普遍为Null,如果不为 Null 则通过 exec 方式启动 app_process 进程来执行Java类(通过执行 exec 的区别前面文章有讲过,fork + exec 和 单独 fork 不执行 exec 的区别)。
正常情况会调用 ZygoteInit.zygoteInit ,主要执行的方法 是 RuntimeInit.commonInit,ZygoteInit.nativeZygoteInit() 和 RuntimeInit.applicationInit
    public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, ClassLoader classLoader) {
        if (RuntimeInit.DEBUG) {
            Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote");
        }

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ZygoteInit");
        RuntimeInit.redirectLogStreams();

        RuntimeInit.commonInit();
        ZygoteInit.nativeZygoteInit();
        return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader);
    }
  • RuntimeInit.commonInit(); 方法
protected static final void commonInit() {
        LoggingHandler loggingHandler = new LoggingHandler();
        // 设置 UncaughtException 的处理方法
        Thread.setUncaughtExceptionPreHandler(loggingHandler);
        Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler));
		// 设置时区
        TimezoneGetter.setInstance(new TimezoneGetter() {
            @Override
            public String getId() {
                return SystemProperties.get("persist.sys.timezone");
            }
        });
        TimeZone.setDefault(null);
        // 重制 Log 系统
        LogManager.getLogManager().reset();
        new AndroidConfig();
        // 设置 http.agent 属性
        String userAgent = getDefaultUserAgent();
        System.setProperty("http.agent", userAgent);
        NetworkManagementSocketTagger.install();
        String trace = SystemProperties.get("ro.kernel.android.tracing");
        if (trace.equals("1")) {
            Slog.i(TAG, "NOTE: emulator trace profiling enabled");
            Debug.enableEmulatorTraceOutput();
        }

        initialized = true;
    }
  • ZygoteInit.nativeZygoteInit(); 方法
    这个是 native 方法 private static final native void nativeZygoteInit(); 调用的是 AndroidRuntime.cpp中的方法

static void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
{
    gCurRuntime->onZygoteInit();
}

gCurRuntime 是 AppRuntime 的全局指针,它的 onZygoteInit() 函数如下:主要是初始化了 Binder 环境,这样应用就可以使用 Binder了。
    virtual void onZygoteInit()
    {
        sp<ProcessState> proc = ProcessState::self();
        ALOGV("App process: starting thread pool.\n");
        proc->startThreadPool();
    }
  • RuntimeInit.applicationInit() 方法
protected static Runnable applicationInit(int targetSdkVersion, String[] argv,
            ClassLoader classLoader) {
        // If the application calls System.exit(), terminate the process
        // immediately without running any shutdown hooks.  It is not possible to
        // shutdown an Android application gracefully.  Among other things, the
        // Android runtime shutdown hooks close the Binder driver, which can cause
        // leftover running threads to crash before the process actually exits.
        nativeSetExitWithoutCleanup(true);
	
        // We want to be fairly aggressive about heap utilization, to avoid
        // holding on to a lot of memory that isn't needed.
        // 这个两个是设置虚拟机的参数
        VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
        VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);

        final Arguments args = new Arguments(argv);

        // The end of of the RuntimeInit event (see #zygoteInit).
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

        // Remaining arguments are passed to the start class's static main
        // findStaticMain 内部的 Run 方法通过 invoke 执行Java层的 main 方法
        return findStaticMain(args.startClass, args.startArgs, classLoader);
    }
findStaticMain 经过一系列的处理最后会调用到 MethodAndArgsCaller 返回 Runnable,执行run方法后执行的是下面方法,mMethod 实际上是 main 方法。

 // Method m;
 //       try {
 //           m = cl.getMethod("main", new Class[] { String[].class });

 mMethod.invoke(null, new Object[] { mArgs });
接下来就进入了应用本身的启动流程了。

预加载系统类和资源

为了加快应用程序的启动,Android把系统常用的 Java 类和一部分 Framework 的资源在 zygote 进程中加载,这些预加载的类和资源在所有经 Zygote fork出的进程中都是共享的。

在这里插入图片描述

ZygoteInit 在 main() 方法中调用的 preload(bootTimingsTraceLog); 加载系统类,系统资源和OpenGL等方法初始化如下
static void preload(TimingsTraceLog bootTimingsTraceLog) {
        beginIcuCachePinning();
      
        preloadClasses();
      
        preloadResources();
     
        nativePreloadAppProcessHALs();
      
        preloadOpenGL();
       
        preloadSharedLibraries();
        preloadTextResources();
        
        WebViewFactory.prepareWebViewInZygote();
        endIcuCachePinning();
        warmUpJcaProviders();
        sPreloadComplete = true;
    }
  • 预加载 Java 类

    系统的 Java 类会频繁的执行,因此,需要把类的信息预先加载到系统内存中,并在所有应用进程中共享,Android将所有需要预加载的类放到了本地文件 preloaded-classes中,android9的目录位于 frameworks/base/config/preloaded-classes

android.R$styleable
android.accessibilityservice.AccessibilityServiceInfo
android.accessibilityservice.AccessibilityServiceInfo$1
android.accounts.Account
android.accounts.Account$1
android.accounts.AccountManager
android.accounts.AccountManager$1
android.accounts.AccountManager$10
android.accounts.AccountManager$11
android.accounts.AccountManager$18
android.accounts.AccountManager$2
android.accounts.AccountManager$20
android.accounts.IAccountManagerResponse
android.accounts.IAccountManagerResponse$Stub
android.accounts.OnAccountsUpdateListener
android.accounts.OperationCanceledException
android.animation.AnimationHandler
android.animation.AnimationHandler$1
android.animation.AnimationHandler$AnimationFrameCallback
android.animation.AnimationHandler$AnimationFrameCallbackProvider

.....
加载这些类的方法是 preloadClasses() ,流程如下
    private static void preloadClasses() {
        final VMRuntime runtime = VMRuntime.getRuntime();
		// 首先获取一个 FileInputStream 通过流读取文件内容
        InputStream is;
        try {
            is = new FileInputStream(PRELOADED_CLASSES);
        } catch (FileNotFoundException e) {
            Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
            return;
        }
		// 。。。。
		while ((line = br.readLine()) != null) {
                // Skip comments and blank lines.
                line = line.trim();
                // 忽略掉注释
                if (line.startsWith("#") || line.equals("")) {
                    continue;
                }

                Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
                try {
                    if (false) {
                        Log.v(TAG, "Preloading " + line + "...");
                    }
                    // 然后通过 Class.forName 装载类的信息(它不会创建一个对象)
                    Class.forName(line, true, null);
                    count++;
                } 
	//。。。。
    }
  • 预加载资源
private static void preloadResources() {
        final VMRuntime runtime = VMRuntime.getRuntime();

        try {
            mResources = Resources.getSystem();
            mResources.startPreloading();
            if (PRELOAD_RESOURCES) {
                Log.i(TAG, "Preloading resources...");

                long startTime = SystemClock.uptimeMillis();
                TypedArray ar = mResources.obtainTypedArray(
                        com.android.internal.R.array.preloaded_drawables);
                int N = preloadDrawables(ar);
                ar.recycle();
                Log.i(TAG, "...preloaded " + N + " resources in "
                        + (SystemClock.uptimeMillis()-startTime) + "ms.");

                startTime = SystemClock.uptimeMillis();
                ar = mResources.obtainTypedArray(
                        com.android.internal.R.array.preloaded_color_state_lists);
                N = preloadColorStateLists(ar);
                ar.recycle();
                Log.i(TAG, "...preloaded " + N + " resources in "
                        + (SystemClock.uptimeMillis()-startTime) + "ms.");

                if (mResources.getBoolean(
                        com.android.internal.R.bool.config_freeformWindowManagement)) {
                    startTime = SystemClock.uptimeMillis();
                    ar = mResources.obtainTypedArray(
                            com.android.internal.R.array.preloaded_freeform_multi_window_drawables);
                    N = preloadDrawables(ar);
                    ar.recycle();
                    Log.i(TAG, "...preloaded " + N + " resource in "
                            + (SystemClock.uptimeMillis() - startTime) + "ms.");
                }
            }
            mResources.finishPreloading();
        } catch (RuntimeException e) {
            Log.w(TAG, "Failure preloading resources", e);
        }
    }

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-03-30 18:36:39  更:2022-03-30 18:40:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 18:43:02-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码