public class IOCloseLeakDetector extends IssuePublisher implements InvocationHandler {
private static final String TAG = “Matrix.CloseGuardInvocationHandler”;

private final Object originalReporter;

public IOCloseLeakDetector(OnIssueDetectListener issueListener, Object originalReporter) {
super(issueListener);
this.originalReporter = originalReporter;
}

@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
MatrixLog.i(TAG, “invoke method: %s”, method.getName());
if (method.getName().equals(“report”)) {
if (args.length != 2) {
MatrixLog.e(TAG, “closeGuard report should has 2 params, current: %d”, args.length);
return null;
}
if (!(args[1] instanceof Throwable)) {
MatrixLog.e(TAG, “closeGuard report args 1 should be throwable, current: %s”, args[1]);
return null;
}
Throwable throwable = (Throwable) args[1];

String stackKey = IOCanaryUtil.getThrowableStack(throwable);
if (isPublished(stackKey)) {
MatrixLog.d(TAG, “close leak issue already published; key:%s”, stackKey);
} else {
Issue ioIssue = new Issue(SharePluginInfo.IssueType.ISSUE_IO_CLOSABLE_LEAK);
ioIssue.setKey(stackKey);
JSONObject content = new JSONObject();
try {
content.put(SharePluginInfo.ISSUE_FILE_STACK, stackKey);
} catch (JSONException e) {
// e.printStackTrace();
MatrixLog.e(TAG, “json content error: %s”, e);
}
ioIssue.setContent(content);
publishIssue(ioIssue);
MatrixLog.i(TAG, “close leak issue publish, key:%s”, stackKey);
markPublished(stackKey);
}

return null;
}
return method.invoke(originalReporter, args);
}
}

对于 Closeable 泄露监控来说,在 Android 10 及上无法兼容的原因是 CloseGuard#getReporter 无法直接通过反射获取, reporter 字段也是无法直接通过反射获取。如果无法获取到原始的 reporter,那么原始的 reporter 在我们 hook 之后就会失效。如果我们狠下决心,这也是可以接受的,但是对于这种情况我们应该尽量避免。

那么我们现在的问题就是如何在高版本上获取到原始的 reporter,那么有办法吗?有的,因为我们前面说到了无法直接通过反射获取,但是可以间接获取到。这里我们可以通过 反射的反射 来获取。实例如下:

private static void doHook() throws Exception {
Class<?> clazz = Class.forName(“dalvik.system.CloseGuard”);
Class<?> reporterClass = Class.forName(“dalvik.system.CloseGuard$Reporter”);

Method setEnabledMethod = clazz.getDeclaredMethod(“setEnabled”, boolean.class);
setEnabledMethod.invoke(null, true);

// 直接反射获取reporter
// Method getReporterMethod = clazz.getDeclaredMethod(“getReporter”);
// final Object originalReporter = getReporterMethod.invoke(null);

// 反射的反射获取
Method getDeclaredMethodMethod = Class.class.getDeclaredMethod(“getDeclaredMethod”, String.class, Class[].class);
Method getReporterMethod = (Method) getDeclaredMethodMethod.invoke(clazz, “getReporter”, null);
final Object originalReporter = getReporterMethod.invoke(null);

Method setReporterMethod = clazz.getDeclaredMethod(“setReporter”, reporterClass);
Object proxy = Proxy.newProxyInstance(
reporterClass.getClassLoader(),
new Class<?>[]{reporterClass},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(originalReporter, args);
}
}
);
setReporterMethod.invoke(null, proxy);
}

系统CloseGuard的实现原理是在一些资源类中预埋一些代码,从而使CloseGuard感知到资源是否被正常关闭。例如系统类FileOutputStream中有如下代码:

private final CloseGuard guard = CloseGuard.get();

public FileOutputStream(File file, boolean append)
throws FileNotFoundException
{

guard.open(“close”);
}

public void close() throws IOException {

guard.close();

}

protected void finalize() throws IOException {
// Android-added: CloseGuard support.
if (guard != null) {
guard.warnIfOpen();
}

if (fd != null) {
if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
flush();
} else {
// Android-removed: Obsoleted comment about shared FileDescriptor handling.
close();
}
}
}

可以看到在调用finalize之前未调用close方法会走到CloseGuard的warnIfOpen方法,从而检测到这次资源未正常关闭的行为。

当然应用也有一些自定义的资源类,对于这种情况Matrix建议使用MatrixCloseGuard这个类模拟系统埋点的方式,达到资源监控的目的。

虽然在 Android 源码中,StrictMode 已经预埋了很多的资源埋点。不过肯定还有埋点是没有的,比如 MediaPlayer、程序内部的一些资源模块。所以在程序中也写了一个 MyCloseGuard 类,对希望增加监控的资源,可以手动增加埋点代码。

Native Hook

IOCanaryJniBridge 负责三种需要 native hook 的检测场景。在 IOCanaryJniBridge#install 操作中,会先加载对应的 so,然后根据配置启动 detector 并设置对应的上报阈值,最后调用 doHook 这个 native 方法进行 native 层面的 hook。

public static void install(IOConfig config, OnJniIssuePublishListener listener) {
MatrixLog.v(TAG, “install sIsTryInstall:%b”, sIsTryInstall);
if (sIsTryInstall) {
return;
}

//load lib
if (!loadJni()) {
MatrixLog.e(TAG, “install loadJni failed”);
return;
}

//set listener
sOnIssuePublishListener = listener;

try {
//set config
if (config != null) {
if (config.isDetectFileIOInMainThread()) {
enableDetector(DetectorType.MAIN_THREAD_IO);
// ms to μs
setConfig(ConfigKey.MAIN_THREAD_THRESHOLD, config.getFileMainThreadTriggerThreshold() * 1000L);
}

if (config.isDetectFileIOBufferTooSmall()) {
enableDetector(DetectorType.SMALL_BUFFER);
setConfig(ConfigKey.SMALL_BUFFER_THRESHOLD, config.getFileBufferSmallThreshold());
}

if (config.isDetectFileIORepeatReadSameFile()) {
enableDetector(DetectorType.REPEAT_READ);
setConfig(ConfigKey.REPEAT_READ_THRESHOLD, config.getFileRepeatReadThreshold());
}
}

//hook
doHook();

sIsTryInstall = true;
} catch (Error e) {
MatrixLog.printErrStackTrace(TAG, e, “call jni method error”);
}
}

private static boolean loadJni() {
if (sIsLoadJniLib) {
return true;
}

try {
System.loadLibrary(“io-canary”);
} catch (Exception e) {
MatrixLog.e(TAG, “hook: e: %s”, e.getLocalizedMessage());
sIsLoadJniLib = false;
return false;
}

sIsLoadJniLib = true;
return true;
}

/**

  • enum DetectorType {
  • kDetectorMainThreadIO = 0,
  • kDetectorSmallBuffer,
  • kDetectorRepeatRead
  • };
    */
    private static final class DetectorType {
    static final int MAIN_THREAD_IO = 0;
    static final int SMALL_BUFFER = 1;
    static final int REPEAT_READ = 2;
    }
    private static native void enableDetector(int detectorType);

/**

  • enum IOCanaryConfigKey {
  • kMainThreadThreshold = 0,
  • kSmallBufferThreshold,
  • kRepeatReadThreshold,
  • };
    */
    private static final class ConfigKey {
    static final int MAIN_THREAD_THRESHOLD = 0;
    static final int SMALL_BUFFER_THRESHOLD = 1;
    static final int REPEAT_READ_THRESHOLD = 2;
    }

private static native void setConfig(int key, long val);

private static native boolean doHook();

上面的 DetectorType 以及 ConfigKey 的值的定义,与注释中定义在 C 层的枚举一致; config.getFileXX获取到的默认值,也与 C 层的默认值一致。如果在 Java 层修改了 detector 的触发阈值, 那么 C 层检测时会以自定义的值为准。

注意到 IOCanaryJniBridge 中还有一个私有静态类 JavaContext 以及一个私有静态方法 getJavaContext,这两个东西是给 C++ 部分进行调用的。该类中有两个参数 threadName 以及 stack,这会作为底层 detector 进行判断的参数,同时上报 IO 问题时也会带上这两个参数。

private static final class JavaContext {
private final String stack;
private final String threadName;

private JavaContext() {
stack = IOCanaryUtil.getThrowableStack(new Throwable());
threadName = Thread.currentThread().getName();
}
}

/**

  • 声明为private,给c++部分调用!!!不要干掉!!!
  • @return
    */
    private static JavaContext getJavaContext() {
    try {
    return new JavaContext();
    } catch (Throwable th) {
    MatrixLog.printErrStackTrace(TAG, th, “get javacontext exception”);
    }

return null;
}

上面就是 Java 层的主要代码,下面我们看看 native 层干的事情,native 层的入口位于 io_canary_jni.cc 中。在加载 so 时,首先被调用的就是 JNI_OnLoad 方法。

在 JNI_OnLoad 方法会持有 Java 层一些方法、成员变量的句柄,供后续使用。相关代码以及对应关系如下:

namespace iocanary {
static jclass kJavaBridgeClass;
static jmethodID kMethodIDOnIssuePublish;

static jclass kJavaContextClass;
static jmethodID kMethodIDGetJavaContext;
static jfieldID kFieldIDStack;
static jfieldID kFieldIDThreadName;

static jclass kIssueClass;
static jmethodID kMethodIDIssueConstruct;

static jclass kListClass;
static jmethodID kMethodIDListConstruct;
static jmethodID kMethodIDListAdd;

extern “C” {

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved){
__android_log_print(ANDROID_LOG_DEBUG, kTag, “JNI_OnLoad”);
kInitSuc = false;

// 获取Java层一些方法、成员变脸的句柄
if (!InitJniEnv(vm)) {
return -1;
}

// 设置上报回调为OnIssuePublish函数
iocanary::IOCanary::Get().SetIssuedCallback(OnIssuePublish);

kInitSuc = true;
__android_log_print(ANDROID_LOG_DEBUG, kTag, “JNI_OnLoad done”);
return JNI_VERSION_1_6;
}

static bool InitJniEnv(JavaVM vm) {
kJvm = vm;
JNIEnv
env = NULL;
if (kJvm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK){
__android_log_print(ANDROID_LOG_ERROR, kTag, “InitJniEnv GetEnv !JNI_OK”);
return false;
}

jclass temp_cls = env->FindClass(“com/tencent/matrix/iocanary/core/IOCanaryJniBridge”);
if (temp_cls == NULL) {
__android_log_print(ANDROID_LOG_ERROR, kTag, “InitJniEnv kJavaBridgeClass NULL”);
return false;
}
// IOCanaryJniBridge
kJavaBridgeClass = reinterpret_cast(env->NewGlobalRef(temp_cls));

jclass temp_java_context_cls = env->FindClass("com/tencent/matrix/iocanary/core/IOCanaryJniBridgeKaTeX parse error: Expected group after '_' at position 54: …ls == NULL) { _̲_android_log_pr…JavaContext
kJavaContextClass = reinterpret_cast(env->NewGlobalRef(temp_java_context_cls));
// IOCanaryJniBridgeJavaContext.stackkFieldIDStack=env−>GetFieldID(kJavaContextClass,"stack","Ljava/lang/String;");//IOCanaryJniBridgeJavaContext.stack kFieldIDStack = env->GetFieldID(kJavaContextClass, "stack", "Ljava/lang/String;"); // IOCanaryJniBridgeJavaContext.stackkFieldIDStack=env>GetFieldID(kJavaContextClass,"stack","Ljava/lang/String;");//IOCanaryJniBridgeJavaContext.threadName
kFieldIDThreadName = env->GetFieldID(kJavaContextClass, “threadName”, “Ljava/lang/String;”);
if (kFieldIDStack == NULL || kFieldIDThreadName == NULL) {
__android_log_print(ANDROID_LOG_ERROR, kTag, “InitJniEnv kJavaContextClass field NULL”);
return false;
}

// IOCanaryJniBridge#onIssuePublish
kMethodIDOnIssuePublish = env->GetStaticMethodID(kJavaBridgeClass, “onIssuePublish”, “(Ljava/util/ArrayList;)V”);
if (kMethodIDOnIssuePublish == NULL) {
__android_log_print(ANDROID_LOG_ERROR, kTag, “InitJniEnv kMethodIDOnIssuePublish NULL”);
return false;
}

// IOCanaryJniBridge#getJavaContext
kMethodIDGetJavaContext = env->GetStaticMethodID(kJavaBridgeClass, “getJavaContext”, “()Lcom/tencent/matrix/iocanary/core/IOCanaryJniBridge$JavaContext;”);
if (kMethodIDGetJavaContext == NULL) {
__android_log_print(ANDROID_LOG_ERROR, kTag, “InitJniEnv kMethodIDGetJavaContext NULL”);
return false;
}

jclass temp_issue_cls = env->FindClass(“com/tencent/matrix/iocanary/core/IOIssue”);
if (temp_issue_cls == NULL) {
__android_log_print(ANDROID_LOG_ERROR, kTag, “InitJniEnv kIssueClass NULL”);
return false;
}
// IOIssue
kIssueClass = reinterpret_cast(env->NewGlobalRef(temp_issue_cls));

// IOIssue#init
kMethodIDIssueConstruct = env->GetMethodID(kIssueClass, “”, “(ILjava/lang/String;JIJJIJLjava/lang/String;Ljava/lang/String;I)V”);
if (kMethodIDIssueConstruct == NULL) {
__android_log_print(ANDROID_LOG_ERROR, kTag, “InitJniEnv kMethodIDIssueConstruct NULL”);
return false;
}

jclass list_cls = env->FindClass(“java/util/ArrayList”);
// ArrayList
kListClass = reinterpret_cast(env->NewGlobalRef(list_cls));
// ArrayList#init
kMethodIDListConstruct = env->GetMethodID(list_cls, “”, “()V”);
// ArrayList#add
kMethodIDListAdd = env->GetMethodID(list_cls, “add”, “(Ljava/lang/Object;)Z”);

return true;
}
}

然后在 Java层 中会进行调用 native 层的 enableDetector 以及 setConfig 函数,后者这个方法就不说了。enableDetector 函数会向 IOCanary 这个单例对象中添加对应的 detector 实例。

// matrix/matrix-android/matrix-io-canary/src/main/cpp/io_canary_jni.cc
JNIEXPORT void JNICALL
Java_com_tencent_matrix_iocanary_core_IOCanaryJniBridge_enableDetector(JNIEnv *env, jclass type, jint detector_type) {
iocanary::IOCanary::Get().RegisterDetector(static_cast(detector_type));
}

// matrix/matrix-android/matrix-io-canary/src/main/cpp/core/io_canary.cc
void IOCanary::RegisterDetector(DetectorType type) {
switch (type) {
case DetectorType::kDetectorMainThreadIO:
detectors_.push_back(new FileIOMainThreadDetector());
break;
case DetectorType::kDetectorSmallBuffer:
detectors_.push_back(new FileIOSmallBufferDetector());
break;
case DetectorType::kDetectorRepeatRead:
detectors_.push_back(new FileIORepeatReadDetector());
break;
default:
break;
}
}

上面出现的三个 Detector 就是对应三种场景的了,我们后面分析具体检测算法的时候再讨论。下面再看看 Java 调用的 doHook 方法,在该方法的实现中,会调用 xHook 来 hook 对应 so 的对应函数。

Native Hook是采用PLT(GOT) Hook的方式hook了系统so中的IO相关的open、read、write、close方法。在代理了这些系统方法后,Matrix做了一些逻辑上的细分,从而检测出不同的IO Issue。

const static char* TARGET_MODULES[] = {
“libopenjdkjvm.so”,
“libjavacore.so”,
“libopenjdk.so”
};
const static size_t TARGET_MODULE_COUNT = sizeof(TARGET_MODULES) / sizeof(char*);

JNIEXPORT jboolean JNICALL
Java_com_tencent_matrix_iocanary_core_IOCanaryJniBridge_doHook(JNIEnv *env, jclass type) {
__android_log_print(ANDROID_LOG_INFO, kTag, “doHook”);

for (int i = 0; i < TARGET_MODULE_COUNT; ++i) {
const char* so_name = TARGET_MODULES[i];
__android_log_print(ANDROID_LOG_INFO, kTag, “try to hook function in %s.”, so_name);

void* soinfo = xhook_elf_open(so_name);
if (!soinfo) {
__android_log_print(ANDROID_LOG_WARN, kTag, “Failure to open %s, try next.”, so_name);
continue;
}

xhook_hook_symbol(soinfo, “open”, (void*)ProxyOpen, (void**)&original_open);
xhook_hook_symbol(soinfo, “open64”, (void*)ProxyOpen64, (void**)&original_open64);

bool is_libjavacore = (strstr(so_name, “libjavacore.so”) != nullptr);
if (is_libjavacore) {
if (xhook_hook_symbol(soinfo, “read”, (void*)ProxyRead, (void**)&original_read) != 0) {
__android_log_print(ANDROID_LOG_WARN, kTag, “doHook hook read failed, try __read_chk”);
if (xhook_hook_symbol(soinfo, “__read_chk”, (void*)ProxyReadChk, (void**)&original_read_chk) != 0) {
__android_log_print(ANDROID_LOG_WARN, kTag, “doHook hook failed: __read_chk”);
xhook_elf_close(soinfo);
return JNI_FALSE;
}
}

if (xhook_hook_symbol(soinfo, “write”, (void*)ProxyWrite, (void**)&original_write) != 0) {
__android_log_print(ANDROID_LOG_WARN, kTag, “doHook hook write failed, try __write_chk”);
if (xhook_hook_symbol(soinfo, “__write_chk”, (void*)ProxyWriteChk, (void**)&original_write_chk) != 0) {
__android_log_print(ANDROID_LOG_WARN, kTag, “doHook hook failed: __write_chk”);
xhook_elf_close(soinfo);
return JNI_FALSE;
}
}
}

xhook_hook_symbol(soinfo, “close”, (void*)ProxyClose, (void**)&original_close);

xhook_elf_close(soinfo);
}

__android_log_print(ANDROID_LOG_INFO, kTag, “doHook done.”);
return JNI_TRUE;
}

在上面的代码中,分别 hook libopenjdkjvm.so、libjavacore.so、libopenjdk.so 中的 open、open64、close函数,此外还会额外 hook libjavacore.so 的 read、__read_chk、write、__write_chk 的方法。这样打开、读写、关闭全流程都可以 hook 到了。hook 之后,调用被 hook 的函数都会先被 matrix 拦截处理。

此外,我们还可以看到 xHook 的使用是非常简单的,流程如下:

  • 调用 xhook_elf_open 打开对应的 so
  • 调用 xhook_hook_symbol hook 对应的方法
  • 调用 xhook_elf_close close 资源,防止资源泄漏
  • 如果需要还原 hook,也是调用 xhook_hook_symbol 进行 hook 点的还原

open

matrix IO 模块目前只检测主线的 IO 问题,当 open 等操作执行成功时,才会进入统计、检测流程。

在 open 操作中,会将入参与出参一起作为参数向下层传递,这里的返回值 ret 实际上是指文件描述符 fd。

int ProxyOpen64(const char *pathname, int flags, mode_t mode) {
if(!IsMainThread()) {
return original_open64(pathname, flags, mode);
}

int ret = original_open64(pathname, flags, mode);

if (ret != -1) {
DoProxyOpenLogic(pathname, flags, mode, ret);
}

return ret;
}

在捕获到 open 操作后,下面就转入了 IOCanary 的处理逻辑了。在 DoProxyOpenLogic 函数中,首先调用 Java 层的 IOCanaryJniBridge#getJavaContext 方法获取当前的上下文环境 JavaContext,然后将 Java 层的 JavaContext 转为 C 层的 java_context;最后调用了 IOCanary#OnOpen 方法。

static void DoProxyOpenLogic(const char pathname, int flags, mode_t mode, int ret) {
JNIEnv
env = NULL;
kJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
if (env == NULL || !kInitSuc) {
__android_log_print(ANDROID_LOG_ERROR, kTag, “ProxyOpen env null or kInitSuc:%d”, kInitSuc);
} else {
jobject java_context_obj = env->CallStaticObjectMethod(kJavaBridgeClass, kMethodIDGetJavaContext);
if (NULL == java_context_obj) {
return;
}

jstring j_stack = (jstring) env->GetObjectField(java_context_obj, kFieldIDStack);
jstring j_thread_name = (jstring) env->GetObjectField(java_context_obj, kFieldIDThreadName);

char* thread_name = jstringToChars(env, j_thread_name);
char* stack = jstringToChars(env, j_stack);
JavaContext java_context(GetCurrentThreadId(), thread_name == NULL ? “” : thread_name, stack == NULL ? “” : stack);
free(stack);
free(thread_name);

iocanary::IOCanary::Get().OnOpen(pathname, flags, mode, ret, java_context);

env->DeleteLocalRef(java_context_obj);
env->DeleteLocalRef(j_stack);
env->DeleteLocalRef(j_thread_name);
}
}

IOCanary#OnOpen 代理调用了 IOInfoCollector#OnOpen 方法。在后者的实现中,会以 fd 为 key, pathname、java_context 等值组成的对象 IOInfo 作为 value,保存到 info_map_ 这个 map 中。 IOInfo 这个对象里面的字段很多,包含了 IOCanary 对 IO 问题检测的各方面所需的字段,具体里面有什么我们下面遇到再说。 IOCanary#OnOpen 代码如下:

// matrix/matrix-android/matrix-io-canary/src/main/cpp/core/io_canary.cc
void IOCanary::OnOpen(const char *pathname, int flags, mode_t mode,
int open_ret, const JavaContext& java_context) {
collector_.OnOpen(pathname, flags, mode, open_ret, java_context);
}

// matrix/matrix-android/matrix-io-canary/src/main/cpp/core/io_info_collector.cc
void IOInfoCollector::OnOpen(const char *pathname, int flags, mode_t mode
, int open_ret, const JavaContext& java_context) {
//__android_log_print(ANDROID_LOG_DEBUG, kTag, “OnOpen fd:%d; path:%s”, open_ret, pathname);

if (open_ret == -1) {
return;
}

if (info_map_.find(open_ret) != info_map_.end()) {
//_android_log_print(ANDROID_LOG_WARN, kTag, "OnOpen fd:%d already in info_map", open_ret);
return;
}

std::shared_ptr info = std::make_shared(pathname, java_context);
info_map_.insert(std::make_pair(open_ret, info));
}

至此,open 流程相关的代码我们梳理了一下,就是以 open 操作中的 fd 为 key,对应的 IOInfo 为 value 保存到哈希表中备用。

read/write

read 操作 hook 了 read、__read_chk 两个函数,函数定义如下:

// read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.
ssize_t read(int fd, void *buf, size_t count);

// The interface __read_chk() shall function in the same way as the interface read(), except that __read_chk() shall check for buffer overflow before computing a result. If an overflow is anticipated, the function shall abort and the program calling it shall exit.
//
// The parameter buflen specifies the size of the buffer buf. If nbytes exceeds buflen, the function shall abort, and the program calling it shall exit.
//
// The __read_chk() function is not in the source standard; it is only in the binary standard.
ssize_t __read_chk(int fd, void * buf, size_t nbytes, size_t buflen);

因此,读写操作的 buffer_size 都应该对应第三个参数才是。

接着,我们看看代理函数。在读的代理函数中,依旧是只处理主线程的调用。这里面 ret 表示的是本次操作中读取到的字节长度,同时还记录本次读的操作耗时 read_cost_us。收集到入参、出参以及耗时这五项参数后,作为入参调用 IOCanary#OnRead 函数。

/**

  • Proxy for read: callback to the java layer
    */
    ssize_t ProxyRead(int fd, void *buf, size_t size) {
    if(!IsMainThread()) {
    return original_read(fd, buf, size);
    }

int64_t start = GetTickCountMicros();

size_t ret = original_read(fd, buf, size);

long read_cost_us = GetTickCountMicros() - start;

//__android_log_print(ANDROID_LOG_DEBUG, kTag, “ProxyRead fd:%d buf:%p size:%d ret:%d cost:%d”, fd, buf, size, ret, read_cost_us);

iocanary::IOCanary::Get().OnRead(fd, buf, size, ret, read_cost_us);

return ret;
}

ssize_t ProxyReadChk(int fd, void* buf, size_t count, size_t buf_size) {
if(!IsMainThread()) {
return original_read_chk(fd, buf, count, buf_size);
}

int64_t start = GetTickCountMicros();

ssize_t ret = original_read_chk(fd, buf, count, buf_size);

long read_cost_us = GetTickCountMicros() - start;

//__android_log_print(ANDROID_LOG_DEBUG, kTag, “ProxyRead fd:%d buf:%p size:%d ret:%d cost:%d”, fd, buf, size, ret, read_cost_us);

iocanary::IOCanary::Get().OnRead(fd, buf, count, ret, read_cost_us);

return ret;
}

在 IOCanary#OnRead 函数中,还是代理调用了 IOInfoCollector#OnRead 函数:

void IOCanary::OnRead(int fd, const void *buf, size_t size,
ssize_t read_ret, long read_cost) {
collector_.OnRead(fd, buf, size, read_ret, read_cost);
}

void IOInfoCollector::OnRead(int fd, const void *buf, size_t size,
ssize_t read_ret, long read_cost) {

if (read_ret == -1 || read_cost < 0) {
return;
}

if (info_map_.find(fd) == info_map_.end()) {
//_android_log_print(ANDROID_LOG_DEBUG, kTag, "OnRead fd:%d not in info_map", fd);
return;
}

CountRWInfo(fd, FileOpType::kRead, size, read_cost);
}

如果 fd 在 map 中,也就是说如果 open 时被捕获到了,那么才会进入 CountRWInfo 这个函数。CountRWInfo 会记录 IOInfo 所代表的文件的累计读写操作次数、累计buffer size、累计操作耗时、单次读写最大耗时、当前连续读写操作耗时、最大连续读写操作耗时、本次操作时间戳、最大操作buffer size、操作类型这些数据,具体看下面代码即可,一目了然。

void IOInfoCollector::CountRWInfo(int fd, const FileOpType &fileOpType, long op_size, long rw_cost) {
if (info_map_.find(fd) == info_map_.end()) {
return;
}

// 获取系统的当前时间,单位微秒(us)
const int64_t now = GetSysTimeMicros();

// 累计读写操作次数累加
info_map_[fd]->op_cnt_ ++;
// 累计buffer size
info_map_[fd]->op_size_ += op_size;
// 累计文件读写耗时
info_map_[fd]->rw_cost_us_ += rw_cost;

// 单次文件读写最大耗时
if (rw_cost > info_map_[fd]->max_once_rw_cost_time_μs_) {
info_map_[fd]->max_once_rw_cost_time_μs_ = rw_cost;
}

//android_log_print(ANDROID_LOG_DEBUG, kTag, "CountRWInfo rw_cost:%d max_once_rw_cost_time:%d current_continual_rw_time:%d;max_continual_rw_cost_time_:%d; now:%lld;last:%lld",
// rw_cost, info_map_[fd]->max_once_rw_cost_time_μs_, info_map_[fd]->current_continual_rw_time_μs_, info_map_[fd]->max_continual_rw_cost_time_μs_, now, info_map_[fd]->last_rw_time_ms_);

// 连续读写耗时,若两次操作超过阈值(8000us,约为一帧耗时16.6667ms的一半),则不累计
if (info_map_[fd]->last_rw_time_μs_ > 0 && (now - info_map_[fd]->last_rw_time_μs_) < kContinualThreshold) {
info_map_[fd]->current_continual_rw_time_μs_ += rw_cost;
} else {
info_map_[fd]->current_continual_rw_time_μs_ = rw_cost;
}

// 最大连续读写耗时
if (info_map_[fd]->current_continual_rw_time_μs_ > info_map_[fd]->max_continual_rw_cost_time_μs_) {
info_map_[fd]->max_continual_rw_cost_time_μs_ = info_map_[fd]->current_continual_rw_time_μs_;
}
// 本次读写记录的时间戳
info_map_[fd]->last_rw_time_μs_ = now;

// 最大读写操作buffer size
if (info_map_[fd]->buffer_size_ < op_size) {
info_map_[fd]->buffer_size_ = op_size;
}

// 读写操作类型
if (info_map_[fd]->op_type_ == FileOpType::kInit) {
info_map_[fd]->op_type_ = fileOpType;
}
}

我们可以看到 read 时就将对 IOInfo 里面的字段进行了赋值。实际上对 write 操作的统计也和 read 操作类似,最后也是调用的 CountRWInfo 函数对写操作进行统计,这里不做更多赘述。

/**

  • Proxy for write: callback to the java layer
    */
    ssize_t ProxyWrite(int fd, const void *buf, size_t size) {
    if(!IsMainThread()) {
    return original_write(fd, buf, size);
    }

int64_t start = GetTickCountMicros();

size_t ret = original_write(fd, buf, size);

long write_cost_μs = GetTickCountMicros() - start;

//__android_log_print(ANDROID_LOG_DEBUG, kTag, “ProxyWrite fd:%d buf:%p size:%d ret:%d cost:%d”, fd, buf, size, ret, write_cost_μs);

iocanary::IOCanary::Get().OnWrite(fd, buf, size, ret, write_cost_μs);
leOpType::kInit) {
info_map_[fd]->op_type_ = fileOpType;
}
}

我们可以看到 read 时就将对 IOInfo 里面的字段进行了赋值。实际上对 write 操作的统计也和 read 操作类似,最后也是调用的 CountRWInfo 函数对写操作进行统计,这里不做更多赘述。

/**

  • Proxy for write: callback to the java layer
    */
    ssize_t ProxyWrite(int fd, const void *buf, size_t size) {
    if(!IsMainThread()) {
    return original_write(fd, buf, size);
    }

int64_t start = GetTickCountMicros();

size_t ret = original_write(fd, buf, size);

long write_cost_μs = GetTickCountMicros() - start;

//__android_log_print(ANDROID_LOG_DEBUG, kTag, “ProxyWrite fd:%d buf:%p size:%d ret:%d cost:%d”, fd, buf, size, ret, write_cost_μs);

iocanary::IOCanary::Get().OnWrite(fd, buf, size, ret, write_cost_μs);

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 2021年安全员-B证考试内容及安全员-B证最新解析

    题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全生产模拟考试一点通&#xff1a;安全员-B证考试内容是安全生产模拟考试一点通总题库中生成的一套安全员-B证最新解析&#xff0c;安全生产模拟考试一点通上安全员-B证作业手机同步练习。2021年安全员-B证考试内容…...

    2024/4/14 17:05:41
  2. 快捷键及基本DOS命令

    1 快捷键 快捷键功能shiftdelete永久删除Altf4关闭窗口winR打开命令窗口winE打开文件框ctrlshiftesc打开任务管理器win/Alttab切换应用程序explorer用于恢复界面Altprt截取窗口 2 基本DOS命令 1&#xff09;打开CMD方式 开始系统命令提示符 Win键R&#xff0c;输入cmd打开控…...

    2024/4/14 17:05:36
  3. 数据库的设计规范

    1、库名与应用名称尽量一致 2、表名、字段名必须使用小写字母或数字&#xff0c;禁止出现数字开头&#xff0c; 3、表名不使用复数名词 4、表的命名最好是加上“业务名称_表的作用”。如&#xff0c;edu_teacher 5、表必备三字段&#xff1a;id, gmt_create, gmt_modified …...

    2024/4/19 17:16:25
  4. SpringBoot+拦截器+自定义异常+自定义注解+全局异常处理简单实现接口权限管理

    前言 提到权限管理这块肯定很多人第一想到的就是Springboot Security或者是Shiro安全框架&#xff0c;但本文介绍的并不是这两种&#xff0c;不是因为他们不好用&#xff0c;实在是自己太懒了&#xff0c;我觉得一个拦截器加上其他的一些处理就能满足项目的需求&#xff0c;我…...

    2024/4/5 5:13:44
  5. day11 学习java

    day11笔记 一.打包 第一步&#xff1a; 第二步&#xff1a; 第三步&#xff1a; 第四步&#xff1a; 第五步&#xff1a; 二.导包 第一步&#xff1a; 将包导入需要到的文件夹里&#xff0c;一般新项目里面创建一个lib文件夹&#xff0c;导入的包放入此文件夹里面 第二步&a…...

    2024/4/14 17:05:41
  6. cesium-添加地下模型

    cesium-添加地下模型 在加载模型的时候&#xff0c;可能会出现地下模型&#xff0c;这时就需要开启深度检测了&#xff0c;不然模型会在视图中乱飘 开启深度检测后&#xff0c;如果加载了图层&#xff0c;就有可能遮挡住地下模型 这是就需要使用根据距离改变地下透视程度 核…...

    2024/4/18 6:27:13
  7. AQS——从ReentrantLock入手,了解锁的获取的释放

    AQS 以下内容可参考美团技术团队&#xff1a;从ReentrantLock的实现看AQS的原理及应用 讲到ReentrantLock就不得不讲AQS&#xff0c;因为Lock的底层就是基于AQS来实现的。那么。什么时AQS呢&#xff1f; AQS全称AbstractQueuedSynchronizer&#xff0c;是JUC中的一个类。它提…...

    2024/4/14 17:05:56
  8. 使用Go开发REST API接口

    本例使用的IDE为Visual Studio Code 首先安装VSCode的Go开发插件 点击Extensions按钮&#xff0c;在输入框中输入go&#xff0c;选择Rich Go Language support for Visual Studio Code.....的插件进行安装 安装依赖的几个Go库 # 安装gocode go get -u -v github.com/nsf/goc…...

    2024/4/14 17:06:01
  9. 建表规约与索引规约—参考阿里开发规范

    1.建表规约 **【强制】**表达是与否概念的字段&#xff0c;必须使用is_xxx的方式命名&#xff0c;数据类型是tinyint(1) &#xff08; 1表示是&#xff0c;0表示否&#xff09;。 说明&#xff1a;任何字段如果为非负数&#xff0c;必须是无符号。 正例&#xff1a;表达逻辑删除…...

    2024/4/26 20:39:24
  10. CentOS 7.6.1810 (server版本) qemu-system-x86_64 虚拟机网桥搭建

    写在前面 好久没写博客了&#xff0c;感觉有点手生。这次给大家带来满满的干货&#xff0c;主要针对建立虚拟机后&#xff0c;虚拟机的联网问题&#xff0c;自己走了很多弯路&#xff0c;希望大家以后碰到类似的问题&#xff0c;参考本博客快速解决&#xff01;&#xff01;&a…...

    2024/4/14 17:06:01
  11. 汽车电子业务升级方式说明

    汽车电子业务升级方式以我个人知道的清楚的了解的&#xff0c;目前有两种升级&#xff0c;一种是实车上的OTA升级方式&#xff0c;一种是测试台架上的USB升级方式。 未接触过安卓测试的新人&#xff0c;会有疑问什么是OTA升级&#xff0c;可以理解为是一种远程的无线升级技术&a…...

    2024/4/14 17:05:46
  12. 1.16 SQL高级

    SELECT TOP 子句用于规定要返回的记录的数目。对于数千条的大型表来说是非常好用的。 除此之外 MySQL 支持 LIMIT 语句来选取指定的条数数据&#xff0c; Oracle 可以使用 ROWNUM 来选取。 limit 就是限制&#xff0c;limit 2就是从数据库里找出来两个 LIKE 操作符用于在 WHE…...

    2024/4/18 11:05:08
  13. 兔的文化素养知识思想来源~万维钢精英日课

    第二部分是单独分开的单个问题小议&#xff0c;兔用简单的概括总结学到的知识供日后的复习&#xff0c;兔定当更加努力~ 当本兔第一次听到【量子纠缠】的时候就是说一整只的震惊住了&#xff0c;兔因无知来寻求科学答案&#xff0c;而鬼魅般的超距离协调使兔从此踏上了物理学探…...

    2024/4/14 17:05:41
  14. PXE高效批量网络装机

    一.部署PXE远程安装服务 1.1 搭建PXE远程安装服务器 1.1.1&#xff1a;服务器的批量部署 规模化&#xff1a;同时装配多台服务器 自动化&#xff1a;安装系统&#xff0c;配置各种服务 远程实现&#xff1a;不需要光盘&#xff0c;U盘等安装介质 1.优点: 规模化:同时装配多…...

    2024/4/7 4:19:36
  15. Ajax复习02-(form 表单,axios 拦截器,FormData文件上传)

    什么是form表单 在网页中&#xff0c;表单主要负责数据采集功能。 表单的三个组成部分 网页中采集数据的表单由三个部分组成&#xff0c;表单标签、表单域、表单按钮。 表单标签 HTML 的form是一个“容器”&#xff0c;用来将页面上指定的区域划定为表单区域 表单域 表单…...

    2024/4/16 14:54:53
  16. 怒肝,2022年最全JAVA学习路线一条龙

    前言 想像自己还是一名刚接触计算机的的小白&#xff0c;如何去学习呢&#xff0c;当然是从互联网开始啦&#xff0c;如何让我们的学习路线不偏航呢&#xff0c;下面就给大家梳理一条“捷径”&#xff0c;让你快速走上人生巅峰&#xff0c;迎娶白富美。 由浅入深的你会了解到…...

    2024/4/7 4:19:34
  17. vue使用组件实现json数据分页

    table表格组件中将数据切割截取 数组.slice(开始&#xff0c;结束) 数组.slice((当前页数-1)*每页条数,当前页数*每页条数) 假设&#xff1a;从第二页开始每页保留3条&#xff0c; 那么当前页2-11,1*每页条数33&#xff0c; 当前页2*每页条数36。 最后得出从第3条数据开始…...

    2024/4/7 4:19:34
  18. 软件测试体系学习及构建(19)-测试活动之缺陷管理

    &#xff08;19&#xff09;-测试活动之缺陷管理1 bug定义2 bug关键信息3 bug书写注意事项4 bug类型说明5 bug严重程度6 bug生命周期7 bug解决方案说明8 bug处理流程8.1 简单流程8.2 某工具复杂流程9 bug管理工具以下为简单概述或者通用型描述&#xff0c;不同的项目或者业务会…...

    2024/4/14 17:06:22
  19. eNSP单臂路由实验

    文章目录一、单臂路由介绍二、eNSP下单臂路由实验三、总结一、单臂路由介绍 单臂路由是指在路由器的一个接口上通过配置子接口&#xff08;或“逻辑接口”&#xff0c;并不存在真正物理接口&#xff09;的方式&#xff0c;实现原来相互隔离的不同VLAN&#xff08;虚拟局域网&a…...

    2024/4/14 17:06:52
  20. 方法【重点、难点、练习点】

    目录 1. 方法引入 2. 格式 3. 无参数无返回值方法 3.1 需求 3.2 方法分析 3.3 方法声明 3.4 方法实现和调用演示 3.5 注意事项 3.6 方法执行流程示意图 4. 有参数无返回值方法 4.1 需求 4.2 方法分析 4.3 方法声明 4.4 方法实现和调用演示 4.5 注意事项 4.6 方法执行流程示…...

    2024/4/14 17:06:57

最新文章

  1. 任何人都可做的兼职副业,一单29.9元,利润70%的怀旧游戏项目

    偶然发现一个博主正在直播玩一款经典的俄罗斯方块游戏机。这款游戏机年代久远&#xff0c;勾起了我对学生时代的无尽回忆。 那时&#xff0c;看到同学们玩这款游戏&#xff0c;我总是心生羡慕。为了能拥有一台自己的游戏机&#xff0c;我曾节衣缩食&#xff0c;悄悄攒钱购买。…...

    2024/5/5 18:47:21
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 第十二届蓝桥杯省赛真题(C/C++大学B组)

    目录 #A 空间 #B 卡片 #C 直线 #D 货物摆放 #E 路径 #F 时间显示 #G 砝码称重 #H 杨辉三角形 #I 双向排序 #J 括号序列 #A 空间 #include <bits/stdc.h> using namespace std;int main() {cout<<256 * 1024 * 1024 / 4<<endl;return 0; } #B 卡片…...

    2024/5/4 16:36:43
  4. react--常见hook

    useState: 用于在函数组件中添加状态。示例&#xff1a; import React, { useState } from react;function Counter() {const [count, setCount] useState(0);return (<div><p>Count: {count}</p><button onClick{() > setCount(count 1)}>Incr…...

    2024/5/5 8:27:38
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/4 23:54:56
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/4 23:54:56
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/5/4 23:54:56
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/4 23:55:17
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/5/4 23:54:56
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/5/4 23:55:05
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/5/4 23:54:56
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/5/4 23:55:16
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/5/4 23:54:56
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/4 18:20:48
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/5/4 23:54:56
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/5/4 23:55:17
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/5/4 23:55:06
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/5/4 23:54:56
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/4 23:55:06
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/5/5 8:13:33
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/5/4 23:55:16
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/5/4 23:54:58
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/4 23:55:01
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/5/4 23:54:56
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57