Android LOG系统原理剖析

2023-05-16

引言

在我们android的开发过程中,最不可少的就是加Log,打印Log的操作。
这样可以帮助我们去查看各个变量,理清楚代码的逻辑。
而Android系统,提供了不同维度,不同层面,不同模块的Log的支持。
本文,将会分析Android Log系统的实现。

简介

Logcat的级别

使用android.util.Log的不同等级,可以在不同的阈值范围内打印出相对应的Log。

方法描述
v(String,String) (vervbose)显示全部信息
d(String,String)(debug)显示调试信息
i(String,String)(information)显示一般信息
w(String,String)(waning)显示警告信息
e(String,String)(error)显示错误信息

logcat缓冲区

android log输出量巨大,特别是通信系统的log。

因此,android把log输出到不同的缓冲区中,目前定义了四个log缓冲区:

1)Radio:输出通信系统的log
2)System:输出系统组件的log
3)Event:输出event模块的log
4)Main:所有java层的log,以及不属于上面3层的log,应用的log都输出到main缓冲区中

其中,默认log输出(不指定缓冲区的情况下)是输出System和Main缓冲区的log

指定缓冲区的命令为:

adb logcat –b radio
adb logcat –b system
adb logcat –b events
adb logcat –b main

Logcat命令说明

参数描述
-b 加载一个可使用的日志缓冲区供查看,比如event和radio。默认值是main
-c清除缓冲区中的全部日志并退出(清除完后可以使用-g查看缓冲区)
-d将缓冲区的log转存到屏幕中然后退出
-f 将log输出到指定的文件中<文件名>.默认为标准输出(stdout)
-g打印日志缓冲区的大小并退出
-n 设置日志的最大数目,默认值是4,需要和-r选项一起使用
-r 没时输出日志,默认值是16,需要和-f选项一起使用
-s设置过滤器
-v 设置输出格式的日志消息。默认是短暂的格式。支持的格式列表

代码逻辑分析

首先对于FW来说,主要的接口类是在android.util.Log。
我们可以看一下简单的举例:

    /**
     * Send a {@link #VERBOSE} log message.
     * @param tag Used to identify the source of a log message.  It usually identifies
     *        the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     */
    public static int v(@Nullable String tag, @NonNull String msg) {
        return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
    }

    /**
     * Send a {@link #VERBOSE} log message and log the exception.
     * @param tag Used to identify the source of a log message.  It usually identifies
     *        the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     * @param tr An exception to log
     */
    public static int v(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) {
        return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
    }

    /**
     * Send a {@link #DEBUG} log message.
     * @param tag Used to identify the source of a log message.  It usually identifies
     *        the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     */
    public static int d(@Nullable String tag, @NonNull String msg) {
        return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
    }

    /**
     * Send a {@link #DEBUG} log message and log the exception.
     * @param tag Used to identify the source of a log message.  It usually identifies
     *        the class or activity where the log call occurs.
     * @param msg The message you would like logged.
     * @param tr An exception to log
     */
    public static int d(@Nullable String tag, @Nullable String msg, @Nullable Throwable tr) {
        return printlns(LOG_ID_MAIN, DEBUG, tag, msg, tr);
    }

可以看到,这边不论是Log.v还是Lod.d其实都是对APP层的一层封装,真正的实现其实是printlns,和println_native函数。

println_native函数实现

println_native是一个native的方法,我们估计要去lib中去寻找一下它的影子。

    /** @hide */
    @UnsupportedAppUsage
    public static native int println_native(int bufID, int priority, String tag, String msg);

我们可以在frameworks/base/core/jni/android_util_Log.cpp中,看到相应的封装。

/*
 * JNI registration.
 */
static const JNINativeMethod gMethods[] = {
    /* name, signature, funcPtr */
    { "isLoggable",      "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
    { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
    { "logger_entry_max_payload_native",  "()I", (void*) android_util_Log_logger_entry_max_payload_native },
};

println_native的具体实现是在android_util_Log_println_native函数中进行的实现。

/*
 * In class android.util.Log:
 *  public static native int println_native(int buffer, int priority, String tag, String msg)
 */
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
        jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
    const char* tag = NULL;
    const char* msg = NULL;

    if (msgObj == NULL) {
        jniThrowNullPointerException(env, "println needs a message");
        return -1;
    }

    if (bufID < 0 || bufID >= LOG_ID_MAX) {
        jniThrowNullPointerException(env, "bad bufID");
        return -1;
    }

    if (tagObj != NULL)
        tag = env->GetStringUTFChars(tagObj, NULL);
    msg = env->GetStringUTFChars(msgObj, NULL);

    int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);

    if (tag != NULL)
        env->ReleaseStringUTFChars(tagObj, tag);
    env->ReleaseStringUTFChars(msgObj, msg);

    return res;
}

在这边,除了进行一些参数的检查,会继续调用__android_log_buf_write来进行Log的输出。
其他一些系统模块,例如debuggered等C++代码都会直接封装or调用__android_log_buf_write来打印日志。
我们来看下__android_log_buf_write的实现,实现位于system/core/liblog/logger_write.cpp中:

int __android_log_buf_write(int bufID, int prio, const char* tag, const char* msg) {
  ErrnoRestorer errno_restorer;

  if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
    return -EPERM;
  }

  __android_log_message log_message = {
      sizeof(__android_log_message), bufID, prio, tag, nullptr, 0, msg};
  __android_log_write_log_message(&log_message);
  return 1;
}

初始化了Log_message的变量,将其往下导入:

void __android_log_write_log_message(__android_log_message* log_message) {
  ErrnoRestorer errno_restorer;

  if (log_message->buffer_id != LOG_ID_DEFAULT && log_message->buffer_id != LOG_ID_MAIN &&
      log_message->buffer_id != LOG_ID_SYSTEM && log_message->buffer_id != LOG_ID_RADIO &&
      log_message->buffer_id != LOG_ID_CRASH) {
    return;
  }

  if (log_message->tag == nullptr) {
    log_message->tag = GetDefaultTag().c_str();
  }

#if __BIONIC__
  if (log_message->priority == ANDROID_LOG_FATAL) {
    android_set_abort_message(log_message->message);
  }
#endif

  logger_function(log_message);
}

Logger_function的实现如下:

#ifdef __ANDROID__
static __android_logger_function logger_function = __android_log_logd_logger;
#else
static __android_logger_function logger_function = __android_log_stderr_logger;
#endif

我们再看下具体的实现:

void __android_log_logd_logger(const struct __android_log_message* log_message) {
  int buffer_id = log_message->buffer_id == LOG_ID_DEFAULT ? LOG_ID_MAIN : log_message->buffer_id;

  struct iovec vec[3];
  vec[0].iov_base =
      const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(&log_message->priority));
  vec[0].iov_len = 1;
  vec[1].iov_base = const_cast<void*>(static_cast<const void*>(log_message->tag));
  vec[1].iov_len = strlen(log_message->tag) + 1;
  vec[2].iov_base = const_cast<void*>(static_cast<const void*>(log_message->message));
  vec[2].iov_len = strlen(log_message->message) + 1;

  write_to_log(static_cast<log_id_t>(buffer_id), vec, 3);
}

这里的,iov_base,其实就是priority,例如通过Log.v调用过来,这里是2(VERBOSE)。
然后继续去调用write_to_log方法去进行实现。

#ifdef __ANDROID__
static int write_to_log(log_id_t log_id, struct iovec* vec, size_t nr) {
  int ret;
  struct timespec ts;

  if (log_id == LOG_ID_KERNEL) {
    return -EINVAL;
  }

  clock_gettime(android_log_clockid(), &ts);

  if (log_id == LOG_ID_SECURITY) {
    if (vec[0].iov_len < 4) {
      return -EINVAL;
    }

    ret = check_log_uid_permissions();
    if (ret < 0) {
      return ret;
    }
    if (!__android_log_security()) {
      /* If only we could reset downstream logd counter */
      return -EPERM;
    }
  } else if (log_id == LOG_ID_EVENTS || log_id == LOG_ID_STATS) {
    if (vec[0].iov_len < 4) {
      return -EINVAL;
    }
  }

  ret = LogdWrite(log_id, &ts, vec, nr);
  PmsgWrite(log_id, &ts, vec, nr);

  return ret;
}
#else
static int write_to_log(log_id_t, struct iovec*, size_t) {
  // Non-Android text logs should go to __android_log_stderr_logger, not here.
  // Non-Android binary logs are always dropped.
  return 1;
}
#endif

LogWriter的实现为:

int LogdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) {
  ssize_t ret;
  static const unsigned headerLength = 1;
  struct iovec newVec[nr + headerLength];
  android_log_header_t header;
  size_t i, payloadSize;
  static atomic_int dropped;
  static atomic_int droppedSecurity;

  GetSocket();

  if (logd_socket <= 0) {
    return -EBADF;
  }

  /* logd, after initialization and priv drop */
  if (getuid() == AID_LOGD) {
    /*
     * ignore log messages we send to ourself (logd).
     * Such log messages are often generated by libraries we depend on
     * which use standard Android logging.
     */
    return 0;
  }

  header.tid = gettid();
  header.realtime.tv_sec = ts->tv_sec;
  header.realtime.tv_nsec = ts->tv_nsec;

  newVec[0].iov_base = (unsigned char*)&header;
  newVec[0].iov_len = sizeof(header);

  int32_t snapshot = atomic_exchange_explicit(&droppedSecurity, 0, memory_order_relaxed);
  if (snapshot) {
    android_log_event_int_t buffer;

    header.id = LOG_ID_SECURITY;
    buffer.header.tag = LIBLOG_LOG_TAG;
    buffer.payload.type = EVENT_TYPE_INT;
    buffer.payload.data = snapshot;

    newVec[headerLength].iov_base = &buffer;
    newVec[headerLength].iov_len = sizeof(buffer);

    ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, 2));
    if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
      atomic_fetch_add_explicit(&droppedSecurity, snapshot, memory_order_relaxed);
    }
  }
  snapshot = atomic_exchange_explicit(&dropped, 0, memory_order_relaxed);
  if (snapshot && __android_log_is_loggable_len(ANDROID_LOG_INFO, "liblog", strlen("liblog"),
                                                ANDROID_LOG_VERBOSE)) {
    android_log_event_int_t buffer;

    header.id = LOG_ID_EVENTS;
    buffer.header.tag = LIBLOG_LOG_TAG;
    buffer.payload.type = EVENT_TYPE_INT;
    buffer.payload.data = snapshot;

    newVec[headerLength].iov_base = &buffer;
    newVec[headerLength].iov_len = sizeof(buffer);

    ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, 2));
    if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
      atomic_fetch_add_explicit(&dropped, snapshot, memory_order_relaxed);
    }
  }

  header.id = logId;

  for (payloadSize = 0, i = headerLength; i < nr + headerLength; i++) {
    newVec[i].iov_base = vec[i - headerLength].iov_base;
    payloadSize += newVec[i].iov_len = vec[i - headerLength].iov_len;

    if (payloadSize > LOGGER_ENTRY_MAX_PAYLOAD) {
      newVec[i].iov_len -= payloadSize - LOGGER_ENTRY_MAX_PAYLOAD;
      if (newVec[i].iov_len) {
        ++i;
      }
      break;
    }
  }

  // The write below could be lost, but will never block.
  // EAGAIN occurs if logd is overloaded, other errors indicate that something went wrong with
  // the connection, so we reset it and try again.
  ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, i));
  if (ret < 0 && errno != EAGAIN) {
    LogdConnect();

    ret = TEMP_FAILURE_RETRY(writev(logd_socket, newVec, i));
  }

  if (ret < 0) {
    ret = -errno;
  }

  if (ret > (ssize_t)sizeof(header)) {
    ret -= sizeof(header);
  } else if (ret < 0) {
    atomic_fetch_add_explicit(&dropped, 1, memory_order_relaxed);
    if (logId == LOG_ID_SECURITY) {
      atomic_fetch_add_explicit(&droppedSecurity, 1, memory_order_relaxed);
    }
  }

  return ret;
}

我们从这个函数可以看到,这边其实执行的就是socket的操作。
但是socket操作是怎么初始化和建立的呢?我们稍后进行分析。

printlns函数实现

    /**
     * Helper function for long messages. Uses the LineBreakBufferedWriter to break
     * up long messages and stacktraces along newlines, but tries to write in large
     * chunks. This is to avoid truncation.
     * @hide
     */
    public static int printlns(int bufID, int priority, @Nullable String tag, @NonNull String msg,
            @Nullable Throwable tr) {
        ImmediateLogWriter logWriter = new ImmediateLogWriter(bufID, priority, tag);
        // Acceptable buffer size. Get the native buffer size, subtract two zero terminators,
        // and the length of the tag.
        // Note: we implicitly accept possible truncation for Modified-UTF8 differences. It
        //       is too expensive to compute that ahead of time.
        int bufferSize = PreloadHolder.LOGGER_ENTRY_MAX_PAYLOAD    // Base.
                - 2                                                // Two terminators.
                - (tag != null ? tag.length() : 0)                 // Tag length.
                - 32;                                              // Some slack.
        // At least assume you can print *some* characters (tag is not too large).
        bufferSize = Math.max(bufferSize, 100);

        LineBreakBufferedWriter lbbw = new LineBreakBufferedWriter(logWriter, bufferSize);

        lbbw.println(msg);

        if (tr != null) {
            // This is to reduce the amount of log spew that apps do in the non-error
            // condition of the network being unavailable.
            Throwable t = tr;
            while (t != null) {
                if (t instanceof UnknownHostException) {
                    break;
                }
                if (t instanceof DeadSystemException) {
                    lbbw.println("DeadSystemException: The system died; "
                            + "earlier logs will point to the root cause");
                    break;
                }
                t = t.getCause();
            }
            if (t == null) {
                tr.printStackTrace(lbbw);
            }
        }

        lbbw.flush();

        return logWriter.getWritten();
    }

首先看一下ImmediateLogWriter的封装, 这个类是一个内部的静态类,主要是初始化一些变量和方法。

    /**
     * Helper class to write to the logcat. Different from LogWriter, this writes
     * the whole given buffer and does not break along newlines.
     */
    private static class ImmediateLogWriter extends Writer {

        private int bufID;
        private int priority;
        private String tag;

        private int written = 0;

        /**
         * Create a writer that immediately writes to the log, using the given
         * parameters.
         */
        public ImmediateLogWriter(int bufID, int priority, String tag) {
            this.bufID = bufID;
            this.priority = priority;
            this.tag = tag;
        }

        public int getWritten() {
            return written;
        }

        @Override
        public void write(char[] cbuf, int off, int len) {
            // Note: using String here has a bit of overhead as a Java object is created,
            //       but using the char[] directly is not easier, as it needs to be translated
            //       to a C char[] for logging.
            written += println_native(bufID, priority, tag, new String(cbuf, off, len));
        }

        @Override
        public void flush() {
            // Ignored.
        }

        @Override
        public void close() {
            // Ignored.
        }
    }

然后在设置了buffersize的大小后,将其传入LineBreakBufferedWriter进行初始化。
LineBreakBufferedWriter类的声明如下:

/**
 * A writer that breaks up its output into chunks before writing to its out writer,
 * and which is linebreak aware, i.e., chunks will created along line breaks, if
 * possible.
 *
 * Note: this class is not thread-safe.
 */
public class LineBreakBufferedWriter extends PrintWriter {
}

接下来就会调用lbbw.println(msg)的方法。

    @Override
    public void println() {
        write(lineSeparator);
    }

这边主要是write的实现:

    @Override
    public void write(int c) {
        if (bufferIndex < buffer.length) {
            buffer[bufferIndex] = (char)c;
            bufferIndex++;
            if ((char)c == '\n') {
                lastNewline = bufferIndex;
            }
        } else {
            // This should be an uncommon case, we mostly expect char[] and String. So
            // let the chunking be handled by the char[] case.
            write(new char[] { (char)c }, 0 ,1);
        }
    }

这边的简单封装以后,就会继续调用write的方法:

    @Override
    public void write(char[] buf, int off, int len) {
        while (bufferIndex + len > bufferSize) {
            // Find the next newline in the buffer, see if that's below the limit.
            // Repeat.
            int nextNewLine = -1;
            int maxLength = bufferSize - bufferIndex;
            for (int i = 0; i < maxLength; i++) {
                if (buf[off + i] == '\n') {
                    if (bufferIndex + i < bufferSize) {
                        nextNewLine = i;
                    } else {
                        break;
                    }
                }
            }

            if (nextNewLine != -1) {
                // We can add some more data.
                appendToBuffer(buf, off, nextNewLine);
                writeBuffer(bufferIndex);
                bufferIndex = 0;
                lastNewline = -1;
                off += nextNewLine + 1;
                len -= nextNewLine + 1;
            } else if (lastNewline != -1) {
                // Use the last newline.
                writeBuffer(lastNewline);
                removeFromBuffer(lastNewline + 1);
                lastNewline = -1;
            } else {
                // OK, there was no newline, break at a full buffer.
                int rest = bufferSize - bufferIndex;
                appendToBuffer(buf, off, rest);
                writeBuffer(bufferIndex);
                bufferIndex = 0;
                off += rest;
                len -= rest;
            }
        }

        // Add to the buffer, this will fit.
        if (len > 0) {
            // Add the chars, find the last newline.
            appendToBuffer(buf, off, len);
            for (int i = len - 1; i >= 0; i--) {
                if (buf[off + i] == '\n') {
                    lastNewline = bufferIndex - len + i;
                    break;
                }
            }
        }
    }

这边的writeBuffer主要的实现为:

    /**
     * Helper method, write the given part of the buffer, [start,length), to the output.
     * @param length The number of characters to flush.
     */
    private void writeBuffer(int length) {
        if (length > 0) {
            super.write(buffer, 0, length);
        }
    }

父类的实现为:

    /*
     * Exception-catching, synchronized output operations,
     * which also implement the write() methods of Writer
     */

    /**
     * Writes a single character.
     * @param c int specifying a character to be written.
     */
    public void write(int c) {
        try {
            synchronized (lock) {
                ensureOpen();
                out.write(c);
            }
        }
        catch (InterruptedIOException x) {
            Thread.currentThread().interrupt();
        }
        catch (IOException x) {
            trouble = true;
        }
    }

而这边,就会去判断是不是有中端的发生。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android LOG系统原理剖析 的相关文章

  • 常识 让世界充满AI

    5 https sci hub cc 下载论文 4 问题 等于 机遇 问题抽象为可以解决执行的问题 xff0c 例如 xff1a 自动驾驶 xff0c 细化为特定场景下的自驾车 xff0c 如观光车 xff0c 公交车等 公司的核心是数据
  • iOS-NSLineBreakMode-lineBreakMode属性详解(UILabel省略号位置)

    apple文档 64 property nonatomic NSLineBreakMode lineBreakMode default is NSLineBreakByTruncatingTail used for single and m
  • spark机器学习笔记:(一)Spark Python初探

    声明 xff1a 版权所有 xff0c 转载请联系作者并注明出处 http blog csdn net u013719780 viewmode 61 contents 博主简介 xff1a 风雪夜归子 xff08 英文名 xff1a All
  • Jackson 解析 JSON 详细教程

    点赞再看 xff0c 动力无限 微信搜 程序猿阿朗 本文 Github com niumoo JavaNotes 和 未读代码博客 已经收录 xff0c 有很多知识点和系列文章 JSON 对于开发者并不陌生 xff0c 如今的 WEB 服务
  • 百度百科全站爬取教程

    百度百科全站 目前有16 330 473个词条 这里介绍一个基于scrapy的分布式百度百科爬虫 xff0c 能够全量爬取百度百科的词条 github地址 特性 百科类网站全站词条抓取 xff0c 包括百度百科 互动百科 wiki中英文站点
  • 贪心法

    贪心法 lt gt 贪心算法并不是从整体最优上加以考虑 xff0c 而是从局部最优考虑 xff0c 每次总是做出当前看起来最好的选择 xff0c 在某种意义上的局部最优选择 xff1b lt gt 最优子结构性质 xff1a lt gt 贪
  • shasum: command not found

    yum install perl Digest SHA
  • 记一次http请求报400问题

    引言 由于之前代码比较老 xff0c 都是采用http1 0方式请求 xff0c 于是采用了之前的代码进行实现 xff0c 结果之前测试没有问题 xff0c 后面投产了就报400错误了 xff0c 重新测试还是没有问题 最后通过接收方日志排
  • 数组下标排序

    前言 平时大家大多都是对数组进行各种方式的排序 xff0c 很少对数组的下标进行排序 xff0c 什么是对数组的下标进行排序 xff1f 即按数组值的大小对相应的数组下标进行排序 具体方法见以下正文 正文 解题的重点是如何保存值和下标的对应
  • SpringBoot项目无法接收到数据(Whitelabel Error Page)

    前言 在一次SpringBoot项目模块迁移的过程中 xff0c 新建的模块无法接收到前端的数据 xff0c 在地址栏输入对应的url后显示Whitelabel Error Page 正文 核对了url以及启动类上注解 64 SpringB
  • 解决android7.1系统出现的Consumer closed input channel or an error occurred. events=0x9错误

    自己实现的一个Socket聊天app xff0c 这个app是在17年的时候写的 xff08 当时也是随便写的 xff0c 没注意太多细节 xff09 xff0c 那个时候还是android4 4系统的手机 xff0c 然后写完在真机上调试
  • Java实现多线程轮流打印1-100的数字

    正文 首先打印1 100数字如果用一个单线程实现那么只要一个for循环即可 xff0c 那么如果要用两个线程打印出来呢 xff1f xff08 一个线程打印奇数 xff0c 一个线程打印偶数 xff09 于是大家会想到可以通过加锁实现 xf
  • 安卓手机利用DroidCam当电脑摄像头使用方法

    笔记本电脑有点老了 xff0c 摄像头好像坏了 xff0c 重装了一下午驱动都没弄好 xff0c 换了ubuntu系统也打不开摄像头 xff0c 然后就放弃了 xff0c 于是想到了能不能用android手机当笔记本电脑的摄像头 xff1f
  • Dell安装驱动程序出现的错误(DupAPI::Execute): *** Shell Execute Error. System error text

    在官网下的驱动却怎么也安装不上 xff0c 一直提示 The update installer operation is unsuccessful 然后打开日志文件查看 xfeff 04 10 19 10 12 11 Update Pack
  • 浮点数大小比较

    引言 在一次某公司的笔试题中出现了一题在一个无序的浮点数数组中找出相同的数 xff0c 那么在计算机中一般的整型的十进制数一般都是直接通过 61 61 来判断两个数是否相等的 xff0c 但是如果是浮点数还可以用这样的方式进行判断吗 xff
  • int的取值范围

    引言 在学C 43 43 或者Java的时候应该都会先了解各种基本数据类型的初值和它们的取值范围 xff0c 有些人可能会不太重视这块内容 xff0c 其实很重要 xff0c 很多大公司面试的过程中都会问到int的取值范围 xff0c 溢出
  • Intel汇编语言程序设计学习-第三章 汇编语言基础-上

    汇编语言基础 3 1 汇编语言的基本元素 有人说汇编难 xff0c 有人说汇编简单 xff0c 我个人不做评价 xff0c 下面是一个简单的实例 xff08 部分代码 xff09 xff1a main PROC mov eax 5 5送 E
  • 向量和矩阵范数

    参考 xff1a https en wikipedia org wiki Matrix norm Frobenius norm https blog csdn net Michael Corleone article details 752
  • ubuntu安装后分辨率只有一个选项

    ubuntu 16 04安装后分辨率只有一个选项 1024x768 xff0c 使用xrandr命令出现错误 xff1a xrandr Failed to get size of gamma for output default xff0c
  • android.hardware.Camera入坑之旅

    1 相机预览方向适配 可以参考谷歌官方适配方案 public static void setCameraDisplayOrientation Activity activity int cameraId android hardware C

随机推荐

  • MySQL深入的学习笔记

    MYSQL高级 MySql架构演变 这个很重要 软件的环境是如何从单应用算法极致优化的方向 到分布式的进化 这个时代 就会淘汰好多单体应用的coder 比如我自己 哈哈哈哈 1 0时代 单机单库 单应用 单数据库 快速 方便 好维护 并发量
  • Java变量的声明、初始化和作用域

    一 Java变量的声明 在 Java 程序设计中 xff0c 每个声明的变量都必须分配一个类型 声明一个变量时 xff0c 应该先声明变量的类型 xff0c 随后再声明变量的名字 下面演示了变量的声明方式 double salary int
  • 无线网卡无法启动(代码 10),怎么办?

    前言 无线网卡突然无法启动 xff0c 代码 10 xff0c 怎么办 xff1f 本文记述了作者遇到这个问题的经历和最终解决方法 xff0c 希望我的分享能给大家节约宝贵时间 一 我遇到的问题 先说明一下 xff1a 我用的是华硕的飞行堡
  • systemd内置变量

    替换符含义 b系统的 34 Boot ID 34 字符串 参见 random 4 手册 C缓存根目录 对于系统实例来说是 var cache xff1b 对于用户实例来说是 XDG CACHE HOME E配置根目录 对于系统实例来说是 e
  • IOS轻松实现仿网易新闻顶部滑动指示器(Scrollview实现)

    实现原理很简单 xff0c 就是利用了scrollview进行自定义 xff0c 对外部传入的scrollview滑动事件进行监听 xff0c 源码如下 xff1a xff08 1 xff09 h文件代码 ScrollViewIndicat
  • 【极客日常】Go语言string、int、float、rune、byte等数据类型的转换方法

    golang的数据类型转换是困惑新gopher的一大问题之一 相对于python xff0c golang的数据类型转换可要麻烦的多 xff0c 而且还不走寻常路地诞生了些新的方法跟名词 因此本文讲解golang常见数据类型string i
  • View的mParent变量初始化

    mParent变量实际上是PhoneWindow DecorView类型 xff0c 是所有应用窗口的根视图 xff0c 是FrameLayout的子类 View的requestLayout 函数也是调用了mParent requestLa
  • java:N的N次方

    题目描述 现给你一个正整数N xff0c 请问N N的最左边的数字是什么 xff1f 输入格式 输入包含多组测试数据 每组输入一个正整数N xff08 N lt 61 1000000 xff09 输出 对于每组输入 xff0c 输出N N的
  • CentOS升级curl

    1 安装repo rpm Uvh http www city fan org ftp contrib yum repo rhel6 x86 64 city fan org release 2 1 rhel6 noarch rpm 2 查看该
  • ACM:入口的选择------深度优先搜索

    入口的选择 Time Limit 1000MS Memory Limit 32768K Description Zeism玩的赛车游戏中 xff0c 有一种树形的赛道 树根表示赛道的终点 xff0c 任何一个叶子结点表示一个赛道的入口 xf
  • ACM:n!的位数 :斯特林公式

    n 的位数 Time Limit 2000MS Memory Limit 65536K Description 针对每个非负整数n xff0c 计算其n 的位数 Input 输入数据中含有一些整数n xff08 0 n xff1c 10 7
  • java 自定义封装jdbc dao类

    手动封装jdbc和dao层 xff0c 体会其中的优点与不足 注 xff1a 本次采用的mysql数据库记得添加数据库的驱动包 Dbhelper类 xff1a 对jdbc进行封装 xff0c 采用单例模式 xff0c 不用每次都去连接数据库
  • Mybatis 二级缓存

    mybatis的缓存分为一级缓存和二级缓存 xff0c 缓存是用来缓存部分经常性访问的数据 xff0c 而不必每一次都跑到数据库获取或运算 xff0c 目标是提高系统的性能 一级缓存 对于每一个sqlSession 其中有一个HashMap
  • virtualbox启动报错:Interface VirtualBox Host-Only Ethernet Adapter is not a Host-Only Adapter interface

    参考 xff1a 参考 xff1a windows下VirtualBox使用过程中遇到的一个错误 报错信息 xff1a Interface 39 VirtualBox Host Only Ethernet Adapter 39 is not
  • Tried to access visual service WindowManager from a non-visual Context

    适配Android12时遇到的一个问题 xff0c 做个记录 xff1a 尝试使用WindowManager做一些事情的时候 xff0c 我们一般会先获取WindowManager的实例 xff0c 即 xff1a WindowManage
  • Electron使用electron-builder打包windows时如何签名

    windows打包基本配置 xff1a span class token string 34 build 34 span span class token operator span span class token punctuation
  • Electron打包mac OS安装包的配置和签名(干货)

    1 先看配置 xff08 electron vue xff09 打包使用的是electron builder package json span class token punctuation span span class token o
  • 【nodejs】exec在windows下读取中文乱码问题

    最近在做一个项目需求是读取计算机名称 在windows上使用nodejs exec读取时会有些电脑上会出现乱码问题 直接在cmd上执行hostname xff0c 返回的是没有问题的 当我使用exec读取时就乱码了 xff0c 虽然可以使用
  • 分享 :CSS常见面试题

    CSS基础 2 1 link和 64 import都可以为页面引入CSS文件 xff0c 其区别是 xff1f 将样式定义在单独的 css 的文件里 xff0c link 和 64 import 都可以在 html 页面引入 css 文件
  • Android LOG系统原理剖析

    引言 在我们android的开发过程中 xff0c 最不可少的就是加Log xff0c 打印Log的操作 这样可以帮助我们去查看各个变量 xff0c 理清楚代码的逻辑 而Android系统 xff0c 提供了不同维度 xff0c 不同层面