zoukankan      html  css  js  c++  java
  • Android OS Startup

    OS puts emphases on how to provide interfaces to user's APPs

    for using hardware device

    in the convenient and efficient way.

    Everything has its own logic of running and must be done step by step,no shortcut. 

    Take a look at Android OS Framework

    Now, how to make the above OS run and every component of OS work to provide the service for user APP. Take a look at the process of startup as following picture

    Step1: Startup from power on, Linux start up

    • System boot:bootloader  @bootable/bootloader/* 
      • Comera+Power--->fastboot,
      • Home+Power--->load recovery.img
      • Power--->load boot.img
    • bootloader loads linux kernel
    • linux kernel initialize: @kernel/*

    Step2: Android startup

    • kernel loads init-process: @ system/core/init/*
    • read config file: @system/rootdir/init.rc to start android service and start other commands and service(adbd for adb service, vold for SD mount)
    • Start native service, and other services such as ServiceManager,Zygote,media service.
    • Init read init process to start ril-daemon, media, bootsound, bootanim, servicemanager...... 
     1 init:@System/Core/Init
     2 Init.c: parse_config_file(Init.rc)  @parse_config_file(Init.marvel.rc)
     3 解析脚本文件:Init.rc和Init.xxxx.rc(硬件平台相关)
     4 Init.rc是Android自己规定的初始化脚本(Android Init Language, System/Core/Init/readme.txt)
     5 该脚本包含四个类型的声明: Actions, Commands, Services, Options.
     6 1.2 服务启动机制,我们来看看Init是这样解析.rc文件开启服务的。
     71)打开.rc文件,解析文件内容 @system/core/init/init.c
     8 将service信息放置到service_list中。@system/core/init/parser.c
     92)restart_service() @system/core/init/init.c
    10 service_start
    11 execve(…).建立service进程。
    12 
    13 
    14 export PATH /sbin:/system/sbin:/system/bin 
    15 export LD_LIBRARY_PATH /system/lib 
    16 mkdir /dev 
    17 mkdir /proc 
    18 mkdir /sys 
    19 mount tmpfs tmpfs /dev 
    20 mkdir /dev/pts 
    21 mkdir /dev/socket 
    22 mount devpts devpts /dev/pts 
    23 mount proc proc /proc 
    24 mount sysfs sysfs /sys 
    25 write /proc/cpu/alignment 4 
    26 ifup lo 
    27 hostname localhost 
    28 domainname localhost 
    29 mount yaffs2 mtd@system /system 
    30 mount yaffs2 mtd@userdata /data 
    31 import /system/etc/init.conf 
    32 class_start default 
    33 service adbd /sbin/adbd 
    34 user adb 
    35 group adb 
    36 service usbd /system/bin/usbd -r 
    37 user usbd 
    38 group usbd 
    39 socket usbd 666 
    40 service zygote /system/bin/app_process -Xzygote /system/bin --zygote 
    41 socket zygote 666 
    42 service runtime /system/bin/runtime 
    43 user system
    44 group system 
    45 on device-added-/dev/compass 
    46 start akmd 
    47 on device-removed-/dev/compass 
    48 stop akmd 
    49 service akmd /sbin/akmd 
    50 disabled 
    51 user akmd 
    52 group akmd 
    53 Zygote
    54 
    55 // @systemcore
    ootdirinit.rc
    56 service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    57 socket zygote stream 666 
    58 onrestart write /sys/android_power/request_state wake 
    59 onrestart write /sys/power/state on 
    60 onrestart restart media 

    Step3: Zygote Startup

    • In init.rc: service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server    
      1 /**
      2  * Main entry of app process.
      3  *
      4  * Starts the interpreted runtime, then starts up the application.
      5  *
      6  */
      7 
      8 #define LOG_TAG "appproc"
      9 
     10 #include <binder/IPCThreadState.h>
     11 #include <binder/ProcessState.h>
     12 #include <utils/Log.h>
     13 #include <cutils/process_name.h>
     14 #include <cutils/memory.h>
     15 #include <android_runtime/AndroidRuntime.h>
     16 
     17 #include <stdio.h>
     18 #include <unistd.h>
     19 
     20 namespace android {
     21 
     22 void app_usage()
     23 {
     24     fprintf(stderr,
     25         "Usage: app_process [java-options] cmd-dir start-class-name [options]
    ");
     26 }
     27 
     28 class AppRuntime : public AndroidRuntime
     29 {
     30 public:
     31     AppRuntime()
     32         : mParentDir(NULL)
     33         , mClassName(NULL)
     34         , mClass(NULL)
     35         , mArgC(0)
     36         , mArgV(NULL)
     37     {
     38     }
     39 
     40 #if 0
     41     // this appears to be unused
     42     const char* getParentDir() const
     43     {
     44         return mParentDir;
     45     }
     46 #endif
     47 
     48     const char* getClassName() const
     49     {
     50         return mClassName;
     51     }
     52 
     53     virtual void onVmCreated(JNIEnv* env)
     54     {
     55         if (mClassName == NULL) {
     56             return; // Zygote. Nothing to do here.
     57         }
     58 
     59         /**
     60          * This is a little awkward because the JNI FindClass call uses the
     61          * class loader associated with the native method we're executing in.
     62          * If called in onStarted (from RuntimeInit.finishInit because we're
     63          * launching "am", for example), FindClass would see that we're calling
     64          * from a boot class' native method, and so wouldn't look for the class
     65          * we're trying to look up in CLASSPATH. Unfortunately it needs to,
     66          * because the "am" classes are not boot classes.
     67          *
     68          * The easiest fix is to call FindClass here, early on before we start
     69          * executing boot class Java code and thereby deny ourselves access to
     70          * non-boot classes.
     71          */
     72         char* slashClassName = toSlashClassName(mClassName);
     73         mClass = env->FindClass(slashClassName);
     74         if (mClass == NULL) {
     75             LOGE("ERROR: could not find class '%s'
    ", mClassName);
     76         }
     77         free(slashClassName);
     78 
     79         mClass = reinterpret_cast<jclass>(env->NewGlobalRef(mClass));
     80     }
     81 
     82     virtual void onStarted()
     83     {
     84         sp<ProcessState> proc = ProcessState::self();
     85         LOGV("App process: starting thread pool.
    ");
     86         proc->startThreadPool();
     87 
     88         AndroidRuntime* ar = AndroidRuntime::getRuntime();
     89         ar->callMain(mClassName, mClass, mArgC, mArgV);
     90 
     91         IPCThreadState::self()->stopProcess();
     92     }
     93 
     94     virtual void onZygoteInit()
     95     {
     96         sp<ProcessState> proc = ProcessState::self();
     97         LOGV("App process: starting thread pool.
    ");
     98         proc->startThreadPool();
     99     }
    100 
    101     virtual void onExit(int code)
    102     {
    103         if (mClassName == NULL) {
    104             // if zygote
    105             IPCThreadState::self()->stopProcess();
    106         }
    107 
    108         AndroidRuntime::onExit(code);
    109     }
    110 
    111 
    112     const char* mParentDir;
    113     const char* mClassName;
    114     jclass mClass;
    115     int mArgC;
    116     const char* const* mArgV;
    117 };
    118 
    119 }
    120 
    121 using namespace android;
    122 
    123 /**
    124  * sets argv0 to as much of newArgv0 as will fit
    125  */
    126 static void setArgv0(const char *argv0, const char *newArgv0)
    127 {
    128     strlcpy(const_cast<char *>(argv0), newArgv0, strlen(argv0));
    129 }
    130 
    131 int main(int argc, const char* const argv[])
    132 {
    133     // These are global variables in ProcessState.cpp
    134     mArgC = argc;
    135     mArgV = argv;
    136 
    137     mArgLen = 0;
    138     for (int i=0; i<argc; i++) {
    139         mArgLen += strlen(argv[i]) + 1;
    140     }
    141     mArgLen--;
    142 
    143     AppRuntime runtime;
    144     const char* argv0 = argv[0];
    145 
    146     // Process command line arguments
    147     // ignore argv[0]
    148     argc--;
    149     argv++;
    150 
    151     // Everything up to '--' or first non '-' arg goes to the vm
    152 
    153     int i = runtime.addVmArguments(argc, argv);
    154 
    155     // Parse runtime arguments.  Stop at first unrecognized option.
    156     bool zygote = false;
    157     bool startSystemServer = false;
    158     bool application = false;
    159     const char* parentDir = NULL;
    160     const char* niceName = NULL;
    161     const char* className = NULL;
    162     while (i < argc) {
    163         const char* arg = argv[i++];
    164         if (!parentDir) {
    165             parentDir = arg;
    166         } else if (strcmp(arg, "--zygote") == 0) {
    167             zygote = true;
    168             niceName = "zygote";
    169         } else if (strcmp(arg, "--start-system-server") == 0) {
    170             startSystemServer = true;
    171         } else if (strcmp(arg, "--application") == 0) {
    172             application = true;
    173         } else if (strncmp(arg, "--nice-name=", 12) == 0) {
    174             niceName = arg + 12;
    175         } else {
    176             className = arg;
    177             break;
    178         }
    179     }
    180 
    181     if (niceName && *niceName) {
    182         setArgv0(argv0, niceName);
    183         set_process_name(niceName);
    184     }
    185 
    186     runtime.mParentDir = parentDir;
    187 
    188     if (zygote) {
    189         runtime.start("com.android.internal.os.ZygoteInit",
    190                 startSystemServer ? "start-system-server" : "");
    191     } else if (className) {
    192         // Remainder of args get passed to startup class main()
    193         runtime.mClassName = className;
    194         runtime.mArgC = argc - i;
    195         runtime.mArgV = argv + i;
    196         runtime.start("com.android.internal.os.RuntimeInit",
    197                 application ? "application" : "tool");
    198     } else {
    199         fprintf(stderr, "Error: no class name or --zygote supplied.
    ");
    200         app_usage();
    201         LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
    202         return 10;
    203     }
    204 }
    app_main
    1 // @systemcore
    ootdirinit.rc
    2 service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
    3 socket zygote stream 666 
    4 onrestart write /sys/android_power/request_state wake 
    5 onrestart write /sys/power/state on 
    6 onrestart restart media 
    • Zygote start up from main(…) of app_process   //@frameworks/base/cmds/app_main.cpp。
    • Create Java Runtime:  runtime.start("com.android.internal.os.ZygoteInit.java", startSystemServer); //@AndroidRuntime.cpp  
    • Make Zygote run:com.android.internal.os.ZygoteInit:main()  //@com.android.internal.os.ZygoteInit
    • registerZygoteSocket();
    • startSystemServer();com.android.internal.os.ZygoteInit:startSystemServer() //@com.android.internal.os.ZygoteInit.
    • Now the Zygote service framework is built, and accept the requet from ActivityManagerService via Socket to fork APP.
    • Zygote.forkSystemServer(@dalvik.system.Zygote.java) is mapped to the process of linux with pid.
     1 public static void main(String argv[]) 
     2 {
     3     try {
     4         // Start profiling the zygote initialization.
     5         SamplingProfilerIntegration.start();
     6         registerZygoteSocket();
     7         EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,SystemClock.uptimeMillis());
     8         preload();
     9         EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,SystemClock.uptimeMillis());
    10         // Finish profiling the zygote initialization.
    11         SamplingProfilerIntegration.writeZygoteSnapshot();
    12         // Do an initial gc to clean up after startup
    13         gc();
    14         // If requested, start system server directly from Zygote
    15         if (argv.length != 2) {
    16             throw new RuntimeException(argv[0] + USAGE_STRING);
    17         }
    18         if (argv[1].equals("start-system-server")) {
    19             startSystemServer();
    20         } else if (!argv[1].equals("")) {
    21             throw new RuntimeException(argv[0] + USAGE_STRING);
    22         }
    23         Log.i(TAG, "Accepting command socket connections");
    24         if (ZYGOTE_FORK_MODE) {
    25             runForkMode();
    26         } else {
    27             runSelectLoopMode();
    28         }
    29         closeServerSocket();
    30     } catch (MethodAndArgsCaller caller) {
    31         caller.run();
    32     } catch (RuntimeException ex) {
    33         Log.e(TAG, "Zygote died with exception", ex);
    34         closeServerSocket();
    35         throw ex;
    36     }
    37 }
     1 private static boolean startSystemServer() throws MethodAndArgsCaller, RuntimeException 
     2 {
     3     /* Hardcoded command line to start the system server */
     4     String args[] = {
     5         "--setuid=1000",
     6         "--setgid=1000",
     7         "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003,3006,3007",
     8         "--capabilities=130104352,130104352",
     9         "--runtime-init",
    10         "--nice-name=system_server",
    11         "com.android.server.SystemServer",
    12     };
    13     ZygoteConnection.Arguments parsedArgs = null; 
    14     int pid;
    15     
    16     try {
    17         parsedArgs = new ZygoteConnection.Arguments(args);
    18         ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
    19         ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);  
    20         /* Request to fork the system server process */
    21         pid = Zygote.forkSystemServer(parsedArgs.uid, parsedArgs.gid,   
    22                                       parsedArgs.gids,                  
    23                                       parsedArgs.debugFlags,            
    24                                       null,                             
    25                                       parsedArgs.permittedCapabilities, 
    26                                       parsedArgs.effectiveCapabilities);
    27     } 
    28     catch (IllegalArgumentException ex) 
    29     {
    30         throw new RuntimeException(ex);
    31     }
    32     /* For child process */
    33     if (pid == 0) {
    34         handleSystemServerProcess(parsedArgs);
    35     }
    36     return true;
    37 }
     1 //@dalvik.system.Zygote.java
     2 public static int forkSystemServer(int uid, int gid, int[] gids,int debugFlags, int[][] rlimits,long permittedCapabilities, long effectiveCapabilities) 
     3 {
     4     preFork();
     5     //call native method to fork a process of system server which is mappged to linux process
     6     int pid = nativeForkSystemServer(uid, gid, gids, debugFlags, rlimits,
     7                                      permittedCapabilities,
     8                                      effectiveCapabilities);
     9     postFork();
    10     return pid;
    11 }

    Step3:SystemServer Startup

    • Zygote fork a process of SystemServer: forkSystemServer() //@dalvik.system.Zygote.java
    • Zygote.nativeForkSystemServer() //@dalvik/vm/native/dalvik_system_Zygote.c
    • SystemServer is created and running.
    • com.android.server.SystemServer:init1(): The native method start all system native service.
    • com.android.server.SystemServer:init2(): Create a ServerThread to add just created native service by init1() to service list and finally loop.

        com.android.server.SystemServer:main()//@com.android.server.SystemServer.java

     1 public static void main(String[] args) 
     2 {
     3     if (System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) {
     4         // If a device's clock is before 1970 (before 0), a lot of
     5         // APIs crash dealing with negative numbers, notably
     6         // java.io.File#setLastModified, so instead we fake it and
     7         // hope that time from cell towers or NTP fixes it
     8         // shortly.
     9         Slog.w(TAG, "System clock is before 1970; setting to 1970.");
    10         SystemClock.setCurrentTimeMillis(EARLIEST_SUPPORTED_TIME);
    11     }
    12     if (SamplingProfilerIntegration.isEnabled()) {
    13         SamplingProfilerIntegration.start();
    14         timer = new Timer();
    15         timer.schedule(new TimerTask() 
    16             {
    17              @Override
    18              public void run() {
    19                 SamplingProfilerIntegration.writeSnapshot("system_server", null);
    20             }
    21         }, SNAPSHOT_INTERVAL, SNAPSHOT_INTERVAL);
    22     }
    23     // Mmmmmm... more memory!
    24     dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
    25     // The system server has to run all of the time, so it needs to be
    26     // as efficient as possible with its memory usage.
    27     VMRuntime.getRuntime().setTargetHeapUtilization(0.8f);
    28System.loadLibrary("android_servers");//load android_servers.so
    29    init1(args);
    30 }
    31 /***
    32  * This method is called from Zygote to initialize the system. This will cause the native
    33  * services (SurfaceFlinger, AudioFlinger, etc..) to be started. After that it will call back
    34  * up into init2() to start the Android services.
    35  */
    36 native public static void init1(String[] args);
    37 
    38 public static final void init2() {
    39     Slog.i(TAG, "Entered the Android system server!");
    40     Thread thr = new ServerThread();
    41     thr.setName("android.server.ServerThread");
    42     thr.start();
    43 }
      1 //@com.android.server.SystemService
      2 class ServerThread extends Thread 
      3 {
      4     ......    
      5     @Override
      6     public void run() 
      7     {
      8         ...............................
      9         Looper.prepare();
     10         android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_FOREGROUND);
     11         BinderInternal.disableBackgroundScheduling(true);
     12         android.os.Process.setCanSelfBackground(false);
     13         // Check whether we failed to shut down last time we tried.
     14         {
     15             ....................
     16         }        
     17         String factoryTestStr = SystemProperties.get("ro.factorytest");
     18         int factoryTest = "".equals(factoryTestStr) ? SystemServer.FACTORY_TEST_OFF
     19                 : Integer.parseInt(factoryTestStr);
     20         .......//declare all native service object.
     21         // Critical services...add all service into list of service manager
     22         try {
     23             Slog.i(TAG, "Entropy Service");
     24             ServiceManager.addService("entropy", new EntropyService());
     25             power = new PowerManagerService();
     26             ServiceManager.addService(Context.POWER_SERVICE, power);
     27             context = ActivityManagerService.main(factoryTest);           
     28             ServiceManager.addService("telephony.registry", new TelephonyRegistry(context));
     29             AttributeCache.init(context);                       
     30             ........................
     31             alarm = new AlarmManagerService(context);
     32             ServiceManager.addService(Context.ALARM_SERVICE, alarm);          
     33             Watchdog.getInstance().init(context, battery, power, alarm,ActivityManagerService.self());
     34             Slog.i(TAG, "Window Manager");
     35             wm = WindowManagerService.main(context, power,factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL,!firstBoot);
     36             ServiceManager.addService(Context.WINDOW_SERVICE, wm);
     37             ActivityManagerService.self().setWindowManager(wm);            
     38             .......................
     39 
     40         } catch (RuntimeException e) {           
     41         }
     42         .............................
     43         // Bring up services needed for UI.
     44         if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) 
     45         {
     46             try {
     47                 Slog.i(TAG, "Input Method Service");
     48                 imm = new InputMethodManagerService(context);
     49                 ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm);
     50             } catch (Throwable e) {
     51                 reportWtf("starting Input Manager Service", e);
     52             }
     53            .......................
     54         }
     55 
     56         try {
     57             wm.displayReady();
     58         } catch (Throwable e) {
     59             reportWtf("making display ready", e);
     60         }
     61 
     62         try {
     63             pm.performBootDexOpt();
     64         } catch (Throwable e) {
     65             reportWtf("performing boot dexopt", e);
     66         }
     67 
     68         try {
     69             ActivityManagerNative.getDefault().showBootMessage(context.getResources().getText(
     70                                         com.android.internal.R.string.android_upgrading_starting_apps),
     71                                        false);
     72         } catch (RemoteException e) {
     73         }
     74         if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
     75             try {
     76                 Slog.i(TAG, "Device Policy");
     77                 devicePolicy = new DevicePolicyManagerService(context);
     78                 ServiceManager.addService(Context.DEVICE_POLICY_SERVICE, devicePolicy);
     79             } catch (Throwable e) {
     80                 reportWtf("starting DevicePolicyService", e);
     81             }
     82             ....................
     83            try {
     84                 Slog.i(TAG, "Wi-Fi Service");
     85                 wifi = new WifiService(context);
     86                 ServiceManager.addService(Context.WIFI_SERVICE, wifi);
     87             } catch (Throwable e) {
     88                 reportWtf("starting Wi-Fi Service", e);
     89             }      
     90            ....................  
     91            try {
     92                 Slog.i(TAG, "NetworkTimeUpdateService");
     93                 networkTimeUpdater = new NetworkTimeUpdateService(context);
     94             } catch (Throwable e) {
     95                 reportWtf("starting NetworkTimeUpdate service", e);
     96             }
     97         }
     98         // Before things start rolling, be sure we have decided whether
     99         // we are in safe mode.
    100         final boolean safeMode = wm.detectSafeMode();
    101         if (safeMode) {
    102             ActivityManagerService.self().enterSafeMode();
    103             // Post the safe mode state in the Zygote class
    104             Zygote.systemInSafeMode = true;
    105             // Disable the JIT for the system_server process
    106             VMRuntime.getRuntime().disableJitCompilation();
    107         } else {
    108             // Enable the JIT for the system_server process
    109             VMRuntime.getRuntime().startJitCompilation();
    110         }
    111         // It is now time to start up the app processes...
    112         if (devicePolicy != null) {
    113             try {
    114                 devicePolicy.systemReady();
    115             } catch (Throwable e) {
    116                 reportWtf("making Device Policy Service ready", e);
    117             }
    118         }
    119         if (notification != null) {
    120             try {
    121                 notification.systemReady();
    122             } catch (Throwable e) {
    123                 reportWtf("making Notification Service ready", e);
    124             }
    125         }
    126         try {
    127             wm.systemReady();
    128         } catch (Throwable e) {
    129             reportWtf("making Window Manager Service ready", e);
    130         }
    131         if (safeMode) {
    132             ActivityManagerService.self().showSafeModeOverlay();
    133         }
    134         // Update the configuration for this context by hand, because we're going
    135         // to start using it before the config change done in wm.systemReady() will
    136         // propagate to it.
    137         ..................................
    138         power.systemReady();
    139         try {
    140             pm.systemReady();
    141         } catch (Throwable e) {
    142             reportWtf("making Package Manager Service ready", e);
    143         }
    144 
    145         // These are needed to propagate to the runnable below.
    146         final Context contextF = context;
    147         final BatteryService batteryF = battery;
    148         .........................
    149         // We now tell the activity manager it is okay to run third party
    150         // code.  It will call back into us once it has gotten to the state
    151         // where third party code can really run (but before it has actually
    152         // started launching the initial applications), for us to complete our
    153         // initialization.
    154         ActivityManagerService.self().systemReady(new Runnable() {
    155             public void run() {
    156                 Slog.i(TAG, "Making services ready");
    157                 startSystemUi(contextF);
    158                 try {
    159                     if (batteryF != null) batteryF.systemReady();
    160                 } catch (Throwable e) {
    161                     reportWtf("making Battery Service ready", e);
    162                 }
    163                 .....................
    164                 try {
    165                     if (textServiceManagerServiceF != null) textServiceManagerServiceF.systemReady();
    166                 } catch (Throwable e) {
    167                     reportWtf("making Text Services Manager Service ready", e);
    168                 }
    169             }
    170         });
    171 
    172         // For debug builds, log event loop stalls to dropbox for analysis.
    173         if (StrictMode.conditionallyEnableDebugLogging()) {
    174             Slog.i(TAG, "Enabled StrictMode for system server main thread.");
    175         }
    176         Looper.loop();
    177         Slog.d(TAG, "System ServerThread is exiting!");
    178     }
    179     static final void startSystemUi(Context context) {
    180         Intent intent = new Intent();
    181         intent.setComponent(new ComponentName("com.android.systemui",
    182                     "com.android.systemui.SystemUIService"));
    183         Slog.d(TAG, "Starting service: " + intent);
    184         context.startService(intent);
    185     }
    186 }

      

    Step4:Home luncher Startup

     

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

    http://wenku.baidu.com/view/fc7729375a8102d276a22f13.html

    http://wenku.baidu.com/view/42ebf459be23482fb4da4c33.html

    http://www.cnblogs.com/linucos/archive/2012/05/22/2513760.html

    Android's Process & Thread

     

  • 相关阅读:
    (转载)delphi文件流
    一个不敢妄称自己是程序员的半拉子编码员的随想
    Xamarin.iOS使用极光JPush进行推送
    Xamarin.IOS问题记录——项目属性里IOS Bundle Signing 配置文件选项没有对应的配置文件选择
    Xamarin问题记录
    Unity3D笔记
    C#Xml To Class生成器
    WPF Mahapps.Metro 设置主题样式
    WPF画N角芒星,正N角星
    WPFPath素材
  • 原文地址:https://www.cnblogs.com/iiiDragon/p/3240697.html
Copyright © 2011-2022 走看看