文档章节

android系统手机启动流程

 等待流星
发布于 2014/01/21 14:58
字数 4028
阅读 54
收藏 1

第三部分:Android启动

    Android的启动过程是从进程init开始的,所以它是后续所有进程的祖先进程。

一、init进程

源码位于system/core/init目录。主要做了以下事情:

1.     重新设置子进程终止时信号SIGCHLD的处理函数。

act.sa_handler = sigchld_handler;  //调用了wait函数等待子进程退出。

act.sa_flags = SA_NOCLDSTOP;

act.sa_mask = 0;

act.sa_restorer = NULL;

sigaction(SIGCHLD, &act, 0);

 

2. 将kernel启动过程中建立好的文件系统框架mount到相应目录。

    mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755");

   …

    mount("devpts", "/dev/pts", "devpts", 0, NULL);

    mount("proc", "/proc", "proc", 0, NULL);

mount("sysfs", "/sys", "sysfs", 0, NULL);

   

3.     open_devnull_stdio(),将init进程的标准输入、输出、出错设备设置为新建的设备节点/dev/__null__。

4.     log_init(),创建并打开设备节点/dev/__kmsg__。

5.     读取并解析rc配置文件。

5.1 先从文件/sys/class/BOOT/BOOT/boot/boot_mode读出启动方式:Factory Mode, '4';ATE Factory Mode, '6'。看是否是facatory模式。

5.2 如果是的话,需要读取并解析两个文件:init.factory.rc和init.rc。

5.3 如果是正常启动,则暂时先读取init.rc。

这里在读取解析文件的时候,是以行为最小可执行单位在解析。关于书写init.rc文件的初始化脚本语言的规则,可以上网查找。解析之后并不会马上执行,而是在init进入服务循环之前统一根据其命令本身所带的条件来执行。

   

6.     导入kernel的cmdline,也就是u-boot传递给kernel的参数,查看其中是否具有androidboot.xxx(androidboot.mode、androidboot.hardware等)参数,如果有,将其保存在同名字的xxx(mode、hardware)全局变量中。这里特别说明的是hardware这个参数,从kernel中导出一部分之后,又要从/proc/cpuinfo中导出一部分来组合成完整的hardware参数,因为后面接下来会读取并解析以特定平台的rc文件。

7.     读取特定平台相关的initrc文件,如:init.mt6516.rc。

需要注意的是:对于service,这里会给每个服务建立一个struct service的结构体,全部挂入链表service_list之中,在init最后才启动。

   

8.     检查解析出来的所有命令行当中是否有属于early-init的,如果有,将其提出来加入到链表action_queue之中,马上将其执行掉。

 

9.     device_init()函数将会打开uevent的netlink socket,遍历/sys/class、/sys/block、/sys/devices目录,检查各级目录的uevent文件,处理在vold服务起来之前由kernel所发出来的device add, remove等事件。

 

10.  property_init(), 顾名思义,是属性初始化。首先创建一个名字为system_properties的匿名共享内存区域,对并本init进程做mmap读写映射,其余共享它的进程只有读的权限。然后将这个prop_area结构体通过全局变量__system_property_area__传递给property services。

接着调用函数load_properties_from_file(PROP_PATH_RAMDISK_DEFAULT)从/default.prop文件中加载编译时生成的属性。

       

11.  如果在root目录下有initlogo.rle文件存在,这个是两张android字样的缕空图片,将其读入fb中显示到LCD上。同时也要往串口上输出"             A N D R O I D "。如果图片不存在,就没有这两项的输出。

 

12.  设置相应的属性:

property_set("ro.factorytest", "0")

property_set("ro.serialno", serialno[0] ? serialno : "");

property_set("ro.bootmode", bootmode[0] ? bootmode : "unknown");

property_set("ro.baseband", baseband[0] ? baseband : "unknown");

property_set("ro.carrier", carrier[0] ? carrier : "unknown");

property_set("ro.bootloader", bootloader[0] ? bootloader : "unknown");

 

property_set("ro.hardware", hardware);

snprintf(tmp, PROP_VALUE_MAX, "%d", revision);

property_set("ro.revision", tmp);

 

13.  开始执行以init为trigger的命令行:

action_for_each_trigger("init", action_add_queue_tail);

drain_action_queue();

        前面有执行过eraly-init的。

       

14.  启动属性服务:property_set_fd = start_property_service();

先读取剩余三个文件中的属性:/system/build.prop、/system/default.prop、/system/default.prop,然后用函数load_persistent_properties()加载persist.开始的属性,这种属性都是保存在目录/data/property下的以属性名为文件名的中。

接下来创建一个名为property_service的socket接口(SOCK_STREAM),然后进入监听状态,等待属性事件到来。

   

15.  创建一对socket,用来做信号方面的处理。

socketpair(AF_UNIX, SOCK_STREAM, 0, s),signal_fd = s[0],signal_recv_fd = s[1]。

 

    16.执行eraly-boot和boot为trigger的命令

        action_for_each_trigger("early-boot", action_add_queue_tail);

        action_for_each_trigger("boot", action_add_queue_tail);

        drain_action_queue();

       

17.执行init.rc中以property:开头的属性设置语句,同时使能属性触发方式。

queue_all_property_triggers();

drain_action_queue();

       

        property_triggers_enabled = 1;  //  可以执行那些以属性为条件的init语句。

       

1.     接下来就是利用poll机制监听前面创建的几个fd的动态。

struct pollfd ufds[4];

ufds[0].fd = device_fd;

ufds[0].events = POLLIN;

ufds[1].fd = property_set_fd;

ufds[1].events = POLLIN;

ufds[2].fd = signal_recv_fd;

ufds[2].events = POLLIN;

 

for(;;) {

     int nr, i, timeout = -1;

           

    for (i = 0; i < fd_count; i++)

        ufds[i].revents = 0;

    

     drain_action_queue(); //执行action_queue链表中后来新出现的command。

     restart_processes();  // 第一次启动所有服务,也包括后来restart这些

服务。restart_service_if_needed() à service_start(svc, NULL) à fork()

    

     …

     nr = poll(ufds, fd_count, timeout);

     if (nr <= 0)

            continue;

    

     if (ufds[2].revents == POLLIN) {

            /* we got a SIGCHLD - reap and restart as needed */

            read(signal_recv_fd, tmp, sizeof(tmp));

            while (!wait_for_one_process(0))

                ;

            continue;

     }

    

     if (ufds[0].revents == POLLIN)

          handle_device_fd(device_fd);  // Vold的netlink类型的socket

    

     if (ufds[1].revents == POLLIN)

          handle_property_set_fd(property_set_fd);//属性服务的socket

     if (ufds[3].revents == POLLIN)

            handle_keychord(keychord_fd);

}

到这里init就进入了死循环中一直在监听ufds中的4个文件描述符的动静,如果有POLLIN的事件,就做相应的处理,所以init并没有退出或者进入idle,而是被当做一个服务在运行。第4个文件描述符是keychord_fd,暂时不清楚这个怎么用,不过通过它也可以启动服务,可参考源码。

下面是init.rc的例子,见附件init.rc

       

二、init中启动的各种服务

在init中启动起来的服务按照init.rc中的先后顺序,大致有:

console: start a shell,code path: system/bin/sh,其源码中包含常用的shell命令,如ls,cd等。

adbd: start adb daemon,通常带有disabled的选项,表明需要按名字启动,code path:system/bin/adb。

servicemanager:这个服务管理着系统内所有binder services。code path: frameworks/base/cmds/servicemanager。

Vold: android 的udev,code path: system/vold。

Netd: start ntd daemon, code path: system/netd。

Debuggerd: start debug system, code path: system/core/debuggerd。

zygote: ['zaigəut]这是一个非常重要的服务,稍后详解。start Android  Java Runtime  and start systemserver。code path:frameworks/base/cmds/app_process。

media: add AudioFlinger,AudioPolicyService,MediaPlayerService and CameraService to servicemanager,同时启动管理binder通讯的机制,依靠这两个类来完成binder机制在android中间层所体现的功能:ProcessState 和IPCThreadState。Code path:frameworks/base/media/mediaserver。

bootanim: 开机动画和铃声,code path:frameworks/base/cmds/bootanimation。

 

接下来就是关于modem的服务,如:ccci_fsd、ccci_mdinit、pppd_gprs、pppd、gsm0710muxd、muxtestapp、sockcli、socksrv、muxreport、ril-daemon等,除了前面2个,后面的都带有disabled的参数,需要按名启动。

 

Installd: start install package daemon, code path:

frameworks/base/cmds/installd。

后面还有很多关于其他硬件的服务,比如BT、WIFI等。

 

2.1 servicemanager

    这个服务进程代码比较简单,功能也简单,c实现的,用来管理系统中所有的binder service,不管是本地的c++实现的还是java语言实现的都需要这个进程来统一管理,最主要的管理就是,注册添加服务,获取服务。

    这些binder服务在对外公开之前都必须将自身拥有的binder实体注册到SMgr中,而其他进程如果需要使用binder service的功能,也必须先经过SMgr取得 binder service中的binder实体在SMgr中对应的引用号(binder的引用号和进程中文件描述符概念类似,有效范围只限于本进程)。

 

#define BINDER_SERVICE_MANAGER ((void*) 0)  

int main(int argc, char **argv) 

    struct binder_state *bs; 

    void *svcmgr = BINDER_SERVICE_MANAGER;  

    bs = binder_open(128*1024);   

    // 打开binder设备,mmap映射当前进程的binder接收缓冲区,返回一个binder_state结构体  。

    if (binder_become_context_manager(bs)) {     

    // 通过这个函数将当前进程设置成服务管理进程MSgr,整个系统就这一个。 

       …

    } 

    svcmgr_handle = svcmgr;    

    binder_loop(bs, svcmgr_handler);  

 /*svcmgr_handler作为处理函数,所能完成的操作有:获取service,查看service是否存在,添加service ,列出service清单。其中用的最多的就是获取、添加。*/ 

    return 0; 

}

   

    2.2 zygote

        zygote服务进程也叫做孵化进程,在linux的用户空间,进程app_process会做

一些zygote进程启动的前期工作,如,启动runtime运行时环境(实例),参数分解,设置startSystemServer标志,接着用runtime.start()来执行zygote服务的代码,其实说简单点,就是zygote抢了app_process这个进程的躯壳,改了名字,将后面的代码换成zygote的main函数,这样顺利地过度到了zygote服务进程。这样我们在控制台用ps看系统所有进程,就不会看到app_process,取而代之的是zygote。

       

        而前面runtime.start()这个函数实际上是类函数AndroidRuntime::start(),在

这个函数中,会新建并启动一个虚拟机实例来执行com.android.internal.os.ZygoteInit这个包的main函数。这个main函数中会fork一个子进程来启动systemserver,父进程就作为真正的孵化进程存在了,每当系统要求执行一个 Android应用程序,Zygote就会收到socket消息FORK出一个子进程来执行该应用程序。因为Zygote进程是在系统启动时产生的,它会完成虚拟机的初始化,库的加载,预置类库的加载和初始化等操作,而在系统需要一个新的虚拟机实例时可以快速地制造出一个虚拟机出来。

   

    每一个Android应用都运行在一个Dalvik虚拟机实例里,而每一个虚拟机实例都是一个独立的进程空间。虚拟机的线程机制,内存分配和管理,Mutex等等都是依赖底层linux实现的。所以android应用程序中创建了线程将会实际调用到linux的线程创建接口,和虚拟机所在进程共享一个虚拟机实例对java代码执行。

   

2.2.1 app_process

下面重点讨论zygote服务,源码位于frameworks/base/cmds/app_process。

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

参数:/system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

 

int main(int argc, const char* const argv[])

{

    …

    AppRuntime runtime; // 这里启动runtime运行时环境(实例),AppRuntime是AndroidRuntime的子类,在创建这个对象的时候会依次调用基类和子类的构造函数。

    const char *arg;

    const char *argv0;

    …

    argv0 = argv[0];

    // ignore argv[0]

    argc--;

argv++;

/*

    argc = 4;

argv[0] = “-Xzygote”;

argv[1] = “/system/bin”;

argv[2] = “--zygote”;

argv[3] = “–start-system-server”;

*/

    int i = runtime.addVmArguments(argc, argv); // 找到参数中第一个不是以单个-开始的参数,这里很明显是第二个参数:/system/bin

    if (i < argc) {

        runtime.mParentDir = argv[i++];  // 将命令目录保存在mParentDir中,之后i = 2。

    }

    if (i < argc) {

        arg = argv[i++];

        if (0 == strcmp("--zygote", arg)) { // 通常这个分支是成立的

            bool startSystemServer = (i < argc) ?

                    strcmp(argv[i], "--start-system-server") == 0 : false;

// startSystemServer = true ,这个bool变量决定着后面执行runtime.start时是否启动systemserver。

            setArgv0(argv0, "zygote");

            set_process_name("zygote");

            runtime.start("com.android.internal.os.ZygoteInit",

                startSystemServer);

        } else {

            … // 只启动AndroidRuntime

        }

    }else{

        … // 错误处理

    }

} // main()

   

    2.2.2 AndroidRuntime

    下面进一步分析

    runtime.start("com.android.internal.os.ZygoteInit",startSystemServer);

    位于文件frameworks/base/core/jni/ AndroidRuntime.cpp,类定义于:

    frameworks/base/include/android_runtime/AndroidRuntime.h

    void AndroidRuntime::start(const char* className,

const bool startSystemServer){

LOGD("\n>>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<\n");

    JNIEnv* env;

    …

    /* start the virtual machine , mJavaVM是类AndroidRuntime的私有变量,env该函数中局部变量 */

    if (startVm(&mJavaVM, &env) != 0)

        goto bail;

 

    /* Register android functions.向刚刚新建的虚拟机注册JNI本地接口。frameworks/base/core/jni/这个目录下的所有jni接口。*/

    if (startReg(env) < 0) {

        …

goto bail;

}

 

//启动虚拟机之前需要构造java形式的参数数组,如下:

jclass stringClass;

jobjectArray strArray;

    jstring classNameStr;

    jstring startSystemServerStr;

 

stringClass = env->FindClass("java/lang/String");

strArray = env->NewObjectArray(2, stringClass, NULL);

classNameStr = env->NewStringUTF(className);

env->SetObjectArrayElement(strArray, 0, classNameStr);

startSystemServerStr = env->NewStringUTF(startSystemServer ?

                                                 "true" : "false");

env->SetObjectArrayElement(strArray, 1, startSystemServerStr);

/*  strArray[0] = “com.android.internal.os.ZygoteInit”

    strArry[1] = “true”*/

 

/*

 * Start VM.  This thread becomes the main thread of the VM, and will

 * not return until the VM exits.

 */

jclass startClass;

jmethodID startMeth;

 

slashClassName = strdup(className);

for (cp = slashClassName; *cp != '\0'; cp++)

    if (*cp == '.')

        *cp = '/';  // 将包名换成路径

 

startClass = env->FindClass(slashClassName); // 根据路径找到这个包

            // com.android.internal.os.ZygoteInit,这个类位于文件

            // com/ndroid/nternal/os/ZygoteInit.java

if (startClass == NULL) {

     LOGE("JavaVM unable to locate class '%s'\n", slashClassName);

     /* keep going */

 } else {

        /*这个参数决定了我们接下来执行的是zygoteInit.java的main函数。*/

        startMeth = env->GetStaticMethodID(startClass, "main",

            "([Ljava/lang/String;)V");

        if (startMeth == NULL) {

            LOGE("JavaVM unable to find main() in '%s'\n", className);

            /* keep going */

        } else {

            env->CallStaticVoidMethod(startClass, startMeth, strArray);

                // 虚拟机启动,将会调用到com.android.internal.os.ZygoteInit包的main函数。

}

}

 

LOGD("Shutting down VM\n");

    if (mJavaVM->DetachCurrentThread() != JNI_OK)

        LOGW("Warning: unable to detach main thread\n");

    if (mJavaVM->DestroyJavaVM() != 0)

        LOGW("Warning: VM did not shut down cleanly\n");

 

bail:

    free(slashClassName);

}

} // start()

   

    2.2.3 ZygoteInit

    下面进入com.android.internal.os.ZygoteInit 包的main函数:

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

    public static void main(String argv[]) {

        try {

            …

            registerZygoteSocket(); // Registers a server socket for zygote command

            // load classs and resources

    preloadClasses();

    preloadResources();

    …

    // Do an initial gc to clean up after startup

gc();/*初始化GC垃圾回收机制*/

 

/* 通过从前面传递过来的第二个参数startsystemserver=”true” 启动systemserver, 在startSystemServer()中会fork一个新的进程命名为system_server, 执行的是com.android.server包中的SystemServer.java文件中的main函数, 源码位置:frameworks/base/services/java/com/android/server/ SystemServer.java。*/

if (argv[1].equals("true")) {

                startSystemServer(); ///*************

} else if(…)

 

if (ZYGOTE_FORK_MODE) {

    runForkMode();      /* ZYGOTE_FORK_MODE 永远为flase */

} else {

    runSelectLoopMode();/* Zygote进程进入无限循环,不再返回。接下来的zygote将会作为一个孵化服务进程来运行。*/

}

 

closeServerSocket();

}

        …

    } // end main()

   

从这里开始android启动分为两条线走,分别是:

startSystemServer(); ---------- Zygote的子进程

runSelectLoopMode(); /* Zygote进程进入无限循环,不再返回,执行孵化工作。*/

 

2.2.4 systemserver

    上面的startSystemServer()函数中,做了如下的调用:

    startSystemServer()

    à Zygote.forkSystemServer()

    à 父进程直接返回true,子进程执行handleSystemServerProcess()

        à RuntimeInit.zygoteInit(parsedArgs.remainingArgs);

        /*

         * Pass the remaining arguments to SystemServer.

         * "--nice-name=system_server com.android.server.SystemServer"

         */

/*上面这个RuntimeInit包定于于文件frameworks\base\core\java\com\android\internal\os\RuntimeInit.java */

à invokeStaticMain(startClass, startArgs)

/* 通过该函数调用执行startClass类的main函数,带参数 system_server 。*/

à ZygoteInit.MethodAndArgsCaller(m, argv)

/* 得到包com.android.server.SystemServer的main()函数。然后执行。*/

   

    下面就开始调用到com.android.server.SystemServer类的main函数,源码位于:

    frameworks/base/services/java/com/android/server/SystemServer.java 

   

This method is called from Zygote to initialize the system. This will cause the nativeservices (SurfaceFlinger, AudioFlinger, etc..) to be started. After that it will call backup into init2() to start the Android services.

    // main--->init1(system_init)--->init2(systemThread)

native public static void init1(String[] args);

       

public static void main(String[] args) {

        ...

System.loadLibrary("android_servers");// libandroid_servers.so是由目录frameworks/base/services/jni下的源码编译所得

init1(args); // init1实际上是一个jni本地接口,位于文件frameworks\base\services\jni\com_android_server_SystemServer.cpp文件中system_init()函数

}

   

init1接口位于com_android_server_SystemServer.cpp中:

extern "C" int system_init();

static void android_server_SystemServer_init1(JNIEnv* env, jobject clazz){

    system_init();

}

 

static JNINativeMethod gMethods[] = {

    /* name, signature, funcPtr */

{"init1","([Ljava/lang/String;)V",(void*)

android_server_SystemServer_init1 },

};

而函数system_init()位于文件

frameworks\base\cmds\system_server\library\System_init.cpp

extern "C" status_t system_init()

{

    …

    char propBuf[PROPERTY_VALUE_MAX];

    property_get("system_init.startsurfaceflinger", propBuf, "1");

    if (strcmp(propBuf, "1") == 0) {

        // Start the SurfaceFlinger

        SurfaceFlinger::instantiate();

    }

    if (!proc->supportsProcesses()) {

       

        // Start the AudioFlinger

        AudioFlinger::instantiate();

       

        // Start the media playback service

        MediaPlayerService::instantiate();

       

        // Start the camera service

        CameraService::instantiate();

       

        // Start the audio policy service

        AudioPolicyService::instantiate();

       

        //start appc service

        APPCService::instantiate();

    }

    …

    AndroidRuntime* runtime = AndroidRuntime::getRuntime();

    runtime->callStatic("com/android/server/SystemServer", "init2");

    // 执行com.android.server.SystemServer类的init2函数

}

com.android.server.SystemServer包的init2函数开启一个ServerThread线程:

public static final void init2() {

    Thread thr = new ServerThread();

    thr.setName("android.server.ServerThread");

    thr.start();

}

ServerThread线程的run函数会启动系统中绝大部分的android service,并最后进入Loop.loop(),,,,(SystemServer.java)

public void run() {

    …

    // Critical services...

    try {

        …

        Slog.i(TAG, "Power Manager");

        power = new PowerManagerService();

        ServiceManager.addService(Context.POWER_SERVICE, power);

       

        Slog.i(TAG, "Activity Manager");

        context = ActivityManagerService.main(factoryTest);

       

        …

        Slog.i(TAG, "Package Manager");

        pm = PackageManagerService.main(context,

                factoryTest != SystemServer.FACTORY_TEST_OFF);

       

        …

        Slog.i(TAG, "Content Manager");

        ContentService.main(context,

             factoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL);

       

        …

        Slog.i(TAG, "Battery Service");

        battery = new BatteryService(context);

        ServiceManager.addService("battery", battery);

        …

        其余addservice的过程类似,只是启动的不同服务罢了,后面还启动了很多

服务,如:Lights、Vibrator、Alarm、Sensor、Bluetooth、Input Method、NetStat、NetworkManagement、Connectivity、Mount、Notification、Audio等。在这些服务都启动完了之后。

        …

        …

        … // run()函数的后半部分

        // It is now time to start up the app processes...

        使用xxx.systemReady()通知各个服务,系统已经就绪。

        …

        ((ActivityManagerService)ActivityManagerNative.getDefault())

                .systemReady(new Runnable() {

         public void run() {

                …

                if (batteryF != null) batteryF.systemReady();

                if (connectivityF != null) connectivityF.systemReady();

                if (dockF != null) dockF.systemReady();

                if (uiModeF != null) uiModeF.systemReady();

                if (recognitionF != null) recognitionF.systemReady();

                Watchdog.getInstance().start();

                …

            }

        });

        …

        Looper.loop(); // Run the message queue in this thread。

        …

}

 

2.2.5 home界面启动

    Home在

((ActivityManagerService)ActivityManagerNative.getDefault()).systemReady(.)

函数调用的过程中启动,其中systemReady()的参数是一段callback代码,如上面灰色显示的部分。

这个函数的实现部分在文件:ActivityManagerService.java中。

public void systemReady(final Runnable goingCallback) {

    …

   

    if (mSystemReady) {

        if (goingCallback != null) goingCallback.run();

        return; // 执行回调

    }

    …

    resumeTopActivityLocked(null);

}

private final boolean resumeTopActivityLocked(HistoryRecord prev) {

    …

    if (next == null) {

        // There are no more activities!  Let's just start up the

        // Launcher...

        return startHomeActivityLocked();

    }

    …

}

private boolean startHomeActivityLocked() {

    …

    if (aInfo != null) {

        …

        if (app == null || app.instrumentationClass == null) {

                intent.setFlags(intent.getFlags() |

                                Intent.FLAG_ACTIVITY_NEW_TASK);

                startActivityLocked(null, intent, null, null, 0, aInfo,

                        null, null, 0, 0, 0, false, false); // 这里启动home

         }

     }

    …

}

三、android启动图示

参考网址:

1.        http://blog.csdn.net/cuijianzhongswust/article/details/6612624

2.        http://blog.csdn.net/sustzombie/article/details/6659622

3.        http://www.docin.com/p-191202348.html

4.        http://blog.csdn.net/maxleng/article/details/5508372

 


本文转载自:http://blog.csdn.net/lizhiguo0532/article/details/7028910

粉丝 5
博文 78
码字总数 10995
作品 0
崇明
私信 提问
五种控制Android应用的权限的方法

五种控制Android应用的权限的方法 这篇文章目的在于介绍Android系统上控制权限的方法,读者只要使用过Android,或是对智能机平台有所了解,就能看懂,不需要专门的编程知识。   1 为什么A...

庸人谷
2013/02/19
251
0
Android基础之Android系统启动

Android系统的启动操作流程由Linux系统启动与Android应用系统启动两个阶段组成。 Linux系统启动 Android操作系统启动次序分别为系统上电,Bootloader引导,Linux内核启动,init初始化系统服务...

柳哥
2014/11/28
743
0
Android手机启动流程与TEE OS

转载:https://cloud.tencent.com/developer/article/1043659 一个移植了TEEOS的Android手机系统启动流程如下: 系统启动流程如图所示,具体为: ①系统上电,PC指针指向芯片内部BOOT ROM地址...

MtrS
06/09
98
0
Android M Launcher3主流程源码浅析

背景 关于Launcher是啥的问题我想这里就没必要再强调了。由于一些原因迫使最近开始需要研究一下Launcher3源码,为了不再像以前那么傻逼(研究Settings等代码没作笔记),故这里赶紧将阶段性的...

aweiloveandroid
2017/06/29
0
0
第一个Android 程序的源代码: TxtReader文本阅读器

刚学习Android开发的时候, 试着开发了一个阅读器,非常简单,所以易懂, 目前在本人的G1上跑得还是不错的 ^-^ 贡献出来给入门者参考: 主要问题: 1. 如何处理cancel事件 2. ListAdapter的使...

晨曦之光
2012/03/09
181
0

没有更多内容

加载失败,请刷新页面

加载更多

一套完整的软件开发流程是怎样的?

做什么事都需要一个流程,软件开发也不例外。 那么,一个软件从无到有到底是怎么开发的?一个软件产品的结果为什么是这样?为什么开发的速度不能再快一点。为什么程序员大多秃顶?他们有那么...

我想造火箭
10分钟前
2
0
漂亮思维导图怎样绘制?教你快速套用思维导图模板绘制d

用MindMaster软件绘制思维导图,会更加高效和美观!因为MindMaster是一款专业的思维导图软件,零基础经验的朋友花费5分钟时间就能掌握它的相关画法。以下是电脑软件思维导图画法的简单步骤。...

工具分享
13分钟前
3
0
linux 软链接与 硬链接的区别

软链接与硬链接的区别 1. 硬链接不会创建inode,即使用的inode都是一样的。软链接会创建新的inode。 2. 硬链接的访问属性和源文件一模一样,没有l的标识。软链接的访问属性写明了是l,且访问...

突突突酱
14分钟前
1
0
新特性解读 | MySQL 8.0.18 有权限控制的复制

原文:Replication with restricted privileges https://mysqlhighavailability.com/replication-with-restricted-privileges/ 作者:Pedro Figueiredo 翻译:管长龙 背景 MySQL 8.0.18 以前......

爱可生
23分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部