Android 性能优化系列:崩溃原因及捕获

这篇具有很好参考价值的文章主要介绍了Android 性能优化系列:崩溃原因及捕获。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

崩溃的基本原因

抛出异常导致崩溃分析

在日常开发中崩溃是我们遇到的很常见的情况,可能是 NullPointerException、IllegalArgumentException 等等,当应用程序抛出这些我们未捕获的异常时,紧跟着的是应用的崩溃,进程被杀死并退出。

或许你到现在都一直认为是因为抛出了异常,所以才会导致的进程被杀死并退出,认为抛出未捕获的异常就是导致进程退出的根本原因。但事实真的如此吗?

主线程也是一个线程,所以我们从 Thread 源码找找是否有什么踪迹可以解释:

Thread.java

/**
 * Dispatch an uncaught exception to the handler. This method is
 * intended to be called only by the JVM.
 */
private void dispatchUncaughtException(Throwable e) {
    getUncaughtExceptionHandler().uncaughtException(this, e);
}

public UncaughtExceptionHandler getUncaughtExceptionHandler() {
	// 如果没有设置 uncaughtExceptionHandler,由 ThreadGroup group 线程组处理
    return uncaughtExceptionHandler != null ?
        uncaughtExceptionHandler : group;
}

public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
    return defaultUncaughtExceptionHandler;
}

ThreadGroup.java

public void uncaughtException(Thread t, Throwable e) {
    if (parent != null) {
        parent.uncaughtException(t, e);
    } else {
        Thread.UncaughtExceptionHandler ueh =
            Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) {
        	// 如果有设置了 UncaughtExceptionHandler 处理异常捕获
            ueh.uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) {
            System.err.print("Exception in thread \""
                             + t.getName() + "\" ");
            e.printStackTrace(System.err);
        }
    }
}

上面的源码是 Thread 处理未捕获的异常时会执行的代码,其中 dispatchUncaughtException() 是当异常未被捕获时,它会由 JVM 发起调用

当程序代码运行错误抛出异常时,如果没有设置 Thread.UncaughtExceptionHandler,从 Thread 中并没有看到有关进程退出的代码,那为什么我们的程序抛出异常会导致进程退出呢?

在 Android 创建 app 进程是由 zygote fork 进程,而在 zygote 进程创建之前则是由 init 进程启动的,每个应用程序的入口都是 main() 函数:

RuntimeInit.java

public static void main(String[] argv) {
	...
	commonInit();
	...
}

protected static final void commonInit() {
	...
	// 在所在线程(即主线程)调用了 Thread.setDefaultUncaughtExceptionHandler
	// 提供 KillApplicationHandler 在应用抛出异常时退出 app 进程
    LoggingHandler loggingHandler = new LoggingHandler();
    Thread.setUncaughtExceptionPreHandler(loggingHandler);
    Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(loggingHandler)); 
    ...
}

private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
	@Override
	public void uncaughtException(Thread t, Throwable e) {
		try {
			...
			ActivityManager.getService().handleApplicationCrash(
					mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
		} catch (Throwable t2) {
			...
		} finally {
			// 捕获到应用程序抛出的异常,最后主动杀死退出进程
			Process.killProcess(Process.myPid());
			System.exit(10);
		}
	}
}

可以发现应用程序抛出异常进程会退出,实际上是 RuntimeInit 在主线程设置了 KillApplicationHandler,捕获到异常时主动将应用进程杀死退出

所以在这里我们可以得出结论:抛出未捕获的异常并不是导致进程杀死退出的根本原因而逝导火索,Android 默认提供了异常未捕获时主动将应用进程杀死退出的逻辑

简单总结下 java crash 未捕获异常的处理流程:

  • Thread 中遇到未捕获异常时会由 JVM 调用 dispatchUncaughtException()

  • dispatchUncaughtException() 内部是运用 Thread.UncaughtExceptionHandler 进行处理

  • Android 默认提供一个 KillApplicationHandler 继承 Thread.UncaughtExceptionHandler,它提供进程退出功能

即我们程序中的代码抛出未捕获的异常时,异常会一路往上抛直到由 JVM 处理,JVM 就会调用 Thread 的 dispatchUncaughtException(),接着就是交给了 Android 在创建应用时设置的 KillApplicationHandler 将进程退出。

AMS 如何承接应用的异常信息上报

在上面分析 RuntimeInit 设置了 KillApplicationHandler 时,在杀死进程前将应用崩溃的信息交给了 AMS:

private static class KillApplicationHandler implements Thread.UncaughtExceptionHandler {
	@Override
	public void uncaughtException(Thread t, Throwable e) {
		try {
			...
			// AMS 处理进程退出前的信息处理
			ActivityManager.getService().handleApplicationCrash(
					mApplicationObject, new ApplicationErrorReport.ParcelableCrashInfo(e));
		} catch (Throwable t2) {
			...
		} finally {
			Process.killProcess(Process.myPid());
			System.exit(10);
		}
	}
}

那 AMS 在进程退出前具体做了哪些事情,接着看源码:

ActivityManagerService.java

public void handleApplicationCrash(IBinder app,
        ApplicationErrorReport.ParcelableCrashInfo crashInfo) {
    // 拿到进程信息
    ProcessRecord r = findAppProcess(app, "Crash");
    final String processName = app == null ? "system_server"
            : (r == null ? "unknown" : r.processName);

    handleApplicationCrashInner("crash", r, processName, crashInfo);
}

// eventType 根据不同类型记录 log
void handleApplicationCrashInner(String eventType, ProcessRecord r, String processName,
        ApplicationErrorReport.CrashInfo crashInfo) {
	...
	// java crash、native crash、anr 都会走这里去记录,通过 eventType 区分记录 log
	addErrorToDropBox(eventType, r, processName, null, null, null, null, null, crashInfo);
	...
}

可以发现 AMS 是拿到了应用进程信息后,最终调用了 addErrorToDropBox() 将错误信息提供给 DropBox。

Android DropBox 是 Android 8 引入的用来持续化存储系统数据的机制。主要用于记录 Android 运行过程中,内核、系统进程、用户进程等出现严重问题时的 log,可以认为这是一个可持续存储的系统级别的 logcat存储的 log 信息存放在 /data/system/dropbox/(该目录没有 root 无法访问),crash 和 anr 的日志都会存储在这里

其中 eventType 参数表示的是记录的 log 信息类型,它有三种类型:

  • 未捕捉的 java 异常:crash

  • ANR 异常:anr

  • native 异常:native crash

对于 native crash 系统如何做处理

上面讲到了 app 是如何捕捉 java crash,那 native crash 又是如何捕捉的?

ActivityManagerService.java

public void startObservingNativeCrashes() {
	// NativeCrashListener 继承自 Thread
    final NativeCrashListener ncl = new NativeCrashListener(this);
    ncl.start();
}

而 AMS 的 startObservingNativeCrashes() 是在 SystemServer 创建启动的时候调用:

SystemServer.java

private void startOtherServices() {
	...
	mActivityManagerService.startObservingNativeCrashes();
	...
}

startOtherService() 是在一些核心服务启动后才会紧接着调用的方法用于开启其他服务,native crash 的监控就是在此时启动。

接下来看下 native crash 是怎么监听的。

NativeCrashListener.java

// 通信地址,C 层是将数据推到这里,java 层也是从这个地址获取到数据
static final String DEBUGGERD_SOCKET_PATH = "/data/system/ndebugsocket";

@Override
public void run() {
	...
	// 这里走的是信号机制通信,当前接收建立管道绑定
	FileDescriptor serverFd = Os.socket(AF_UNIX, SOCK_STREAM, 0);
	final UnixSocketAddress sockAddr = UnixSocketAddress.createFileSystem(
			DEBUGGERD_SOCKET_PATH);
	Os.bind(serverFd, sockAddr);
	Os.listen(serverFd, 1);
	Os.chmod(DEBUGGERD_SOCKET_PATH, 0777);
	
	while (true) {
		FileDescriptor peerFd = null;
		try {
			...
			// 挂载等待
			peerFd = Os.accept(serverFd, null /* peerAddress */);
			...
			if (peerFd != null) {
				// 处理 native crash 数据
				consumeNativeCrashData(peerFd);
			}
			...
		}
	}
	...
}

void consumeNativeCrashData(FileDescriptor fd) {
	// 根据 fd 拿到数据写到 ByteArrayOutputStream
	...
	final String reportString = new String(os.toByteArray(), "UTF-8");
	// 启动一个子线程将数据结果给到 AMS 调用 addErrorToDropBox()
	(new NativeCrashReport(pr, signal, reportString)).start();
}

class NativeCrashReport extends Thread {
	ProcessRecord mApp;
	int mSignal;
	String mCrashReport;

	NativeCrashReport(ProcessRecord app, int signal, String report) {
		super("NativeCrashReport");
		mApp = app;
		mSignal = signal;
		mCrashReport = report;
	}
	
	@Override
	public void run() {
		try {
			// 封装数据,将数据交给 AMS
            CrashInfo ci = new CrashInfo();
            ci.exceptionClassName = "Native crash";
            ci.exceptionMessage = Os.strsignal(mSignal);
            ci.throwFileName = "unknown";
            ci.throwClassName = "unknown";
            ci.throwMethodName = "unknown";
            ci.stackTrace = mCrashReport;

            if (DEBUG) Slog.v(TAG, "Calling handleApplicationCrash()");
            // eventType = native_crash,还是调用的 AMS.addErrorToDropBox()
            mAm.handleApplicationCrashInner("native_crash", mApp, mApp.processName, ci);
            if (DEBUG) Slog.v(TAG, "<-- handleApplicationCrash() returned");			
		} catch (Exception e) {
			Slog.e(TAG, "Unable to report native crash", e);
		}
		...
	}
}

通过源码可以知道,native crash 监听是通过信号量的方式监听一个 fd 文件描述符并挂载等待,当 fd 返回数据时说明出现 native crash,此时就可以通过 fd 拿到 native crash 的字节数据进行封装,最终还是将数据结果给到 AMS 调用 addErrorToDropBox(),只是 eventType 传的是 native_crash。

简单总结下 native crash 异常的处理流程:

  • 通信机制建立,监听一个 fd 文件描述符

  • 接收数据,挂载等待

  • fd 文件描述符有数据返回,处理数据

  • 封装数据,将结果给 AMS 调用 addErrorToDropBox() 写入存储信息

系统如何处理 ANR 异常数据

因为这里我们只说 ANR 异常最终是如何处理的,所以关于 ANR 异常发生的原因和具体流程可以查看 ANR 触发原理和分析,这里不再赘述,我们直接定位到 ANR 已经发生的位置:

AppErrors.java

final void appNotResponding(ProcessRecord app, ActivityRecord activity,
        ActivityRecord parent, boolean aboveSystem, final String annotation) {
    // ANR 数据处理封装
	...
	// 这里就是我们常用的 anr 日志存放在 /data/anr 的文件向外输出了一份
    File tracesFile = ActivityManagerService.dumpStackTraces(
            true, firstPids,
            (isSilentANR) ? null : processCpuTracker,
            (isSilentANR) ? null : lastPids,
            nativePids);
	...
	// 调用 AMS 的 addErrorToDropBox()
    mService.addErrorToDropBox("anr", app, app.processName, activity, parent, annotation,
            cpuInfo, tracesFile, null);	
	...
}

ANR 异常的信息是调用的 AppErrors 的 appNotResponding(),也是将数据处理封装后给到 AMS 调用 addErrorToDropBox(),eventType 传入的是 anr。

简单总结下 anr 异常的处理流程:

  • 记录 anr 对应数据到 SLOG(framework 日志体系中)

  • 记录 anr 数据到 LOG(主日志体系中)

  • dump 具体数据到指定文件中

  • 调用 AMS 的 addErrorToDropBox() 写入存储信息

addErrorToDropBox()

经过上面的分析我们知道,无论是 java crash、native crash 还是 anr,最终都会交由 AMS 的 addErrorToDropBox() 处理。那它又是怎么处理的呢?

ActivityManagerService.java

public void addErrorToDropBox(String eventType,
        ProcessRecord process, String processName, ActivityRecord activity,
        ActivityRecord parent, String subject,
        final String report, final File dataFile,
        final ApplicationErrorReport.CrashInfo crashInfo) {
	...
    final StringBuilder sb = new StringBuilder(1024);
    // 添加头信息
    appendDropBoxProcessHeaders(process, processName, sb);
    // 添加一些头部 log 信息
    if (process != null) {
        sb.append("Foreground: ")
                .append(process.isInterestingToUserLocked() ? "Yes" : "No")
                .append("\n");
    }
    if (activity != null) {
        sb.append("Activity: ").append(activity.shortComponentName).append("\n");
    }
    if (parent != null && parent.app != null && parent.app.pid != process.pid) {
        sb.append("Parent-Process: ").append(parent.app.processName).append("\n");
    }
    if (parent != null && parent != activity) {
        sb.append("Parent-Activity: ").append(parent.shortComponentName).append("\n");
    }
    if (subject != null) {
        sb.append("Subject: ").append(subject).append("\n");
    }
    sb.append("Build: ").append(Build.FINGERPRINT).append("\n");
    if (Debug.isDebuggerConnected()) {
        sb.append("Debugger: Connected\n");
    }
    sb.append("\n");
	...

	// 读取对应异常数据拼装成字符数据
    if (lines > 0) {
        sb.append("\n");

        // Merge several logcat streams, and take the last N lines
        InputStreamReader input = null;
        try {
            java.lang.Process logcat = new ProcessBuilder(
                    "/system/bin/timeout", "-k", "15s", "10s",
                    "/system/bin/logcat", "-v", "threadtime", "-b", "events", "-b", "system",
                    "-b", "main", "-b", "crash", "-t", String.valueOf(lines))
                            .redirectErrorStream(true).start();

            try { logcat.getOutputStream().close(); } catch (IOException e) {}
            try { logcat.getErrorStream().close(); } catch (IOException e) {}
            input = new InputStreamReader(logcat.getInputStream());

            int num;
            char[] buf = new char[8192];
            while ((num = input.read(buf)) > 0) sb.append(buf, 0, num);
        } catch (IOException e) {
            Slog.e(TAG, "Error running logcat", e);
        } finally {
            if (input != null) try { input.close(); } catch (IOException e) {}
        }
    }

	// 交给 DropBoxManager 录入
    dbox.addText(dropboxTag, sb.toString());
    ...
}

DropBoxManager.java

public void addText(String tag, String data) {
    try {
    	// mService 就是 DropBoxManagerService
        mService.add(new Entry(tag, 0, data));
    } catch (RemoteException e) {
        if (e instanceof TransactionTooLargeException
                && mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N) {
            Log.e(TAG, "App sent too much data, so it was ignored", e);
            return;
        }
        throw e.rethrowFromSystemServer();
    }
}

当 AMS 调用 addErrorToDropBox() 时,它会准备添加到 DropBoxManager 所需要的头信息、异常信息的字符数据,最终交由 DropBoxManager 录入信息。

DropBoxManager 在 Crash 方案中扮演的角色

DropBox 是 Android 在 API 8 引入的用来持续化存储系统数据的机制,主要用于记录 Android 运行过程中,内核、系统进程、用户进程等出现严重问题时的 log,可以认为 DropBox 就是一个可持续存储的系统级别的 logcat。

DropBoxManagerService.java

public DropBoxManagerService(final Context context) {
	// DropBox 信息的存储日志目录是 /data/system/dropbox
    this(context, new File("/data/system/dropbox"), FgThread.get().getLooper());
}

public DropBoxManagerService(final Context context, File path, Looper looper) {
    super(context);
    mDropBoxDir = path;
    ...
}

// 最终其实就是 IO 写入
public void add(DropBoxManager.Entry entry) {
    File temp = null;
    InputStream input = null;
    OutputStream output = null;
    final String tag = entry.getTag();
    try {
        int flags = entry.getFlags();
        if ((flags & DropBoxManager.IS_EMPTY) != 0) throw new IllegalArgumentException();

        init();
        if (!isTagEnabled(tag)) return;
        long max = trimToFit();
        long lastTrim = System.currentTimeMillis();

        byte[] buffer = new byte[mBlockSize];
        input = entry.getInputStream();

        // First, accumulate up to one block worth of data in memory before
        // deciding whether to compress the data or not.

        int read = 0;
        while (read < buffer.length) {
            int n = input.read(buffer, read, buffer.length - read);
            if (n <= 0) break;
            read += n;
        }

        // If we have at least one block, compress it -- otherwise, just write
        // the data in uncompressed form.
		
		// mDropBoxDir 就是 /data/system/dropbox 目录
        temp = new File(mDropBoxDir, "drop" + Thread.currentThread().getId() + ".tmp");
        int bufferSize = mBlockSize;
        if (bufferSize > 4096) bufferSize = 4096;
        if (bufferSize < 512) bufferSize = 512;
        FileOutputStream foutput = new FileOutputStream(temp);
        output = new BufferedOutputStream(foutput, bufferSize);
        if (read == buffer.length && ((flags & DropBoxManager.IS_GZIPPED) == 0)) {
            output = new GZIPOutputStream(output);
            flags = flags | DropBoxManager.IS_GZIPPED;
        }

        do {
            output.write(buffer, 0, read);

            long now = System.currentTimeMillis();
            if (now - lastTrim > 30 * 1000) {
                max = trimToFit();  // In case data dribbles in slowly
                lastTrim = now;
            }

            read = input.read(buffer);
            if (read <= 0) {
                FileUtils.sync(foutput);
                output.close();  // Get a final size measurement
                output = null;
            } else {
                output.flush();  // So the size measurement is pseudo-reasonable
            }

            long len = temp.length();
            if (len > max) {
                Slog.w(TAG, "Dropping: " + tag + " (" + temp.length() + " > " + max + " bytes)");
                temp.delete();
                temp = null;  // Pass temp = null to createEntry() to leave a tombstone
                break;
            }
        } while (read > 0);

        long time = createEntry(temp, tag, flags);
        temp = null;

        final Intent dropboxIntent = new Intent(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED);
        dropboxIntent.putExtra(DropBoxManager.EXTRA_TAG, tag);
        dropboxIntent.putExtra(DropBoxManager.EXTRA_TIME, time);
        if (!mBooted) {
            dropboxIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
        }
        // Call sendBroadcast after returning from this call to avoid deadlock. In particular
        // the caller may be holding the WindowManagerService lock but sendBroadcast requires a
        // lock in ActivityManagerService. ActivityManagerService has been caught holding that
        // very lock while waiting for the WindowManagerService lock.
        mHandler.sendMessage(mHandler.obtainMessage(MSG_SEND_BROADCAST, dropboxIntent));
    } catch (IOException e) {
        Slog.e(TAG, "Can't write: " + tag, e);
    } finally {
        IoUtils.closeQuietly(output);
        IoUtils.closeQuietly(input);
        entry.close();
        if (temp != null) temp.delete();
    }
}

当发生异常信息时,最终的异常信息是交给 DropBoxManager(具体说是 DropBoxManagerService)通过 IO 将信息写入到指定目录文件。

总结

上面主要分析了当发生 java crash、native crash 和 anr 时,Android 为我们做了哪些事情,在这里在简单总结一下:

  • java crash 由 JVM 触发处理,最终走到 /data/system/dropbox 目录用文件保存

  • native crash 由管道通信建立 socket 接收通知,最终走到 /data/system/dropbox 目录用文件保存

  • anr 由多种情况(事件、前后台服务)触发器处理,最终走到 /data/system/dropbox 目录用文件保存

所有的 crash 处理在 Android 系统内部都会将对应的数据收集到 /data/system/dropbox 目录下。

同时我们也梳理了 Android 的 crash 处理机制:

  • Java 没有捕获异常时会由 JVM 调用 dispatchUncaughtException 调用一个 UncaughtExceptionHandler 处理,默认 RuntimeInit 提供了一个 KillApplicationHandler 会直接退出进程。

如果我们要自己处理异常,可以自定义一个 UncaughtExceptionHandler 拦截处理文章来源地址https://www.toymoban.com/news/detail-505204.html

到了这里,关于Android 性能优化系列:崩溃原因及捕获的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • MySQL SQL性能分析,快速排查SQL执行慢的原因(SQL优化 一)

    在开发和维护数据库应用程序时,优化SQL查询的性能是至关重要的。MySQL提供了一些强大的工具和技术,帮助我们进行SQL性能分析,找出潜在的瓶颈并进行相应的优化。 查看SQL的执行频率 show [ session| global ] status 命令查看服务器状态信息,可以查看当前数据库的INSERT、UPDATE、

    2024年02月07日
    浏览(100)
  • Android 性能优化——APP启动优化

            首先在《Android系统和APP启动流程》中我们介绍了 APP 的启动流程,但都是 FW 层的流程,这里我们主要分析一下在 APP 中的启动流程。要了解 APP 层的启动流程,首先要了解 APP 启动的分类。 冷启动         应用从头开始启动,即应用的首次启动。需要做大量的工

    2024年04月12日
    浏览(44)
  • Android复杂UI的性能优化实践 - PTQBookPageView 性能优化记录

    作者:彭泰强 要做性能优化,首先得知道性能怎么度量、怎么表示。因为性能是一个很抽象的词,我们必须把它量化、可视化。那么,因为是UI组件优化,我首先选用了 GPU呈现模式分析 这一工具。 在手机上的开发者模式里可以开启 GPU呈现(渲染)模式分析 这一工具,有的

    2024年02月14日
    浏览(45)
  • Android 性能优化(六):启动优化的详细流程

    书接上文,Android 性能优化(一):闪退、卡顿、耗电、APK 从用户体验角度有四个性能优化方向: 追求稳定,防止崩溃 追求流畅,防止卡顿 追求续航,防止耗损 追求精简,防止臃肿 卡顿的场景通常与用户交互体验最直接,分别为UI、启动、跳转、响应四个方面,如下图所示

    2024年04月17日
    浏览(56)
  • Android性能优化—ViewPagers + Fragment缓存优化

    大家看标题,可能会有点儿懵,什么是ViewPagers,因为在很久之前,我们使用的都是ViewPager,但是现在更多的是在用ViewPager2,因此用ViewPagers(ViewPager、ViewPager2)来代替两者,主要介绍两者的区别。 ViewPagers嵌套Fragment架构,在我们常用的App中随处可见,抖音的首页、各大电商

    2024年02月01日
    浏览(56)
  • QT--崩溃原因分析

    本文为学习记录,若有错误,请联系作者,谦虚受教。 你从来来去自由,若你不想要了跑开便是。 发布的客户版本里分析崩溃原因,便于解决问题。 在自己QT安装的目录下,例如:D:QtQt5.12.3Toolsmingw730_32bin,找到adde2line.exe。 将add2line.exe复制到自己发布的版本中。 在代码

    2024年02月13日
    浏览(59)
  • Android中级——性能优化

    画面流畅需要帧数为60帧每秒 Android通过VSYNC信号触发对UI的绘制,其间隔时间是1000ms/60=16ms(即1000ms内显示60帧画面的单位时间) 故需在16ms之内完成绘制才可以保证画面的流畅 否则会造成丢帧,如一次绘制耗时20ms,当16ms时系统发出VSYNC信号还未绘制完,下一个帧就会被丢弃,

    2023年04月20日
    浏览(39)
  • Android系统-性能-优化概述

    目录 引言: APP优化: 网络优化: 内存优化: 卡顿优化: 先大概对Android性能优化做一个简单分类和梳理。由于性能影响因素多,比如本文分类的APP,内存,网络,卡顿都是互相影响的。卡顿应该是用户最直观可见的性能问题了。 APP优化侧重于启动,UI绘制以及资源优化这三

    2024年02月10日
    浏览(35)
  • ubuntu系统崩溃通过日志查看原因

    里面的日志包括: /var/log/syslog Syslog是一种标准的日志记录工具。它收集包括内核在内的各种程序和服务的消息,并根据设置将它们存储在通常位于 /var/log . 在某些数据中心设置中,有 数百个 设备,每个设备都有自己的日志; syslog 在这里也很方便。一个人只需设置一个专用

    2024年02月05日
    浏览(40)
  • Angular系列教程之变更检测与性能优化

    Angular 除了默认的变化检测机制,也提供了ChangeDetectionStrategy.OnPush,用 OnPush 可以跳过某个组件或者某个父组件以及它下面所有子组件的变化检测。 在本文中,我们将探讨Angular中的变更检测机制,并通过示例代码来说明其工作原理。 当我们在 model 中改变数据时,框架层需要

    2024年01月17日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包