公司法
当前位置: 首页 法律大全

boost安卓版(Android低版本上首次启动时间减少80)

时间:2023-07-30 作者: 小编 阅读量: 1 栏目名: 法律大全

并且,不同于目前业界所有优化方案,我们是从AndroidDalvik虚拟机底层机制入手,从根本上解决了安装后首次执行MultiDex耗时过长问题。然而用这个方法加载DEX文件,相比于ODEX优化后的方式,其Java代码执行性能上还是有所损失的。这样当后台做完ODEX后,APP第二次启动时,就可以直接加载之前做好的ODEX,得到较好的执行性能。于是,在RUNNING状态下,做OPT的线程就会被要求挂起。而此时由于正在执行耗时的write操作,无法响应挂起请求,便出现了如上的崩溃。

前情回顾:抖音BoostMultiDex:Android低版本上首次启动时间减少80%(一)

抖音自研的 BoostMultiDex 方案,可以大幅改善 Android 低版本(4.4 及其以下)手机更新或安装后首次冷启动时间。并且,不同于目前业界所有优化方案,我们是从 Android Dalvik 虚拟机底层机制入手,从根本上解决了安装后首次执行 MultiDex 耗时过长问题。

我们上一篇文章中已经介绍了 BoostMultiDex 的核心优化思路,即如何避免 ODEX,直接加载原始 DEX 完成启动。然而用这个方法加载 DEX 文件,相比于 ODEX 优化后的方式,其 Java 代码执行性能上还是有所损失的。我们也可以从前面方法的注释里面看出,虚拟机对于直接加载原始 DEX 的情况只是做了些基本优化:

The system will only perform "essential" optimizations on the given file.

所以,虽然第一次启动我们是加载了原始 DEX 来执行的,但从长远的角度考虑,后续的启动,还是应该尽量采用 ODEX 的方式来执行。因此,我们还需要在第一次启动完成后,在后台适当的时候做好 ODEX 优化。

一开始我们是做法也比较简单,在顺利加载 DEX 字节数组,完成启动之后,在后台开辟单独的线程执行DexFile.loadDex就可以了。这样当后台做完 ODEX 后,APP 第二次启动时,就可以直接加载之前做好的 ODEX,得到较好的执行性能。这种做法在线下测试的时候也很正常,然而在上线之后,我们遇到了这样一个问题……

SIGSTKFLT 问题

线上报上来一个 Native Crash,它的堆栈如下所示:

Signal 16(SIGSTKFLT), Code -6(SI_TKILL)#00 pc 00016db4 /system/lib/libc.so (write 12) [armeabi-v7a]#01 pc 000884a5 /system/lib/libdvm.so (sysWriteFully(int, void const*, unsigned int, char const*) 28) [armeabi-v7a]#02 pc 00088587 /system/lib/libdvm.so (sysCopyFileToFile(int, int, unsigned int) 114) [armeabi-v7a]#03 pc 00050d41 /system/lib/libdvm.so (dvmRawDexFileOpen(char const*, char const*, RawDexFile**, bool) 392) [armeabi-v7a]#04 pc 00064a41 /system/lib/libdvm.so [armeabi-v7a]#05 pc 000276e0 /system/lib/libdvm.so [armeabi-v7a]#06 pc 0002b5c4 /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*) 184) [armeabi-v7a]#07 pc 0005fc79 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list) 272) [armeabi-v7a]#08 pc 0005fca3 /system/lib/libdvm.so (dvmCallMethod(Thread*, Method const*, Object*, JValue*, ...) 20) [armeabi-v7a]#09 pc 0005481f /system/lib/libdvm.so [armeabi-v7a]#10 pc 0000e3e8 /system/lib/libc.so (__thread_entry 72) [armeabi-v7a]#11 pc 0000dad4 /system/lib/libc.so (pthread_create 160) [armeabi-v7a]

APP 收到 SIGSTKFLT 信号崩溃了,同时还输出了这样的日志:

06-25 15:10:53.821 7449 7450 E dalvikvm: threadid=2: stuck on threadid=135, giving up06-25 15:10:53.821 7449 7450 D dalvikvm: threadid=2: sending two SIGSTKFLTs to threadid=135 (tid=8021) to cause debuggerd dump

SIGSTKFLT 是 Dalvik 虚拟机特有的一个信号。当虚拟机发生了 ANR 或者需要做 GC 的时候,就需要挂起所有 RUNNING 状态的线程,如果此时 Dalvik 虚拟机等待了足够长时间,线程仍旧无法被挂起,就会调用dvmNukeThread函数发送 SIGSTKFLT 信号给相应线程,从而杀死 APP。

具体代码如下:

static void waitForThreadSuspend(Thread* self, Thread* thread){const int kMaxRetries = 10;... ...while (thread->status == THREAD_RUNNING) {... ...if (retryCount== kMaxRetries) {ALOGE("Fatal spin-on-suspend, dumping threads");dvmDumpAllThreads(false);/* log this after -- long traces will scroll off log */=>ALOGE("threadid=%d: stuck on threadid=%d, giving up",self->threadId, thread->threadId);/* try to get a debuggerd dump from the spinning thread */=>dvmNukeThread(thread);/* abort the VM */dvmAbort();... ...}

而从堆栈我们看出,杀死进程的时候,我们正调用DexFile.loadDex,这个方法最后会调用到dvmRawDexFileOpen里面,执行 write 操作。而这个 write 涉及 I/O 操作,是比较耗时的。所以,当线程在做 dexopt,长时间无法响应虚拟机的挂起请求时,就会触发这个问题。

一般来说,虚拟机在执行 Java 代码的时候,都会是 RUNNING 状态。而只要调用了 JNI 方法,在执行到 C/C代码的时候,就会切换为 NATIVE 状态。而虚拟机只会在 RUNNING 状态下会挂起线程,如果是在 NATIVE 状态下,虚拟机是不会要求线程必须挂起的。

不过,这里有一个特殊之处。虽然DexFile.loadDex方法最终也走到了 JNI 里面调用dvmRawDexFileOpen函数,但由于DexFile类是虚拟机的内部类,Dalvik 虚拟机不会在内部类执行 JNI 方法的时候将线程切换为 NATIVE 状态,仍然会保持原来的 RUNNING 状态。于是,在 RUNNING 状态下,做 OPT 的线程就会被要求挂起。而此时由于正在执行耗时的 write 操作,无法响应挂起请求,便出现了如上的崩溃。

当然,可能有人会想到在 Native 代码中,用CallStaticObjectMethod来触发DexFile.loadDex,不过这种方式是不可行的。因为CallStaticObjectMethod调用 Java 方法DexFile.loadDex时,会使得状态再次切换为 RUNNING。

具体来看下 CallStatciXXXMethod 方法的定义处:

static _ctype CallStatic##_jname##Method(JNIEnv* env, jclass jclazz,\jmethodID methodID, ...)\{ \UNUSED_PARAMETER(jclazz);\ScopedJniThreadState ts(env);\JValue result;\va_list args;\va_start(args, methodID);\dvmCallMethodV(ts.self(), (Method*)methodID, NULL, true, &result, args);\va_end(args);\if (_isref && !dvmCheckException(ts.self()))\result.l = (Object*)addLocalReference(ts.self(), result.l); \return _retok;\}

关键在于 ScopedJniThreadState:

explicit ScopedJniThreadState(JNIEnv* env) {mSelf = ((JNIEnvExt*) env)->self;... ...CHECK_STACK_SUM(mSelf);dvmChangeStatus(mSelf, THREAD_RUNNING);}~ScopedJniThreadState() {dvmChangeStatus(mSelf, THREAD_NATIVE);COMPUTE_STACK_SUM(mSelf);}

在使用dvmCallMethodV调用 Java 方法前,会先切换状态为THREAD_RUNNING,执行完毕后,ScopedJniThreadState析构,再切换回THREAD_NATIVE。这样,JNI 执行DexFile.loadDex就和直接执行 Java 代码一样,状态会有问题。不只是CallStaticXXXMethod,所有使用CallXXXMethod函数在 Native 下调用 Java 方法的情况都是如此。

好在,我们想到了另一个办法:既然 Dalvik 不会对内部类的 JNI 调用做切换,我们就自己写一个 JNI 调用,使其走到 Native 代码中,这样线程就会变为 Native 状态,然后直接调用虚拟机内部函数做 dexopt 即可。这样在做 dexopt 的时候,始终会处于 NATIVE 的状态,不会切为 RUNNING,也不会被要求挂起,也就能避免这个问题。

这个虚拟机内部函数就是dvmRawDexFileOpen,我们先来看下它的代码说明:

/* * Open a raw ".dex" file, optimize it, and load it. * * On success, returns 0 and sets "*ppDexFile" to a newly-allocated DexFile. * On failure, returns a meaningful error code [currently just -1]. */int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName,RawDexFile** ppDexFile, bool isBootstrap);

这个函数可以用来打开原始 DEX 文件,并且对它做优化和加载。对应到 libdvm.so 中的符号是_Z17dvmRawDexFileOpenPKcS0_PP10RawDexFileb,我们只需要用 dlsym 在 libdvm.so 里面找到它,就可以直接调用了,完整代码如下:

using func = int (*)(const char* fileName, const char* odexOutputName, void* ppRawDexFile, bool isBootstrap);void* handler = dlopen("libdvm.so", RTLD_NOW);dvmRawDexFileOpen = (func) dlsym(handler, "_Z17dvmRawDexFileOpenPKcS0_PP10RawDexFileb");dvmRawDexFileOpen(file_path, opt_file_path, &arg, false);

这样,我们自己写一个 JNI 调用,在 Native 状态下执行上述代码,就能达到完成 ODEX 的目的,从而根本上杜绝这个异常了。

另外,我们把 dexopt 操作放到了单独进程执行,由此可以避免 ODEX 操作对主进程造成其他性能影响。此外,由于设备情况多种多样,运行环境十分复杂,还可能会有一些厂商魔改,导致的 dlsym 找不到_Z17dvmRawDexFileOpenPKcS0_PP10RawDexFileb符号,虽然这种情况极为罕见,但理论上仍有可能发生。单独进程里面由于环境比较纯粹,基本很少发生 ANR 和 GC 事件,挂起的情况就很少,也能最大程度规避这个问题。

多级加载

我们发现,相比于官方 MultiDex 加载 ZIP 形态的 DEX 文件,非 ZIP 方式的 DEX(也就是直接对 DEX 文件做 ODEX,而不用先把 DEX 压缩进 ZIP 里面)对于整体时间也有一定程度的优化,因为这种非 ZIP 方式避免了原先的两个耗时:

  1. 把原始 DEX 压缩为 ZIP 格式的时间;
  2. ODEX 优化的时候从 ZIP 中解压出原始 DEX 的时间。

非 ZIP 的方式相比于 ZIP 方式,整体耗时会减少 40%左右,但是 DEX 文件磁盘占用空间比原先 ZIP 文件的方式增加一倍多。因此我们可以只在磁盘空间充裕的时候,优先使用非 ZIP 方式加载。

而我们openDexFile_bytearray加载 DEX 的方式,需要的只是原始 DEX 文件的字节数组(byte[])。这个字节数组我们在首次冷启动的时候是直接从 APK 里面解压提取得到的。我们可以在这次启动提取完成后,先把这些字节数组落地为 DEX 文件。这样如果再次启动 APP 的时候,ODEX 没做完,就可以直接使用前面保存的 DEX 文件来得到字节数组了,从而避免了从 APK 解压的时间。

总体来看,我们整套方案中一共存在四种形态的 DEX:

  1. 从 APK 文件里面解压得到的 DEX 字节数组;
  2. 从落地的 DEX 文件里面得到的 DEX 字节数组;
  3. 从 DEX 文件优化得到的 ODEX 文件;
  4. 从 ZIP 文件优化得到的 ODEX 文件。

生成各个产物的时序图如下所示:

我们依次说明每一步:

正常情况下,我们会依次按 A -> B -> C 的时序依次产生各个文件,如果中间有中断的情况,我们下次启动后会继续按照当前已有产物做对应操作。我们仅在磁盘空间不够,且所在系统不支持直接加载字节数组的情况下才会走 ZIP&ODEX 方式的 D 路径。这里不支持的情况主要是一些特殊机型,比如 4.4 却采用了 ART 虚拟机的机型、阿里 Yun OS 机型等。

接下来我们继续看下加载流程图:

这么一来,APP 就可以根据当前情况,选择最合适的方式执行加载 DEX 了。从而保证了任意时刻的最优性能。

进程锁优化

前面提到,OPT 优化是在单独的进程里面执行的。单独进程除了可以减少前面的 SIGSTKFLT 问题,还能在做完 OPT 后及时终止后台进程,避免过多的资源占用。

然而,在单独进程处理 OPT 和其他进程执行 install 的时候,都涉及到 DEX 和 ODEX 文件的访问和生成,因此在这些进程之间涉及到文件访问和 OPT 时,都是加文件锁互斥执行的。这样可以避免加载的同时,另一个进程在操作 DEX 和 ODEX 文件导致的文件损坏。在官方的 MultiDex 中也是采用这种文件锁的方式来进行互斥访问的。

但这带来了另一个问题,如果 OPT 进程在长时间做 dexopt,而此时主进程(或者其他后台进程)需要再次启动,便会因为 OPT 进程持有互斥文件锁,而导致这些进程被阻塞住无法继续启动。可以看流程图来理解这一过程:

正如图中描绘的场景,用户第一次打开了 APP,然后运行一会之后因为一些情况杀死了 APP,这时,后台进程已经启动并正在做 OPT。如果此时用户想要再次打开,就会由于 OPT 进程互斥锁导致阻塞而黑屏。这显然是不可接受的。

因此,我们就需要采取更好的策略,使得在主进程能够正常地继续往下执行,而不至于被阻塞住。

这个问题的关键在于,主进程需要依赖 OPT 进程的产物,才能继续往下执行,而 OPT 进程此时正在操作 DEX 文件,这个过程中的产物必定无法被主进程直接使用。

所以,如果想要主进程不再因 OPT 操作阻塞,我们很容易想到可以无视 OPT 进程,不使用 DEX 文件,只从 APK 里面获取内存形式的 DEX 字节码就可以了。不过这种方式的主要问题在于,如果 OPT 时间非常长,在这段时间内就不得不一直使用内存方式的 DEX 启动 APP,这样性能就会处于比较差的水平。

因此我们采用的是另一种方案。在主进程退出而再次启动的时候,先中止 OPT 进程,直接取得现有 DEX 产物进行加载,然后再唤起 OPT 进程。

如下图所示:

这里关键点在于如何中止进程。当然,我们可以直接在主进程发信号杀死 OPT 进程,不过这种方式过于粗暴,很可能导致 DEX 文件损坏。而且 kill 信号的方式没有回调,我们无法得知是否进程确实地退出了。

因此,我们采取的方式是用两个文件锁来做同步,保证进程启动和退出的信息可以在多个进程之间传达。

第一个文件锁就是单纯用来作为互斥锁,保证处理 DEX 和加载 DEX 的过程是互斥发生的。第二个文件锁用来表示进程即将获取互斥锁,我们称之为准备锁,它可以用来通知 OPT 进程:此时有其他进程正需要加载 DEX 产物。

对于 OPT 进程而言,获取文件锁的步骤如下:

  1. 获取互斥锁;
  2. 执行 OPT;
  3. 非阻塞地尝试获取准备锁;
  4. 如果没有获取到准备锁,表示此时有其他进程已经持有准备锁,则释放互斥锁,并退出 OPT 进程;
  5. 如果获取到了准备锁,表示此时没有其他进程正常持有准备锁,则再次执行第 2 步,做下个文件的 OPT;
  6. 完成所有 DEX 文件的 OPT 操作,释放互斥锁,退出。

对于主进程(或其他非 OPT 进程)而言,获取文件锁的步骤如下:

  1. 阻塞等待获取准备锁;
  2. 阻塞等待获取互斥锁;
  3. 释放准备锁;
  4. 完成 DEX 加载;
  5. 释放互斥锁;
  6. 继续往下执行业务代码。

具体情形见下图:

首先,OPT 进程开始执行,会获取到互斥锁,然后做 DEX 处理。OPT 进程在处理完第一个 DEX 文件后,由于没有其他进程持有准备锁,因此 OPT 进程获取准备锁成功,然后释放准备锁,继续做下一个 DEX 优化。

这时候,主进程(或其他非 OPT 进程)启动,先成功地获取准备锁。然后继续阻塞地获取互斥锁,此时由于 OPT 进程已经在前一步获取到了互斥锁,因此只能等待其释放。

OPT 进程在处理完第二个 DEX 后,检测到准备锁已经被其他进程持有了,因此获取失败,从而停止继续做 OPT,释放互斥锁并退出。

此时主进程就可以成功地获取到互斥锁,并且立即释放准备锁,以便其他进程可以获取。接着,在完成 DEX 加载后,释放互斥锁,继续执行后续业务流程。最后再唤起 OPT 进程接着做完原先的 DEX 处理。

总体看来,在这种模式下,OPT 进程可以主动发现有其他进程需要加载 DEX,从而中断 DEX 处理,并释放互斥锁。主进程便不需要等待整个 DEX 处理完成,只需要等 OPT 进程完成最近一个 DEX 文件的处理就可以继续执行了。

实测数据

我们本地选取了几台 4.4 及以下的设备,对它们首次启动的 DEX 加载时间进行了对比:

Android版本厂商机型原始MultiDex耗时(s)BoostMultiDex耗时(s)4.4.2LGLGMS32333.5455.0144.4.4MOTOG45.6916.7194.3SamsungGT-N710024.1863.6604.3.0SamsungSGH-T99930.3313.7914.2.2HUAWEIHol-T00崩溃3.7244.2.1HUAWEIG610-U0036.4654.9814.1.2SamsungI910030.9625.345

以上是在抖音上测得的实际数据,APK 中共有 6 个 Secondary DEX,显而易见,BoostMultiDex 方案相比官方 MultiDex 方案,其耗时有着本质上的优化,基本都只到原先的 11%~17%之间。也就是说 BoostMultiDex 减少了原先过程 80%以上的耗时。 另外我们看到,其中有一个机型,在官方 MultiDex 下是直接崩溃,无法启动的。使用 BoostMultiDex 也将使得这些机型可以焕发新生。 另外,我们在线上采取了对半分的方式,也就是 BoostMultiDex 和原始 MultiDex 随机各自选取一半线上设备,对比二者的耗时。

我们先以设备维度来看,这里随机选取了 15 分钟的线上数据,图中横轴为每个 Android 版本 4.4 及以下的设备,纵轴为首次启动加载 DEX 的耗时,按耗时升序排列,单位为纳秒。

BoostMultiDex 下的设备耗时:

MultiDex 下的设备耗时:

两张图最大的区别在于纵轴的时间刻度。可以看到,绝大多数设备的 BoostMultiDex 耗时在 5s 左右,最多耗时也不会超过 35s。而反观 MultiDex,大多数都需要耗时 30 多 s,最长的耗时甚至达到了将近 200s。

上面的图可能差别不够明显,我们选取一段时间,每半小时取所有设备耗时的中位数,可以得到下面的对比曲线:

其中,下方橙色线为 BoostMultiDex,上方蓝色线为原始 MultiDex,可以明显看出,耗时下降的幅度非常巨大。

耗时的大幅减少会带来怎样的效果呢?我们统计了 4.4 及以下机型中,两者进入到抖音播放页的设备数占比,时间范围为一周,其中右边橙色为 BoostMultiDex,左边蓝色为原始 MultiDex。

由于我们所有设备对于两种方案的选取是对半开的,所以理论上二者的设备数应该接近于 1 比 1,不过从图中我们可以看到,BoostMultiDex 的设备数已经大幅超过 MultiDex 的设备数,两者比例接近于 2 比 1。

从中可以看出,MultiDex 耗时的减少对于设备活跃数的提升,效果十分显著!

总结

最后,我们再梳理一下整个方案的实现要点:

  1. 采用openDexFile_bytearray函数,可以直接加载原始 DEX 字节码;
  2. 提前注入dex_object对象,以解决 4.4 机型上加载原始 DEX 字节码时,getDex的崩溃问题;
  3. 采用dvmRawDexFileOpen函数做 ODEX,以解决 SIGSTKFLT 问题;
  4. 多级加载,在 DEX 字节码、DEX 文件、ODEX 文件中选取最合适的产物启动 APP;
  5. 单独进程做 OPT,并实现合理的中断及恢复机制。

对于国内偏远地区,尤其对于海外许多发展中国家,Android 低版本机型仍然占比较高。目前 BoostMultiDex 方案在抖音和 TikTok 已经全量上线,这会使得这部分低版本 Android 用户直接受益,极大优化升级和安装启动体验。

我们后续将开源 BoostMultiDex 方案,以协助其他 APP 在低版本 Android 手机上改进性能体验。

今后,各家对下沉市场有需要的 APP,都能直接使用 BoostMultiDex 方案,立即获得飞一般的升级安装体验!这也是我们为改善 Android 生态贡献的一小份力,后续很快就会发布开源地址,敬请期待!

最后的最后,仍然再提一句,抖音/TikTok Android 基础技术团队正在北上深杭四地寻求优秀 Android 开发人才,目前疫情期间我们也支持完全远程无接触面试。只要你的技术功力深厚或者潜力巨大,都可以通过 字节跳动招聘官网查询抖音 Android 相关职位「链接」 或者联系 xiaolin.gan@bytedance.com 来投递简历,我们十分期待你的加入!

欢迎关注字节跳动技术团队

,
    推荐阅读
  • 合肥徽宴楼罍街店怎么样(合肥狮城徽宴楼)

    据此,合肥高新区法院一审判决,合肥佳源徽宴楼餐饮有限公司立即停止侵害安徽狮城徽宴楼饮食管理有限公司相关商标专用权的行为,并赔偿狮城徽宴楼经济损失3万元。佳源徽宴楼不服一审判决,提起上诉。近日,合肥中院二审驳回上诉,维持原判。

  • 打着火怠速下多少转最好(打着火怠速下100转最好)

    我们一起去了解并探讨一下这个问题吧!打着火怠速下多少转最好1000转最好。车辆在冷车时启动。发动机为了尽快进入状态,会在高转速下运行,俗称热车~如果说你车一启动就在1000转以下,这个你要等待下。转速还再下降,说明你车辆的怠速过低,需要提高一点~如果始终保持不变的转速,说明你车在启动之前,状态很好,不需要热车。

  • 植物大战僵尸搞笑动画植物是反派(青春守沪)

    青春守沪青年报·青春上海记者范彦萍“有一天,小草正在散步,突然病毒老大冲了过来小草很害怕,撒腿就跑,眼看病毒老大就要追上来了,忽然,西瓜和辣椒跑了过来,与病毒老大发起了战斗……”最近,上海理工大学附属小学教育。

  • 波比认猪猪侠老大(超人特工队强势回归)

    《超人特工队2》的焦点在妈妈海伦身上,她骑着重型机车阻止各种悲剧,屡创功劳。续集《超人特工队2》时隔14年强势回归。《超2》延续首集的内容,由于超人身份引起疑虑,有心人士希望让超人重新在社会获得重视,但事情并非想象的简单,正义感与超能力毫无用武之地,可以想象巴鲍勃的无力感。除了妈妈与小儿子,片中的酷冰侠、小飞、小倩、巴鲍勃,以及其他英雄例如“砖头”与“空间女侠”等,都各有特色,贡献了很多笑点。

  • 猜成语一个太阳和一个星星(众多星星围绕月亮)

    大家好,看图猜成语,你最棒!昨天答案分别是:背道而驰,一鸣惊人,空中楼阁,如虎添翼,拔刀相助成语释义:背道而驰:朝着相反的方向跑。比喻行动方向和所要达到的目标完全相反。比喻增添力量,使强大的更加强大,或使凶恶的更加凶恶。每天进步一点点,离成功更进一步,加油!

  • 湖南端午节有哪些风俗拨龙舟(端午佳节看龙舟飞渡)

    沅江和溆水泛舟是江口的魅力所在。溆浦的传统龙舟一般头高尾翘,成梭子形,全长大约在26至28米之间,尾部翘起呈燕尾状,尾高达2.8米,可载70余人。两艘龙舟共108人,意取梁山泊108将。凡成年男子不管身处何地均必须赶回参加,或组织或自发,供四面八方慕名而来的客人亲友观赏。铜湾镇今年将有30多艘龙舟下水,场面非常热闹。全长约28米,28节,可坐人24节,计48人。黄溪口龙舟比赛没有号令,没有起跑线,观众是裁判。

  • 红米note5a手机一直重复重启该怎么办(这5种办法都可供选择)

    红米note5a手机一直重复重启该怎么办?红米note5a手机一直重复重启该怎么办首先遇到这个情况,先查看一下手机的是否还有电量,如果没有足够的电量的话,红米Note就会出现这个问题,解决这个问题的方法很简单,那就是为手机充电。另外,红米Note无限自动重启的也有可能是因为安装了root之后,把系统的自带的软件卸载掉,从而导致红米Note无限重启。

  • 饭后多久运动(饭后多久运动合适及原因)

    吃完饭多长时间可以运动,主要看采取运动强度是怎么样的如果是轻度运动,比如太极拳或者散步,如果不是太饱的情况之下,饭后10分钟左右就可以进行但是如果是一些高强度的体育锻炼,需要2个小时后甚至更长时间,比如长跑、打篮球、拳击、健身等建议饭后2小时以上,甚至更久再活动,我来为大家科普一下关于饭后多久运动?如果是轻度运动,比如太极拳或者散步,如果不是太饱的情况之下,饭后10分钟左右就可以进行。

  • 鳜鱼养殖周期,育苗培育、鱼种培育及成鱼养殖3个阶段

    鳜鱼养殖周期主要分为鱼苗培育阶段、鱼种培育阶段及成鱼养殖阶段。在放进鳜鱼苗之前,鱼池都要进行消毒,然后还可以放一些培育水质用来培育饵料鱼。