Android4.4的zygote进程(下)
3.2.4启动Android系统服务——startSystemServer()
接下来就是启动Android的重头戏了,此时ZygoteInit的main()函数会调用startSystemServer(),该函数用于启动整个Android系统的系统服务。其大体做法是先fork一个子进程,然后在子进程中做一些初始化动作,继而执行SystemServer类的main()静态函数。需要注意的是,startSystemServer()并不是在函数体内直接调用Java类的main()函数的,而是通过抛异常的方式,在startSystemServer()之外加以处理的。
startSystemServer()的代码如下:
- private static boolean startSystemServer()
- throws MethodAndArgsCaller, RuntimeException
- {
- . . . . . .
- /* Hardcoded command line to start the system server */
- String args[] = {
- "--setuid=1000",
- "--setgid=1000",
- "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1032,
- 3001,3002,3003,3006,3007",
- "--capabilities=" + capabilities + "," + capabilities,
- "--runtime-init",
- "--nice-name=system_server",
- "com.android.server.SystemServer",
- };
- ZygoteConnection.Arguments parsedArgs = null;
- int pid;
- try {
- parsedArgs = new ZygoteConnection.Arguments(args);
- ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
- ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);
- // fork出系统服务对应的进程
- pid = Zygote.forkSystemServer(parsedArgs.uid, parsedArgs.gid,
- parsedArgs.gids, parsedArgs.debugFlags, null,
- parsedArgs.permittedCapabilities,
- parsedArgs.effectiveCapabilities);
- } catch (IllegalArgumentException ex) {
- throw new RuntimeException(ex);
- }
- // 对新fork出的系统进程,执行handleSystemServerProcess()
- if (pid == 0) {
- handleSystemServerProcess(parsedArgs);
- }
- return true;
- }
args[]中的字符串 | 对应 |
"--setuid=1000" | parsedArgs.uid |
"--setgid=1000" | parsedArgs.gid |
"--setgroups=1001,1002,1003,1004,1005,1006,1007,1008, |
parsedArgs.gids |
"--capabilities=" + capabilities + "," + capabilities | capabilitiesSpecified = true; permittedCapabilities = Long.decode(capStrings[0]); effectiveCapabilites = Long.decode(capString[1]); |
"--runtime-init" | parsedArgs.runtimeInit设为true |
"--nice-name=system_server" | parsedArgs.niceName |
"com.android.server.SystemServer" | parsedArgs.remainingArgs |
3.2.4.1Zygote.forkSystemServer()
Zygote.forkSystemServer()的代码如下:
【libcore/dalvik/src/main/java/dalvik/system/Zygote.java】
- public static int forkSystemServer(int uid, int gid, int[] gids, int debugFlags,
- int[][] rlimits, long permittedCapabilities, long effectiveCapabilities)
- {
- preFork();
- int pid = nativeForkSystemServer(uid, gid, gids, debugFlags, rlimits,
- permittedCapabilities, effectiveCapabilities);
- postFork();
- return pid;
- }
【dalvik/vm/native/dalvik_system_Zygote.cpp】
- const DalvikNativeMethod dvm_dalvik_system_Zygote[] = {
- { "nativeFork", "()I",
- Dalvik_dalvik_system_Zygote_fork },
- { "nativeForkAndSpecialize", "(II[II[[IILjava/lang/String;Ljava/lang/String;)I",
- Dalvik_dalvik_system_Zygote_forkAndSpecialize },
- { "nativeForkSystemServer", "(II[II[[IJJ)I",
- Dalvik_dalvik_system_Zygote_forkSystemServer },
- { NULL, NULL, NULL },
- };
- static void Dalvik_dalvik_system_Zygote_forkSystemServer(
- const u4* args, JValue* pResult)
- {
- pid_t pid;
- pid = forkAndSpecializeCommon(args, true);
- if (pid > 0) {
- int status;
- ALOGI("System server process %d has been created", pid);
- gDvm.systemServerPid = pid;
- if (waitpid(pid, &status, WNOHANG) == pid) {
- ALOGE("System server process %d has died. Restarting Zygote!", pid);
- kill(getpid(), SIGKILL);
- }
- }
- RETURN_INT(pid);
- }
3.2.4.2SystemServer的handleSystemServerProgress()函数
接着,startSystemServer()会在新fork出的子进程中调用handleSystemServerProgress(),让这个新进程成为真正的系统进程(SystemServer进程)。
- // 对新fork出的系统进程,执行handleSystemServerProcess()
- if (pid == 0) {
- handleSystemServerProcess(parsedArgs);
- }
【frameworks/base/core/java/com/android/internal/os/ZygoteInit.java】
- private static void handleSystemServerProcess(ZygoteConnection.Arguments parsedArgs)
- throws ZygoteInit.MethodAndArgsCaller
- {
- closeServerSocket();
- Libcore.os.umask(S_IRWXG | S_IRWXO);
- if (parsedArgs.niceName != null) {
- Process.setArgV0(parsedArgs.niceName); // niceName就是”system_server”
- }
- if (parsedArgs.invokeWith != null) {
- WrapperInit.execApplication(parsedArgs.invokeWith,
- parsedArgs.niceName, parsedArgs.targetSdkVersion,
- null, parsedArgs.remainingArgs);
- } else {
- // 此时的remainingArgs就是”com.android.server.SystemServer”
- RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs);
- }
- }
3.2.4.2.1closeServerSocket()
因为当前已经不是运行在zygote进程里了,所以zygote里的那个监听socket就应该关闭了。这就是closeServerSocket()的意义,其代码如下:
- static void closeServerSocket()
- {
- try {
- if (sServerSocket != null) {
- FileDescriptor fd = sServerSocket.getFileDescriptor();
- sServerSocket.close();
- if (fd != null) {
- Libcore.os.close(fd);
- }
- }
- } catch (IOException ex) {
- Log.e(TAG, "Zygote: error closing sockets", ex);
- } catch (libcore.io.ErrnoException ex) {
- Log.e(TAG, "Zygote: error closing descriptor", ex);
- }
- sServerSocket = null;
- }
3.2.4.2.2RuntimeInit.zygoteInit()
RuntimeInit.zygoteInit()的代码如下:
【frameworks/base/core/java/com/android/internal/os/RuntimeInit.java】
- public static final void zygoteInit(int targetSdkVersion, String[] argv)
- throws ZygoteInit.MethodAndArgsCaller
- {
- if (DEBUG) Slog.d(TAG, "RuntimeInit: Starting application from zygote");
- redirectLogStreams();
- commonInit();
- nativeZygoteInit();
- applicationInit(targetSdkVersion, argv);
- }
3.2.4.2.2.1.调用redirectLogStreams()
首先,在新fork出的系统进程里,需要重新定向系统输出流。
- public static void redirectLogStreams()
- {
- System.out.close();
- System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
- System.err.close();
- System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
- }
3.2.4.2.2.2.调用commonInit()
- private static final void commonInit()
- {
- . . . . . .
- Thread.setDefaultUncaughtExceptionHandler(new UncaughtHandler());
- TimezoneGetter.setInstance(new TimezoneGetter()
- . . . . . .
- . . . . . .
- String trace = SystemProperties.get("ro.kernel.android.tracing");
- . . . . . .
- initialized = true;
- }
3.2.4.2.2.3.调用nativeZygoteInit()
接下来调用的nativeZygoteInit()是个JNI函数,在AndroidRuntime.cpp文件中可以看到:
【frameworks/base/core/jni/AndroidRuntime.cpp】
- static JNINativeMethod gMethods[] = {
- { "nativeFinishInit", "()V",
- (void*) com_android_internal_os_RuntimeInit_nativeFinishInit },
- { "nativeZygoteInit", "()V",
- (void*) com_android_internal_os_RuntimeInit_nativeZygoteInit },
- { "nativeSetExitWithoutCleanup", "(Z)V",
- (void*) com_android_internal_os_RuntimeInit_nativeSetExitWithoutCleanup },
- };
- static void com_android_internal_os_RuntimeInit_nativeZygoteInit(JNIEnv* env, jobject clazz)
- {
- gCurRuntime->onZygoteInit();
- }
gCurRuntime = this。不过实际调用的onZygoteInit()应该是AndroidRuntime的子类AppRuntime的:
【frameworks/base/cmds/app_process/App_main.cpp】
- class AppRuntime : public AndroidRuntime
- {
- . . . . . .
- virtual void onZygoteInit()
- {
- // Re-enable tracing now that we're no longer in Zygote.
- atrace_set_tracing_enabled(true);
- sp<ProcessState> proc = ProcessState::self();
- ALOGV("App process: starting thread pool.\n");
- proc->startThreadPool();
- }
ProcessState对象是典型的单例模式,它的self()函数如下:
- sp<ProcessState> ProcessState::self()
- {
- Mutex::Autolock _l(gProcessMutex);
- if (gProcess != NULL) {
- return gProcess;
- }
- gProcess = new ProcessState;
- return gProcess;
- }
我们整理一下思路,画一张startSystemServer()的调用关系图:
接下来我们来讲上图中zygoteInit()调用的最后一行:applicationInit()。
3.2.4.2.2.4.调用applicationInit()
applicationInit()函数的代码如下:
【frameworks/base/core/java/com/android/internal/os/RuntimeInit.java】
- private static void applicationInit(int targetSdkVersion, String[] argv)
- throws ZygoteInit.MethodAndArgsCaller
- {
- nativeSetExitWithoutCleanup(true);
- VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);
- VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);
- final Arguments args;
- try {
- args = new Arguments(argv);
- } catch (IllegalArgumentException ex) {
- Slog.e(TAG, ex.getMessage());
- return;
- }
- invokeStaticMain(args.startClass, args.startArgs);
- }
其中的invokeStaticMain()一句最为关键,它承担向外抛出“特殊异常”的作用。我们先画一张startSystemServer()的调用关系图:
看到了吧,最后一步抛出了异常。这相当于一个“特殊的goto语句”!上面的cl = Class.forName(className)一句,其实加载的就是SystemServer类。这个类名是从前文的parsedArgs.remainingArgs得来的,其值就是“com.android.server.SystemServer”。此处抛出的异常,会被本进程的catch语句接住,在那里才会执行SystemServer类的main()函数。示意图如下:
如上图所示,新fork出的SystemServer子进程直接跳过了中间那句runSelectLoop(),径直跳转到caller.run()一步了。
当然,父进程Zygote在fork动作后,会退出startSystemServer()函数,并走到runSelectLoop(),从而进入一种循环监听状态,每当Activity Manager Service向它发出“启动新应用进程”的命令时,它又会fork一个子进程,并在子进程里抛出一个异常,这样子进程还是会跳转到catch一句。
我们可以把上面的示意图再丰富一下:
还有一点需要说明一下,fork出的SystemServer进程在跳转到catch语句后,会执行SystemServer类的main()函数,而其他情况下,fork出的应用进程在跳转的catch语句后,则会执行ActivityThread类的main()函数。这个ActivityThread对于应用程序而言非常重要,但因为和本篇主题关系不大,我们就不在这里展开讲了。
3.2.4.3 SystemServer的main()函数
前文我们已经看到了,startSystemServer()创建的新进程在执行完applicationInit()之后,会抛出一个异常,并由新fork出的SystemServer子进程的catch语句接住,继而执行SystemServer类的main()函数。
那么SystemServer的main()函数又在做什么事情呢?其调用关系图如下:
在Android4.4版本中,ServerThread已经不再继承于Thread了,它现在只是个辅助类,其命名还残留有旧代码的味道。在以前的Android版本中,ServerThread的确继承于Thread,而且在线程的run()成员函数里,做着类似addService、systemReady的工作。
因为本文主要是阐述zygote进程的,所以我们就不在这里继续细说system server进程啦,有兴趣的同学可以继续研究。我们还是回过头继续说zygote里的动作吧。
3.2.5监听zygote socket
3.2.5.1runSelectLoop()
ZygoteInit的main()函数在调用完startSystemServer()之后,会进一步走到runSelectLoop()。runSelectInit()的代码如下:
【frameworks/base/core/java/com/android/internal/os/ZygoteInit.java】
- private static void runSelectLoop() throws MethodAndArgsCaller
- {
- ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
- ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
- FileDescriptor[] fdArray = new FileDescriptor[4];
- fds.add(sServerSocket.getFileDescriptor());
- peers.add(null);
- int loopCount = GC_LOOP_COUNT;
- while (true) {
- int index;
- if (loopCount <= 0) {
- gc();
- loopCount = GC_LOOP_COUNT;
- } else {
- loopCount--;
- }
- try {
- fdArray = fds.toArray(fdArray);
- index = selectReadable(fdArray);
- } catch (IOException ex) {
- throw new RuntimeException("Error in select()", ex);
- }
- if (index < 0) {
- throw new RuntimeException("Error in select()");
- } else if (index == 0) {
- ZygoteConnection newPeer = acceptCommandPeer();
- peers.add(newPeer);
- fds.add(newPeer.getFileDesciptor());
- } else {
- boolean done;
- done = peers.get(index).runOnce();
- if (done) {
- peers.remove(index);
- fds.remove(index);
- }
- }
- }
- }
【frameworks/base/core/jni/com_android_internal_os_ZygoteInit.cpp】
- static jint com_android_internal_os_ZygoteInit_selectReadable (JNIEnv *env, jobject clazz,
- jobjectArray fds)
- {
- . . . . . .
- int err;
- do {
- err = select (nfds, &fdset, NULL, NULL, NULL);
- } while (err < 0 && errno == EINTR);
- . . . . . .
- for (jsize i = 0; i < length; i++) {
- jobject fdObj = env->GetObjectArrayElement(fds, i);
- . . . . . .
- int fd = jniGetFDFromFileDescriptor(env, fdObj);
- . . . . . .
- if (FD_ISSET(fd, &fdset)) {
- return (jint)i;
- }
- }
- return -1;
- }
可以看到,主要就是调用select()而已。在Linux的socket编程中,select()负责监视若干文件描述符的变化情况,我们常见的变化情况有:读、写、异常等等。在zygote中,
err = select (nfds, &fdset, NULL, NULL, NULL);一句的最后三个参数都为NULL,表示该select()操作只打算监视文件描述符的“读变化”,而且如果没有可读的文件,select()就维持阻塞状态。
在被监视的文件描述符数组(fds)中,第一个文件描述符对应着“zygote接收其他进程连接申请的那个socket(及sServerSocket)”,一旦它发生了变化,我们就尝试建立一个ZygoteConnection。
- // (index == 0)的情况
- ZygoteConnection newPeer = acceptCommandPeer();
- peers.add(newPeer);
- fds.add(newPeer.getFileDesciptor());
如果select动作发现文件描述符数组(fds)的其他文件描述符有东西可读了,说明有其他进程通过某个已建立好的ZygoteConnection发来了命令,此时我们需要调用runOnce()。
- // (index > 0)的情况
- boolean done;
- done = peers.get(index).runOnce();
- if (done) {
- peers.remove(index);
- fds.remove(index);
- }
- private static ZygoteConnection acceptCommandPeer() {
- try {
- return new ZygoteConnection(sServerSocket.accept());
- } catch (IOException ex) {
- throw new RuntimeException(
- "IOException during accept()", ex);
- }
- }
3.2.5.1.1ZygoteConnection的runOnce()
ZygoteConnection的runOnce()代码截选如下:
- boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {
- String args[];
- Arguments parsedArgs = null;
- FileDescriptor[] descriptors;
- . . . . . .
- args = readArgumentList();
- descriptors = mSocket.getAncillaryFileDescriptors();
- . . . . . .
- int pid = -1;
- FileDescriptor childPipeFd = null;
- FileDescriptor serverPipeFd = null;
- try {
- parsedArgs = new Arguments(args);
- . . . . . .
- pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
- parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal,
- parsedArgs.seInfo, parsedArgs.niceName);
- }
- . . . . . .
- if (pid == 0) {
- // in child
- IoUtils.closeQuietly(serverPipeFd);
- serverPipeFd = null;
- handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
- return true;
- } else {
- // in parent...pid of < 0 means failure
- IoUtils.closeQuietly(childPipeFd);
- childPipeFd = null;
- return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
- }
- . . . . . .
- }
3.2.5.1.2readArgumentList()
runOnce()中从socket中读取参数数据的动作是由readArgumentList()完成的,该函数的代码如下:
- private String[] readArgumentList()
- throws IOException
- {
- int argc;
- . . . . . .
- String s = mSocketReader.readLine();
- . . . . . .
- argc = Integer.parseInt(s);
- . . . . . .
- String[] result = new String[argc];
- for (int i = 0; i < argc; i++) {
- result[i] = mSocketReader.readLine();
- if (result[i] == null) {
- // We got an unexpected EOF.
- throw new IOException("truncated request");
- }
- }
- return result;
- }
我们知道,当AMS需要启动一个新进程时,会调用类似下面的句子:
- Process.ProcessStartResult startResult = Process.start("android.app.ActivityThread",
- app.processName, uid, uid, gids, debugFlags, mountExternal,
- app.info.targetSdkVersion, app.info.seinfo, null);
3.2.5.1.3handleChildProc()
runOnce()在读完参数之后,会进一步调用到handleChildProc()。正如前文所说,该函数会间接抛出特殊的MethodAndArgsCaller异常,只不过此时抛出的异常携带的类名为ActivityThread。
- private void handleChildProc(Arguments parsedArgs, FileDescriptor[] descriptors,
- FileDescriptor pipeFd, PrintStream newStderr)
- throws ZygoteInit.MethodAndArgsCaller
- {
- closeSocket();
- ZygoteInit.closeServerSocket();
- . . . . . .
- if (parsedArgs.niceName != null) {
- Process.setArgV0(parsedArgs.niceName);
- }
- if (parsedArgs.runtimeInit) {
- if (parsedArgs.invokeWith != null) {
- WrapperInit.execApplication(parsedArgs.invokeWith,
- parsedArgs.niceName, parsedArgs.targetSdkVersion,
- pipeFd, parsedArgs.remainingArgs);
- } else {
- RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
- parsedArgs.remainingArgs);
- }
- } else {
- String className;
- . . . . . .
- className = parsedArgs.remainingArgs[0];
- . . . . . .
- String[] mainArgs = new String[parsedArgs.remainingArgs.length - 1];
- System.arraycopy(parsedArgs.remainingArgs, 1,
- mainArgs, 0, mainArgs.length);
- if (parsedArgs.invokeWith != null) {
- WrapperInit.execStandalone(parsedArgs.invokeWith,
- parsedArgs.classpath, className, mainArgs);
- } else {
- ClassLoader cloader;
- if (parsedArgs.classpath != null) {
- cloader = new PathClassLoader(parsedArgs.classpath,
- ClassLoader.getSystemClassLoader());
- } else {
- cloader = ClassLoader.getSystemClassLoader();
- }
- try {
- ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
- } catch (RuntimeException ex) {
- logAndPrintError(newStderr, "Error starting.", ex);
- }
- }
- }
- }
4小结
至此,zygote进程就阐述完毕了。作为一个最原始的“受精卵”,它必须在合适的时机进行必要的细胞分裂。分裂动作也没什么大的花样,不过就是fork()新进程而已。如果fork()出的新进程是system server,那么其最终执行的就是SystemServer类的main()函数,而如果fork()出的新进程是普通的用户进程的话,那么其最终执行的就是ActivityThread类的main()函数。有关ActivityThread的细节,我们有时间再深入探讨,这里就不细说了。
本篇文章和我的上一篇文章《Android4.4的init进程》可以算是姊妹篇啦。读完这两篇文章,我相信大家对Android的启动流程能有一些大面上的认识了。
转自http://blog.csdn.net/codefly/article/details/48413829
Android4.4的zygote进程(下)的更多相关文章
- Android4.4的zygote进程(上)
1背景 前些天为了在科室做培训,我基于Android 4.4重新整理了一份关于zygote的文档.从技术的角度看,这几年zygote并没有出现什么大的变化,所以如果有人以前研究过zygote,应该不会 ...
- Android4.4 Framework分析——Zygote进程的启动过程
Android启动过程中的第一个进程init.在启动过程中会启动两个关键的系统服务进程ServiceManager和Zygote. 本文要介绍的就是Zygote进程的启动,Zygote俗称孵化器,专门 ...
- Zygote进程【1】——Zygote的诞生
在Android中存在着C和Java两个完全不同的世界,前者直接建立在Linux的基础上,后者直接建立在JVM的基础上.zygote的中文名字为"受精卵",这个名字很好的诠释了zy ...
- Zygote进程【3】——SystemServer的诞生
在ZygoteInit的main()方法中做了几件大事,其中一件便是启动Systemserver进程,代码如下: @/frameworks/base/core/Java/com/Android/int ...
- Android Zygote进程是如何fork一个APP进程的
进程创建流程 不管从桌面启动应用还是应用内启动其它应用,如果这个应用所在进程不存在的话,都需要发起进程通过Binder机制告诉system server进程的AMS system server进程的A ...
- Android Zygote进程启动分析
dvm,app进程,linux进程三者关系 DVM指 dalivk 的虚拟机.每一个 Android 应用程序都在它自己的进程中运行,都拥有一个独立的 Dalvik 虚拟机实例.而每一个 DVM 都是 ...
- Zygote进程介绍【转】
本文转载自:http://blog.csdn.net/yangwen123/article/details/17258023 Zygote进程介绍 在Android系统中,存在不同的服务,这些服务 ...
- Zygote进程【2】——Zygote的分裂
在Zygote的诞生一文中init进程是如何一步步创建Zygote进程的,也了解了Zygote的进程的作用.Zygote进程的诞生对于整个Java世界可以说有着"开天辟地"的作用, ...
- 父进程结束,其子进程不会结束,会挂到init进程下
结论:一个父进程产生子进程,父进程结束(kill),子进程不会结束,子进程被init进程托管 下面是过程: d.sh脚本是一个ping命令,执行d.sh 目前,103310 进程为父进程,103344 ...
随机推荐
- Android 卡顿优化 4 布局优化实际技巧
今天分享一些layout布局书写中的一些技巧,希望看过之后你也一样可以写出性价比高的布局.我个人的目标是用最少的View写出一样效果的布局.因为我相信View的数量减少伴随着的就是层级的减少.从而达到 ...
- STSDB 一
STSdb 4.0 是一个开源的NoSQL 数据库和虚拟文件系统,支持实时索引,完全用c#开发的. 引擎原理基于WaterfallTree(瀑布树)数据结构搭建 以下内容基于stsdb4.dll(4. ...
- 如何解决weblogic server启动中在IIOP后运行缓慢
WebLogic Server在Linux环境中,有时因为linux OS的安全包没有安装,导致weblogic server 在启动的时候会在长时间的停留在 <2/07/2009 08:54: ...
- 如何编写一个shellcode
ShellCode的编写就是将函数或变量在内存中的间接地址改为函数或变量在内存中的直接地址,直接调用! 以MessageBox函数为例进行讲解如下 新建shellcode.cpp: 编写代码如下: 运 ...
- how to use coffee script
TABLE OF CONTENTS TRY COFFEESCRIPT ANNOTATED SOURCE CoffeeScript is a little language that compiles ...
- 探讨android更新UI的几种方法(转)
作为IT新手,总以为只要有时间,有精力,什么东西都能做出来.这种念头我也有过,但很快就熄灭了,因为现实是残酷的,就算一开始的时间和精力非常充足,也会随着项目的推进而逐步消磨殆尽.我们会发现,自己越来越 ...
- 【Linux】Ubuntu vi 上下左右变ABCD及 apt-get install报错问题解决方法
新装的ubuntu12.04,本人绝对新手,在使用VI编辑器编辑文本时觉得实在是难用,因此找了几个解决方法如下: 1. 安装vim full版本 由于Ubuntu预安装的是tiny版本,就会导致我们在 ...
- OpenCV 之 霍夫变换
Hough 变换,对图像中直线的残缺部分.噪声.以及其它的共存结构不敏感,因此,具有很强的鲁棒性. 它常用来检测 直线和曲线 (圆形),识别图像中的几何形状,甚至可用来分割重叠或有部分遮挡的物体. 1 ...
- Linux命令之编辑
vi是终端命令行里功能最强的文本编辑器了,但眼下须要用到的仅仅是文本编辑功能.与GCC.make等工具的整合应用如今还不须要,所以操作难度不大,习惯就好. Linux发行版所带的一般不是vi,而是vi ...
- java中的super限定
super的用法: (1)如果需要在子类中调用父类中被覆盖的实例方法,可以用super限定来调用父类中被覆盖的方法.当然,也可以调用从父类继承的实例变量. public void callOverri ...