<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/rss.xsl" type="text/xsl"?>
<rss version="2.0">
  <channel>
    <title>IT瘾android推荐</title>
    <link>https://itindex.net/categories/android</link>
    <description>IT社区推荐资讯 - ITIndex.net</description>
    <language>zh</language>
    <copyright>https://itindex.net/</copyright>
    <generator>https://itindex.net/</generator>
    <docs>http://backend.userland.com/rss</docs>
    <image>
      <url>https://itindex.net/images/logo.gif</url>
      <title>IT社区推荐资讯 - ITIndex.net</title>
      <link>https://itindex.net/categories/android</link>
    </image>
    <item>
      <title>将 Android 手机变成监听工具</title>
      <link>https://itindex.net/detail/62947-android-%E6%89%8B%E6%9C%BA-%E7%9B%91%E5%90%AC</link>
      <description>之前的实验表明，智能手机中的陀螺仪和加速计等惯性测量单元（IMU），可以通过检测声波振动监听对话。这意味着，即使是一个没有开启麦克风权限的应用程序也可以通过 IMU 获得对话内容。为了不让攻击者获得准确信息，Google 将 Android 应用从 IMU 采样数据的频率限制在每秒 200 次，使攻击者无法准确获得对话内容。根据发表在预印本平台 arXiv 上的预印本，研究人员发现了一个漏洞——通过欺骗陀螺仪和运动传感器在时间上稍微偏移地进行测量，将应用实际采样率从每秒 200 次提高到 400 次，可以突破上述保护措施。利用这种方法，攻击者能修复获得的音频量大大提升。与每秒仅采集 200 个样本相比，他们的方法在 AI 转录时单词错误率降低了 83%。这表明，目前的安全保护措施“不足以防止复杂的窃听攻击发生”，应该对其重新评估。
 &lt;p&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62947-android-%E6%89%8B%E6%9C%BA-%E7%9B%91%E5%90%AC</guid>
      <pubDate>Tue, 15 Oct 2024 23:48:56 CST</pubDate>
    </item>
    <item>
      <title>Android Crash 前的最后抢救</title>
      <link>https://itindex.net/detail/62735-android-crash</link>
      <description>&lt;p&gt;众所周知，当 Andoird 程序发生未捕获的异常的时候，程序会直接 Crash 退出。而所谓安全气囊，是指在 Crash 发生时捕获异常，然后触发兜底逻辑，在程序退出前做最后的抢救。&lt;/p&gt; &lt;h1&gt;一，Java  &lt;strong&gt;捕获异常&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;在实现安全气囊之前，我们先思考一个问题，像 bugly、sentry 这种监控框架是如何捕获异常并上传堆栈的呢？要了解这个问题，我们首先要了解一下当异常发生时是怎么传播的。&lt;/p&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://segmentfault.com/img/remote/1460000043664470" title="image.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;可以看到，异常到奔溃的流程很简单，主要分为以下几步：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;当抛出异常时，通过Thread.dispatchUncaughtException进行分发。&lt;/li&gt;  &lt;li&gt;依次由Thread，ThreadGroup，Thread.getDefaultUncaughtExceptionHandler处理。&lt;/li&gt;  &lt;li&gt;在默认情况下，KillApplicationHandler会被设置defaultUncaughtExceptionHandler。&lt;/li&gt;  &lt;li&gt;然后KillApplicationHandler中会调用Process.killProcess退出应用。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;可以看出，如果我们通过Thread.setDefaultUncaughtExceptionHandler设置自定义处理器，就可以捕获异常做一些兜底操作了，其实 bugly 这些库也是这么做的。&lt;/p&gt; &lt;h1&gt;二、  &lt;strong&gt;自定义异常处理器&lt;/strong&gt;&lt;/h1&gt; &lt;p&gt;那么如果我们设置了自定义处理器，在里面只做一些打印日志的操作，而不是退出应用，是不是就可以让 app 永不崩溃了呢？答案当然是否定的，主要有以下两个问题：&lt;/p&gt; &lt;h2&gt;2.1 Looper 循环问题&lt;/h2&gt; &lt;p&gt;我们知道，App 的运行在很大程序上依赖于 Handler 消息机制，Handler 不断的往 MessageQueue 中发送 Message，而Looper则死循环的不断从MessageQueue中取出Message并消费，整个 app 才能运行起来。而当异常发生时，Looper.loop 循环被退出了，事件也就不会被消费了，因此虽然 app 不会直接退出，但也会因为无响应发生 ANR。因此，当崩溃发生在主线程时，我们需要恢复一下Looper.loop。&lt;/p&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://segmentfault.com/img/remote/1460000043664471" title="image.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h2&gt;2.2 主流程抛出异常问题&lt;/h2&gt; &lt;p&gt;当我们在主淤积抛出异常时，比如在onCreate方法中，虽然我们捕获住了异常，但程序的执行也被中断了，界面的绘制可能无法完成，点击事件的设置也没有生效。这就导致了 app 虽然没有退出，但用户却无法操作的问题，这种情况似乎还不如直接 Crash 了呢。&lt;/p&gt; &lt;p&gt;因此我们的安全气囊应该支持配置，只处理那些非主流程的操作，比如点击按钮触发的崩溃，或者一些打点等对用户无感知操作造成的崩溃。  &lt;br /&gt; &lt;/p&gt; &lt;h1&gt;三、方案设计&lt;/h1&gt; &lt;p&gt;为了解决上面提到的两个问题，我们提出了如下的方案：&lt;/p&gt; &lt;p&gt;  &lt;img alt="image.png" src="https://segmentfault.com/img/remote/1460000043664472" title="image.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;思路如下：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;注册自定义DefaultUncaughtExceptionHandler。&lt;/li&gt;  &lt;li&gt;当异常发生时捕获异常。&lt;/li&gt;  &lt;li&gt;匹配异常堆栈是否符合配置，如果符合则捕获，否则交给默认处理器处理。&lt;/li&gt;  &lt;li&gt;判断异常发生时是否是主线程，如果是则重启Looper。   &lt;br /&gt; &lt;/li&gt;&lt;/ol&gt; &lt;p&gt;下面是实现代码：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;fun setUpJavaAirBag(configList: List&amp;lt;JavaAirBagConfig&amp;gt;) {
    val preDefaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
    // 设置自定义处理器
    Thread.setDefaultUncaughtExceptionHandler { thread, exception -&amp;gt;
        handleException(preDefaultExceptionHandler, configList, thread, exception)
        if (thread == Looper.getMainLooper().thread) {
            // 重启 Looper
            while (true) {
                try {
                    Looper.loop()
                } catch (e: Throwable) {
                    handleException(
                        preDefaultExceptionHandler, configList, Thread.currentThread(), e
                    )
                }
            }
        }
    }
}
private fun handleException(
    preDefaultExceptionHandler: Thread.UncaughtExceptionHandler,
    configList: List&amp;lt;JavaAirBagConfig&amp;gt;,
    thread: Thread,
    exception: Throwable
) {
    // 匹配配置
    if (configList.any { isStackTraceMatching(exception, it) }) {
        Log.w(&amp;quot;StabilityOptimize&amp;quot;, &amp;quot;Java Crash 已捕获&amp;quot;)
    } else {
        Log.w(&amp;quot;StabilityOptimize&amp;quot;, &amp;quot;Java Crash 未捕获，交给原有 ExceptionHandler 处理&amp;quot;)
        preDefaultExceptionHandler.uncaughtException(thread, exception)
    }
}
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;通过上面的步骤，我们实现了一个 Java 层安全气囊，但是如果发生 Native 层崩溃时，程序还是会崩溃。那么我们能不能按照 Java 层安全气囊的思路，实现一个 Native 层的安全气囊。&lt;/p&gt; &lt;p&gt; &lt;/p&gt; &lt;h1&gt;四、Native 层安全气囊&lt;/h1&gt; &lt;p&gt;我们知道，Android Native 层异常是通过信号机制实现的。  &lt;br /&gt;  &lt;img alt="image.png" src="https://segmentfault.com/img/remote/1460000043664473" title="image.png"&gt;&lt;/img&gt;&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;当crash产生后，会在用户态阶段调用中断进入内核态。&lt;/li&gt;  &lt;li&gt;在处理完内核操作，返回用户态时，会检查信号队列上是否有信号需要处理。&lt;/li&gt;  &lt;li&gt;如果有信号需要处理，则会调用sigaction函数进行相应处理。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;此时，我们通过注册信号处理函数sigaction设置自定义信号处理器，即可实现Native的安全气囊。&lt;/p&gt; &lt;p&gt;需要注意的是，我们可以通过sigaction设置自定义信号处理器，但是SIGKILL与SIGSTOP信号我们是无法更改其默认行为的，如果我们设置了自定义信号处理器，没有退出 app，但错误实际还是产生了，当错误实在不可控时，系统还是会发送SIGKILL/SIGSTOP信号，这个时候还会导致我们 crash 时无法获取真正的堆栈，因此我们在自定义信号处理器时需要慎重。可以看出，要了解 Native 异常捕获，需要对 Linux 信号机制有一定了解。&lt;/p&gt; &lt;h1&gt;五、Native层实现&lt;/h1&gt; &lt;p&gt;在了解了 Native 层异常处理的原理之后，我们通过自定义信号处理器来实现一个 Native 层的安全气囊，主要分为以下几步：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;注册自定义信号处理器。&lt;/li&gt;  &lt;li&gt;获取 Native 堆栈并与配置堆栈进行比较。&lt;/li&gt;  &lt;li&gt;如果匹配上了则忽略相关崩溃，如果未匹配上则交给原信号处理器处理。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;下面是Native层的代码实现：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;extern &amp;quot;C&amp;quot; JNIEXPORT void JNICALL
Java_com_zj_android_stability_optimize_StabilityNativeLib_openNativeAirBag(
        JNIEnv *env,
        jobject /* this */,
        jint signal,
        jstring soName,
        jstring backtrace) {
    do {
        //...
        struct sigaction sigc;
        // 自定义处理器
        sigc.sa_sigaction = sig_handler;
        sigemptyset(&amp;amp;sigc.sa_mask);
        sigc.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART;
        // 注册信号
        int flag = sigaction(signal, &amp;amp;sigc, &amp;amp;old);
    } while (false);
}
static void sig_handler(int sig, struct siginfo *info, void *ptr) {
    // 获取堆栈
    auto stackTrace = getStackTraceWhenCrash();
    // 与配置的堆栈进行匹配
    if (sig == airBagConfig.signal &amp;amp;&amp;amp;
        stackTrace.find(airBagConfig.soName) != std::string::npos &amp;amp;&amp;amp;
        stackTrace.find(airBagConfig.backtrace) != std::string::npos) {
        LOG(&amp;quot;异常信号已捕获&amp;quot;);
    } else {
        // 没匹配上的交给原有处理器处理
        LOG(&amp;quot;异常信号交给原有信号处理器处理&amp;quot;);
        sigaction(sig, &amp;amp;old, nullptr);
        raise(sig);
    }
}
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt; &lt;/p&gt; &lt;p&gt;通过上面的步骤，其实 Native 层的安全气囊已经实现了，在 demo 中触发 Native Crash 可以被捕获到。&lt;/p&gt; &lt;p&gt;但是信号处理函数必须是async-signal-safe和可重入的，理论上不应该在信号处理函数中做太多工作，比如malloc等函数都不是可重入的。而我们在信号处理函数中获取了堆栈，打印了日志，很可能会造成一些意料之外的问题。&lt;/p&gt; &lt;p&gt;理论上我们可以在子线程获取堆栈，在信号处理函数中只需要发出信号就可以了，但我尝试在子线程中使用 unwind 获取堆栈，发现获取不到真正的堆栈，因此还存在一定的问题。&lt;/p&gt; &lt;p&gt;Native 层安全气囊的方案也可以看看@Pika 写的  &lt;a href="https://github.com/TestPlanB/mooner" rel="nofollow noreferrer"&gt;https://github.com/TestPlanB/mooner&lt;/a&gt;，支持捕获 Android 基于“pthread_create” 产生的子线程中异常业务逻辑产生信号，导致的native crash。&lt;/p&gt; &lt;p&gt;参考代码：  &lt;a href="https://github.com/RicardoJiang/android-performance" rel="nofollow noreferrer"&gt;https://github.com/RicardoJiang/android-performance&lt;/a&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>android</category>
      <guid isPermaLink="true">https://itindex.net/detail/62735-android-crash</guid>
      <pubDate>Fri, 14 Apr 2023 11:33:49 CST</pubDate>
    </item>
    <item>
      <title>Windows Subsystem for Android v1.0 发布</title>
      <link>https://itindex.net/detail/62464-windows-subsystem-for</link>
      <description>微软 Windows Subsystem for Android（WSA）项目开发经理 Cory Hendrixson  &lt;a href="https://www.theregister.com/2022/10/21/windows_subsystem_for_android_released/"&gt;宣布&lt;/a&gt;发布 v1.0 版本，即 WSA 现在可供所有人使用。WSA 是微软在 2021 年宣布的运行 Android 应用的子系统，类似  Windows Subsystem for Linux (WSL)，在 Android 应用模型和 Windows 应用模型之间提供一个代理原生应用，将有一个虚拟机提供对 Android Open Source Project (AOSP)的兼容。微软和亚马逊合作将其应用商店带到 Windows 11 上，目前 Windows 用户只能通过亚马逊的商店而不是 Google 应用商店运行 Android 应用，而支持的大部分 Android 应用都是游戏。WSA 1.0 目前 &lt;a href="https://blogs.windows.com/windows-insider/2022/10/20/update-to-windows-subsystem-for-android-on-windows-11-october-2022/"&gt;提供给了&lt;/a&gt; Windows Insider 和 Beta，版本号为 2209.40000.26.0。&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62464-windows-subsystem-for</guid>
      <pubDate>Sat, 22 Oct 2022 20:27:38 CST</pubDate>
    </item>
    <item>
      <title>YOLOv5 的 Android 部署，基于 tflite</title>
      <link>https://itindex.net/detail/62409-yolov5-android-tflite</link>
      <description>&lt;h2&gt;环境&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;window 10 64bit&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://xugaoxiang.com/tag/yolo/" target="_blank" title="&amp;#26597;&amp;#30475;&amp;#20851;&amp;#20110; yolo &amp;#30340;&amp;#25991;&amp;#31456;"&gt;yolo&lt;/a&gt;v5 v6.2&lt;/li&gt;
  &lt;li&gt;torch1.7.1+cuda101&lt;/li&gt;
  &lt;li&gt;tensorflow-gpu 2.9.1&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;前言&lt;/h2&gt;
 &lt;p&gt;前文   &lt;a href="https://xugaoxiang.com/2021/02/02/android-ncnn-yolov5/"&gt;借助NCNN，在Android上运行YOLOv5目标检测&lt;/a&gt; 和   &lt;a href="https://xugaoxiang.com/2021/06/19/yolov5-for-android-using-torchscript/"&gt;在Android上进行yolov5目标检测，使用torchscript方式&lt;/a&gt;，我们分别使用了   &lt;code&gt;ncnn&lt;/code&gt; 和   &lt;code&gt;torchscript&lt;/code&gt; 这2种方式将   &lt;code&gt;YOLOv5&lt;/code&gt; 部署到了   &lt;code&gt;   &lt;a href="https://xugaoxiang.com/tag/android/" target="_blank" title="&amp;#26597;&amp;#30475;&amp;#20851;&amp;#20110; android &amp;#30340;&amp;#25991;&amp;#31456;"&gt;android&lt;/a&gt;&lt;/code&gt; 手机上。本篇我们将使用另一种方式   &lt;code&gt;tflite&lt;/code&gt; 来进行部署，所以，喜欢哪个就用哪个吧。&lt;/p&gt;
 &lt;h2&gt;具体步骤&lt;/h2&gt;
 &lt;p&gt;这里使用最新的   &lt;a href="https://xugaoxiang.com/2022/08/19/yolov5-v6-2/"&gt;v6.2&lt;/a&gt; 版本代码，使用官方的   &lt;code&gt;   &lt;a href="https://xugaoxiang.com/tag/yolo/" target="_blank" title="&amp;#26597;&amp;#30475;&amp;#20851;&amp;#20110; yolo &amp;#30340;&amp;#25991;&amp;#31456;"&gt;yolo&lt;/a&gt;v5s.pt&lt;/code&gt; 模型。要使用自己的数据集，需要自己先行训练，训练步骤可以参考文末的链接。&lt;/p&gt;
 &lt;p&gt;模型转换过程还需要   &lt;code&gt;tensorflow&lt;/code&gt; 的环境，安装一下&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;pip instal tensorflow-gpu &lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;然后就可以使用   &lt;code&gt;export.py&lt;/code&gt; 脚本进行转换了，命令如下&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;python export.py --weights    &lt;a href="https://xugaoxiang.com/tag/yolo/" target="_blank" title="&amp;#26597;&amp;#30475;&amp;#20851;&amp;#20110; yolo &amp;#30340;&amp;#25991;&amp;#31456;"&gt;yolo&lt;/a&gt;v5s.pt --include tflite --img 416&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;参数   &lt;code&gt;weights&lt;/code&gt; 指定   &lt;code&gt;.pt&lt;/code&gt; 模型，  &lt;code&gt;--include&lt;/code&gt; 参数指定要转换的目标模型，  &lt;code&gt;--img&lt;/code&gt; 参数指定图片大小&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;(pytorch1.7) PS D:\Downloads\yolov5-6.2&amp;gt; python export.py --weights yolov5s.pt --include tflite --img 416
export: data=D:\Downloads\yolov5-6.2\data\coco128.yaml, weights=[&amp;apos;yolov5s.pt&amp;apos;], imgsz=[416], batch_size=1, device=cpu, half=False, inplace=False, train=False, keras=False, optimize=False, int8=False, dynamic=False, simplify=False, opset=12, verbose=False, workspace=4, nms=False, agnostic_nms=False, topk_per_class=100, topk_all=100, iou_thres=0.45, conf_thres=0.25, include=[&amp;apos;tflite&amp;apos;]
YOLOv5  2022-8-17 Python-3.8.8 torch-1.7.1+cu110 CPU

Fusing layers...
YOLOv5s summary: 213 layers, 7225885 parameters, 0 gradients

PyTorch: starting from yolov5s.pt with output shape (1, 10647, 85) (14.1 MB)

TensorFlow SavedModel: starting export with tensorflow 2.9.1...

                 from  n    params  module                                  arguments
2022-08-23 15:19:30.085029: E tensorflow/stream_executor/cuda/cuda_driver.cc:271] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected
2022-08-23 15:19:30.091351: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:169] retrieving CUDA diagnostic information for host: USERMIC-Q2OSJCT
2022-08-23 15:19:30.092531: I tensorflow/stream_executor/cuda/cuda_diagnostics.cc:176] hostname: USERMIC-Q2OSJCT
2022-08-23 15:19:30.094067: I tensorflow/core/platform/cpu_feature_guard.cc:193] This TensorFlow binary is optimized with oneAPI Deep Neural Network Library (oneDNN) to use the following CPU instructions in performance-critical operations:  AVX AVX2
To enable them in other operations, rebuild TensorFlow with the appropriate compiler flags.
  0                -1  1      3520  models.common.Conv                      [3, 32, 6, 2, 2]
  1                -1  1     18560  models.common.Conv                      [32, 64, 3, 2]
  2                -1  1     18816  models.common.C3                        [64, 64, 1]
  3                -1  1     73984  models.common.Conv                      [64, 128, 3, 2]
  4                -1  1    115712  models.common.C3                        [128, 128, 2]
  5                -1  1    295424  models.common.Conv                      [128, 256, 3, 2]
  6                -1  1    625152  models.common.C3                        [256, 256, 3]
  7                -1  1   1180672  models.common.Conv                      [256, 512, 3, 2]
  8                -1  1   1182720  models.common.C3                        [512, 512, 1]
  9                -1  1    656896  models.common.SPPF                      [512, 512, 5]
 10                -1  1    131584  models.common.Conv                      [512, 256, 1, 1]
 11                -1  1         0  torch.nn.modules.upsampling.Upsample    [None, 2, &amp;apos;nearest&amp;apos;]
 12           [-1, 6]  1         0  models.common.Concat                    [1]
 13                -1  1    361984  models.common.C3                        [512, 256, 1, False]
 14                -1  1     33024  models.common.Conv                      [256, 128, 1, 1]
 15                -1  1         0  torch.nn.modules.upsampling.Upsample    [None, 2, &amp;apos;nearest&amp;apos;]
 16           [-1, 4]  1         0  models.common.Concat                    [1]
 17                -1  1     90880  models.common.C3                        [256, 128, 1, False]
 18                -1  1    147712  models.common.Conv                      [128, 128, 3, 2]
 19          [-1, 14]  1         0  models.common.Concat                    [1]
 20                -1  1    296448  models.common.C3                        [256, 256, 1, False]
 21                -1  1    590336  models.common.Conv                      [256, 256, 3, 2]
 22          [-1, 10]  1         0  models.common.Concat                    [1]
 23                -1  1   1182720  models.common.C3                        [512, 512, 1, False]
 24      [17, 20, 23]  1    229245  models.yolo.Detect                      [80, [[10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119], [116, 90, 156, 198, 373, 326]], [128, 256, 512], [416, 416]]
Model: &amp;quot;model&amp;quot;
__________________________________________________________________________________________________
 Layer (type)                   Output Shape         Param #     Connected to
==================================================================================================
 input_1 (InputLayer)           [(1, 416, 416, 3)]   0           []

 tf_conv (TFConv)               (1, 208, 208, 32)    3488        [&amp;apos;input_1[0][0]&amp;apos;]

 tf_conv_1 (TFConv)             (1, 104, 104, 64)    18496       [&amp;apos;tf_conv[0][0]&amp;apos;]

 tfc3 (TFC3)                    (1, 104, 104, 64)    18624       [&amp;apos;tf_conv_1[0][0]&amp;apos;]

 tf_conv_7 (TFConv)             (1, 52, 52, 128)     73856       [&amp;apos;tfc3[0][0]&amp;apos;]

 tfc3_1 (TFC3)                  (1, 52, 52, 128)     115200      [&amp;apos;tf_conv_7[0][0]&amp;apos;]

 tf_conv_15 (TFConv)            (1, 26, 26, 256)     295168      [&amp;apos;tfc3_1[0][0]&amp;apos;]

 tfc3_2 (TFC3)                  (1, 26, 26, 256)     623872      [&amp;apos;tf_conv_15[0][0]&amp;apos;]

 tf_conv_25 (TFConv)            (1, 13, 13, 512)     1180160     [&amp;apos;tfc3_2[0][0]&amp;apos;]

 tfc3_3 (TFC3)                  (1, 13, 13, 512)     1181184     [&amp;apos;tf_conv_25[0][0]&amp;apos;]

 tfsppf (TFSPPF)                (1, 13, 13, 512)     656128      [&amp;apos;tfc3_3[0][0]&amp;apos;]

 tf_conv_33 (TFConv)            (1, 13, 13, 256)     131328      [&amp;apos;tfsppf[0][0]&amp;apos;]

 tf_upsample (TFUpsample)       (1, 26, 26, 256)     0           [&amp;apos;tf_conv_33[0][0]&amp;apos;]

 tf_concat (TFConcat)           (1, 26, 26, 512)     0           [&amp;apos;tf_upsample[0][0]&amp;apos;,
                                                                  &amp;apos;tfc3_2[0][0]&amp;apos;]

 tfc3_4 (TFC3)                  (1, 26, 26, 256)     361216      [&amp;apos;tf_concat[0][0]&amp;apos;]

 tf_conv_39 (TFConv)            (1, 26, 26, 128)     32896       [&amp;apos;tfc3_4[0][0]&amp;apos;]

 tf_upsample_1 (TFUpsample)     (1, 52, 52, 128)     0           [&amp;apos;tf_conv_39[0][0]&amp;apos;]

 tf_concat_1 (TFConcat)         (1, 52, 52, 256)     0           [&amp;apos;tf_upsample_1[0][0]&amp;apos;,
                                                                  &amp;apos;tfc3_1[0][0]&amp;apos;]

 tfc3_5 (TFC3)                  (1, 52, 52, 128)     90496       [&amp;apos;tf_concat_1[0][0]&amp;apos;]

 tf_conv_45 (TFConv)            (1, 26, 26, 128)     147584      [&amp;apos;tfc3_5[0][0]&amp;apos;]

 tf_concat_2 (TFConcat)         (1, 26, 26, 256)     0           [&amp;apos;tf_conv_45[0][0]&amp;apos;,
                                                                  &amp;apos;tf_conv_39[0][0]&amp;apos;]

 tfc3_6 (TFC3)                  (1, 26, 26, 256)     295680      [&amp;apos;tf_concat_2[0][0]&amp;apos;]

 tf_conv_51 (TFConv)            (1, 13, 13, 256)     590080      [&amp;apos;tfc3_6[0][0]&amp;apos;]

 tf_concat_3 (TFConcat)         (1, 13, 13, 512)     0           [&amp;apos;tf_conv_51[0][0]&amp;apos;,
                                                                  &amp;apos;tf_conv_33[0][0]&amp;apos;]

 tfc3_7 (TFC3)                  (1, 13, 13, 512)     1181184     [&amp;apos;tf_concat_3[0][0]&amp;apos;]

 tf_detect (TFDetect)           ((1, 10647, 85),     229245      [&amp;apos;tfc3_5[0][0]&amp;apos;,
                                 [(1, 2704, 3, 85),               &amp;apos;tfc3_6[0][0]&amp;apos;,
                                 (1, 676, 3, 85),                 &amp;apos;tfc3_7[0][0]&amp;apos;]
                                 (1, 169, 3, 85)])

==================================================================================================
Total params: 7,225,885
Trainable params: 0
Non-trainable params: 7,225,885
__________________________________________________________________________________________________
2022-08-23 15:19:33.627375: I tensorflow/core/grappler/devices.cc:66] Number of eligible GPUs (core count &amp;gt;= 8, compute capability &amp;gt;= 0.0): 0
2022-08-23 15:19:33.627658: I tensorflow/core/grappler/clusters/single_machine.cc:358] Starting new session
Assets written to: yolov5s_saved_model\assets
TensorFlow SavedModel: export success, saved as yolov5s_saved_model (27.8 MB)

TensorFlow Lite: starting export with tensorflow 2.9.1...
Found untraced functions such as tf_conv_2_layer_call_fn, tf_conv_2_layer_call_and_return_conditional_losses, tf_conv_3_layer_call_fn, tf_conv_3_layer_call_and_return_conditional_losses, tf_conv_4_layer_call_fn while saving (showing 5 of 268). These functions will not be directly callable after loading.
Assets written to: C:\Users\ADMINI~1\AppData\Local\Temp\tmp2dfxq2oq\assets
2022-08-23 15:21:33.820763: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:362] Ignored output_format.
2022-08-23 15:21:33.821578: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:365] Ignored drop_control_dependency.
2022-08-23 15:21:33.827268: I tensorflow/cc/saved_model/reader.cc:43] Reading SavedModel from: C:\Users\ADMINI~1\AppData\Local\Temp\tmp2dfxq2oq
2022-08-23 15:21:33.933152: I tensorflow/cc/saved_model/reader.cc:81] Reading meta graph with tags { serve }
2022-08-23 15:21:33.934302: I tensorflow/cc/saved_model/reader.cc:122] Reading SavedModel debug info (if present) from: C:\Users\ADMINI~1\AppData\Local\Temp\tmp2dfxq2oq
2022-08-23 15:21:34.244556: I tensorflow/compiler/mlir/mlir_graph_optimization_pass.cc:354] MLIR V1 optimization pass is not enabled
2022-08-23 15:21:34.301240: I tensorflow/cc/saved_model/loader.cc:228] Restoring SavedModel bundle.
2022-08-23 15:21:35.141922: I tensorflow/cc/saved_model/loader.cc:212] Running initialization op on SavedModel bundle at path: C:\Users\ADMINI~1\AppData\Local\Temp\tmp2dfxq2oq
2022-08-23 15:21:35.504643: I tensorflow/cc/saved_model/loader.cc:301] SavedModel load for tags { serve }; Status: success: OK. Took 1677383 microseconds.
2022-08-23 15:21:36.609861: I tensorflow/compiler/mlir/tensorflow/utils/dump_mlir_util.cc:263] disabling MLIR crash reproducer, set env var `MLIR_CRASH_REPRODUCER_DIRECTORY` to enable.
2022-08-23 15:21:38.044500: I tensorflow/compiler/mlir/lite/flatbuffer_export.cc:1972] Estimated count of arithmetic ops: 7.666 G  ops, equivalently 3.833 G  MACs

Estimated count of arithmetic ops: 7.666 G  ops, equivalently 3.833 G  MACs
TensorFlow Lite: export success, saved as yolov5s-fp16.tflite (13.9 MB)

Export complete (136.66s)
Results saved to D:\Downloads\yolov5-6.2
Detect:          python detect.py --weights yolov5s-fp16.tflite
Validate:        python val.py --weights yolov5s-fp16.tflite
PyTorch Hub:     model = torch.hub.load(&amp;apos;ultralytics/yolov5&amp;apos;, &amp;apos;custom&amp;apos;, &amp;apos;yolov5s-fp16.tflite&amp;apos;)
Visualize:       https://netron.app&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;可以看到，转换后的模型是   &lt;code&gt;yolov5s-fp16.tflite&lt;/code&gt;，同时，在同层目录下，还有个文件夹   &lt;code&gt;yolov5s_saved_model&lt;/code&gt;，里面包含了   &lt;code&gt;.pb&lt;/code&gt; 文件，就是   &lt;code&gt;protobuf&lt;/code&gt; 文件，这里有个细节，就是   &lt;code&gt;.pt&lt;/code&gt; 文件是先被转换成   &lt;code&gt;.pb&lt;/code&gt;，然后再转换成   &lt;code&gt;.tflite&lt;/code&gt; 的。&lt;/p&gt;
 &lt;p&gt;接着，使用脚本   &lt;code&gt;detect.py&lt;/code&gt; 来验证一下，用上面生成的   &lt;code&gt;.tflite&lt;/code&gt; 模型&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;python detect.py --weights yolov5s-fp16.tflite --source data/images/bus.jpg --img 416&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;检测没有问题&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" src="https://image.xugaoxiang.com/imgs/2022/08/be5da60ee980aec5.jpg"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;有了模型文件，接下来就可以来到   &lt;code&gt;   &lt;a href="https://xugaoxiang.com/tag/android/" target="_blank" title="&amp;#26597;&amp;#30475;&amp;#20851;&amp;#20110; android &amp;#30340;&amp;#25991;&amp;#31456;"&gt;android&lt;/a&gt;&lt;/code&gt; 端了，我把示例代码上传到了   &lt;code&gt;github&lt;/code&gt; 上，可以直接   &lt;code&gt;clone&lt;/code&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;git clone https://github.com/xugaoxiang/yolov5_   &lt;a href="https://xugaoxiang.com/tag/android/" target="_blank" title="&amp;#26597;&amp;#30475;&amp;#20851;&amp;#20110; android &amp;#30340;&amp;#25991;&amp;#31456;"&gt;android&lt;/a&gt;_tflite.git&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;需要替换的是   &lt;code&gt;yolov5_   &lt;a href="https://xugaoxiang.com/tag/android/" target="_blank" title="&amp;#26597;&amp;#30475;&amp;#20851;&amp;#20110; android &amp;#30340;&amp;#25991;&amp;#31456;"&gt;android&lt;/a&gt;_tflite/app/src/main/assets&lt;/code&gt; 文件夹下的2个文件，  &lt;code&gt;class.txt&lt;/code&gt; 和   &lt;code&gt;yolov5s-fp16.tflite&lt;/code&gt;&lt;/p&gt;
 &lt;p&gt;编译后安装到手机，就可以开始进行基于摄像头的目标检测了&lt;/p&gt;
 &lt;div&gt;
  &lt;a href="https://xugaoxiang.com/wp-content/uploads/2022/09/yolov5-tflite.mp4"&gt;https://xugaoxiang.com/wp-content/uploads/2022/09/yolov5-tflite.mp4&lt;/a&gt;&lt;/div&gt;
 &lt;h2&gt;参考资料&lt;/h2&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;a href="https://xugaoxiang.com/2021/02/02/android-ncnn-yolov5/"&gt;借助NCNN，在Android上运行YOLOv5目标检测&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://xugaoxiang.com/2019/12/18/windows-10-cuda-cudnn/"&gt;windows 10安装cuda和cudnn&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://xugaoxiang.com/2020/07/02/yolov5-training/"&gt;YOLOv5模型训练&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://xugaoxiang.com/2021/06/08/yolov5-release-5-0/"&gt;YOLOv5 5.0版本&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;   &lt;a href="https://xugaoxiang.com/2021/06/09/android-studio-gradle-sync-failed/"&gt;Android studio gradle构建失败的解决方法&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>YOLO android yolo</category>
      <guid isPermaLink="true">https://itindex.net/detail/62409-yolov5-android-tflite</guid>
      <pubDate>Tue, 06 Sep 2022 22:35:12 CST</pubDate>
    </item>
    <item>
      <title>Android 恶意程序 BRATA 能在窃取数据之后抹掉设备所有数据</title>
      <link>https://itindex.net/detail/62072-android-%E6%81%B6%E6%84%8F%E7%A8%8B%E5%BA%8F-brata</link>
      <description>最新版本的 Android 恶意程序 BRATA  &lt;a href="https://www.bleepingcomputer.com/news/security/android-malware-brata-wipes-your-device-after-stealing-data/" target="_blank"&gt;能在窃取数据之后将设备恢复到出厂设置&lt;/a&gt;，抹掉设备上的所有数据掩盖其活动痕迹。BRATA 在 2019 年最早被发现时属于一种 Android RAT（远程访问工具），主要针对巴西用户。安全公司 Cleafy 在 2021 年 12 月报告 BRATA 开始在欧洲等地出现，并增加了更多功能，它发展成为窃取电子银行登录凭证的恶意程序。其最新版本针对了英国、波兰、意大利、西班牙、中国和拉美的电子银行用户，每个变种针对了不同的银行，都使用了类似的混淆技术以躲避安全软件的检测。它会寻找设备上安全程序的痕迹，会在执行渗透前删除安全工具。 &lt;div&gt;
  &lt;a href="http://feeds.feedburner.com/~ff/solidot?a=tUlvJQvhMLg:f15NvfwUPUQ:yIl2AUoC8zA"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/solidot?d=yIl2AUoC8zA"&gt;&lt;/img&gt;&lt;/a&gt;   &lt;a href="http://feeds.feedburner.com/~ff/solidot?a=tUlvJQvhMLg:f15NvfwUPUQ:7Q72WNTAKBA"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/solidot?d=7Q72WNTAKBA"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/62072-android-%E6%81%B6%E6%84%8F%E7%A8%8B%E5%BA%8F-brata</guid>
      <pubDate>Sat, 29 Jan 2022 13:32:37 CST</pubDate>
    </item>
    <item>
      <title>Fiddler 抓包 Android</title>
      <link>https://itindex.net/detail/62050-fiddler-android</link>
      <description>&lt;h1&gt;引言&lt;/h1&gt; &lt;a&gt;&lt;/a&gt; &lt;h1&gt;准备&lt;/h1&gt; &lt;ul&gt;  &lt;li&gt;Fiddler&lt;/li&gt;  &lt;li&gt;Android 模拟器   &lt;ul&gt;    &lt;li&gt;我这里是 夜神模拟器，BlueStacks蓝叠 模拟器 没找到      &lt;code&gt;WLAN&lt;/code&gt; 设置&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h1&gt;Fiddler 基本配置&lt;/h1&gt; &lt;p&gt;参考:&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://blog.csdn.net/f_yunsheng_t/article/details/104413324"&gt;使用Fiddler+安卓模拟器对app抓包，包含https_f_yunsheng_t的专栏-CSDN博客_fiddler安卓模拟器抓包&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;&lt;/li&gt;&lt;/ul&gt; &lt;blockquote&gt;  &lt;p&gt;Fiddler 配置代理, 允许远程的计算机连接&lt;/p&gt;&lt;/blockquote&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121200044128.png" title="image-20220121200044128"&gt;&lt;/img&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121200212802.png" title="image-20220121200212802"&gt;&lt;/img&gt; &lt;p&gt;点击   &lt;code&gt;OK&lt;/code&gt;，保存确定后，  &lt;strong&gt;需要重启 Fiddler 才能生效&lt;/strong&gt;&lt;/p&gt; &lt;h1&gt;Fiddler 配置 HTTPS&lt;/h1&gt; &lt;blockquote&gt;  &lt;p&gt;Fiddler启用https：Options中勾选&amp;quot;Decrypt HTTPS traffic&amp;quot;和&amp;quot;Ignore server certificate errors(unsafe)&amp;quot;，弹出的提示都选Yes&lt;/p&gt;&lt;/blockquote&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121203755407.png" title="image-20220121203755407"&gt;&lt;/img&gt; &lt;h1&gt;Android 模拟器 配置&lt;/h1&gt; &lt;p&gt;参考:&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://blog.csdn.net/qq_27384769/article/details/79765345"&gt;Fiddler 抓包逍遥安卓模拟器_架构师的成长之路的博客-CSDN博客_逍遥模拟器抓包&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;配置 Android 模拟器的网络代理&lt;/h2&gt; &lt;blockquote&gt;  &lt;p&gt;打开    &lt;code&gt;设置&lt;/code&gt;, 选择    &lt;code&gt;WLAN&lt;/code&gt;, 在显示已连接的WIFI上长按鼠标左键，选择    &lt;code&gt;修改网络&lt;/code&gt; ,&lt;/p&gt;&lt;/blockquote&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121201744631.png" title="image-20220121201744631"&gt;&lt;/img&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121201853450.png" title="image-20220121201853450"&gt;&lt;/img&gt; &lt;blockquote&gt;  &lt;p&gt;勾选    &lt;code&gt;高级选项&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121202109398.png" title="image-20220121202109398"&gt;&lt;/img&gt; &lt;blockquote&gt;  &lt;p&gt;选择    &lt;code&gt;手动&lt;/code&gt;，填写本机 ip 以及 端口 ( 8888 )&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;Fiddler 默认为 8888&lt;/p&gt;&lt;/blockquote&gt;  &lt;p&gt;查看本机端口, cmd 执行&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;td&gt;     &lt;pre&gt;1      &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;    &lt;td&gt;     &lt;pre&gt;ipconfig      &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121202929817.png" title="image-20220121202929817"&gt;&lt;/img&gt;  &lt;p&gt;图中    &lt;code&gt;192.168.0.103&lt;/code&gt; 即为本机 ip,&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;我这里是     &lt;code&gt;WiFi 连接&lt;/code&gt;，因此才是     &lt;code&gt;Wireless LAN adapter WLAN&lt;/code&gt;, 如果是     &lt;code&gt;网线连接&lt;/code&gt; ，则是最上面的     &lt;code&gt;以太网&lt;/code&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;/blockquote&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121203035166.png" title="image-20220121203035166"&gt;&lt;/img&gt; &lt;blockquote&gt;  &lt;p&gt;PS: BlueStacks蓝叠 模拟器居然没找到    &lt;code&gt;WLAN&lt;/code&gt; 设置, 于是这里用 夜神模拟器&lt;/p&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;应该说，到这里，就可以抓包 HTTP 了&lt;/p&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;针对 HTTPS 的请求，需要在Fiddler中启用 HTTPS，并把Fiddler的证书安装到模拟器中&lt;/p&gt;&lt;/blockquote&gt; &lt;h2&gt;配置 Fiddler 抓包 模拟器 的 HTTPS&lt;/h2&gt; &lt;blockquote&gt;  &lt;p&gt;模拟器 安装 来自 Fiddler 的 HTTPS 证书 有两种方式:&lt;/p&gt;  &lt;ol type="1"&gt;   &lt;li&gt;Fiddler 导出证书，然后模拟器 导入电脑文件, 然后安装证书&lt;/li&gt;   &lt;li&gt;在模拟器中通过浏览器访问 Fiddler 的 http://ip:8888 ，下载安装证书&lt;/li&gt;&lt;/ol&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;这里选择第二种方式，方便&lt;/p&gt;  &lt;p&gt;如果打开后浏览器提示证书错误，点击    &lt;code&gt;继续&lt;/code&gt; 即可&lt;/p&gt;&lt;/blockquote&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121204712455.png" title="image-20220121204712455"&gt;&lt;/img&gt; &lt;blockquote&gt;  &lt;p&gt;点击下载    &lt;code&gt;Fiddler&lt;/code&gt; 证书，下载完成后，点击安装，这里给证书取名，随便取,&lt;/p&gt;  &lt;p&gt;凭据用途有两个选项:&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;VPN和应用&lt;/li&gt;   &lt;li&gt;WLAN&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;我这里只安装    &lt;code&gt;VPN和应用&lt;/code&gt; ，也可以两个都安装一遍&lt;/p&gt;&lt;/blockquote&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121205018569.png" title="image-20220121205018569"&gt;&lt;/img&gt; &lt;blockquote&gt;  &lt;p&gt;这里需要设置密码，设置好后即可&lt;/p&gt;&lt;/blockquote&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121205049216.png" title="image-20220121205049216"&gt;&lt;/img&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121205131298.png" title="image-20220121205131298"&gt;&lt;/img&gt; &lt;blockquote&gt;  &lt;p&gt;确定安装完毕；在    &lt;code&gt;设置&lt;/code&gt; -    &lt;code&gt;个人&lt;/code&gt; -    &lt;code&gt;安全&lt;/code&gt; -    &lt;code&gt;信任的凭据&lt;/code&gt; 中，   &lt;code&gt;用户&lt;/code&gt; 标签页可以看到安装的证书&lt;/p&gt;&lt;/blockquote&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121205532863.png" title="image-20220121205532863"&gt;&lt;/img&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121205638408.png" title="image-20220121205638408"&gt;&lt;/img&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121205704421.png" title="image-20220121205704421"&gt;&lt;/img&gt; &lt;blockquote&gt;  &lt;p&gt;点击证书，可以查看详情&lt;/p&gt;&lt;/blockquote&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121205813050.png" title="image-20220121205813050"&gt;&lt;/img&gt; &lt;blockquote&gt;  &lt;p&gt;接下来就可以打开Fiddler，在模拟器中打开浏览器测试下https的数据包是否可以正常抓到和解析了，以百度为例&lt;/p&gt;  &lt;p&gt;打开模拟器默认带的浏览器，输入 m.baidu.com，百度现在默认使用https，随便搜索一下，查看刚才的包&lt;/p&gt;&lt;/blockquote&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121210608089.png" title="image-20220121210608089"&gt;&lt;/img&gt; &lt;blockquote&gt;  &lt;p&gt;成功&lt;/p&gt;&lt;/blockquote&gt; &lt;h1&gt;部分 APP 不走 系统代理&lt;/h1&gt; &lt;p&gt;参考:&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://www.cnblogs.com/lulianqi/p/11380794.html"&gt;部分APP无法代理抓包的原因及解决方法（flutter 抓包） - lulianqi15 - 博客园&lt;/a&gt; - 重要参考: 原理, 详解&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.cnblogs.com/bytegoing/p/14482957.html"&gt;部分App无法使用系统代理抓包的原因及解决办法(Fiddler+Drony) - BYTEGOING - 博客园&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://blog.csdn.net/u013356254/article/details/104725952"&gt;如何让app不走系统代理？_阳光下的小树-CSDN博客_app不走代理&lt;/a&gt; - 重要参考&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://blog.csdn.net/Zdelta/article/details/111031966"&gt;app不走系统代理？如何抓包？_Zdelta-CSDN博客_app不走代理如何抓包&lt;/a&gt; - 重要参考&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;现象&lt;/h2&gt; &lt;p&gt;设置系统代理后，依然抓不到包 ( 没有相关请求的包 )，但是 app 却依旧正常返回数据，没有断网。&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;使用 Fiddler 或 Charles 这类代理抓包软件默认情况下无法抓取请求的，&lt;/p&gt;  &lt;p&gt;但使用 Wireshark 这类网卡抓包软件可以看到这些 APP 的流量，&lt;/p&gt;  &lt;p&gt;就表明这些 APP 使用的主要应用层协议仍然是 HTTP（HTTPS）&lt;/p&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;Fiddler 或 Charles 这类使用的代理的抓包软件与 Wireshark 是完全不同的（Wireshark 使用的网卡数据复制，只要是经过指定网卡都会被抓取），其只能对使用代理的应用层网络协议生效，比如常见的HTTP（https），Websocket 。&lt;/p&gt;&lt;/blockquote&gt; &lt;h2&gt;APP 不走系统代理 原因/实现&lt;/h2&gt; &lt;blockquote&gt;  &lt;blockquote&gt;   &lt;p&gt;网络请求代理设置     &lt;strong&gt;NO_PROXY&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;  &lt;p&gt;在网络请求的时候，通过OkHttp可以设置一个选项，代理的类型。我们在这里直接设置成Proxy.NO_PROXY。这样话即使android设置了代理，我们的app也不会走代理。&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;android系统设置的代理并不是强制对所有app生效的&lt;/li&gt;   &lt;li&gt;app可以在网络请求类库中通过自定义代理设置，选择是否要走系统代理&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt; &lt;table&gt;  &lt;tr&gt;   &lt;td&gt;    &lt;pre&gt;1     &lt;br /&gt;2     &lt;br /&gt;3     &lt;br /&gt;4     &lt;br /&gt;5     &lt;br /&gt;6     &lt;br /&gt;7     &lt;br /&gt;8     &lt;br /&gt;9     &lt;br /&gt;10     &lt;br /&gt;11     &lt;br /&gt;12     &lt;br /&gt;13     &lt;br /&gt;14     &lt;br /&gt;15     &lt;br /&gt;16     &lt;br /&gt;17     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;   &lt;td&gt;    &lt;pre&gt;public void run() {     &lt;br /&gt;    Looper.prepare();     &lt;br /&gt;    OkHttpClient okHttpClient = new OkHttpClient.Builder().     &lt;br /&gt;        proxy(Proxy.NO_PROXY).//okhttp不设置代理     &lt;br /&gt;        build();     &lt;br /&gt;    Request request = new Request.Builder()     &lt;br /&gt;        .url(&amp;quot;http://www.baidu.com&amp;quot;)     &lt;br /&gt;        .build();     &lt;br /&gt;    Response response = null;     &lt;br /&gt;    try {     &lt;br /&gt;        response = okHttpClient.newCall(request).execute();     &lt;br /&gt;        Toast.makeText(this, Objects.requireNonNull(response.body()).string(), Toast.LENGTH_SHORT).show();     &lt;br /&gt;    } catch (IOException e) {     &lt;br /&gt;        e.printStackTrace();     &lt;br /&gt;    }     &lt;br /&gt;    Looper.loop();     &lt;br /&gt;}     &lt;br /&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt; &lt;h2&gt;解决&lt;/h2&gt; &lt;p&gt;TODO: 未完&lt;/p&gt; &lt;h1&gt;Q&amp;amp;A&lt;/h1&gt; &lt;h1&gt;补充&lt;/h1&gt; &lt;h2&gt;Fiddler 默认端口&lt;/h2&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121204343078.png" title="image-20220121204343078"&gt;&lt;/img&gt; &lt;h2&gt;Fiddler 导出 HTTPS 证书&lt;/h2&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121204307868.png" title="image-20220121204307868"&gt;&lt;/img&gt; &lt;h2&gt;代理抓包原理&lt;/h2&gt; &lt;p&gt;参考:&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://www.cnblogs.com/lulianqi/p/11380794.html"&gt;部分APP无法代理抓包的原因及解决方法（flutter 抓包） - lulianqi15 - 博客园&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;blockquote&gt;  &lt;p&gt;下面内容 来自上面的参考内容，根据自己理解，修改简化&lt;/p&gt;&lt;/blockquote&gt; &lt;h3&gt;原理&lt;/h3&gt; &lt;blockquote&gt;  &lt;p&gt;为什么Fiddler 或 Charles对这些APP无效，我们有必要先了解代理抓包我原理&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;Fiddler 或 Charles 这类使用的代理的抓包软件与Wireshark是完全不同的（Wireshark 使用的网卡数据复制，只要是经过指定网卡都会被抓取），其只能对使用代理的应用层网络协议生效，比如常见的HTTP（https），Websocket 。&lt;/p&gt; &lt;p&gt;这里以HTTP为例简单说明下&lt;/p&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121220633107.png" title="image-20220121220633107"&gt;&lt;/img&gt; &lt;p&gt;客户端需要完成一次HTTP请求，通常需要先找到服务器，客户端会根据http请求中url的主机名（实际会使用host中的主角名）及其端口与目标主机建立tcp连接，建立连接后会将http报文发送给目标服务器 （更多细节请参考https://tools.ietf.org/html/rfc7232）&lt;/p&gt; &lt;p&gt;接下来我来看下HTTP代理是如何运作的，我们启动Fiddler 或 Charles就是启动了一个HTTP代理服务器，这类工具会通知操作系统，“现在我在系统上创建了一个HTTP代理，IP为XXXXXX端口为XX。如果您使用的是linux您可以手动通知操作系统(export http_proxy=ip:port export https_proxy=$http_proxy),如果您使用的是手机等移动设备您可以在当前wifi设置处告诉系统你要使用http代理。 现在我们已经告诉系统我们想要使用代理，这个时候运行在系统上的http客户端再去发送请求的时候，他就不会再去进行DNS解析，去连接目标服务器，而是直接连接系统告诉他代理所在的地址（代理的ip及端口，注意无论是http或https或其他支持代理的协议都会连接同一个端口）。然后代理服务器会与客户端建立连接，再然后代理服务器根据请求信息再去连接真正的服务器。&lt;/p&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121220729772.png" title="image-20220121220729772"&gt;&lt;/img&gt; &lt;p&gt;这里还有个细节正常在   &lt;strong&gt;没有代理&lt;/strong&gt; 的情况下客户端向服务器发送的请求行里   &lt;strong&gt;只包含部分URI&lt;/strong&gt; （实际上是没有方案，主机名及端口的）&lt;/p&gt; &lt;img src="https://moeci.com/posts/2022/01/fiddler-android/image-20220121220818729.png" title="image-20220121220818729"&gt;&lt;/img&gt; &lt;blockquote&gt;  &lt;p&gt;有代理的情况下，应该是 黄色标记的那样 完整URL&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;如上图如果在没有代理的情况下，对 www.baidu.com/index.html 的请求的请求行实际上是 GET /index.html HTTP/1.1 其实并不是我们常见的完整uri。因为在原始的HTTP设计中没有考虑中间服务器（即代理）的情况，客户端在发送报文前已经知道服务器的地址并与之建立了连接，没有必要再发送方案，主机名及端口。不过代理出现后这种做法就会有问题了，客户端连接了代理服务器，而代理服务器却没有办法连接正确的服务器。因此客户端发送给代理的请求其实稍有不同，客户端会在请求行里使用完整的uri，这样代理服务器才能解析真实的服务器的地址。&lt;/p&gt; &lt;p&gt;现在我们的请求实际上都是通过代理服务器（Fiddler 或 Charles）发送出去的，所以代理抓包软件不仅知道http请求及响应的所有报文，甚至还可以随时修改请求及响应。&lt;/p&gt; &lt;h3&gt;部分应用不能抓包的原因&lt;/h3&gt; &lt;blockquote&gt;  &lt;p&gt;代理抓包的关键就是需要HTTP客户端按照要求去连接代理服务器&lt;/p&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;一般情况下我们已经在系统层面上设置了代理，通常http客户端都是按要求去实现的，在进行http请求前会先检查系统代理，如果有设置代理，客户端会直接使用完整uri去连接代理服务器。不同的平台通常会实现自己的的http客户端的，虽然他们都按照协议要求实现了代理功能，但是并不一定在默认情况下会直接使用系统代理。&lt;/p&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;在现实中这种况下这种情况还不少，Flutter 就是这种情况，默认Flutter不会主动使用系统代理，需要单独设置。（当然个人认为这种策略也是有理由，虽然给测试及调试带来了不便不过也在一程度上提高了些许数据安全）&lt;/p&gt;  &lt;p&gt;正是因为HTTP客户端没有使用我们设置的系统代理，他们自然也不会连接Fiddler 或 Charles创建的代理服务器，最终导致我们无法获取任何请求。&lt;/p&gt;&lt;/blockquote&gt; &lt;h3&gt;解决方案&lt;/h3&gt; &lt;p&gt;不过既然我们已经知道了Fiddler 和 Charles不能抓包的具体原因，前面也提到了代理抓包的原理，那我们就总有办法解决。&lt;/p&gt; &lt;p&gt;前面说到了我们APP使用的HTTP客户端没有连接到代理服务器，导致我们的代理抓包软件无法正常抓包，那我们只要想办法让客户端重新连接到代理服务器就好了（当然这一切都是以不修改客户端软件APP为前提的）&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;方法1：控制DNS解析，通过修改dns的方式让客户端以为我们的代理服务器就是目标服务器。&lt;/p&gt;  &lt;p&gt;优势：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;操作方便，通过修改设备的hosts可以十分方便的首先&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;劣势：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;需要为每个需要操作的域名提前添加host&lt;/li&gt;   &lt;li&gt;在手机等手持设备上难以修改hosts（即对移动APP这类应用很难实现）&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;方法2：在网络设备上直接做流量转发，将指定终端设备上发往80及443端口的数据直接转发到代理服务器的 目标端口上&lt;/p&gt;  &lt;p&gt;优势：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;可以针对连接到网络设备上的终端设备进行分别配置，而手机等终端设备不需要进行任何设备&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;劣势：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;需要单独的硬件设备&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt; &lt;blockquote&gt;  &lt;p&gt;方法3：使用VPN将终端设备的流量转发到代理服务器&lt;/p&gt;  &lt;p&gt;优势：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;使用VPN软件不用添加其他测试。&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;劣势：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;终端上的VPN默认会直接对所有流量进行转发，要进行合理的配置可能需要额外的学习成本&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt; &lt;p&gt;TODO: 未复制完&lt;/p&gt; &lt;h1&gt;参考&lt;/h1&gt; &lt;p&gt;感谢帮助！&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://www.jmwww.net/yidong/26092.html"&gt;Fiddler安卓手机APP抓包-杰米博客&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="http://niyuanma.com/archives/33"&gt;关于Android 9.0 FD抓包证书处理-逆猿码&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.bilibili.com/video/BV1UE41187hhs"&gt;fiddler-003-抓取app视频，抓取抖音、西瓜、快手视频，小小的七色花，想抓保存什么就保存什么_哔哩哔哩_bilibili&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.bilibili.com/video/BV1UR4y1t7Z4"&gt;B站最新Fiddler抓包（抓APP的包）全套完整教程_哔哩哔哩_bilibili&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.bilibili.com/video/BV1c4411c7zH?spm_id_from=333.999.0.0"&gt;Fiddler抓包工具实战全网最全最细教程，没有之一【柠檬班】_哔哩哔哩_bilibili&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.bilibili.com/video/BV167411E7bF?spm_id_from=333.999.0.0"&gt;【决清教程】FD抓包工具Fiddler基本抓包-可以抓电脑或者安卓苹果手机_哔哩哔哩_bilibili&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.bilibili.com/video/BV1JZ4y1S7gj?spm_id_from=333.999.0.0"&gt;frida抓不到包解决办法_哔哩哔哩_bilibili&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>Fiddler Android 网络</category>
      <guid isPermaLink="true">https://itindex.net/detail/62050-fiddler-android</guid>
      <pubDate>Tue, 04 Jan 2022 19:34:26 CST</pubDate>
    </item>
    <item>
      <title>Android/iOS判断是否使用代理或VPN</title>
      <link>https://itindex.net/detail/62008-android-ios-%E4%BB%A3%E7%90%86</link>
      <description>&lt;p&gt;针对APP的黑产，我们提到部分用户会通过改变IP来绕过风控策略。更改IP比较方便的方法是使用代理IP或VPN。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="" height="286" src="https://www.biaodianfu.com/wp-content/uploads/2022/01/proxy-vpn.png" width="800"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;在检测APP安全性是需要对是否使用代码和VPN做判断。以下为整理的一些代码供参考。&lt;/p&gt;
 &lt;h2&gt;Android判断是否使用代理IP&lt;/h2&gt;
 &lt;pre&gt;private boolean isWifiProxy(Context context) {
    final boolean IS_ICS_OR_LATER = Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
    String proxyAddress;
    int proxyPort;

    if (IS_ICS_OR_LATER) {
        proxyAddress = System.getProperty(&amp;quot;http.proxyHost&amp;quot;);
        String portStr = System.getProperty(&amp;quot;http.proxyPort&amp;quot;);
        proxyPort = Integer.parseInt((portStr != null ? portStr : &amp;quot;-1&amp;quot;));
    } else {
        proxyAddress = android.net.Proxy.getHost(context);
        proxyPort = android.net.Proxy.getPort(context);
    }
    return (!TextUtils.isEmpty(proxyAddress)) &amp;amp;&amp;amp; (proxyPort != -1);
}
&lt;/pre&gt;
 &lt;h2&gt;Android判断是否使用VPN&lt;/h2&gt;
 &lt;pre&gt;boolean checkVPN(ConnectivityManager connMgr) {
    //don&amp;apos;t know why always returns null:
    NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_VPN);

    boolean isVpnConn = networkInfo == null ? false : networkInfo.isConnected();
    return isVpnConn;
}
&lt;/pre&gt;
 &lt;h2&gt;iOS判断是否使用代理IP&lt;/h2&gt;
 &lt;pre&gt;#import &amp;quot;CETCProxyStatus.h&amp;quot;

@implementation CETCProxyStatus

+ (BOOL)getProxyStatus {
    NSDictionary *proxySettings = NSMakeCollectable([(NSDictionary *)CFNetworkCopySystemProxySettings() autorelease]);

    NSArray *proxies = NSMakeCollectable([(NSArray *)CFNetworkCopyProxiesForURL((CFURLRef)[NSURL URLWithString:@&amp;quot;http://www.baidu.com&amp;quot;], (CFDictionaryRef)proxySettings) autorelease]);
    NSDictionary *settings = [proxies objectAtIndex:0];

    NSLog(@&amp;quot;host=%@&amp;quot;, [settings objectForKey:(NSString *)kCFProxyHostNameKey]);
    NSLog(@&amp;quot;port=%@&amp;quot;, [settings objectForKey:(NSString *)kCFProxyPortNumberKey]);
    NSLog(@&amp;quot;type=%@&amp;quot;, [settings objectForKey:(NSString *)kCFProxyTypeKey]);

    if ([[settings objectForKey:(NSString *)kCFProxyTypeKey] isEqualToString:@&amp;quot;kCFProxyTypeNone&amp;quot;]) {
        //没有设置代理
        return NO;
    } else {
        //设置代理了
        return YES;
    }
}
&lt;/pre&gt;
 &lt;h2&gt;iOS判断是否使用VPN&lt;/h2&gt;
 &lt;pre&gt;- (BOOL)isVPNOn
{
   BOOL flag = NO;
   NSString *version = [UIDevice currentDevice].systemVersion;
   // need two ways to judge this.
   if (version.doubleValue &amp;gt;= 9.0)
   {
       NSDictionary *dict = CFBridgingRelease(CFNetworkCopySystemProxySettings());
       NSArray *keys = [dict[@&amp;quot;__SCOPED__&amp;quot;] allKeys];
       for (NSString *key in keys) {
           if ([key rangeOfString:@&amp;quot;tap&amp;quot;].location != NSNotFound ||
               [key rangeOfString:@&amp;quot;tun&amp;quot;].location != NSNotFound ||
               [key rangeOfString:@&amp;quot;ipsec&amp;quot;].location != NSNotFound ||
               [key rangeOfString:@&amp;quot;ppp&amp;quot;].location != NSNotFound){
               flag = YES;
               break;
           }
       }
   }
   else
   {
       struct ifaddrs *interfaces = NULL;
       struct ifaddrs *temp_addr = NULL;
       int success = 0;
       
       // retrieve the current interfaces - returns 0 on success
       success = getifaddrs(&amp;amp;interfaces);
       if (success == 0)
       {
           // Loop through linked list of interfaces
           temp_addr = interfaces;
           while (temp_addr != NULL)
           {
               NSString *string = [NSString stringWithFormat:@&amp;quot;%s&amp;quot; , temp_addr-&amp;gt;ifa_name];
               if ([string rangeOfString:@&amp;quot;tap&amp;quot;].location != NSNotFound ||
                   [string rangeOfString:@&amp;quot;tun&amp;quot;].location != NSNotFound ||
                   [string rangeOfString:@&amp;quot;ipsec&amp;quot;].location != NSNotFound ||
                   [string rangeOfString:@&amp;quot;ppp&amp;quot;].location != NSNotFound)
               {
                   flag = YES;
                   break;
               }
               temp_addr = temp_addr-&amp;gt;ifa_next;
           }
       }
       
       // Free memory
       freeifaddrs(interfaces);
   }


   return flag;
}
&lt;/pre&gt;
 &lt;div&gt;
  &lt;h3&gt;相关文章:&lt;/h3&gt;  &lt;ol&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/django-tutorial.html" rel="bookmark" title="Django&amp;#23454;&amp;#25112;&amp;#65306;&amp;#25237;&amp;#31080;&amp;#31995;&amp;#32479;&amp;#25645;&amp;#24314;"&gt;Django实战：投票系统搭建 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/google-protocol-buffers.html" rel="bookmark" title="Google Protocol Buffers&amp;#20351;&amp;#29992;&amp;#25351;&amp;#21335;"&gt;Google Protocol Buffers使用指南 &lt;/a&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;a href="https://www.biaodianfu.com/ios-jailbreak-check.html" rel="bookmark" title="iOS&amp;#26159;&amp;#21542;&amp;#36234;&amp;#29425;&amp;#21028;&amp;#26029;&amp;#26041;&amp;#27861;"&gt;iOS是否越狱判断方法 &lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>数据 术→技巧 研发 Android APP</category>
      <guid isPermaLink="true">https://itindex.net/detail/62008-android-ios-%E4%BB%A3%E7%90%86</guid>
      <pubDate>Fri, 07 Jan 2022 18:57:06 CST</pubDate>
    </item>
    <item>
      <title>Google 准备将 Android 游戏带到 Windows 平台</title>
      <link>https://itindex.net/detail/61953-google-android-%E6%B8%B8%E6%88%8F</link>
      <description>微软正与亚马逊等公司合作让包括游戏在内的 Android 应用能运行在 Windows 11 平台。现在，Google  &lt;a href="https://www.theverge.com/2021/12/9/22827037/google-android-games-windows-pc-google-play-games"&gt;准备了应对措施&lt;/a&gt;，将 Android 游戏带到所有 Windows 平台，不限于 Windows 11。Google 将在 2022 年发布 Google Play Games app，允许  Google Play 上的游戏应用能运行在 Windows 笔记本、平板和 PC 上。Google 自己开发了该应用，没有与微软等公司合作，将允许在不同平台上持续游戏，比如在手机上玩了之后可以在 PC 上恢复继续游戏。Google 没有透露它在 Windows 上模拟 Android 应用所采用的技术。 &lt;div&gt;
  &lt;a href="http://feeds.feedburner.com/~ff/solidot?a=A8XIGymGDs0:5evA65IE47s:yIl2AUoC8zA"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/solidot?d=yIl2AUoC8zA"&gt;&lt;/img&gt;&lt;/a&gt;   &lt;a href="http://feeds.feedburner.com/~ff/solidot?a=A8XIGymGDs0:5evA65IE47s:7Q72WNTAKBA"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/solidot?d=7Q72WNTAKBA"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/61953-google-android-%E6%B8%B8%E6%88%8F</guid>
      <pubDate>Sat, 11 Dec 2021 13:00:40 CST</pubDate>
    </item>
    <item>
      <title>中国大学 MOOC Android 性能优化：冷启动优化总结</title>
      <link>https://itindex.net/detail/61661-%E4%B8%AD%E5%9B%BD%E5%A4%A7%E5%AD%A6-mooc-android</link>
      <description>&lt;p&gt;  &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#39318;&amp;#22270;.gif"&gt;   &lt;img alt="&amp;#39318;&amp;#22270;" height="118" src="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#39318;&amp;#22270;.gif" width="700"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/08/&amp;#23553;&amp;#38754;4.png"&gt;   &lt;img alt="&amp;#23553;&amp;#38754;4" height="298" src="http://techblog.youdao.com/wp-content/uploads/2021/08/&amp;#23553;&amp;#38754;4.png" width="700"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;联系我们:&lt;/strong&gt;   &lt;strong&gt;有道技术团队助手&lt;/strong&gt;：ydtech01 /   &lt;strong&gt;邮箱&lt;/strong&gt;：ydtech@rd.netease.com&lt;/p&gt;
 &lt;p&gt;本文的重点在于如何定量的排查冷启动过程中的耗时操作，并提供对应的优化思路和实践方法总结。同时本文涉及到的冷启动优化主要涵盖两个方面：Application 的性能优化和 Launcher Activity 的性能优化。&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;一、背景&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;中国大学 MOOC 是网易与高教社携手推出的在线教育平台，目前，经过长期的产品打磨和专研，在课程数量、质量以及影响力，中国大学 MOOC 已成为全球领先的中文慕课平台。同时经过此次优化，冷启动速度  &lt;strong&gt;整体提升27%。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;在我们日常开发中，随着 app 整体迭代次数增多，由于长久以来的迭代需求，android app 本身也集成了较多的第三方组件和 SDK，同时在日常迭代中，也是以业务迭代需求实现为主要目的，导致现在 app 本身，或多或少存在一些性能可优化空间。所以有必要进行性能优化，提升用户体验&lt;/p&gt;
 &lt;p&gt;此次优化，主要侧重于两个方面：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;Application 的性能优化&lt;/li&gt;
  &lt;li&gt;app 启动页性能优化&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;该文档重点不在于代码规范和业务代码逻辑导致的性能问题，而是在假设代码无明显、严重性能漏洞，并且不改变原有业务逻辑，量化性能监测数据和问题，并针对其进行优化修改。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;h2&gt;  &lt;strong&gt;二、冷启动速度优化&lt;/strong&gt;&lt;/h2&gt;
 &lt;h3&gt;  &lt;strong&gt;2.1 相关知识点&lt;/strong&gt;&lt;/h3&gt;
 &lt;h4&gt;  &lt;strong&gt;2.1.1 冷启动耗时统计&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;  &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#20195;&amp;#30721;18.png"&gt;   &lt;img alt="&amp;#20195;&amp;#30721;1" height="94" src="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#20195;&amp;#30721;18.png" width="784"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;上述 adb 命令中，几个关键参数说明：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;-S&lt;/strong&gt;：表示启动该 app 前先彻底关闭当前 app 进程&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;-W&lt;/strong&gt;：启动并输出相关耗时数据&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;packageName&lt;/strong&gt;：app 的 applicationID&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;activityName&lt;/strong&gt;：app 启动需要拉起的 Activity，如果用于统计冷启动耗时，那么该参数即为应用的第一个启动的 Activity（intent-filter 为 LAUNCHER 的 Activity）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;再执行上诉 adb 后，会成功唤起 APP，并在控制台输出三个比较关键的参数：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#22270;12.png"&gt;   &lt;img alt="&amp;#22270;1" height="112" src="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#22270;12.png" width="600"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;   &lt;strong&gt;LaunchState&lt;/strong&gt;：启动模式，上诉启动模式为冷启动&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;WaitTime&lt;/strong&gt;：系统启动应用耗时= TotalTime +系统资源启动时间（单位 ms ）&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;TotalTime&lt;/strong&gt;：应用自身启动耗时=该 Activity 启动时间+应用 application 等资源启动时间（单位 ms ）&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;对于应用层面得冷启动性能优化，我们关注的时间 TotalTime，该时间大致可以概括为：Application 构造方法→该 Activity 的 onWindowFocusChange 方法时间总和。而这个过程也可以粗略认知为，用户点击桌面图标到 app 第一个 Activity 获取焦点，业务代码执行的总时间（针对业务代码的优化，我们暂时不关心 Zygote 进程、Launcher 进程、AMS 进程的交互）。&lt;/strong&gt;&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;2.1.2 冷启动耗时堆栈观察方法：&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;在 Android API&amp;gt;=26 的系统版本中，建议使用 CPU Profile 或者 Debug.startMethodTracing 进行监控并导出 trace 文件进行分析。不管哪种方式，采集堆栈信息都有两种模式：采样模式和追踪模式。追踪模式会一直抓取数据，对设备性能要求较高。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;（1）CPU Profile&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#22270;22.png"&gt;   &lt;img alt="&amp;#22270;2" height="494" src="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#22270;22.png" width="550"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;（2）Debug.startMethodTracing&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;由于冷启动涉及到业务应用层面的时间是：该 Activity 启动时间+应用 application 等资源启动时间，所以我们在 Application 构造方法中开始采集，在第一个 Activity 的 onWindowFocusChange 中停止采集，并输出 trace文件。&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#20195;&amp;#30721;24.png"&gt;   &lt;img alt="&amp;#20195;&amp;#30721;2" height="584.5" src="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#20195;&amp;#30721;24.png" width="600"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#20195;&amp;#30721;33.png"&gt;   &lt;img alt="&amp;#20195;&amp;#30721;3" height="331" src="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#20195;&amp;#30721;33.png" width="785"&gt;&lt;/img&gt;&lt;/a&gt;   &lt;strong&gt;同时，由于该操作涉及到文件读写权限，需要手动授予 APP 该权限&lt;/strong&gt;&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;2.1.3 .trace 日志文件阅读：&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;在导出获取到 .trace 文件后，把 .trace 拖动至 androidStudio 编 辑区；或者直接浏览 CPU Profile 视图，便可对程序运行的堆栈进行分析：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#22270;31.png"&gt;   &lt;img alt="&amp;#22270;3" height="327" src="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#22270;31.png" width="800"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;上图就是 trace 文件打开后的效果，展示的是基于 CPU 使用和线程运行状况，针对启动速度的优化，需要关注的上图标注的几个点：&lt;/p&gt;
 &lt;p&gt;（1）CPU 运行时间轴：横向拖动可以选择查看的时间范围&lt;/p&gt;
 &lt;p&gt;（4）当前设备 CPU 轮转的线程：点击可以选择需要查看的线程，我们重点关注主线程&lt;/p&gt;
 &lt;p&gt;（2）当前选择线程，跟随时间轴，各个方法栈的调用情况和其耗时状况。其不同颜色分别代表&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;ul&gt;
   &lt;li&gt;    &lt;strong&gt;黄色&lt;/strong&gt;：android 系统方法（FrameWork 层代码，如果需要最终更底层的方法，需要最终 C/C++ 方法调用栈）&lt;/li&gt;
   &lt;li&gt;    &lt;strong&gt;蓝色&lt;/strong&gt;：Java JDK 方法&lt;/li&gt;
   &lt;li&gt;    &lt;strong&gt;绿色&lt;/strong&gt;：属于当前 app 进程执行的方法，包括一些类加载器和我们的业务代码（启动速度优化主要针对这一部分）&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
 &lt;p&gt;（3）各个方法栈的调用顺序和耗时情况，可以选择不用的排序方式和视图。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;所以一般排查耗时方法时，建议先通过（2）视图直观检测到耗时较为严重的方法，锁定后，在（3）视图中查看具体的方法调用顺序。&lt;/strong&gt;&lt;/p&gt;
 &lt;h3&gt;  &lt;strong&gt;2.2 优化步骤&lt;/strong&gt;&lt;/h3&gt;
 &lt;p&gt;由于在冷启动过程中，业务代码耗时主要集中在 Application 和 launcher Activity 中，所以优化过程也是分别针对这两块进行优化。&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;2.2.1 优化成果&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;使用2.1.1的方式，在优化前后，分别做了10次冷启动耗时统计，结果如下：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#34920;&amp;#26684;2.png"&gt;   &lt;img alt="&amp;#34920;&amp;#26684;2" height="590" src="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#34920;&amp;#26684;2.png" width="700"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;启动速度  &lt;strong&gt;整体提升 27%。&lt;/strong&gt;&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;2.2.2 Application 优化&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;  &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#22270;4.png"&gt;   &lt;img alt="&amp;#22270;4" height="185" src="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#22270;4.png" width="700"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;通过 trace 文件，可以直观的发现，在 application 中，耗时最长的方法是其生命周期中的 onCreate 方法，其中在 onCreate 方法中，耗时比较长的方法有：  &lt;strong&gt;initMudleFactory、initURS、Unicorn.init、initUmeng。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#22270;5.png"&gt;   &lt;img alt="&amp;#22270;5" height="184" src="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#22270;5.png" width="600"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;在 Top Down 视图中，可以更加直观的看出，此次采样，也正是这四个方法耗时最多。&lt;/p&gt;
 &lt;p&gt;通过源码排查，这是个方法，均是第三方 SDK 的初始化，同时在这几个 SDK 内部，都含有较多的 IO 操作，并且内部实现了线程管理以保证线程安全，所以可以将这几个 SDK 的初始化，放在子线程中完成。这里以友盟 SDK 为例：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;/**
* 友盟SDK中有涉及到线程不安全的地方，都自己维护了线程，保证线程安全
**/
try {
    var6 = getClass(&amp;quot;com.umeng.umzid.ZIDManager&amp;quot;);
    if (var6 == null) {
        Log.e(&amp;quot;UMConfigure&amp;quot;, &amp;quot;---&amp;gt;&amp;gt;&amp;gt; SDK 初始化失败，请检查是否集成umeng-asms-1.2.x.aar库。&amp;lt;&amp;lt;&amp;lt;--- &amp;quot;);
        (new Thread() {
            public void run() {
                try {
                    Looper.prepare();
                    Toast.makeText(var5, &amp;quot;SDK 初始化失败，请检查是否集成umeng-asms-1.2.X.aar库。&amp;quot;, 1).show();
                    Looper.loop();
                } catch (Throwable var2) {
                }

            }
        }).start();
        return;
    }
} catch (Throwable var27) {
}

/**
* 在友盟SDK内部中有很多IO操作的地方，和加锁操作，所以可以将SDK初始化操作，放在子线程中
**/
if (!TextUtils.isEmpty(var1)) {
    sAppkey = var1;
    sChannel = var2;
    UMGlobalContext.getInstance(var3);
    k.a(var3);
    if (!needSendZcfgEnv(var3)) {
        FieldManager var4 = FieldManager.a();
        var4.a(var3);
    }

    synchronized(PreInitLock) {
        preInitComplete = true;
    }
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;最终，我们可以把上面提到的几个 SDK 初始化工作放入在子线程中：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;private void initSDKAsyn(){
    new Thread(() -&amp;gt; {
        if (Util.inMainProcess()){
            // 登录
            initURS();

            if (BuildConfig.ENTERPRISE) {
                Unicorn.init(BaseApplication.this, &amp;quot;&amp;quot;, QiyuOptionConfig.options(), new QiyuImageLoader());
                initModuleRegister();
            } else {
                Unicorn.init(BaseApplication.this, &amp;quot;&amp;quot;, QiyuOptionConfig.options(), new QiyuImageLoader());
            }

            // 初始化下载服务
            try {
                initDownload();
            } catch (Exception e) {
                NTLog.f(TAG, e.toString());
            }
        }
        initModuleFactory();
        initUmeng();
    }).start();
}
&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;对于一些必须在主线程中初始化完成的 SDK，可以考虑使用 IdleHandler，在主线程空闲时，完成初始化（关于 IdleHandler 会在下面讲到）。&lt;/strong&gt;&lt;/p&gt;
 &lt;h4&gt;  &lt;strong&gt;2.2.3 Launcher Activity 优化&lt;/strong&gt;&lt;/h4&gt;
 &lt;p&gt;auncher Activity 是 WelcomeActivity，在对 Application 优化结束后，再对 WelcomeActivity 进行优化，还是和上路的思路一样，先通过 trace 文件追踪：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#22270;6.png"&gt;   &lt;img alt="&amp;#22270;6" height="337" src="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#22270;6.png" width="700"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;可以看到，在 WelcomeActivity 的 onCreate 方法中，耗时较多的三个地方，分别是：initActionBar、EventBus.register、setContentView，下面针对这三块内容，分别进行对应的优化操作：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#22270;72.png"&gt;   &lt;img alt="&amp;#22270;7" height="132" src="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#22270;72.png" width="700"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;blockquote&gt;
  &lt;p&gt;   &lt;strong&gt;（1）initActionBar&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;在上图中，可以看到，initActionBar 中最耗时的操作是 getSupportActionBar，通过研究代码发现，在WelcomeActivity中，并不需要操作 actionBar，所以直接复写父类方法，去掉 super 调用即可。&lt;/p&gt;
  &lt;p&gt;   &lt;strong&gt;（2）EventBus.register&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;EventBus 注册时，性能较差，是因为在改过程中涉及到大量的反射操作，所以对性能损耗较大。通过查看官方文档，该问题在 EventBus3.0 中得到了很好的处理，主要是通过 apt 技术增加索引，提升效率。（当前项目未升级版本，待后期优化）&lt;/p&gt;
  &lt;p&gt;   &lt;strong&gt;（3）setContentView&lt;/strong&gt;&lt;/p&gt;
  &lt;p&gt;setContentView 是 Activity 渲染布局时的必要方法，其耗时的点在于，解析 xml 布局文件时，使用了反射，所以如果 xml 布局文件非常复查的时候，可以使用androidx.asynclayoutinflater:asynclayoutinflater进行异步加载 xml 文件，使用方式如下：&lt;/p&gt;
  &lt;p&gt;   &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#20195;&amp;#30721;6.png"&gt;    &lt;img alt="&amp;#20195;&amp;#30721;6" height="171" src="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#20195;&amp;#30721;6.png" width="749"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h2&gt;  &lt;strong&gt;三、优化总体方法汇总&lt;/strong&gt;&lt;/h2&gt;
 &lt;p&gt;上面针对冷启动优化是基于当前项目本身做的步骤，这里汇总一些冷启动通用的  &lt;strong&gt;优化思路&lt;/strong&gt;：&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;（1）合理的使用异步初始化、延迟初始化和懒加载机制&lt;/strong&gt;：主要针对 Application 中各种 SDK 的初始化&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;（2）在主线程中应当避免很耗时的操作&lt;/strong&gt;，比如 IO 操作、数据库读写操作&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;（3）简化 launcher Activity 的布局结构&lt;/strong&gt;，如果非常复杂的布局，可以有以下两种方式进行优化：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;建议使用约束布局（ConstraintLayout）来减少布局嵌套避免过度渲染。&lt;/li&gt;
  &lt;li&gt;使用 androidx.asynclayoutinflater:asynclayoutinflater 进行异步加载 xml 文件。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;strong&gt;（4）合理使用 IdleHandler 进行延迟初始化&lt;/strong&gt;，使用方式如下：&lt;/p&gt;
 &lt;p&gt;  &lt;a href="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#20195;&amp;#30721;71.png"&gt;   &lt;img alt="&amp;#20195;&amp;#30721;7" height="439" src="http://techblog.youdao.com/wp-content/uploads/2021/07/&amp;#20195;&amp;#30721;71.png" width="786"&gt;&lt;/img&gt;&lt;/a&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;（5）开始严苛模式（StrictMode）&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;该模式并不能帮我们自动优化性能，而是可以帮助我们检测出我们可能无意中或者一些第三方 SDK 中做的会阻塞 Main 线程的事情（比如磁盘操作、网络操作），并将它们提醒出来，以便在开发阶段进行修复。其检测策略有线程检测策略和虚拟机检测策略，我们可以设置需要检测的操作，当代码操作违规时，可以通过 Logcat 或者直接崩溃的形式提醒我们，  &lt;strong&gt;具体使用方式如下&lt;/strong&gt;：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;/**
 * 开启严苛模式，当代码有违规操作时，可以通过Logcat或崩溃的方式提醒我们
 */
private void startStrictMode() {
    if (BuildConfig.DEBUG) { //一定要在Debug模式下使用，避免在生产环境中发生不必要的奔溃和日志输出

        //线程检测策略
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                .detectDiskReads()  //检测主线程磁盘读取操作
                .detectDiskWrites() //检测主线程磁盘写入操作
                .detectNetwork() //检测主线程网络请求操作
                .penaltyLog() //违规操作以log形式输出
                .build());

        //虚拟机检测策略
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                .detectLeakedSqlLiteObjects() //检测SqlLite泄漏
                .detectLeakedClosableObjects() //检测未关闭的closable对象泄漏
                .penaltyDeath() //发生违规操作时，直接奔溃
                .build());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>未分类</category>
      <guid isPermaLink="true">https://itindex.net/detail/61661-%E4%B8%AD%E5%9B%BD%E5%A4%A7%E5%AD%A6-mooc-android</guid>
      <pubDate>Thu, 05 Aug 2021 10:09:04 CST</pubDate>
    </item>
    <item>
      <title>Google用AAB取代Android APK 讓Windows 11的Android支援生變？亞馬遜表示會搞定</title>
      <link>https://itindex.net/detail/61619-google-aab-%E5%8F%96%E4%BB%A3</link>
      <description>Android就表示未來Google Play將全面改用AAB格式來，這也讓很多微軟的用戶感到有疑慮，那麼Windows 11怎麼辦？ &lt;p&gt;微軟先前在 Windows 11 發表會上表示，未來Windows 11 將能執行 Android 應用程式，讓很多用戶都感到興奮。不過，就在他們宣布之後沒幾天，Android就表示未來Google Play將全面改用AAB格式來，這也讓很多微軟的用戶感到有疑慮，那麼Windows 11怎麼辦？&lt;/p&gt;
 &lt;p&gt;在Windows 11上的 Android 應用程式，主要是從 Microsoft Store 提供，但實際下載過程將通過亞馬遜應用商店（Amazon Appstore）進行。&lt;/p&gt;
 &lt;p&gt;微軟正在將亞馬遜應用商店串接到微軟應用程式商店中。使用者可以在Windows 11裡面直接尋找 Android 應用程式，打開微軟應用程式商店中的亞馬遜應用程式商店，然後點擊幾下就可以安裝應用。&lt;/p&gt;
 &lt;p&gt;6 月下旬，Google確認將要求應用開發者使用新的 Android App Bundle (AAB) 標準，而不是 Android Application Package (APK)。據Google表示，此政策適用於 Play 商店中的新應用程式，開發者將被要求未來的新應用程式要放棄當前的 APK 格式。&lt;/p&gt;
 &lt;p&gt;預計這種安裝格式將減少APK的可用性，App Bundles格式本身有一些優勢，包括較小的分發模組。這種做法的一個缺點是，新格式是針對特定裝置進行微調的，這意味著提取應用程式並使其可在其他地方分發將更加困難。目前，APK文件將在符合架構要求的大多數裝置上都可以直接執行和安裝。&lt;/p&gt;
 &lt;p&gt;這一變化將影響Windows 11上的應用程式的可用性，因為安裝將變得更加複雜。雖然從技術上講，Android APK 應用程式不會停止使用，因為 Android 將繼續直支援現有格式，但在Google開始對 Play 商店實施 AAB 格式後，開發人員可能會停止以 APK 格式發佈應用程式。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Google&amp;#29992;AAB&amp;#21462;&amp;#20195;Android APK &amp;#35731;Windows 11&amp;#30340;Android&amp;#25903;&amp;#25588;&amp;#29983;&amp;#35722;&amp;#65311;&amp;#20126;&amp;#39340;&amp;#36956;&amp;#34920;&amp;#31034;&amp;#26371;&amp;#25630;&amp;#23450;" height="471" src="https://cdn0.techbang.com/system/images/602939/original/3842390bbdcab66da94f5474b904ea44.jpeg?1626570129" width="700"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;不過，這對 Windows 11 來說應該並不是問題。亞馬遜已經確認，他們的應用程式商店將會支援 Android App Bundle (AAB)。亞馬遜表示「亞馬遜應用程式商店正在積極針對新的格式進行支援。」&lt;/p&gt;
 &lt;p&gt;與Google Play不同，App Bundles在Amazon App Store中算是一種可選的格式而非強制性的，開發者可以繼續為他們的應用程式上傳APK文件，或者在新格式得到支援後使用。&lt;/p&gt;
 &lt;p&gt;Google Play和Amazon Appstore要求的另一個區別是，Google要求開發者提交他們的應用程式簽名密鑰，而Amazon則不需要。&lt;/p&gt;
 &lt;p&gt;想要解決AAB格式的問題時，無疑Google Play具有優勢，但微軟看來是談不到與Google的合作，也只能透過亞馬遜的App Store在Windows上提供Android應用程式，至少可以幫助微軟拉近應用程式之間的距離。&lt;/p&gt;
 &lt;p&gt;亞馬遜應用程式商店中的大多數應用程式，在獲得開發者許可後，未來都可以在 Windows 11 上下載執行。&lt;/p&gt; &lt;a href="https://www.facebook.com/TKbang"&gt;加入T客邦Facebook粉絲團&lt;/a&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>新聞</category>
      <guid isPermaLink="true">https://itindex.net/detail/61619-google-aab-%E5%8F%96%E4%BB%A3</guid>
      <pubDate>Sun, 18 Jul 2021 18:55:00 CST</pubDate>
    </item>
    <item>
      <title>Android WebView与Native通信总结</title>
      <link>https://itindex.net/detail/61456-android-webview-native</link>
      <description>&lt;div&gt;  &lt;p&gt;当前移动端App的开发很多都需要内嵌WebView来方便业务的快速开展，特别是电商App中，业务变化快，活动多。仅仅依靠native的开发方式难以满足快速的业务发展，于是混合开发模式便出现。当前比较知名的有   &lt;code&gt;Cordova&lt;/code&gt;,    &lt;code&gt;Ionic&lt;/code&gt;, 国内的有   &lt;code&gt;Appcan&lt;/code&gt;,    &lt;code&gt;APICloud&lt;/code&gt;开发平台，这几种都是依赖于WebView的实现。而Facebook的   &lt;code&gt;React Native&lt;/code&gt;和阿里的   &lt;code&gt;Weex&lt;/code&gt;是混合开发的另一种实现，   &lt;code&gt;React Native&lt;/code&gt;和   &lt;code&gt;Weex&lt;/code&gt;可以让原生开发者像H5开发一样写前端的代码，然后通过自己的SDK渲染成原生的组件，不依赖于   &lt;code&gt;WebView&lt;/code&gt;。本文主要总结一下当前   &lt;code&gt;WebView&lt;/code&gt;和native的交互方式。&lt;/p&gt;
  &lt;p&gt;Android中   &lt;code&gt;WebView&lt;/code&gt;和   &lt;code&gt;JavaScript&lt;/code&gt;的交互，其实就是Android native与网页中的   &lt;code&gt;Javascript&lt;/code&gt;之间的交互, 所以搞清楚了它们之间数据是如何传递的就明白了。以下从两个方面进行介绍：&lt;/p&gt;
  &lt;h2&gt;Native 向 Javascript 发送数据&lt;/h2&gt;
  &lt;p&gt;Native 向 JavaScript发送数据有两种方式, 一种是   &lt;code&gt;evaluateJavascript&lt;/code&gt; 另一种是   &lt;code&gt;loadUrl&lt;/code&gt;。区别在于   &lt;code&gt;evaluateJavascript&lt;/code&gt;比   &lt;code&gt;loadUrl&lt;/code&gt;更高效，   &lt;code&gt;evaluateJavascript&lt;/code&gt;在android 4.4之后才能用，该方法的执行不会使页面刷新, 而   &lt;code&gt;loadUrl&lt;/code&gt;则会。所以通常我们如下使用：&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.KITKAT) {
    evaluateJavascript(jsCommand, null);
} else {
    loadUrl(jsCommand);
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;当然，如果想要直接获得javascript代码的执行结果，我们可以这样写:&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;String command = &amp;quot;ABC&amp;quot;;
webView.evaluateJavascript(&amp;quot;(function() { return &amp;quot; + command + &amp;quot;; })();&amp;quot;, new ValueCallback&amp;lt;String&amp;gt;() {
    @Override
    public void onReceiveValue(String result) {
        // 此处的result便是 ABC
    }
});
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;h2&gt;Javascript 向 Native 发送数据&lt;/h2&gt;
  &lt;p&gt;Javascript向Native发送数据有4种方式，第一种方式是借助   &lt;code&gt;webChromClient&lt;/code&gt;中的   &lt;code&gt;onJsAlert()&lt;/code&gt;,    &lt;code&gt;onJsPromot()&lt;/code&gt;的方法来获取Javascript相关数据。第二种方式是采用覆盖   &lt;code&gt;shouldOverrideUrlLoading&lt;/code&gt;方法，拦截url协议。第三种是最方便的,也就是   &lt;code&gt;@JavascriptInterface&lt;/code&gt;方案, 现在大多数App都会用到这种方式, 后面会详细介绍。最后一种是利用在   &lt;code&gt;webView&lt;/code&gt;中嵌入   &lt;code&gt;iframe&lt;/code&gt;的方式，通过更新   &lt;code&gt;iframe&lt;/code&gt;的url。比较出名的混合框架   &lt;a href="https://github.com/lzyzsd/JsBridge" rel="nofollow noopener noreferrer" target="_blank"&gt;    &lt;code&gt;JsBridge&lt;/code&gt;&lt;/a&gt;之前就是采用这种方式，现已改成采用   &lt;code&gt;@JavascriptInterface&lt;/code&gt;这种方式了。以下简单介绍一下各种方式的使用。&lt;/p&gt;
  &lt;h3&gt;   &lt;code&gt;onJsPrompt&lt;/code&gt;&lt;/h3&gt;
  &lt;p&gt;   &lt;code&gt;webChromeClient&lt;/code&gt;中提供了   &lt;code&gt;onJsAlert&lt;/code&gt;,    &lt;code&gt;onJsPrompt&lt;/code&gt;方法，方便开发者重写Javascript中的   &lt;code&gt;alert&lt;/code&gt;,    &lt;code&gt;prompt&lt;/code&gt;方法对应的行为。我们可以在这两个方法中任选一个做为native和js进行交互的桥梁。通常我们借助于   &lt;code&gt;onJsPrompt&lt;/code&gt; 方法来实现, 就是因为在js中，这个方法通常我们用得比较少。而对于   &lt;code&gt;onJsAlert()&lt;/code&gt;, 当调用js中的   &lt;code&gt;alert()&lt;/code&gt;时会触发，我们可以通过重写这个方法来实现自定义的提示View&lt;/p&gt;
  &lt;p&gt;但是这种方式对传入的数据量有限制，和手机的WebView版本有关，以我的测试机为例，在   &lt;code&gt;oppo reno&lt;/code&gt;手机 android 10上面, 其传递数据最多只能是10k。 而用   &lt;code&gt;@JavascriptInterface&lt;/code&gt; 方案, 传递的数据最多可达20 - 30M&lt;/p&gt;
  &lt;p&gt;我们来看前端网页的写法, 直接调用   &lt;code&gt;prompt&lt;/code&gt;函数&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;var data = prompt(&amp;quot;native://getUserInfo?id=1&amp;quot;);
console.log(&amp;apos;data:&amp;apos; + data);
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;在为WebView设置   &lt;code&gt;WebChromeClient&lt;/code&gt;的时候重写   &lt;code&gt;onJsPrompt&lt;/code&gt;方法，如下：&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;
 @Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
    Uri uri = Uri.parse(message);
    //如果是调nativeAPI.
    if (url.startsWith(&amp;quot;native://&amp;quot;)) {
        result.confirm(&amp;quot;call natvie api success&amp;quot;);
        return true;
    }
    return super.onJsPrompt(view, url, message, defaultValue, result);
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;h3&gt;   &lt;code&gt;shouldOverrideUrlLoading&lt;/code&gt;&lt;/h3&gt;
  &lt;p&gt;前端页面的Js代码：&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;document.location=&amp;quot;native://getUserInfo?id=1&amp;quot;;
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;native层面在为WebView设置   &lt;code&gt;WebViewClient&lt;/code&gt;对象时，我们需要重写   &lt;code&gt;shouldOverrideUrlLoading&lt;/code&gt;方法。需要注意的是，   &lt;code&gt;WebViewClient&lt;/code&gt;中有两个   &lt;code&gt;shouldOverrideUrlLoading&lt;/code&gt;方法的定义:&lt;/p&gt;
  &lt;ul&gt;
   &lt;li&gt;    &lt;code&gt;public boolean shouldOverrideUrlLoading(WebView view, String url)&lt;/code&gt;&lt;/li&gt;
   &lt;li&gt;    &lt;code&gt;public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
  &lt;p&gt;其中上面一个在sdk中已被标记   &lt;code&gt;Deprecated&lt;/code&gt;, 下面一个是在android 7.0中才引入的，所以为了避免兼容性问题。在使用时，建议这两个方法都重写。&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
    //如果是调nativeAPI.
    if (url.startsWith(&amp;quot;native://&amp;quot;)) {
        Log.i(&amp;quot;CommonWebViewClient&amp;quot;, &amp;quot;shouldOverrideUrlLoading execute------&amp;gt;&amp;quot;)
        return true;
    }
    return super.shouldOverrideUrlLoading(view, url);
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;h3&gt;   &lt;code&gt;@JavascriptInterface&lt;/code&gt;&lt;/h3&gt;
  &lt;p&gt;在 Android 4.2以下有安全漏洞, 但目前我们的app大部份最小支持版本都已经升到5.0了，这个可以忽略，当然感兴趣可以自己搜索。&lt;/p&gt;
  &lt;p&gt;在native层面，我们需为要WebView注入一个对象，用来处理两边的数据交互。注入方式如下：&lt;/p&gt;
  &lt;ul&gt;
   &lt;li&gt;首先定义一个类来处理两边的交互:&lt;/li&gt;
&lt;/ul&gt;
  &lt;pre&gt;   &lt;code&gt;public class HybridAPI {
    public static final String TAG = &amp;quot;HybridAPI&amp;quot;;

    @JavascriptInterface
    public void sendToNative(final String message) {
        Log.i(TAG, &amp;quot;get data from js------------&amp;gt;&amp;quot; + message);

    }
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;ul&gt;
   &lt;li&gt;在    &lt;code&gt;WebView&lt;/code&gt;中注入这个类的实例&lt;/li&gt;
&lt;/ul&gt;
  &lt;pre&gt;   &lt;code&gt;HybridAPI hybridAPI = new HybridAPI();
webview.addJavascriptInterface(hybridAPI, &amp;quot;HybridAPI&amp;quot;)
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;在网页中直接用如下代码便可以将数据发送到native端&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt; HybridAPI.sendToNative(&amp;apos;Hello&amp;apos;);
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;h3&gt;   &lt;code&gt;iframe&lt;/code&gt;&lt;/h3&gt;
  &lt;p&gt;我们还可以利用   &lt;code&gt;iframe&lt;/code&gt;进行请求伪造向native端发送数据的。思路是向网页中添加一个   &lt;code&gt;iframe&lt;/code&gt;控件，通过修改其   &lt;code&gt;src&lt;/code&gt;属性，触发native端的   &lt;code&gt;shouldOverrideUrlLoading&lt;/code&gt;方法的执行, 同样，native端通过重写该方法，去拿到js端传过来的数据。具体操作方式如下：&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;var iframe = document.createElement(&amp;apos;iframe&amp;apos;);
iframe.style.display = &amp;apos;none&amp;apos;;
document.documentElement.appendChild(iframe);
iframe.src=&amp;quot;native://getUserInfo?id=1&amp;quot;;
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;在操作完成后，我们再从当前的dom结构中移除这个组件。&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;setTimeout(function() {
    iframe &amp;amp;&amp;amp; iframe.parentNode &amp;amp;&amp;amp; iframe.parentNode.removeChild(iframe);
}, 100);
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;h2&gt;具体实践&lt;/h2&gt;
  &lt;p&gt;在前面总结了WebView和Native交互的几种方案。但距离实际项目使用还有一段距离，在实际项目开发中还有很多问题需要考虑。如：&lt;/p&gt;
  &lt;ul&gt;
   &lt;li&gt;交互的规则如何定义&lt;/li&gt;
   &lt;li&gt;数据如何传递&lt;/li&gt;
   &lt;li&gt;调用之后，如何拿到回调的结果&lt;/li&gt;
   &lt;li&gt;对于Javascript的请求，native端应该如何设计?&lt;/li&gt;
   &lt;li&gt;....&lt;/li&gt;
&lt;/ul&gt;
  &lt;p&gt;native端向JavaScript发送消息只有   &lt;code&gt;loadUrl&lt;/code&gt;,    &lt;code&gt;evaluateJavascript&lt;/code&gt;这两种方式。Javascript向native端发送信息可以利用   &lt;code&gt;onJsPrompt&lt;/code&gt;,    &lt;code&gt;@JavascriptInterface&lt;/code&gt;,    &lt;code&gt;shouldOverrideUrlLoading&lt;/code&gt;等几种方案，以下
我们通过采用   &lt;code&gt;@JavascriptInterface&lt;/code&gt;这种方式(也就是大家通常说的注解方案)为例来看看如何解决实际项目开发中碰到的问题。&lt;/p&gt;
  &lt;h3&gt;交互的规则&lt;/h3&gt;
  &lt;p&gt;首先我们来定义两端的交互规则。&lt;/p&gt;
  &lt;h4&gt;Javascript向native发数据：&lt;/h4&gt;
  &lt;p&gt;我们约定在H5中采用   &lt;code&gt;HybridAPI.sendToNative&lt;/code&gt;方法向native端发送数据，于是我们需要在native端做如下支持：&lt;/p&gt;
  &lt;ul&gt;
   &lt;li&gt;定义一个    &lt;code&gt;HybridAPI&lt;/code&gt;类，并向WebView中注册&lt;/li&gt;
&lt;/ul&gt;
  &lt;pre&gt;   &lt;code&gt;HybridAPI hybridAPI = new HybridAPI(this);
webview.addJavascriptInterface(hybridAPI, &amp;quot;HybridAPI&amp;quot;);
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;ul&gt;
   &lt;li&gt;在    &lt;code&gt;HybridAPI&lt;/code&gt;类中定义一个方法    &lt;code&gt;sendToNative&lt;/code&gt;, 该方法暴露给Javascript用来给native发送数据&lt;/li&gt;
&lt;/ul&gt;
  &lt;pre&gt;   &lt;code&gt;@JavascriptInterface
public void sendToNative(final String message) {
    Log.i(TAG, &amp;quot;get data from js------------&amp;gt;&amp;quot; + message);

}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;h4&gt;native层向Javascript发数据：&lt;/h4&gt;
  &lt;pre&gt;   &lt;code&gt;public final String TO_JAVASCRIPT_PREFIX = &amp;quot;javascript:HybridAPI.onReceiveData(&amp;apos;%s&amp;apos;)&amp;quot;;

public void sendToJavaScript(Map message) {
    String str = new Gson().toJson(message);
    final String jsCommand = String.format(TO_JAVASCRIPT_PREFIX, escapeString(str));

    if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.KITKAT) {
        evaluateJavascript(jsCommand, null);
    } else {
        loadUrl(jsCommand);
    }
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;在H5中，我们这样写, 当native向Javascript发送数据时，便会触发Javascript中的   &lt;code&gt;Hybrid.onReceiveData&lt;/code&gt;方法, 该方法就能接收到native层传过来的数据&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;HybridAPI.onReceiveData = function(message) {
    console.log(&amp;apos;[response from native]&amp;apos; + message);
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;h3&gt;数据结构的定义&lt;/h3&gt;
  &lt;p&gt;在上面我们已经基于   &lt;code&gt;@JavascriptInterface&lt;/code&gt;方案完成了native与WebView间通信机制的实现，双方可以交换数据，但开发的时候需要考虑更多问题。比如，如果是Javascript向native发送数据，需要将数据转换成一个字符串，然后再将字符串发给native, native再去解析这个字符串，找到对应的处理方法，提取出相关的业务参数，再进行相应的处理。所以我们需要定义这个字符串的数据结构。&lt;/p&gt;
  &lt;p&gt;在上面我们已经约定了，H5端可以采用   &lt;code&gt;HybridAPI.sendToNative&lt;/code&gt;向native发送数据，该方法只有一个字符串参数, 以   &lt;code&gt;获取用户信息&lt;/code&gt;这个业务功能为例，我们的字符串参数是   &lt;code&gt;native://getUserInfo?id=1&lt;/code&gt;，这个字符串中的   &lt;code&gt;getUserInfo&lt;/code&gt;表示当前通信的目的或行为(为了拿用户信息)，    &lt;code&gt;?&lt;/code&gt; 后面的   &lt;code&gt;id=1&lt;/code&gt; 表示的是参数（用户id为1), 如果参数多了，这个字符串会更长，再如果上面涉及到中文的转码，其可读性会大大降低，所以这种交互方式不够直观和友好，我们期望用户采用下面这个方法去与native通信:&lt;/p&gt;
  &lt;p&gt;   &lt;code&gt;HybridAPI.invoke(methodName, params, callbackFun)&lt;/code&gt;&lt;/p&gt;
  &lt;ul&gt;
   &lt;li&gt;    &lt;code&gt;methodName&lt;/code&gt;: 当前通信的行为&lt;/li&gt;
   &lt;li&gt;    &lt;code&gt;params&lt;/code&gt;: 传递的参数&lt;/li&gt;
   &lt;li&gt;    &lt;code&gt;callbackFun&lt;/code&gt;: 接收native端的返回数据&lt;/li&gt;
&lt;/ul&gt;
  &lt;p&gt;于是，我们在js层面进行一层的封装&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;var callbackId = 0;
var callbackFunList = {}
HybridAPI.invoke = function(method, params, callbackFun) {
    var message = {
        method,
        params
    }
    if (callbackFun) {
        callbackId  = callbackId + 1;
        message.id = &amp;apos;Hybrid_CB_&amp;apos; + callbackId;
        callbackFunList[callbackId] = callbackFun
    }
    HybridAPI.sendToNative(JSON.stringify(message));
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;最终还是调用的是   &lt;code&gt;sendToNative&lt;/code&gt;与native层进行通信，但是采用   &lt;code&gt;HybridAPI.invoke&lt;/code&gt;方法对开发者更加友好。&lt;/p&gt;
  &lt;p&gt;由于需要在执行成功后调用回调函数。为此在发送消息的时候先把   &lt;code&gt;callbackFun&lt;/code&gt;保存起来，在执行成功后再响应。
当Javascript请求发送到native层时，会触发   &lt;code&gt;sendToNative&lt;/code&gt;方法，在该方法中, 我们来解析前端的数据：&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;@JavascriptInterface
public void sendToNative(final String message) {
    JSONObject object = DataUtil.str2JSONObject(message);
    if (object == null) {
        return;
    }
    final String callbackId = DataUtil.getStrInJSONObject(object, &amp;quot;id&amp;quot;);
    final String method = DataUtil.getStrInJSONObject(object, &amp;quot;method&amp;quot;);
    final String params = DataUtil.getStrInJSONObject(object, &amp;quot;params&amp;quot;);

    handleAPI(method, params, callbackId);
}

private void handleAPI(String method, String params, String callbackId)  {
    if (&amp;quot;getDeviceInfo&amp;quot;.equals(method)) {
        getDeviceInfo();
    } else if (&amp;quot;getUserInfo&amp;quot;.equals(method)) {
        getUserInfo();
    } else if (&amp;apos;login&amp;apos;.equals(method)) {
        login();
    }
    ....
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;native端在处理完成后，再调用   &lt;code&gt;evaluateJavascript&lt;/code&gt;或   &lt;code&gt;loadUrl&lt;/code&gt;方法，反馈给前端。操作流程示例：&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;//指定了js端的接收入口 
public final String TO_JAVASCRIPT_PREFIX = &amp;quot;javascript:HybridAPI.onReceiveData(&amp;apos;%s&amp;apos;)&amp;quot;;


public void callJs() {
    Map responseData = new HashMap&amp;lt;&amp;gt;();
    responseData.put(&amp;quot;error&amp;quot;, error);
    responseData.put(&amp;quot;data&amp;quot;, result);
    //回调函数的id标识，返回给js,这样才能找到对应的回调函数
    responseData.put(&amp;quot;id&amp;quot;, callbackId);
    sendToJavaScript(responseData);
}

public void sendToJavaScript(Map message) {
    String str = new Gson().toJson(message);
    final String jsCommand = String.format(TO_JAVASCRIPT_PREFIX, escapeString(str));

    if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.KITKAT) {
        evaluateJavascript(jsCommand, null);
    } else {
        loadUrl(jsCommand);
    }
}

// 转义
private String escapeString(String javascript) {
    String result;
    result = javascript.replace(&amp;quot;\\&amp;quot;, &amp;quot;\\\\&amp;quot;);
    result = result.replace(&amp;quot;\&amp;quot;&amp;quot;, &amp;quot;\\\&amp;quot;&amp;quot;);
    result = result.replace(&amp;quot;\&amp;apos;&amp;quot;, &amp;quot;\\\&amp;apos;&amp;quot;);
    result = result.replace(&amp;quot;\n&amp;quot;, &amp;quot;\\n&amp;quot;);
    result = result.replace(&amp;quot;\r&amp;quot;, &amp;quot;\\r&amp;quot;);
    result = result.replace(&amp;quot;\f&amp;quot;, &amp;quot;\\f&amp;quot;);
    return result;
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;在上面的   &lt;code&gt;callJs&lt;/code&gt;方法中组织好相关的数据，然后利用   &lt;code&gt;Gson&lt;/code&gt;进行序列化，再转进行字符串的转义，最终调用   &lt;code&gt;evaluateJavascript&lt;/code&gt;或者   &lt;code&gt;loadUrl&lt;/code&gt;来传递给js。于是js端便可以利用   &lt;code&gt;HybridAPI.onReceiveData&lt;/code&gt;来接收到。&lt;/p&gt;
  &lt;p&gt;还记得这段代码中定义的   &lt;code&gt;callbackFunList&lt;/code&gt;吗？在上面native给js返回数据的时候，会带上一个   &lt;code&gt;id&lt;/code&gt;, 我们可以根据这个id找到本次通信的回调函数，然后将数据回调过去。&lt;/p&gt;
  &lt;blockquote&gt;
   &lt;pre&gt;    &lt;code&gt;var callbackId = 0;
var callbackFunList = {} //看这里
HybridAPI.invoke = function(method, params, callbackFun) {
   var message = {
       method,
      params
   }
   if (callbackFun) {
       callbackId  = callbackId + 1;
       message.id = &amp;apos;Hybrid_CB_&amp;apos; + callbackId;
       callbackFunList[callbackId] = callbackFun
   }
   HybridAPI.sendToNative(JSON.stringify(message));
}
复制代码&lt;/code&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
  &lt;p&gt;所以，我们js端接收数据，可能是这样子：&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;HybridAPI.onReceiveData = function(message) {
    var callbackFun = this.callbackFunList[message.id];
    if (callbackFun) {
      callbackFun(message.error || null, message.data);
    }
    delete this.callbackFunList[message.id];
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;再回到我们上面的   &lt;code&gt;获取用户信息&lt;/code&gt;这个业务功能，我们的写法就会是这样子了：&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;HybridAPI.invoke(&amp;apos;getUserInfo&amp;apos;, {&amp;quot;id&amp;quot;: &amp;quot;1&amp;quot;}, function(error, data) {
    if (error) {
        console.log(&amp;apos;获取用户信息失败&amp;apos;);
    } else {
        console.log(&amp;apos;username:&amp;apos; + data.username + &amp;apos;, age:&amp;apos; + data.age);
    }
});
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;至此，我们就将一具完整的数据通信流程实现了，由js端用   &lt;code&gt;HybridAPI.invoke(method, params, callbackFun)&lt;/code&gt;来向native端来发送数据，native处理完毕后，js端通过   &lt;code&gt;callbackFun&lt;/code&gt;来接收数据。&lt;/p&gt;
  &lt;h2&gt;改进&lt;/h2&gt;
  &lt;p&gt;在上面的java代码中，我们可以看到，native层的入口是   &lt;code&gt;sendToNative&lt;/code&gt;方法，该方法中解析传入的字符串，再交给   &lt;code&gt;handleAPI&lt;/code&gt;方法来处理&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;@JavascriptInterface
public void sendToNative(final String message) {
    JSONObject object = DataUtil.str2JSONObject(message);
    if (object == null) {
        return;
    }
    final String callbackId = DataUtil.getStrInJSONObject(object, &amp;quot;id&amp;quot;);
    final String method = DataUtil.getStrInJSONObject(object, &amp;quot;method&amp;quot;);
    final String params = DataUtil.getStrInJSONObject(object, &amp;quot;params&amp;quot;);

    handleAPI(method, params, callbackId);
}

private void handleAPI(String method, String params, String callbackId)  {
    if (&amp;quot;getDeviceInfo&amp;quot;.equals(method)) {
        getDeviceInfo();
    } else if (&amp;quot;getUserInfo&amp;quot;.equals(method)) {
        getUserInfo();
    } else if (&amp;apos;login&amp;apos;.equals(method)) {
        login();
    }
    ....
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;我们会发现，随着业务的发展，项目的迭代，js端可能会需要native提供越来越多的能力，所以我们的   &lt;code&gt;handleAPI&lt;/code&gt;方法中就会有越来越多的   &lt;code&gt;if...else if...&lt;/code&gt;了。&lt;/p&gt;
  &lt;p&gt;于是，我们可以按业务来划分，新建一个   &lt;code&gt;UserController&lt;/code&gt;类来处理   &lt;code&gt;getUserInfo&lt;/code&gt;,    &lt;code&gt;login&lt;/code&gt;,    &lt;code&gt;logout&lt;/code&gt;这种与用户相关的native 接口。新建一个   &lt;code&gt;DeviceController&lt;/code&gt;来处理类似于   &lt;code&gt;getDeviceInfo&lt;/code&gt;,    &lt;code&gt;getDeviceXXX&lt;/code&gt;,... 等与设备信息相关的接口。然后我们再维护一个controller list, 每次调用js api的时候从这个list里面去找对应的 controller中的方法处理。&lt;/p&gt;
  &lt;p&gt;这样，就可以把具体的业务处理方法抽取出来。然而即便这样，还是避免不了在每个Controller中去写一段这个   &lt;code&gt;if...else if ...&lt;/code&gt;这种代码。于是，其实我们可以很自然的想到用反射来做点事。&lt;/p&gt;
  &lt;p&gt;我们和H5开发约定好了，如果需要获取用户的信息，就调用   &lt;code&gt;getUserInfo&lt;/code&gt;方法，这个方法名始终不变。同时，我们在Java端这样定义   &lt;code&gt;UserController&lt;/code&gt;:&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;public class UserController implements IController{

    private volatile static UserController instance;
    private UserController() {}

    public static UserController getInstance() {
        if (instance == null) {
            synchronized(UserController.class) {
                if (instance == null) {
                    instance = new UserController();
                }
            }
        }
        return instance;
    }

    @APIMethod
    public UserInfo getUserInfo(Map params, String callbackId) {
        //TODO
    }

    @APIMethod
    public void login(Map params, INativeCallback callback) {
        //TODO
    }

    @APIMethod
    public boolean logout(Map params, INativeCallback callback) {
        //TODO
    }
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;我们将该   &lt;code&gt;UserController&lt;/code&gt;添加到上面提到的controller list中，然后我们在handleAPI方法中:&lt;/p&gt;
  &lt;pre&gt;   &lt;code&gt;private void handleNativeAPI(String methodName,  String params, String callback) {
    for (IController controller : controllerList) {
        Method[] methods = controller.getClass().getDeclaredMethods();
        for (Method method : methods) {
            Annotation[] annotations = method.getAnnotations();
            for (Annotation annotation : annotations) {
                // 获取注解的具体类型
                Class annotationType = annotation.annotationType();
                if (method.getName().equals(methodName) &amp;amp;&amp;amp;  APIMethod.class == annotationType) {
                    try {
                        Map map = DataUtil.jsonStr2Map(params);
                        method.invoke(controller, map, callback);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                    return;
                }
            }
        }

    }
}
复制代码&lt;/code&gt;&lt;/pre&gt;
  &lt;p&gt;后面，每当新增一个交互的方法时，我们只需要在对应的java类中写一个方法，并用   &lt;code&gt;@APIMethod&lt;/code&gt;标识就可以。&lt;/p&gt;
  &lt;p&gt;以上我们总结了WebView与native通信的几种方式，并结合具体实践给出相应的实现思路，当然因为篇幅原因，这里并没有面面俱到。比如：&lt;/p&gt;
  &lt;ul&gt;
   &lt;li&gt;如何实现H5端监听native端的某个事件的功能？&lt;/li&gt;
   &lt;li&gt;H5端监听native事件后，进行相应的操作，如何将操作的结果再返给native?&lt;/li&gt;
   &lt;li&gt;如果js端调了一个不存的native的方法，应该如何处理？&lt;/li&gt;
   &lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
  &lt;p&gt;如果仔细理解了前面介绍的两端通信方式，实现上面的这些功能应该不是问题。但如果想把代码更好的封装，使开发者用起来更舒服，那就需要下一点功夫了。&lt;/p&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/61456-android-webview-native</guid>
      <pubDate>Sat, 22 May 2021 15:26:40 CST</pubDate>
    </item>
    <item>
      <title>Android 面试之必问高级知识点</title>
      <link>https://itindex.net/detail/61419-android-%E9%9D%A2%E8%AF%95-%E7%9F%A5%E8%AF%86</link>
      <description>&lt;p&gt;  &lt;a href="https://segmentfault.com/a/1190000039983902"&gt;Android 面试之必问Java基础&lt;/a&gt;  &lt;br /&gt;  &lt;a href="https://segmentfault.com/a/1190000039960026"&gt;Android 面试之必问Android基础知识&lt;/a&gt;&lt;/p&gt; &lt;h1&gt;1，编译模式&lt;/h1&gt; &lt;h2&gt;1.1 概念&lt;/h2&gt; &lt;p&gt;在Android早期的版本中，应用程序的运行环境是需要依赖Dalvik虚拟机的。不过，在后来的版本（大概是4.x版本），Android的运行环境却换到了 Android Runtime，其处理应用程序执行的方式完全不同于 Dalvik，Dalvik 是依靠一个 Just-In-Time (JIT) 编译器去解释字节码。&lt;/p&gt; &lt;p&gt;不过，Dalvik模式下，开发者编译后的应用代码需要通过一个解释器在用户的设备上运行，这一机制并不高效，但让应用能更容易在不同硬件和架构上运 行。ART 则完全改变了这套做法，在应用安装时就预编译字节码到机器语言，这一机制叫 Ahead-Of-Time (AOT）编译。在移除解释代码这一过程后，应用程序执行效率更高、启动也更快。&lt;/p&gt; &lt;h2&gt;1.2 AOT优点&lt;/h2&gt; &lt;p&gt;下面是AOT编译方式的一些优点：&lt;/p&gt; &lt;h3&gt;1.2.1 预先编译&lt;/h3&gt; &lt;p&gt;ART 引入了预先编译机制，可提高应用的性能。ART 还具有比 Dalvik 更严格的安装时验证。在安装时，ART 使用设备自带的 dex2oat 工具来编译应用。该实用工具接受 DEX 文件作为输入，并为目标设备生成经过编译的应用可执行文件，该工具能够顺利编译所有有效的 DEX 文件。&lt;/p&gt; &lt;h3&gt;1.2.2 垃圾回收优化&lt;/h3&gt; &lt;p&gt;垃圾回收 (GC) 可能有损于应用性能，从而导致显示不稳定、界面响应速度缓慢以及其他问题。ART模式从以下几个方面优化了垃圾回收的策略：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;只有一次（而非两次）GC 暂停&lt;/li&gt;  &lt;li&gt;在 GC 保持暂停状态期间并行处理&lt;/li&gt;  &lt;li&gt;在清理最近分配的短时对象这种特殊情况中，回收器的总 GC 时间更短&lt;/li&gt;  &lt;li&gt;优化了垃圾回收的工效，能够更加及时地进行并行垃圾回收，这使得 GC_FOR_ALLOC 事件在典型用例中极为罕见&lt;/li&gt;  &lt;li&gt;压缩 GC 以减少后台内存使用和碎片&lt;/li&gt;&lt;/ul&gt; &lt;h3&gt;1.2.3 开发和调试方面的优化&lt;/h3&gt; &lt;p&gt;  &lt;strong&gt;支持采样分析器&lt;/strong&gt;  &lt;br /&gt;一直以来，开发者都使用 Traceview 工具（用于跟踪应用执行情况）作为分析器。虽然 Traceview 可提供有用的信息，但每次方法调用产生的开销会导致 Dalvik 分析结果出现偏差，而且使用该工具明显会影响运行时性能ART 添加了对没有这些限制的专用采样分析器的支持，因而可更准确地了解应用执行情况，而不会明显减慢速度。支持的版本从KitKat （4.4）版本开始，为 Dalvik 的 Traceview 添加了采样支持。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;支持更多调试功能&lt;/strong&gt;  &lt;br /&gt;ART 支持许多新的调试选项，特别是与监控和垃圾回收相关的功能。例如，查看堆栈跟踪中保留了哪些锁，然后跳转到持有锁的线程；询问指定类的当前活动的实例数、请求查看实例，以及查看使对象保持有效状态的参考；过滤特定实例的事件（如断点）等。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;优化了异常和崩溃报告中的诊断详细信息&lt;/strong&gt;  &lt;br /&gt;当发生运行时异常时，ART 会为您提供尽可能多的上下文和详细信息。ART 会提供 java.lang.ClassCastException、java.lang.ClassNotFoundException 和 java.lang.NullPointerException 的更多异常详细信息（较高版本的 Dalvik 会提供 java.lang.ArrayIndexOutOfBoundsException 和 java.lang.ArrayStoreException 的更多异常详细信息，这些信息现在包括数组大小和越界偏移量；ART 也提供这类信息）。&lt;/p&gt; &lt;h2&gt;1.3 垃圾回收&lt;/h2&gt; &lt;p&gt;ART 提供了多个不同的 GC 方案，这些方案运行着不同垃圾回收器，默认的GC方案是 CMS（并发标记清除），主要使用粘性 CMS 和部分 CMS。粘性 CMS 是 ART 的不移动分代垃圾回收器。它仅扫描堆中自上次 GC 后修改的部分，并且只能回收自上次 GC 后分配的对象。除 CMS 方案外，当应用将进程状态更改为察觉不到卡顿的进程状态（例如，后台或缓存）时，ART 将执行堆压缩。&lt;/p&gt; &lt;p&gt;除了新的垃圾回收器之外，ART 还引入了一种基于位图的新内存分配程序，称为 RosAlloc（插槽运行分配器）。此新分配器具有分片锁，当分配规模较小时可添加线程的本地缓冲区，因而性能优于 DlMalloc（内存分配器）。&lt;/p&gt; &lt;p&gt;内存分配器的相关知识可以参考：  &lt;a href="https://www.dazhuanlan.com/2019/12/24/5e02168e6f8b0/" rel="nofollow noreferrer"&gt;内存分配器&lt;/a&gt;&lt;/p&gt; &lt;p&gt;同时，与 Dalvik 相比，ART的 CMS垃圾回收也带来了其他方面的改善，如下：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;与 Dalvik 相比，暂停次数从 2 次减少到 1 次。Dalvik 的第一次暂停主要是为了进行根标记，即在 ART 中进行并发标记，让线程标记自己的根，然后马上恢复运行。&lt;/li&gt;  &lt;li&gt;与 Dalvik 类似，ART GC 在清除过程开始之前也会暂停 1 次。两者在这方面的主要差异在于：在此暂停期间，某些 Dalvik 环节在 ART 中并发进行。这些环节包括 java.lang.ref.Reference 处理、系统弱清除（例如，jni 弱全局等）、重新标记非线程根和卡片预清理。在 ART 暂停期间仍进行的阶段包括扫描脏卡片以及重新标记线程根，这些操作有助于缩短暂停时间。&lt;/li&gt;  &lt;li&gt;相对于 Dalvik，ART GC 改进的最后一个方面是粘性 CMS 回收器增加了 GC 吞吐量。不同于普通的分代 GC，粘性 CMS 不移动。系统会将年轻对象保存在一个分配堆栈（基本上是 java.lang.Object 数组）中，而非为其设置一个专属区域。这样可以避免移动所需的对象以维持低暂停次数，但缺点是容易在堆栈中加入大量复杂对象图像而使堆栈变长。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;ART GC 与 Dalvik 的另一个主要区别在于 ART GC 引入了移动垃圾回收器。使用移动 GC 的目的在于通过堆压缩来减少后台应用使用的内存。目前，触发堆压缩的事件是 ActivityManager 进程状态的改变。当应用转到后台运行时，它会通知 ART 已进入不再“感知”卡顿的进程状态。此时 ART 会进行一些操作（例如，压缩和监视器压缩），从而导致应用线程长时间暂停。&lt;/p&gt; &lt;p&gt;目前，Android的ART正在使用的两个移动 GC 是同构空间压缩和半空间压缩，它们的区别如下：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;半空间压缩&lt;/strong&gt;：将对象在两个紧密排列的碰撞指针空间之间进行移动。这种移动 GC 适用于小内存设备，因为它可以比同构空间压缩稍微多节省一点内存，额外节省出的空间主要来自紧密排列的对象，这样可以避免 RosAlloc/DlMalloc 分配器占用开销。&lt;/li&gt;  &lt;li&gt;同构空间压缩通过将对象从一个 RosAlloc 空间复制到另一个 RosAlloc 空间来实现。这有助于通过减少堆碎片来减少内存使用量。这是目前非低内存设备的默认压缩模式。相比半空间压缩，同构空间压缩的主要优势在于应用从后台切换到前台时无需进行堆转换。&lt;/li&gt;&lt;/ul&gt; &lt;h1&gt;2，类加载器&lt;/h1&gt; &lt;h2&gt;2.1 类加载器分类&lt;/h2&gt; &lt;p&gt;目前，Android的类加载器从下到上主要分为BootstrapClassLoader（根类加载器）、 ExtensionClassLoader （扩展类加载器）和 AppClassLoader（应用类加载器）三种。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;根类加载器&lt;/strong&gt;：该加载器没有父加载器。它负责加载虚拟机的核心类库，如java.lang.*等。例如java.lang.Object就是由根类加载器加载的。根类加载器从系统属性sun.boot.class.path所指定的目录中加载类库。根类加载器的实现依赖于底层操作系统，属于虚拟机的实现的一部分，它并没有继承java.lang.ClassLoader类。&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;扩展类加载器&lt;/strong&gt;：它的父加载器为根类加载器。它从java.ext.dirs系统属性所指定的目录中加载类库，或者从JDK的安装目录的jre/lib/ext子目录（扩展目录）下加载类库，如果把用户创建的JAR文件放在这个目录下，也会自动由扩展类加载器加载。扩展类加载器是纯Java类，是java.lang.ClassLoader类的子类。&lt;/li&gt;  &lt;li&gt;   &lt;strong&gt;系统类加载器&lt;/strong&gt;：也称为应用类加载器，它的父加载器为扩展类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中加载类，它是用户自定义的类加载器的默认父加载器。系统类加载器是纯Java类，是java.lang.ClassLoader类的子类。   &lt;br /&gt;父子加载器并非继承关系，也就是说子加载器不一定是继承了父加载器。&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;2.2 双亲委托模式&lt;/h2&gt; &lt;p&gt;所谓双亲委托模式，指的是某个特定的类加载器在接到加载类的请求时，首先将加载任务委托给父类加载器，依次递归，如果父类加载器可以完成类加载任务，就成功返回；只有父类加载器无法完成此加载任务时，才自己去加载。&lt;/p&gt; &lt;p&gt;因为这样可以避免重复加载，当父亲已经加载了该类的时候，就没有必要子 ClassLoader 再加载一次。如果不使用这种委托模式，那我们就可以随时使用自定义的类来动态替代一些核心的类，存在非常大的安全隐患。&lt;/p&gt; &lt;p&gt;举个例子，事实上，java.lang.String这个类并不会被我们自定义的classloader加载，而是由bootstrap classloader进行加载，为什么会这样？实际上这就是双亲委托模式的原因，因为在任何一个自定义ClassLoader加载一个类之前，它都会先 委托它的父亲ClassLoader进行加载，只有当父亲ClassLoader无法加载成功后，才会由自己加载。&lt;/p&gt; &lt;h2&gt;2.3 Android的类加载器&lt;/h2&gt; &lt;p&gt;下面是Android类加载器的模型图：  &lt;br /&gt;  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/remote/1460000040006724" title="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;下面看一下DexClassLoader，DexClassLoader 重载了 findClass 方法，在加载类时会调用其内部的 DexPathList 去加载。DexPathList 是在构造 DexClassLoader 时生成的，其内部包含了 DexFile，涉及的源码如下。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;···
public Class findClass(String name) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;
        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    return null;
}
···
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;类加载器更多的内容，可以参考：  &lt;a href="https://blog.csdn.net/xiangzhihong8/article/details/65446152?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162073199416780261961826%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&amp;request_id=162073199416780261961826&amp;biz_id=0&amp;utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_v2~rank_v29-10-65446152.nonecase&amp;utm_term=%E7%B1%BB%E5%8A%A0%E8%BD%BD%E5%99%A8" rel="nofollow noreferrer"&gt;android 类加载器双亲委托模式&lt;/a&gt;&lt;/p&gt; &lt;h1&gt;3，Android Hook&lt;/h1&gt; &lt;p&gt;所谓Hook，就是在程序执行的过程中去截取其中的某段信息，示意图如下。  &lt;br /&gt;  &lt;img alt="&amp;#35828;&amp;#21040;" src="https://segmentfault.com/img/remote/1460000040006725" title="&amp;#35828;&amp;#21040;"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Android的Hook大体的流程可以分为如下几步：  &lt;br /&gt;1、根据需求确定需要 hook 的对象  &lt;br /&gt;2、寻找要hook的对象的持有者，拿到需要 hook 的对象  &lt;br /&gt;3、定义“要 hook 的对象”的代理类，并且创建该类的对象  &lt;br /&gt;4、使用上一步创建出来的对象，替换掉要 hook 的对象&lt;/p&gt; &lt;p&gt;下面是一段简单的Hook的示例代码，用到了Java的反射机制。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;@SuppressLint({&amp;quot;DiscouragedPrivateApi&amp;quot;, &amp;quot;PrivateApi&amp;quot;})
public static void hook(Context context, final View view) {//
    try {
        // 反射执行View类的getListenerInfo()方法，拿到v的mListenerInfo对象，这个对象就是点击事件的持有者
        Method method = View.class.getDeclaredMethod(&amp;quot;getListenerInfo&amp;quot;);
        method.setAccessible(true);//由于getListenerInfo()方法并不是public的，所以要加这个代码来保证访问权限
        Object mListenerInfo = method.invoke(view);//这里拿到的就是mListenerInfo对象，也就是点击事件的持有者

        // 要从这里面拿到当前的点击事件对象
        Class&amp;lt;?&amp;gt; listenerInfoClz = Class.forName(&amp;quot;android.view.View$ListenerInfo&amp;quot;);// 这是内部类的表示方法
        Field field = listenerInfoClz.getDeclaredField(&amp;quot;mOnClickListener&amp;quot;);
        final View.OnClickListener onClickListenerInstance = (View.OnClickListener) field.get(mListenerInfo);//取得真实的mOnClickListener对象

        // 2. 创建我们自己的点击事件代理类
        //   方式1：自己创建代理类
        //   ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
        //   方式2：由于View.OnClickListener是一个接口，所以可以直接用动态代理模式
        // Proxy.newProxyInstance的3个参数依次分别是：
        // 本地的类加载器;
        // 代理类的对象所继承的接口（用Class数组表示，支持多个接口）
        // 代理类的实际逻辑，封装在new出来的InvocationHandler内
        Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Log.d(&amp;quot;HookSetOnClickListener&amp;quot;, &amp;quot;点击事件被hook到了&amp;quot;);//加入自己的逻辑
                return method.invoke(onClickListenerInstance, args);//执行被代理的对象的逻辑
            }
        });
        // 3. 用我们自己的点击事件代理类，设置到&amp;quot;持有者&amp;quot;中
        field.set(mListenerInfo, proxyOnClickListener);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

// 自定义代理类
static class ProxyOnClickListener implements View.OnClickListener {
    View.OnClickListener oriLis;

    public ProxyOnClickListener(View.OnClickListener oriLis) {
        this.oriLis = oriLis;
    }

    @Override
    public void onClick(View v) {
        Log.d(&amp;quot;HookSetOnClickListener&amp;quot;, &amp;quot;点击事件被hook到了&amp;quot;);
        if (oriLis != null) {
            oriLis.onClick(v);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;而在Android开发中，想要实现Hook，肯定是没有这么简单的，我们需要借助一些Hook框架，比如Xposed、Cydia Substrate、Legend等。&lt;/p&gt; &lt;p&gt;参考资料：  &lt;a href="https://blog.csdn.net/xiangzhihong8/article/details/80581194?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162073326516780261980885%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&amp;request_id=162073326516780261980885&amp;biz_id=0&amp;utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_v2~rank_v29-2-80581194.nonecase&amp;utm_term=Android%20Hook" rel="nofollow noreferrer"&gt;Android Hook机制&lt;/a&gt;&lt;/p&gt; &lt;h1&gt;4，代码混淆&lt;/h1&gt; &lt;h2&gt;4.1 Proguard&lt;/h2&gt; &lt;p&gt;众所周知，Java代码是非常容易反编译的，为了更好的保护Java源代码，我们往往会对编译好的Class类文件进行混淆处理。而ProGuard就是一个混淆代码的开源项目。它的主要作用就是混淆，当然它还能对字节码进行缩减体积、优化等，但那些对于我们来说都算是次要的功能。&lt;/p&gt; &lt;p&gt;具体来说，ProGuard具有如下功能：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;压缩（Shrink）: 检测和删除没有使用的类，字段，方法和特性。&lt;/li&gt;  &lt;li&gt;优化（Optimize） : 分析和优化Java字节码。&lt;/li&gt;  &lt;li&gt;混淆（Obfuscate）: 使用简短的无意义的名称，对类，字段和方法进行重命名。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;在Android开发中，开启混淆需要将app/build.gradle文件下的minifyEnabled属性设置为true，如下所示。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;minifyEnabled true
proguardFiles getDefaultProguardFile(&amp;apos;proguard-android.txt&amp;apos;), &amp;apos;proguard-rules.pro&amp;apos;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;proguard-android.txt是Android提供的默认混淆配置文件，我们需要的混淆的规则都放在这个文件中。&lt;/p&gt; &lt;h2&gt;4.2 混淆规则&lt;/h2&gt; &lt;p&gt;  &lt;strong&gt;混淆命令&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;keep：保留类和类中的成员，防止被混淆或移除&lt;/li&gt;  &lt;li&gt;keepnames：保留类和类中的成员，防止被混淆，成员没有被引用会被移除&lt;/li&gt;  &lt;li&gt;keepclassmembers：只保留类中的成员，防止被混淆或移除&lt;/li&gt;  &lt;li&gt;keepclassmembernames：只保留类中的成员，防止被混淆，成员没有引用会被移除&lt;/li&gt;  &lt;li&gt;keepclasseswithmembers：保留类和类中的成员，防止被混淆或移除，保留指明的成员&lt;/li&gt;  &lt;li&gt;keepclasseswithmembernames：保留类和类中的成员，防止被混淆，保留指明的成员，成员没有引用会被移除&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;混淆通配符&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;code&gt;&amp;lt;field&amp;gt;&lt;/code&gt;：匹配类中的所有字段&lt;/li&gt;  &lt;li&gt;   &lt;code&gt;&amp;lt;method&amp;gt;&lt;/code&gt;：匹配类中所有的方法&lt;/li&gt;  &lt;li&gt;   &lt;code&gt;&amp;lt;init&amp;gt;&lt;/code&gt;：匹配类中所有的构造函数&lt;/li&gt;  &lt;li&gt;   &lt;code&gt;*&lt;/code&gt;：    匹配任意长度字符，不包含包名分隔符(.)&lt;/li&gt;  &lt;li&gt;   &lt;code&gt;**&lt;/code&gt;：    匹配任意长度字符，包含包名分隔符(.)&lt;/li&gt;  &lt;li&gt;   &lt;code&gt;***&lt;/code&gt;：    匹配任意参数类型&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;keep的规则的格式如下：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;[keep命令] [类] {
        [成员]
}&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;4.3 混淆模版&lt;/h2&gt; &lt;p&gt;ProGuard中有些公共的模版是可以复用的，比如压缩比、大小写混合和一些系统提供的Activity、Service不能混淆等。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;# 代码混淆压缩比，在 0~7 之间，默认为 5，一般不做修改
-optimizationpasses 5

# 混合时不使用大小写混合，混合后的类名为小写
-dontusemixedcaseclassnames

# 指定不去忽略非公共库的类
-dontskipnonpubliclibraryclasses

# 这句话能够使我们的项目混淆后产生映射文件
# 包含有类名-&amp;gt;混淆后类名的映射关系
-verbose

# 指定不去忽略非公共库的类成员
-dontskipnonpubliclibraryclassmembers

# 不做预校验，preverify 是 proguard 的四个步骤之一，Android 不需要 preverify，去掉这一步能够加快混淆速度。
-dontpreverify

# 保留 Annotation 不混淆
-keepattributes *Annotation*,InnerClasses

# 避免混淆泛型
-keepattributes Signature

# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable

# 指定混淆是采用的算法，后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法，一般不做更改
-optimizations !code/simplification/cast,!field/*,!class/merging/*


#############################################
#
# Android开发中一些需要保留的公共部分
#
#############################################

# 保留我们使用的四大组件，自定义的 Application 等等这些类不被混淆
# 因为这些子类都有可能被外部调用
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService


# 保留 support 下的所有类及其内部类
-keep class android.support.** { *; }

# 保留继承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**

# 保留 R 下面的资源
-keep class **.R$* { *; }

# 保留本地 native 方法不被混淆
-keepclasseswithmembernames class * {
    native &amp;lt;methods&amp;gt;;
}

# 保留在 Activity 中的方法参数是view的方法，
# 这样以来我们在 layout 中写的 onClick 就不会被影响
-keepclassmembers class * extends android.app.Activity {
    public void *(android.view.View);
}

# 保留枚举类不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 保留我们自定义控件（继承自 View）不被混淆
-keep public class * extends android.view.View {
    *** get*();
    void set*(***);
    public &amp;lt;init&amp;gt;(android.content.Context);
    public &amp;lt;init&amp;gt;(android.content.Context, android.util.AttributeSet);
    public &amp;lt;init&amp;gt;(android.content.Context, android.util.AttributeSet, int);
}

# 保留 Parcelable 序列化类不被混淆
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}

# 保留 Serializable 序列化的类不被混淆
-keepnames class * implements java.io.Serializable
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient &amp;lt;fields&amp;gt;;
    !private &amp;lt;fields&amp;gt;;
    !private &amp;lt;methods&amp;gt;;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# 对于带有回调函数的 onXXEvent、**On*Listener 的，不能被混淆
-keepclassmembers class * {
    void *(**On*Event);
    void *(**On*Listener);
}

# webView 处理，项目中没有使用到 webView 忽略即可
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
    public *;
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.webView, java.lang.String);
}

# js
-keepattributes JavascriptInterface
-keep class android.webkit.JavascriptInterface { *; }
-keepclassmembers class * {
    @android.webkit.JavascriptInterface &amp;lt;methods&amp;gt;;
}

# @Keep
-keep,allowobfuscation @interface android.support.annotation.Keep
-keep @android.support.annotation.Keep class *
-keepclassmembers class * {
    @android.support.annotation.Keep *;
}&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果是aar这种插件，可以在aar的build.gralde中添加如下混淆配置。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;android {
    ···
    defaultConfig {
        ···
        consumerProguardFile &amp;apos;proguard-rules.pro&amp;apos;
    }
    ···
}&lt;/code&gt;&lt;/pre&gt; &lt;h1&gt;5，NDK&lt;/h1&gt; &lt;p&gt;如果要问Android的高级开发知识，那么NDK肯定是必问的。那么什么的NDK，NDK 全称是 Native Development Kit，是一组可以让开发者在 Android 应用中使用C/C++ 的工具。通常，NDK可以用在如下的场景中：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;从设备获取更好的性能以用于计算密集型应用，例如游戏或物理模拟。&lt;/li&gt;  &lt;li&gt;重复使用自己或其他开发者的 C/C++ 库，便利于跨平台。&lt;/li&gt;  &lt;li&gt;NDK 集成了譬如 OpenSL、Vulkan 等 API 规范的特定实现，以实现在 Java 层无法做到的功能，如音视频开发、渲染。&lt;/li&gt;  &lt;li&gt;增加反编译难度。&lt;/li&gt;&lt;/ul&gt; &lt;h2&gt;5.1, JNI基础&lt;/h2&gt; &lt;p&gt;JNI即java native interface，是Java和Native代码进行交互的接口。&lt;/p&gt; &lt;h3&gt;5.1.1 JNI 访问 Java 对象方法&lt;/h3&gt; &lt;p&gt;假如，有如下一个Java类，代码如下。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;package com.xzh.jni;

public class MyJob {
    public static String JOB_STRING = &amp;quot;my_job&amp;quot;;
    private int jobId;

    public MyJob(int jobId) {
        this.jobId = jobId;
    }

    public int getJobId() {
        return jobId;
    }
}&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后，在cpp目录下，新建native_lib.cpp，添加对应的native实现。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;#include &amp;lt;jni.h&amp;gt;

extern &amp;quot;C&amp;quot;
JNIEXPORT jint JNICALL
Java_com_xzh_jni_MainActivity_getJobId(JNIEnv *env, jobject thiz, jobject job) {

    // 根据实例获取 class 对象
    jclass jobClz = env-&amp;gt;GetObjectClass(job);
    // 根据类名获取 class 对象
    jclass jobClz = env-&amp;gt;FindClass(&amp;quot;com/xzh/jni/MyJob&amp;quot;);

    // 获取属性 id
    jfieldID fieldId = env-&amp;gt;GetFieldID(jobClz, &amp;quot;jobId&amp;quot;, &amp;quot;I&amp;quot;);
    // 获取静态属性 id
    jfieldID sFieldId = env-&amp;gt;GetStaticFieldID(jobClz, &amp;quot;JOB_STRING&amp;quot;, &amp;quot;Ljava/lang/String;&amp;quot;);

    // 获取方法 id
    jmethodID methodId = env-&amp;gt;GetMethodID(jobClz, &amp;quot;getJobId&amp;quot;, &amp;quot;()I&amp;quot;);
    // 获取构造方法 id
    jmethodID  initMethodId = env-&amp;gt;GetMethodID(jobClz, &amp;quot;&amp;lt;init&amp;gt;&amp;quot;, &amp;quot;(I)V&amp;quot;);

    // 根据对象属性 id 获取该属性值
    jint id = env-&amp;gt;GetIntField(job, fieldId);
    // 根据对象方法 id 调用该方法
    jint id = env-&amp;gt;CallIntMethod(job, methodId);

    // 创建新的对象
    jobject newJob = env-&amp;gt;NewObject(jobClz, initMethodId, 10);
    return id;
}&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;5.2 NDK开发&lt;/h2&gt; &lt;h3&gt;5.2.1 基本流程&lt;/h3&gt; &lt;p&gt;首先，在 Java代码中声明 Native 方法，如下所示。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class MainActivity extends AppCompatActivity {

    // Used to load the &amp;apos;native-lib&amp;apos; library on application startup.
    static {
        System.loadLibrary(&amp;quot;native-lib&amp;quot;);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d(&amp;quot;MainActivity&amp;quot;, stringFromJNI());
    }
    private native String stringFromJNI();
}&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后，新建一个 cpp 目录，并且新建一个名为native-lib.cpp的cpp 文件，实现相关方法。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;#include &amp;lt;jni.h&amp;gt;

extern &amp;quot;C&amp;quot; JNIEXPORT jstring JNICALL
Java_com_xzh_jni_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = &amp;quot;Hello from C++&amp;quot;;
    return env-&amp;gt;NewStringUTF(hello.c_str());
}&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;cpp文件遵循如下的规则：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;函数名的格式遵循遵循如下规则：Java_包名_类名_方法名。&lt;/li&gt;  &lt;li&gt;extern &amp;quot;C&amp;quot; 指定采用 C 语言的命名风格来编译，否则由于 C 与 C++ 风格不同，导致链接时无法找到具体的函数&lt;/li&gt;  &lt;li&gt;JNIEnv*：表示一个指向 JNI 环境的指针，可以通过他来访问 JNI 提供的接口方法&lt;/li&gt;  &lt;li&gt;jobject：表示 java 对象中的 this&lt;/li&gt;  &lt;li&gt;JNIEXPORT 和 JNICALL：JNI 所定义的宏，可以在 jni.h 头文件中查找到&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;System.loadLibrary()的代码位于java/lang/System.java文件中，源码如下：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;@CallerSensitive
public static void load(String filename) {
    Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
}&lt;/code&gt;&lt;/pre&gt; &lt;h2&gt;5.3 CMake 构建 NDK&lt;/h2&gt; &lt;p&gt;CMake 是一个开源的跨平台工具系列，旨在构建、测试和打包软件，从 Android Studio 2.2 开始，Android Sudio 默认地使用 CMake 与 Gradle 搭配使用来构建原生库。具体来说，我们可以使用 Gradle 将 C \ C++ 代码 编译到原生库中，然后将这些代码打包到我们的应用中， Java 代码随后可以通过 Java 原生接口 ( JNI ) 调用 我们原生库中的函数。&lt;/p&gt; &lt;p&gt;使用CMake开发NDK项目需要下载如下一些套件：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;Android 原生开发工具包 (NDK)：这套工具集允许我们 开发 Android 使用 C 和 C++ 代码，并提供众多平台库，让我们可以管理原生 Activity 和访问物理设备组件，例如传感器和触摸输入。&lt;/li&gt;  &lt;li&gt;CMake：一款外部构建工具，可与 Gradle 搭配使用来构建原生库。如果你只计划使用 ndk-build，则不需要此组件。&lt;/li&gt;  &lt;li&gt;LLDB：一种调试程序，Android Studio 使用它来调试原生代码。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;我们可以打开Android Studio，依次选择 【Tools】 &amp;gt; 【Android】&amp;gt; 【SDK Manager】&amp;gt; 【SDK Tools】选中LLDB、CMake 和 NDK即可。&lt;/p&gt; &lt;p&gt;启用CMake还需要在 app/build.gradle 中添加如下代码。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;android {
    ···
    defaultConfig {
        ···
        externalNativeBuild {
            cmake {
                cppFlags &amp;quot;&amp;quot;
            }
        }

        ndk {
            abiFilters &amp;apos;arm64-v8a&amp;apos;, &amp;apos;armeabi-v7a&amp;apos;
        }
    }
    ···
    externalNativeBuild {
        cmake {
            path &amp;quot;CMakeLists.txt&amp;quot;
        }
    }
}&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后，在对应目录新建一个 CMakeLists.txt 文件，添加代码。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;# 定义了所需 CMake 的最低版本
cmake_minimum_required(VERSION 3.4.1)

# add_library() 命令用来添加库
# native-lib 对应着生成的库的名字
# SHARED 代表为分享库
# src/main/cpp/native-lib.cpp 则是指明了源文件的路径。
add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        src/main/cpp/native-lib.cpp)

# find_library 命令添加到 CMake 构建脚本中以定位 NDK 库，并将其路径存储为一个变量。
# 可以使用此变量在构建脚本的其他部分引用 NDK 库
find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

# 预构建的 NDK 库已经存在于 Android 平台上，因此，无需再构建或将其打包到 APK 中。
# 由于 NDK 库已经是 CMake 搜索路径的一部分，只需要向 CMake 提供希望使用的库的名称，并将其关联到自己的原生库中

# 要将预构建库关联到自己的原生库
target_link_libraries( # Specifies the target library.
        native-lib

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})
···&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;参考：  &lt;a href="https://juejin.cn/post/6952049081464127518#heading-4" rel="nofollow noreferrer"&gt;Android NDK开发基础&lt;/a&gt;&lt;/p&gt; &lt;h1&gt;6，动态加载&lt;/h1&gt; &lt;h2&gt;6.1 基本概念&lt;/h2&gt; &lt;p&gt;动态加载技术在Web中很常见，对于Android项目来说，动态加载的目的是让用户不用重新安装APK就能升级应用的功能，主要的应用场景是插件化和热修复。&lt;/p&gt; &lt;p&gt;首先需要明确的一点，插件化和热修复不是同一个概念，虽然站在技术实现的角度来说，他们都是从系统加载器的角度出发，无论是采用hook方式，亦或是代理方式或者是其他底层实现，都是通过“欺骗”Android 系统的方式来让宿主正常的加载和运行插件（补丁）中的内容；但是二者的出发点是不同的。&lt;/p&gt; &lt;p&gt;插件化，本质上是把需要实现的模块或功能当做一个独立的功能提取出来，减少宿主的规模，当需要使用到相应的功能时再去加载相应的模块。而热修复则往往是从修复bug的角度出发，强调的是在不需要二次安装应用的前提下修复已知的bug。&lt;/p&gt; &lt;p&gt;为了方便说明，我们先理清几个概念：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;宿主： 当前运行的APP。&lt;/li&gt;  &lt;li&gt;插件： 相对于插件化技术来说，就是要加载运行的apk类文件。&lt;/li&gt;  &lt;li&gt;补丁： 相对于热修复技术来说，就是要加载运行的.patch,.dex,*.apk等一系列包含dex修复内容的文件。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;下图展示了Android动态化开发框架的整体的架构。  &lt;br /&gt;  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/remote/1460000040006726" title="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;&lt;/p&gt; &lt;h2&gt;6.2 插件化&lt;/h2&gt; &lt;p&gt;关于插件化技术，最早可以追溯到2012年的   &lt;a href="https://github.com/mmin18/AndroidDynamicLoader" rel="nofollow noreferrer"&gt;AndroidDynamicLoader&lt;/a&gt; ，其原理是动态加载不同的Fragment实现UI替换，不过随着15，16年更好的方案，这个方案渐渐的被淘汰了。再后来有了任玉刚的  &lt;a href="https://github.com/singwhatiwanna/dynamic-load-apk" rel="nofollow noreferrer"&gt;dynamic-load-apk&lt;/a&gt;方案，开始有了插件化的标准方案。而后面的方案大多基于Hook和动态代理两个方向进行。&lt;/p&gt; &lt;p&gt;目前，插件化的开发并没有一个官方的插件化方案，它是国内提出的一种技术实现，利用虚拟机的类的加载机制实现的一种技术手段，往往需要hook一些系统api，而Google从Android9.0开始限制对系统私有api的使用，也就造成了插件化的兼容性问题，现在几个流行的插件化技术框架，都是大厂根据自己的需求，开源出来的，如滴滴的VirtualAPK，360的RePlugin等，大家可以根据需要自行了解技术的实现原理。&lt;/p&gt; &lt;h2&gt;6.3 热修复&lt;/h2&gt; &lt;h4&gt;6.3.1 热修复原理&lt;/h4&gt; &lt;p&gt;说到热修复的原理，就不得不提到类的加载机制，和常规的JVM类似，在Android中类的加载也是通过ClassLoader来完成，具体来说就是PathClassLoader 和 DexClassLoader 这两个Android专用的类加载器，这两个类的区别如下。&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;PathClassLoader：只能加载已经安装到Android系统中的apk文件（/data/app目录），是Android默认使用的类加载器。&lt;/li&gt;  &lt;li&gt;DexClassLoader：可以加载任意目录下的dex/jar/apk/zip文件，也就是我们一开始提到的补丁。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;这两个类都是继承自BaseDexClassLoader，BaseDexClassLoader的构造函数如下。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这个构造函数只做了一件事，就是通过传递进来的相关参数，初始化了一个DexPathList对象。DexPathList的构造函数，就是将参数中传递进来的程序文件（就是补丁文件）封装成Element对象，并将这些对象添加到一个Element的数组集合dexElements中去。&lt;/p&gt; &lt;p&gt;前面说过类加载器的作用，就是将一个具体的类（class）加载到内存中，而这些操作是由虚拟机完成的，对于开发者来说，只需要关注如何去找到这个需要加载的类即可，这也是热修复需要干的事情。&lt;/p&gt; &lt;p&gt;在Android中，查找一个名为name的class需要经历如下两步：&lt;/p&gt; &lt;ol&gt;  &lt;li&gt;在DexClassLoader的findClass 方法中通过一个DexPathList对象findClass()方法来获取class。&lt;/li&gt;  &lt;li&gt;在DexPathList的findClass 方法中，对之前构造好dexElements数组集合进行遍历，一旦找到类名与name相同的类时，就直接返回这个class，找不到则返回null。&lt;/li&gt;&lt;/ol&gt; &lt;p&gt;因此，基于上面的理论，我们可以想到一个最简单的热修复方案。假设现在代码中的某一个类出现Bug，那么我们可以在修复Bug之后，将这些个类打包成一个补丁文件，然后通过这个补丁文件封装出一个Element对象，并且将这个Element对象插到原有dexElements数组的最前端。这样，当DexClassLoader去加载类时，由于双亲加载机制的特点，就会优先加载插入的这个Element，而有缺陷的Element则没有机会再被加载。事实上，QQ早期的热修复方案就是这样的。&lt;/p&gt; &lt;h4&gt;6.3.2 QQ 空间超级补丁方案&lt;/h4&gt; &lt;p&gt;QQ 空间补丁方案就是使用javaassist 插桩的方式解决了CLASS_ISPREVERIFIED的难题。涉及的步骤如下：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;在apk安装的时候系统会将dex文件优化成odex文件，在优化的过程中会涉及一个预校验的过程。&lt;/li&gt;  &lt;li&gt;   &lt;p&gt;如果一个类的static方法，private方法，override方法以及构造函数中引用了其他类，而且这些类都属于同一个dex文件，此时该类就会被打上CLASS_ISPREVERIFIED。&lt;/p&gt;   &lt;ul&gt;    &lt;li&gt;如果在运行时被打上CLASS_ISPREVERIFIED的类引用了其他dex的类，就会报错。&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;  &lt;li&gt;正常的分包方案会保证相关类被打入同一个dex文件。&lt;/li&gt;  &lt;li&gt;想要使得patch可以被正常加载，就必须保证类不会被打上CLASS_ISPREVERIFIED标记。而要实现这个目的就必须要在分完包后的class中植入对其他dex文件中类的引用。&lt;/li&gt;&lt;/ul&gt; &lt;h4&gt;6.3.3 Tinker&lt;/h4&gt; &lt;p&gt;QQ空间超级补丁方案在遇到补丁文件很大的时候耗时是非常严重的，因为一个大文件夹加载到内存中构建一个Element对象时，插入到数组最前端是需要耗费时间的，而这非常影响应用的启动速度。基于这些问题，微信提出了Tinker 方案。&lt;/p&gt; &lt;p&gt;Tinker的思路是，通过修复好的class.dex 和原有的class.dex比较差生差量包补丁文件patch.dex，在手机上这个patch.dex又会和原有的class.dex 合并生成新的文件fix_class.dex，用这个新的fix_class.dex 整体替换原有的dexPathList的中的内容，进而从根本上修复Bug，下图是演示图。&lt;/p&gt; &lt;p&gt;  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/remote/1460000040006727" title="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;相比QQ空间超级补丁方案，Tinker 提供的思路可以说效率更高。对Tinker热修复方案感兴趣的同学可以去看看  &lt;a href="https://blog.csdn.net/lmj623565791/article/details/60874334" rel="nofollow noreferrer"&gt;Tinker 源码分析之DexDiff / DexPatch&lt;/a&gt;&lt;/p&gt; &lt;h4&gt;6.3.4 HotFix&lt;/h4&gt; &lt;p&gt;以上提到的两种方式，虽然策略有所不同，但总的来说都是从上层ClassLoader的角度出发，由于ClassLoader的特点，如果想要新的补丁文件再次生效，无论你是插桩还是提前合并，都需要重新启动应用来加载新的DexPathList，从而实现Bug的修复。&lt;/p&gt; &lt;p&gt;AndFix 提供了一种运行时在Native修改Filed指针的方式，实现方法的替换，达到即时生效无需重启，对应用无性能消耗的目的。不过，由于Android在国内变成了安卓，各大手机厂商定制了自己的ROM，所以很多底层实现的差异，导致AndFix的兼容性并不是很好。&lt;/p&gt; &lt;h4&gt;6.3.5 Sophix&lt;/h4&gt; &lt;p&gt;Sophix采用的是类似类修复反射注入方式,把补丁so库的路径插入到nativeLibraryDirectories数组的最前面, 这样加载so库的时候就是补丁so库而不是原来的so库。&lt;/p&gt; &lt;p&gt;在修复类代码的缺陷时，Sophix对旧包与补丁包中classes.dex的顺序进行了打破与重组，使得系统可以自然地识别到这个顺序,以实现类覆盖的目的。&lt;/p&gt; &lt;p&gt;在修复资源的缺陷时，Sophix构造了一个package id 为 0x66 的资源包，这个包里只包含改变了的资源项，然后直接在原有AssetManager中addAssetPath这个包即可，无需变更AssetManager对象的引用。&lt;/p&gt; &lt;p&gt;除了这些方案外，热修复方案还有美团的Robust、饿了吗的Amigo等。不过，对于Android的热修复来说，很难有一种十分完美的解决方案。比如，在Android开发中，四大组件使用前需要在AndroidManifest中提前声明，而如果需要使用热修复的方式，无论是提前占坑亦或是动态修改，都会带来很强的侵入性。同时，Android碎片化的问题，对热修复方案的适配也是一大考验。&lt;/p&gt; &lt;p&gt;参考：  &lt;a href="https://juejin.cn/post/6844904138464034823" rel="nofollow noreferrer"&gt;Android 热修复的简析&lt;/a&gt;  &lt;br /&gt;  &lt;a href="https://juejin.cn/post/6844903603451199502" rel="nofollow noreferrer"&gt;深入探索Android热修复技术原理&lt;/a&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>android react-native</category>
      <guid isPermaLink="true">https://itindex.net/detail/61419-android-%E9%9D%A2%E8%AF%95-%E7%9F%A5%E8%AF%86</guid>
      <pubDate>Sat, 15 May 2021 22:05:31 CST</pubDate>
    </item>
    <item>
      <title>Android敏感数据泄露引发的思考</title>
      <link>https://itindex.net/detail/60924-android-%E6%95%B0%E6%8D%AE-%E6%80%9D%E8%80%83</link>
      <description>&lt;div&gt;    &lt;h3&gt;1.事件始末&lt;/h3&gt;    &lt;p&gt;一个平淡的午后，我还悠哉悠哉的敲着代码品着茶。突然服务端同事告诉我，关注接口正在被机械式调用，怀疑是有人在使用脚本刷接口（目的主要是从平台导流）。      &lt;br /&gt;纳尼？不会吧，因为据我所知接口请求是做了加密处理的，除非知道加密的密钥和加密方式，不然是不会调用成功的，一定是你感觉错了。然而当服务端同事把接口调用日志发给我看时，彻底否定了我的侥幸心理。&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;接口调用频率固定为        &lt;strong&gt;          &lt;code&gt;1s&lt;/code&gt;&lt;/strong&gt;一次&lt;/li&gt;      &lt;li&gt;被关注者的        &lt;strong&gt;          &lt;code&gt;id&lt;/code&gt;&lt;/strong&gt;每次调用依次加一（目前业务上用户id的生成是按照注册时间依次递增的）&lt;/li&gt;      &lt;li&gt;加密的        &lt;strong&gt;          &lt;code&gt;密钥&lt;/code&gt;&lt;/strong&gt;始终使用固定的一个（正常的是在固定的几个密钥中每次会随机使用一个）&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;综合以上三点就可以断定，肯定是存在刷接口的行为了。&lt;/p&gt;    &lt;h3&gt;2.事件分析&lt;/h3&gt;    &lt;p&gt;既然上述刷接口的行为成立，也就意味着密钥和加密方式被对方知道了，原因无非是以下两点：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;内部人员泄露&lt;/li&gt;      &lt;li&gt;apk被破解&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;经过确认基本排除了第一点，那就只剩下apk被破解了，可是apk发布出去的包是进行过加固和混淆处理的，难道对方脱壳了？不管三七二十一，自己先来反编译试试。于是乎从最近发布的版本一个一个去反编译，最后在反编译到较早前的一个版本时发现，保存密钥和加密的工具类居然源码完全暴露了。      &lt;img alt="&amp;#21453;&amp;#32534;&amp;#35793;&amp;#20986;&amp;#26469;&amp;#30340;&amp;#21152;&amp;#23494;&amp;#24037;&amp;#20855;&amp;#31867;&amp;#37096;&amp;#20998;"&gt;&lt;/img&gt;炸了锅了，排查了一下这个版本居然未加固过就发布出去了，而且这个加密工具类未被混淆。虽然还不太清楚对方是不是按照这种方式获取的密钥和加密算法，但无疑这是客户端存在的一个安全漏洞。&lt;/p&gt;    &lt;h3&gt;3.事件处理&lt;/h3&gt;    &lt;p&gt;既然已经发现了上述问题，那就要想办法解决。首先不考虑加固，如何尽最大可能保证客户端中的敏感数据不泄露？另一方面即使对方想要破解，也要想办法设障，增大破解难度。想到这里基本就大致确定了一个思路：      &lt;strong&gt;        &lt;em&gt;使用NDK,将敏感数据和加密方式放到native层，因为C++代码编译后生成的so库是一个二进制文件，这无疑会增加破解的难度。利用这个特性，可以将客户端的敏感数据写在C++代码中，从而增强应用的安全性。&lt;/em&gt;&lt;/strong&gt;说干就干吧！！！&lt;/p&gt;    &lt;hr&gt;&lt;/hr&gt;    &lt;p&gt;      &lt;strong&gt;1.首先创建了加密工具类：&lt;/strong&gt;&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;public class HttpKeyUtil {
    static {
        System.loadLibrary(&amp;quot;jniSecret&amp;quot;);
    }
    //根据随机值去获取密钥
    public static native String getHttpSecretKey(int index);
    //将待加密的数据传入，返回加密后的结果
    public static native String getSecretValue(byte[] bytes);
}复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;strong&gt;2.生成相应的头文件：&lt;/strong&gt;      &lt;br /&gt;com_test_util_HttpKeyUtil.h&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;#include &amp;lt;jni.h&amp;gt;
#ifndef _Included_com_test_util_HttpKeyUtil
#define _Included_com_test_util_HttpKeyUtil
#ifdef __cplusplus
extern &amp;quot;C&amp;quot; {
#endif
JNIEXPORT jstring JNICALL Java_com_esky_common_component_util_HttpKeyUtil_getHttpSecretKey
        (JNIEnv *, jclass, jint);
        
JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getSecretValue
        (JNIEnv *, jclass, jbyteArray);

#ifdef __cplusplus
}
#endif
#endif复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;      &lt;strong&gt;3.编写相应的cpp文件：&lt;/strong&gt;      &lt;br /&gt;在相应的Module中创建jni目录，将      &lt;strong&gt;        &lt;em&gt;com_test_util_HttpKeyUtil.h&lt;/em&gt;&lt;/strong&gt;拷贝进来，然后再创建      &lt;strong&gt;        &lt;em&gt;com_test_util_HttpKeyUtil.cpp&lt;/em&gt;&lt;/strong&gt;文件      &lt;img alt="jni"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;#include &amp;lt;jni.h&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;malloc.h&amp;gt;
#include &amp;quot;com_test_util_HttpKeyUtil.h&amp;quot;

extern &amp;quot;C&amp;quot;
const char *KEY1 = &amp;quot;密钥1&amp;quot;;
const char *KEY2 = &amp;quot;密钥2&amp;quot;;
const char *KEY3 = &amp;quot;密钥3&amp;quot;;
const char *UNKNOWN = &amp;quot;unknown&amp;quot;;

jstring toMd5(JNIEnv *pEnv, jbyteArray pArray);

extern &amp;quot;C&amp;quot; JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getHttpSecretKey
        (JNIEnv *env, jclass cls, jint index) {
    if (随机数条件1) {
        return env-&amp;gt;NewStringUTF(KEY1);
    } else if (随机数条件2) {
        return env-&amp;gt;NewStringUTF(KEY2);
    } else if (随机数条件3) {
        return env-&amp;gt;NewStringUTF(KEY3);
    } else {
        return env-&amp;gt;NewStringUTF(UNKNOWN);
    }
}

extern &amp;quot;C&amp;quot; JNIEXPORT jstring JNICALL
Java_com_test_util_HttpKeyUtil_getSecretValue
        (JNIEnv *env, jclass cls, jbyteArray jbyteArray1) {
        //加密算法各有不同，这里我就用md5做个示范
        return toMd5(env, jbyteArray1);
}

//md5
jstring toMd5(JNIEnv *env, jbyteArray source) {
    // MessageDigest
    jclass classMessageDigest = env-&amp;gt;FindClass(&amp;quot;java/security/MessageDigest&amp;quot;);
    // MessageDigest.getInstance()
    jmethodID midGetInstance = env-&amp;gt;GetStaticMethodID(classMessageDigest, &amp;quot;getInstance&amp;quot;,
                                                      &amp;quot;(Ljava/lang/String;)Ljava/security/MessageDigest;&amp;quot;);
    // MessageDigest object
    jobject objMessageDigest = env-&amp;gt;CallStaticObjectMethod(classMessageDigest, midGetInstance,
                                                           env-&amp;gt;NewStringUTF(&amp;quot;md5&amp;quot;));

    jmethodID midUpdate = env-&amp;gt;GetMethodID(classMessageDigest, &amp;quot;update&amp;quot;, &amp;quot;([B)V&amp;quot;);
    env-&amp;gt;CallVoidMethod(objMessageDigest, midUpdate, source);

    // Digest
    jmethodID midDigest = env-&amp;gt;GetMethodID(classMessageDigest, &amp;quot;digest&amp;quot;, &amp;quot;()[B&amp;quot;);
    jbyteArray objArraySign = (jbyteArray) env-&amp;gt;CallObjectMethod(objMessageDigest, midDigest);

    jsize intArrayLength = env-&amp;gt;GetArrayLength(objArraySign);
    jbyte *byte_array_elements = env-&amp;gt;GetByteArrayElements(objArraySign, NULL);
    size_t length = (size_t) intArrayLength * 2 + 1;
    char *char_result = (char *) malloc(length);
    memset(char_result, 0, length);
    toHexStr((const char *) byte_array_elements, char_result, intArrayLength);
    // 在末尾补\0
    *(char_result + intArrayLength * 2) = &amp;apos;\0&amp;apos;;
    jstring stringResult = env-&amp;gt;NewStringUTF(char_result);
    // release
    env-&amp;gt;ReleaseByteArrayElements(objArraySign, byte_array_elements, JNI_ABORT);
    // 指针
    free(char_result);
    return stringResult;
}

//转换为16进制字符串
void toHexStr(const char *source, char *dest, int sourceLen) {
    short i;
    char highByte, lowByte;
    for (i = 0; i &amp;lt; sourceLen; i++) {
        highByte = source[i] &amp;gt;&amp;gt; 4;
        lowByte = (char) (source[i] &amp;amp; 0x0f);
        highByte += 0x30;
        if (highByte &amp;gt; 0x39) {
            dest[i * 2] = (char) (highByte + 0x07);
        } else {
            dest[i * 2] = highByte;
        }
        lowByte += 0x30;
        if (lowByte &amp;gt; 0x39) {
            dest[i * 2 + 1] = (char) (lowByte + 0x07);
        } else {
            dest[i * 2 + 1] = lowByte;
        }
    }
}复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;h3&gt;4.事件就此结束?&lt;/h3&gt;    &lt;p&gt;到这里就此结束了？too yuang too simple！！！虽然将密钥和加密算法写在了c++中，貌似好像是比较安全了。但是但是万一别人反编译后，拿到c++代码最终生成的so库，然后直接调用so库里的方法去获取密钥并调用加密方法怎么破？看来我们还是要加一步身份校验才行：即在native层对应用的包名、签名进行鉴权校验，校验通过才返回正确结果。下面就是获取apk包名和签名校验的代码：&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;const char *PACKAGE_NAME = &amp;quot;你的ApplicationId&amp;quot;;
//(签名的md5值自己可以写方法获取,或者用签名工具直接获取，一般对接微信sdk的时候也会要应用签名的MD5值)
const char *SIGN_MD5 = &amp;quot;你的应用签名的MD5值注意是大写&amp;quot;;

//获取Application实例
jobject getApplication(JNIEnv *env) {
    jobject application = NULL;
    //这里是你的Application的类路径,混淆时注意不要混淆该类和该类获取实例的方法比如getInstance
    jclass baseapplication_clz = env-&amp;gt;FindClass(&amp;quot;com/test/component/BaseApplication&amp;quot;);
    if (baseapplication_clz != NULL) {
        jmethodID currentApplication = env-&amp;gt;GetStaticMethodID(
                baseapplication_clz, &amp;quot;getInstance&amp;quot;,
                &amp;quot;()Lcom/test/component/BaseApplication;&amp;quot;);
        if (currentApplication != NULL) {
            application = env-&amp;gt;CallStaticObjectMethod(baseapplication_clz, currentApplication);
        }
        env-&amp;gt;DeleteLocalRef(baseapplication_clz);
    }
    return application;
}


bool isRight = false;
//获取应用签名的MD5值并判断是否与本应用的一致
jboolean getSignature(JNIEnv *env) {
    LOGD(&amp;quot;getSignature isRight: %d&amp;quot;, isRight ? 1 : 0);
    if (!isRight) {//避免每次都进行校验浪费资源，只要第一次校验通过后，后边就不在进行校验
        jobject context = getApplication(env);
        // 获得Context类
        jclass cls = env-&amp;gt;FindClass(&amp;quot;android/content/Context&amp;quot;);
        // 得到getPackageManager方法的ID
        jmethodID mid = env-&amp;gt;GetMethodID(cls, &amp;quot;getPackageManager&amp;quot;,
                                         &amp;quot;()Landroid/content/pm/PackageManager;&amp;quot;);

        // 获得应用包的管理器
        jobject pm = env-&amp;gt;CallObjectMethod(context, mid);

        // 得到getPackageName方法的ID
        mid = env-&amp;gt;GetMethodID(cls, &amp;quot;getPackageName&amp;quot;, &amp;quot;()Ljava/lang/String;&amp;quot;);
        // 获得当前应用包名
        jstring packageName = (jstring) env-&amp;gt;CallObjectMethod(context, mid);
        const char *c_pack_name = env-&amp;gt;GetStringUTFChars(packageName, NULL);

        // 比较包名,若不一致，直接return包名
        if (strcmp(c_pack_name, PACKAGE_NAME) != 0) {
            return false;
        }
        // 获得PackageManager类
        cls = env-&amp;gt;GetObjectClass(pm);
        // 得到getPackageInfo方法的ID
        mid = env-&amp;gt;GetMethodID(cls, &amp;quot;getPackageInfo&amp;quot;,
                               &amp;quot;(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;&amp;quot;);
        // 获得应用包的信息
        jobject packageInfo = env-&amp;gt;CallObjectMethod(pm, mid, packageName,
                                                    0x40); //GET_SIGNATURES = 64;
        // 获得PackageInfo 类
        cls = env-&amp;gt;GetObjectClass(packageInfo);
        // 获得签名数组属性的ID
        jfieldID fid = env-&amp;gt;GetFieldID(cls, &amp;quot;signatures&amp;quot;, &amp;quot;[Landroid/content/pm/Signature;&amp;quot;);
        // 得到签名数组
        jobjectArray signatures = (jobjectArray) env-&amp;gt;GetObjectField(packageInfo, fid);
        // 得到签名
        jobject signature = env-&amp;gt;GetObjectArrayElement(signatures, 0);

        // 获得Signature类
        cls = env-&amp;gt;GetObjectClass(signature);
        mid = env-&amp;gt;GetMethodID(cls, &amp;quot;toByteArray&amp;quot;, &amp;quot;()[B&amp;quot;);
        // 当前应用签名信息
        jbyteArray signatureByteArray = (jbyteArray) env-&amp;gt;CallObjectMethod(signature, mid);
        //转成jstring
        jstring str = toMd5(env, signatureByteArray);
        char *c_msg = (char *) env-&amp;gt;GetStringUTFChars(str, 0);
        LOGD(&amp;quot;getSignature release sign md5: %s&amp;quot;, c_msg);
        isRight = strcmp(c_msg, SIGN_MD5) == 0;
        return isRight;
    }
    return isRight;
}


//有了校验的方法，所以我们要对第3步中，获取密钥和加密方法的进行修改，添加校验的逻辑
extern &amp;quot;C&amp;quot; JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getHttpSecretKey
        (JNIEnv *env, jclass cls, jint index) {
    if (getSignature(env)){//校验通过
      if (随机数条件1) {
        return env-&amp;gt;NewStringUTF(KEY1);
      } else if (随机数条件2) {
        return env-&amp;gt;NewStringUTF(KEY2);
      } else if (随机数条件3) {
        return env-&amp;gt;NewStringUTF(KEY3);
      } else {
        return env-&amp;gt;NewStringUTF(UNKNOWN);
      }
    }else {
        return env-&amp;gt;NewStringUTF(UNKNOWN);
    }
}

extern &amp;quot;C&amp;quot; JNIEXPORT jstring JNICALL
Java_com_test_util_HttpKeyUtil_getSecretValue
        (JNIEnv *env, jclass cls, jbyteArray jbyteArray1) {
        //加密算法各有不同，这里我就用md5做个示范
    if (getSignature(env)){//校验通过
       return toMd5(env, jbyteArray1);
    }else {
        return env-&amp;gt;NewStringUTF(UNKNOWN);
    }
}复制代码&lt;/code&gt;&lt;/pre&gt;    &lt;h3&gt;5.总结&lt;/h3&gt;    &lt;p&gt;以上就是此次事件native的相关代码，至于如何生成so库可以自行百度。从此次事件中需要反思的几点是：&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;安全性的认识，安全无小事&lt;/li&gt;      &lt;li&gt;发布出去的包必须走加固流程，为了防止疏漏，禁止人工打包加固，全部通过脚本实现&lt;/li&gt;      &lt;li&gt;服务端增加相关风险的报警机制&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/60924-android-%E6%95%B0%E6%8D%AE-%E6%80%9D%E8%80%83</guid>
      <pubDate>Sun, 11 Oct 2020 08:18:26 CST</pubDate>
    </item>
    <item>
      <title>Android 升级适配爬坑历程 (mp.weixin.qq.com)</title>
      <link>https://itindex.net/detail/60798-android-%E5%8D%87%E7%BA%A7-mp</link>
      <description>&lt;p&gt;code小生一个专注大前端领域的技术平台公众号回复  &lt;code&gt;Android&lt;/code&gt;加入安卓技术群&lt;/p&gt; &lt;blockquote&gt;  &lt;p&gt;作者：如果执着   &lt;br /&gt;链接：https://www.jianshu.com/p/20252fc5c836   &lt;br /&gt;声明：本文已获   &lt;code&gt;如果执着&lt;/code&gt;授权发表，转发等请联系原作者授权&lt;/p&gt;&lt;/blockquote&gt; &lt;p&gt;  &lt;strong&gt;以下部分适配是针对的是混合开发的项目，使用的是mui及h5+ api和原生代码实现&lt;/strong&gt;&lt;/p&gt; &lt;p&gt;最近接手了一个公司项目，项目比较老了，从Android 5.0之后就再也没有适配过了，然而重写时间又来不及，然后我的爬坑之旅便开始了。（以下适配方案是按照项目需求顺序来的）&lt;/p&gt; &lt;h3&gt;相机部分&lt;/h3&gt; &lt;p&gt;适配嘛，肯定得一步一步来，先从Android 6.0开始适配吧。起因是有同事反馈说h5调用原生相机的时候一片漆黑，经过调试发现是权限被拒绝，然后我给他的应用管理中开启，相机就能正常调用了，然而我在测试过程中发现，应用新安装的话bug会重现，后来才发现原来根本没有加入权限的申请这个功能，不得不说还是有点意外的，好吧，言归正传，开始适配吧。&lt;/p&gt; &lt;p&gt;回顾一下6.0最大的改动，不，应该说是新特性-就是加入了权限管理机制，从6.0（API 23）开始，用户开始在应用运行时向其授予权限，而不是在应用安装时授予。这种权限机制可以让用户更好的管理应用的权限，保障用户隐私。&lt;/p&gt; &lt;p&gt;Android将权限分为普通权限和危险权限，危险权限需要在使用时让用户手动允许。危险权限一共9组24个权限，包括拨打电话，相机，定位等等，下面是一个权限的表格，给个参考。  &lt;code&gt;https://blog.csdn.net/cui130/article/details/80772186&lt;/code&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;危险权限列表&lt;/strong&gt;&lt;/p&gt; &lt;img&gt;&lt;/img&gt; &lt;p&gt;我们项目是用的mui的框架，然后相机部分是原生代码实现的，所以适配这个，还需要Dcloud提供的h5+ api 附上官方文档说明(http://www.html5plus.org/doc/zh_cn/camera.html)&lt;/p&gt; &lt;p&gt;还是贴以下代码吧，有些比较懒的小伙伴就不用查了&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;var Context = plus.android.runtimeMainActivity();   &lt;br /&gt;var res = plus.android.invoke(&amp;quot;android.support.v4.app.ActivityCompat&amp;quot;, &amp;quot;checkSelfPermission&amp;quot;, Context, &amp;quot;android.permission.CAMERA&amp;quot;);   &lt;br /&gt;var PERMISSIONS_STORAGE = new Array();   &lt;br /&gt;PERMISSIONS_STORAGE.push(&amp;quot;android.permission.CAMERA&amp;quot;);   &lt;br /&gt;if(res != &amp;quot;0&amp;quot;) {   &lt;br /&gt;  plus.android.invoke(&amp;quot;android.support.v4.app.ActivityCompat&amp;quot;, &amp;quot;requestPermissions&amp;quot;, Context, PERMISSIONS_STORAGE, 1);   &lt;br /&gt;}   &lt;br /&gt;plus.scanplug.GetScanCodeFunction(function(result) {}, function(result) {});   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这是前段时间的实现方式，后来有小伙伴说由于迁移到了Android X,这个方式用不了，其实只需要把相应的类名更换下就行了，上述代码部分需要同步适配下,同样的贴出源码&lt;/p&gt; &lt;p&gt;具体映射查看官方文档（developer.android.com)&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;var Context = plus.android.runtimeMainActivity();   &lt;br /&gt;var res = plus.android.invoke(&amp;quot;androidx.core.app.ActivityCompat&amp;quot;, &amp;quot;checkSelfPermission&amp;quot;, Context, &amp;quot;android.permission.CAMERA&amp;quot;);   &lt;br /&gt;var PERMISSIONS_STORAGE = new Array();   &lt;br /&gt;PERMISSIONS_STORAGE.push(&amp;quot;android.permission.CAMERA&amp;quot;);   &lt;br /&gt;if(res != &amp;quot;0&amp;quot;) {   &lt;br /&gt;  plus.android.invoke(&amp;quot;androidx.core.app.ActivityCompat&amp;quot;, &amp;quot;requestPermissions&amp;quot;, Context, PERMISSIONS_STORAGE, 1);   &lt;br /&gt;}   &lt;br /&gt;plus.scanplug.GetScanCodeFunction(function(result) {}, function(result) {});   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;以上是在mui的js中做的相应的适配，为了更好的兼容性，原生代码中也需要对权限进行相应的处理，本来项目中是没有这一部分的管理的，我自己使用最原始的方式封装了一个，后来发现兼容性貌似有问题，然后更改成了rxpermissions，最容易上手，最简单的使用。同样的先贴地址 RxPermissions。虽然有一年多没更新了，但是权限这一部分基本还是能用。针对Android 10以后需要做相应的更改，这个之后会提到。&lt;/p&gt; &lt;p&gt;只需要在 module 的 build.gradle中配置如下依赖就成功引用了&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;implementation &amp;apos;com.tbruyelle.rxpermissions:rxpermissions:0.7.0&amp;apos;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后在需要调用的地方这样实现&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;if (Build.VERSION.SDK_INT &amp;gt; 23){   &lt;br /&gt;    RxPermissions.getInstance(mContext)   &lt;br /&gt;            .request(Manifest.permission.CAMERA)   &lt;br /&gt;            .subscribe(new Action1&amp;lt;Boolean&amp;gt;() {   &lt;br /&gt;                @Override   &lt;br /&gt;                public void call(Boolean granted) {   &lt;br /&gt;                    if (granted){   &lt;br /&gt;                        //实际实现功能的代码   &lt;br /&gt;                    }   &lt;br /&gt;                }   &lt;br /&gt;            });   &lt;br /&gt;}else {   &lt;br /&gt;    //这个说明系统版本在6.0之下，不需要动态获取权限   &lt;br /&gt;    //实际实现功能的代码   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这样有了双重保证，相机的权限应该是没有问题了，因为手上设备有限，如果你在使用中遇到了问题，留言或者私信我，我尽可能解决。&lt;/p&gt; &lt;h3&gt;Android O （8.0）升级自动安装问题&lt;/h3&gt; &lt;p&gt;到上面做了相机的权限适配后，项目总算是上线了，之前因为相机权限的问题，我也被逼上梁山，翻了无数文档，可算是解决了。上线之后，在一次更新中，部分用户反馈自己无法安装应用，提示升级后点击立即升级，安装包下载完成后会闪退或者还是停留在升级页面。网上一搜无法自动升级，全是8.0以后自动安装受限了，在后来的测试中，发现确实是8.0以上的设备才会出现这个问题，解决方式很简单，只需要在AndroidManifest.xml中添加以下这句就可以了&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;uses-permission android:name=&amp;quot;android.permission.REQUEST_INSTALL_PACKAGES&amp;quot; /&amp;gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果还是没解决，可以再加上下面这一句&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;uses-permission android:name=&amp;quot;android.permission.MOUNT_UNMOUNT_FILESYSTEMS&amp;quot;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;如果做得更细致一些，需要对是否开启未知来源安装权限做提醒，不过好在很多设备厂商对这个在底层已经做了处理了，这里就暂时忽略，有探索精神的可以尝试下。&lt;/p&gt; &lt;p&gt;PS：多说几句。很多人回复我说这个处理方式是开发Android最基本的，也会困扰我吗。我这里说明下，由于公司的业务发展，前端几乎都是h5实现的，比如公众号中的页面，或者是小程序，对于安卓这部分的了解确实少了，从中也可以做一个反面教材吧，提醒各位做开发的小伙伴们，不要忘了自己的本行技能，给别人以嘲笑你的理由。&lt;/p&gt; &lt;h3&gt;网络请求部分&lt;/h3&gt; &lt;p&gt;这个是在内部测试时发现的，打包后同事的手机安装后都无法请求网络，他们一度以为我打的包是测试包，hah。我一开始也是很茫然的，想了好多搜索的关键词找答案，然而还是没有结果，最后发现访问不到数据的同事设备都是Android P（9.0），通过这个一查，就有了很多的解答了。&lt;/p&gt; &lt;p&gt;最直接的就把问题抛给做后端的同事吧，这个是9.0之后Google禁止明文的数据传输，所以只要把http请求换成 https就可完美解决。但是如果后端不好做或者是不配合，那就只能使用内功心法了。1）targetSdkVersion 降到27以下
2）就是下面这种最简单的方式了&lt;/p&gt; &lt;p&gt;在AndroidManifest.xml， &amp;lt;application 节点中添加以下一句&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;android:usesCleartextTraffic=&amp;quot;true&amp;quot;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;PS:更新一下，很多小伙伴说加上这句没有效果，经过测试需要gradle版本3.2.0及更新的版本才能够正常使用。至于原因嘛，就不深究了。有兴趣的可以去尝试，解决了可以给我留言哈。&lt;/p&gt; &lt;p&gt;3）更改网络安全配置
①在res文件夹下创建一个xml文件夹，然后创建一个network_security_config.xml文件，文件内容如下&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;   &lt;br /&gt;&amp;lt;network-security-config&amp;gt;   &lt;br /&gt;    &amp;lt;base-config cleartextTrafficPermitted=&amp;quot;true&amp;quot; /&amp;gt;   &lt;br /&gt;&amp;lt;/network-security-config&amp;gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;②接着，在AndroidManifest.xml文件下的application标签增加以下属性&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;android:networkSecurityConfig=&amp;quot;@xml/network_security_config&amp;quot;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;我在项目中是用的第二种方案，大家就结合自己的项目，各取所需吧。&lt;/p&gt; &lt;h3&gt;Android N（7.0） 权限收缩，需要申请权限更难了&lt;/h3&gt; &lt;p&gt;最近有两个同事换了新手机，一个是一加7T，一个是华为meta 30，这两款手机出厂都是Android Q（10），在这两个手机上发现了一个问题，就是自动更新时出现了和适配Android 8.0时同样的问题，反复查找问题，调试，最终也没找到解决方案。之前的更新是用 h5+ api 实现的，迫于这是个第三方的框架，在论坛寻求解决方式无果后，决心把升级这块用原生的实现，然后就到了我的Android 10 适配之旅，至于为什么小标题是 Android 7.0的适配，请耐心往下看。&lt;/p&gt; &lt;p&gt;我的第一步就是直接在网上寻找已经封装好了的实现方案，找了很久，写了个demo，发现可用，简单，方便，交互友好，想想这么好的东西要是集成到我负责项目，想想都激动，于是我就着手准备了。引用来源--&amp;gt;&amp;gt;  &lt;code&gt;https://www.jianshu.com/p/cdf707aeb76c&lt;/code&gt;&lt;/p&gt; &lt;p&gt;接下来就是爬坑之旅了，开始的详细步骤就是按照这个简书操作的，只是作者可能只是贴了最关键的更新实现的代码。关于升级的这部分的过程就不赘述了，只提我在集成中遇到的问题。&lt;/p&gt; &lt;p&gt;集成之后第一步，很开心的点击studio中绿色的run按钮，然后安装包就开始pupu的开始下载了，通知栏也是很配合的有进度加载，然后。。。然后。。。然后下载完成之后安装时提示错误，我很惊奇，demo中好好的为啥集成后会报错呢。于是赶紧开始了调试大法--debug一下，然后看到错误了。提示的是&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;FileUriExposedException   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;一看到这个错误就知道，肯定是apk的下载路径配置的不对，查找之后发现这个是Android 7.0之后权限不够导致的，下载的apk安装包不在包名的目录下，所以没有权限去获取到apk，肯定也就无法调用安装的操作。&lt;/p&gt; &lt;p&gt;同样的百度了一下这个错误，找到了问题所在，Android 7.0之后需要使用FileProvider提供的方法去获得临时访问外部文件夹的权限。这里同样找到了一篇很好的文章来解决这个问题。引用来源--&amp;gt;&amp;gt;  &lt;code&gt;https://www.jianshu.com/p/55b817530fa3&lt;/code&gt;，这篇文章介绍的很详细，包括之后导致了哪些错误，然后怎么解决的都有了说明。&lt;/p&gt; &lt;p&gt;官方文档说明
https://developer.android.com/training/secure-file-sharing/index.html&lt;/p&gt; &lt;p&gt;简而言之就是在AndroidManifest.xml中配置一个provider组件，用这个组件去申请一个临时的访问外部路径的权限，然后配置一个你需要访问的目录的路径，达到即能够访问这个目录又对用户隐私起到保护的目的。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt; &amp;lt;provider   &lt;br /&gt;    android:name=&amp;quot;androidx.core.content.FileProvider&amp;quot;   &lt;br /&gt;    android:authorities=&amp;quot;com.xg.shfcoc.fileProvider&amp;quot;   &lt;br /&gt;    android:exported=&amp;quot;false&amp;quot;   &lt;br /&gt;    android:grantUriPermissions=&amp;quot;true&amp;quot;&amp;gt;   &lt;br /&gt;    &amp;lt;meta-data   &lt;br /&gt;        android:name=&amp;quot;android.support.FILE_PROVIDER_PATHS&amp;quot;   &lt;br /&gt;        android:resource=&amp;quot;@xml/file_paths&amp;quot; /&amp;gt;   &lt;br /&gt;&amp;lt;/provider&amp;gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;接着在res目录下新建一个xml文件夹，新建一个和provider中配置的文件名一样的文件，我创建的文件名就是file_paths。&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;   &lt;br /&gt;&amp;lt;paths &amp;gt;   &lt;br /&gt;    &amp;lt;external-files-path name=&amp;quot;external-files-path&amp;quot; path=&amp;quot;VersionChecker/&amp;quot;/&amp;gt;   &lt;br /&gt;   &lt;br /&gt;&amp;lt;/paths&amp;gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;中可以定义以下子节点&lt;/p&gt; &lt;img&gt;&lt;/img&gt; &lt;p&gt;再具体点的话就是下面的解释了&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;1.&amp;lt;root-path/&amp;gt; 代表设备的根目录new File(&amp;quot;/&amp;quot;);   &lt;br /&gt;2.&amp;lt;files-path/&amp;gt; 代表context.getFilesDir()   &lt;br /&gt;3.&amp;lt;cache-path/&amp;gt; 代表context.getCacheDir()   &lt;br /&gt;4.&amp;lt;external-path/&amp;gt; 代表Environment.getExternalStorageDirectory()   &lt;br /&gt;5.&amp;lt;external-files-path&amp;gt;代表context.getExternalFilesDirs()   &lt;br /&gt;6.&amp;lt;external-cache-path&amp;gt;代表getExternalCacheDirs()   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;再多说几句，因为这里有坑，所以提前提醒下
1）name代表什么
name 属性用于给 path 属性所指定的子目录名称取一个别名。后续生成 content:// URI 时，会使用这个别名代替真实目录名。这样做的目的，很显然是为了提高安全性
2）path是什么意思
path 属性用于指定当前子元素所代表目录下需要共享的子目录名称。注意：path 属性值不能使用具体的独立文件名，只能是目录名。&lt;/p&gt; &lt;p&gt;再多的就不说了，实在还是不懂，本文与各大搜索引擎达成战略合作，可以自行搜索结果。&lt;/p&gt; &lt;p&gt;假如我要替换的目录是  &lt;code&gt;/storage/emulated/0/diary sdcard/photo/&lt;/code&gt;那么配置应该写成&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;   &lt;br /&gt;&amp;lt;paths xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot;&amp;gt;   &lt;br /&gt;&amp;lt;external-path name=&amp;quot;external_files&amp;quot; path=&amp;quot;VersionChecker&amp;quot;/&amp;gt;   &lt;br /&gt;&amp;lt;/paths&amp;gt;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这里特别说明下，木来我的目录配置是这个的，之后在Android 10上会出现闪退现象，就换成了这个配置，原因不明，但是这样就能解决，不多纠结了，能解决问题才是最重要的。&lt;/p&gt; &lt;p&gt;之后就是在你需要存储文件地方做如下的获取方式&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;getExternalFilesDir(&amp;quot;文件夹名字&amp;quot;).getAbsolutePath()+&amp;quot;/&amp;quot;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后获取自动更新的方式如下所示&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;Intent installIntent = new Intent(Intent.ACTION_VIEW);   &lt;br /&gt;installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);   &lt;br /&gt;// System.out.println(result.getPath());   &lt;br /&gt;if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.N) {   &lt;br /&gt;    installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK );   &lt;br /&gt;     //添加这一句表示对目标应用临时授权该Uri所代表的文件   &lt;br /&gt;    installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);   &lt;br /&gt;    Uri contentUri = FileProvider.getUriForFile(DownApkService.this,   &lt;br /&gt;            BuildConfig.APPLICATION_ID + &amp;quot;.fileProvider&amp;quot;, new File(result.getPath()));   &lt;br /&gt;   &lt;br /&gt;    installIntent.setDataAndType(contentUri, &amp;quot;application/vnd.android.package-archive&amp;quot;);   &lt;br /&gt;} else {   &lt;br /&gt;    installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);   &lt;br /&gt;    installIntent.setDataAndType(Uri.fromFile(new File(result.getPath())), &amp;quot;application/vnd.android.package-archive&amp;quot;);   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这里有个坑，先提醒下大家&lt;/p&gt; &lt;p&gt;  &lt;code&gt;installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK );&lt;/code&gt;一定是在  &lt;code&gt;installIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);&lt;/code&gt;之前设置，还有上一句一定是  &lt;code&gt;addFlags&lt;/code&gt;而不是  &lt;code&gt;setFlags&lt;/code&gt;，不然在Android 10 的设备上会出现解析软件包异常。&lt;/p&gt; &lt;p&gt;在多说一句，BuildConfig一定是引用的项目中你自己的包，而不是其他jar包中的方法，不然也会抛异常，小伙伴有兴趣的也可以尝试一下。&lt;/p&gt; &lt;p&gt;还是觉得要再补充一下在这个项目中使用的网络请求框架是Xutils，xUtils3 github传送门:  &lt;code&gt;https://github.com/wyouflf/xUtils3&lt;/code&gt;&lt;/p&gt; &lt;p&gt;具体解决方法如下&lt;/p&gt; &lt;p&gt;只需要在 module 的 build.gradle中配置如下依赖就成功引用了&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;implementation &amp;apos;org.xutils:xutils:3.8.3&amp;apos;   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;然后写一个Activity，在这里去初始化这个Xutils&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;@Override   &lt;br /&gt;public void onCreate() {   &lt;br /&gt;   super.onCreate();   &lt;br /&gt;   &lt;br /&gt;   x.Ext.init(this);   &lt;br /&gt;   x.Ext.setDebug(BuildConfig.DEBUG);   &lt;br /&gt;   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这里的Activity的onCreate()中不能有setContentView(R.layout.xxx) 否则会报错，切记。&lt;/p&gt; &lt;p&gt;由上面的初始化引出了下面的问题。由于是本项目使用的是mui还有原生的混合开发，Dcloud官方称呼这种是离线打包，怎么叫不重要，重要的是用法，Xutils在使用时需要初始化，一般的处理方式是在  &lt;code&gt;AndroidManifest.xml&lt;/code&gt;，  &lt;code&gt;&amp;lt;application&lt;/code&gt;节点中添加  &lt;code&gt;android:name=&amp;quot;.ApplicationContext&amp;quot;&lt;/code&gt;，但是我们的项目必须要这样配置  &lt;code&gt;android:name=&amp;quot;io.dcloud.application.DCloudApplication&amp;quot;&lt;/code&gt;，才能使用mui提供的plus调用原生方法。所以解决方法是，这里贴出全部的源码&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;package com.xxx;   &lt;br /&gt;   &lt;br /&gt;import android.content.Context;   &lt;br /&gt;import org.xutils.x;   &lt;br /&gt;import io.dcloud.application.DCloudApplication;   &lt;br /&gt;   &lt;br /&gt;public class ApplicationContext extends DCloudApplication {   &lt;br /&gt;   private static ApplicationContext context;   &lt;br /&gt;   &lt;br /&gt;   public static ApplicationContext getContext(){   &lt;br /&gt;       return context;   &lt;br /&gt;   }   &lt;br /&gt;   &lt;br /&gt;   @Override   &lt;br /&gt;   protected void attachBaseContext(Context base) {   &lt;br /&gt;       super.attachBaseContext(base);   &lt;br /&gt;       context = this;   &lt;br /&gt;   }   &lt;br /&gt;   &lt;br /&gt;   @Override   &lt;br /&gt;   public void onCreate() {   &lt;br /&gt;       super.onCreate();   &lt;br /&gt;   &lt;br /&gt;       x.Ext.init(this);   &lt;br /&gt;       x.Ext.setDebug(BuildConfig.DEBUG);   &lt;br /&gt;   &lt;br /&gt;   }   &lt;br /&gt;}   &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt; &lt;p&gt;这样就可以  &lt;code&gt;android:name=&amp;quot;.ApplicationContext&amp;quot;&lt;/code&gt;这样配置了。以上就完美的解决了Xutils的引用，也不会影响mui的原生调用。&lt;/p&gt; &lt;p&gt;这是适配的第一篇，重新捡起丢失的Android技能，后续会尽快更新。&lt;/p&gt; &lt;h3&gt;相关阅读&lt;/h3&gt; &lt;p&gt;1  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ==&amp;mid=2247489901&amp;idx=1&amp;sn=ca1abe9bc12760de5149324d210b8855&amp;chksm=97f6a7d9a0812ecf2239b771728dcc67a24e48e11a22fb322eaa5480540bf1be02307d0eee38&amp;scene=21#wechat_redirect" target="_blank"&gt;记录项目升级 AndroidX+API29 的各种坑&lt;/a&gt;  &lt;br /&gt;2  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ==&amp;mid=2247489792&amp;idx=1&amp;sn=5afc78b145fdcfa949a2ae97fd0d8306&amp;chksm=97f6a7b4a0812ea2ebef0d87221283cdebea3b4ff8b9fecc77fca3a160debc6f72b4bfa1041c&amp;scene=21#wechat_redirect" target="_blank"&gt;Flutter1.12 升级后的问题&lt;/a&gt;  &lt;br /&gt;3  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ==&amp;mid=2247488444&amp;idx=1&amp;sn=c779481f08134fe2c038b0aea846d461&amp;chksm=97f6ad08a081241ee4cad7bc9fe21e833e9cacb262a476b3a82834344f258740115fba2f28ec&amp;scene=21#wechat_redirect" target="_blank"&gt;无懈可击 Android 内部升级&lt;/a&gt;  &lt;br /&gt;4  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ==&amp;mid=2247490313&amp;idx=1&amp;sn=fc33a55b2e5e52efe5f17f0c8f72759d&amp;chksm=97f6a5bda0812cab5857b6d2780e95baf99d7e3da51050c7e5f1b5e9fa45f2b6c3b745f90330&amp;scene=21#wechat_redirect" target="_blank"&gt;如何在项目中封装 Kotlin + Android Databinding&lt;/a&gt;  &lt;br /&gt;5  &lt;a href="http://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ==&amp;mid=2247490087&amp;idx=1&amp;sn=fbf69ef286fa0cd41e0082f74e19fabb&amp;chksm=97f6a493a0812d859584dde64670f16319e6cea8e0e17b96f238a3bf64bcf8ca61372bfe4f97&amp;scene=21#wechat_redirect" target="_blank"&gt;不使用第三方库，Bitmap 的优化策略&lt;/a&gt;  &lt;br /&gt;&lt;/p&gt; &lt;br /&gt; &lt;br /&gt; &lt;img&gt;&lt;/img&gt;如果你有写博客的好习惯 &lt;a href="https://mp.weixin.qq.com/s?__biz=MzIxNzU1Nzk3OQ==&amp;mid=2247483965&amp;idx=1&amp;sn=cfc50055cf38594eebb1befc72447e8c&amp;scene=21#wechat_redirect" target="_blank"&gt;欢迎投稿&lt;/a&gt; &lt;p&gt;  &lt;strong&gt;   &lt;strong&gt;    &lt;strong&gt;     &lt;strong&gt;      &lt;strong&gt;点个在看,小生感恩&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;  &lt;strong&gt;   &lt;strong&gt;    &lt;strong&gt;     &lt;strong&gt;      &lt;strong&gt;❤️&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/strong&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>jianshu</category>
      <guid isPermaLink="true">https://itindex.net/detail/60798-android-%E5%8D%87%E7%BA%A7-mp</guid>
      <pubDate>Sun, 02 Aug 2020 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>百度APP-Android H5首屏优化实践</title>
      <link>https://itindex.net/detail/59967-%E7%99%BE%E5%BA%A6-app-android</link>
      <description>&lt;h2&gt;一、背景&lt;/h2&gt;
 &lt;p&gt;百度App自2016年上半年尝试Feed流业务形态，至2017年下半年，历经10个版本的迭代，基本完成了产品形态的初步探索。在整个Feed流形态的闭环中，新闻详情页（文中称为落地页）作为重要的组成部分，如果打开页面后，loading时间过长，会严重影响用户体验。因此我们针对落地页这种H5的首屏展现速度进行了长期优化，本文会详细阐述整个优化思路和技术细节&lt;/p&gt;
 &lt;h2&gt;二、方法论&lt;/h2&gt;
 &lt;p&gt;通过分析用户反馈，发现当时的落地页从点击到首屏展现平均需要3s的时间，每次用户兴致勃勃的想要浏览感兴趣的文章时，却因为过长的loading时间，而不耐烦的选择了back。为了提升用户体验，我们进行了以下工作：  &lt;br /&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwiSo?w=995&amp;h=895" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;通过用户反馈、QA测试等多种渠道，发现落地页首屏加载慢问题&lt;/li&gt;
  &lt;li&gt;定义首屏性能指标（首屏含图，以图片加载为准；首屏无图，以文字渲染结束为准）&lt;/li&gt;
  &lt;li&gt;NA、内核、H5三方针对自己加载H5的流程进行划分并埋点上报&lt;/li&gt;
  &lt;li&gt;统计侧根据三端上报的数据产出平均值、80分位值的性能报表&lt;/li&gt;
  &lt;li&gt;分析性能报表，找到不合理的耗时点，并进行优化&lt;/li&gt;
  &lt;li&gt;以AB实验方式，对比优化前后的性能报表数据，产出优化效果，同时评估用户体验等相关指标&lt;/li&gt;
  &lt;li&gt;按照长期优化的方式，不断分析定位性能瓶颈点并优化，以AB实验方式评估效果，最终达到我们的落地页秒开目标&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;三、Hybrid方案简述及性能瓶颈&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;（一）方案简述&lt;/strong&gt;  &lt;br /&gt;优化之前，我们与业内大多数的App一样，在落地页的技术选型中，为了满足跨平台和动态性的要求，采用了Hybrid这种比较成熟的方案。Hybrid，顾名思义，即混合开发，也就是半原生半Web的方式。页面中的复杂交互功能采用端能力的方式，调用原生API来实现。成本低，灵活性较好，适合偏信息展示类的H5场景。  &lt;br /&gt;下面用一张图来表示百度App中Hybrid的实现机制和加载流程  &lt;br /&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwiSZ?w=1799&amp;h=814" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;strong&gt;（二）性能瓶颈&lt;/strong&gt;  &lt;br /&gt;为了分析Hybrid方案首屏展现较慢的原因，找到具体的性能瓶颈，客户端和前端分别针对各自加载过程中的关键节点进行埋点统计，并借由性能监控平台日志进行展示，下图是截取的某一天全网用户的落地页首屏展现速度80分位数据  &lt;br /&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwiTb?w=1974&amp;h=589" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;各阶段性能点可以按Hybrid加载流程进行划分，可以看到，从点击到首屏展现，大致需要2600ms，其中初始化NA组件需要350ms，Hybrid初始化需要170ms，前端H5执行JS获取正文并渲染需要1400ms，完成图片加载和渲染需要700ms的时间  &lt;br /&gt;我们具体分析下四个阶段的性能损耗主要发生在哪些地方：  &lt;br /&gt;1) 初始化NA组件  &lt;br /&gt;从点击到落地页框架初始化完成，主要工作为初始化WebView，尤其是第一次进入（WebView首次创建耗时均值为500ms）  &lt;br /&gt;2) Hybrid初始化  &lt;br /&gt;这个阶段的工作主要包含两部分，一个是根据调起协议中传入的相关参数，校验解压下发到本地的Hybrid模板，大致需要100ms的时间；此外，WebView.loadUrl执行后，会触发对Hybrid模板头部和Body的解析  &lt;br /&gt;3) 正文加载&amp;amp;渲染  &lt;br /&gt;执行到这个阶段，内核已经完成了对Hybrid模板头部和body的解析，此时需要加载解析页面所需的JS文件，并通过JS调用端能力发起对正文数据的请求，客户端从Server拿到数据后，用JsCallback的方式回传给前端，前端需要对客户端传来的JSON格式的正文数据进行解析，并构造DOM结构，进而触发内核的渲染流程；此过程中，涉及到对JS的请求，加载、解析、执行等一系列步骤，并且存在端能力调用、JSON解析、构造DOM等操作，较为耗时  &lt;br /&gt;4) 图片加载  &lt;br /&gt;第（3）步中，前端获取到的正文数据包含落地页的图片地址集，在完成正文的渲染后，需要前端再次执行图片请求的端能力，客户端这边接收到图片地址集后按顺序请求服务器，完成下载后，客户端会调用一次IO将文件写入缓存，同时将对应图片的本地地址回传给前端，最终通过内核再发起一次IO操作获取到图片数据流，进行渲染；总体来看，图片渲染的时间依赖前端的解析效率、端能力执行效率、下载速度、IO速度等因素  &lt;br /&gt;通过分析，延伸出对Hybrid方案的一些思考：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;渲染为什么这么慢&lt;/li&gt;
  &lt;li&gt;图片请求能否提前&lt;/li&gt;
  &lt;li&gt;串行逻辑是否可以改为并行&lt;/li&gt;
  &lt;li&gt;WebView初始化时间是否还可以优化&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;四、百度App落地页优化方案&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;（一）CloudHybrid&lt;/strong&gt;  &lt;br /&gt;基于之前对Hybrid性能的分析，我们内部孵化了一个叫做CloudHybrid的项目，用来解决落地页首屏展现慢的痛点；一句话来形容CloudHybrid方案，就是采用后端直出+预取+拦截的方式，简化页面渲染流程，提前化&amp;amp;并行化网络请求逻辑，进而提升H5首屏速度&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt; 1.后端直出-快速渲染首屏 &lt;/strong&gt;  &lt;br /&gt;  &lt;strong&gt; a. 页面静态直出 &lt;/strong&gt;  &lt;br /&gt;对于Hybrid方案来说，端上预置和加载的html文件只是一个模板文件，内部包含一些简单的JS和CSS文件，端上加载HTML后，需要执行JS通过端能力从Server异步请求正文数据，得到数据后，还需要解析JSON，构造DOM，应用CSS样式等一系列耗时的步骤，最终才能由内核进行渲染上屏；为了提升首屏展示速度，可以利用后端渲染技术(smarty)对正文数据和前端代码进行整合，直出首屏内容，直出后的html文件包含首屏展现所需的内容和样式，内核可以直接渲染；首屏外的内容（包括相关推荐、广告等）可以在内核渲染完首屏后，执行JS，并利用preact进行异步渲染&lt;/p&gt;
 &lt;blockquote&gt;百度APP直出方案：&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwiTJ?w=1739&amp;h=435" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;对于客户端来说，从CDN中拉取到的html都是已经在server渲染好首屏的，这样的内容无需二次加工，展现速度可以大大提升，仅直出一点，手百Feed落地页的首屏性能数据就从2600ms优化到2000ms以内  &lt;br /&gt;  &lt;strong&gt;b. 动态信息回填&lt;/strong&gt;  &lt;br /&gt;为了保证首屏渲染结果的准确性，除了在server侧对正文内容和前端代码进行整合外，还需要一些影响页面渲染的客户端状态信息，例如首图地址、字体大小、夜间模式等  &lt;br /&gt;这里我们采用动态回填的方式，前端会在直出的html中定义一系列特殊字符，用来占位；客户端在loadUrl之前，会利用正则匹配的方式，查找这些占位字符，并按照协议映射成端信息；经过客户端回填处理后的html内容，已经具备了展现首屏的所有条件  &lt;br /&gt;  &lt;strong&gt;c. 动画间渲染&lt;/strong&gt;  &lt;br /&gt;先看下优化前后效果（上图：优化前；下图：优化后）：  &lt;br /&gt;  &lt;img alt="&amp;#20248;&amp;#21270;&amp;#21069;" src="https://segmentfault.com/img/bVbwvEv?w=310&amp;h=640" title="&amp;#20248;&amp;#21270;&amp;#21069;"&gt;&lt;/img&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwvEJ?w=310&amp;h=640" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;正常来说，直出后的页面展现速度已经很快了；但在实际开发中，你可能会遇到即使自己的数据加载速度再快，仍然会出现Activity切换过程中无法渲染H5页面的问题（可以通过开发者模式放慢动画时间来验证），产生视觉上的白屏现象（如上面上图）  &lt;br /&gt;我们通过研究源码发现，系统处理view绘制的时候，有一个属性setDrawDuringWindowsAnimating，从命名可以看出来，这个属性是用来控制window做动画的过程中是否可以正常绘制，而恰好在Android 4.2到Android N之间，系统为了组件切换的流程性考虑，该字段为false，我们可以利用反射的方式去手动修改这个属性，改进后的效果见上面下图&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;
/**
     * 让 activity transition 动画过程中可以正常渲染页面
     */
    private void setDrawDuringWindowsAnimating(View view) {
        if (Build.VERSION.SDK_INT &amp;gt; Build.VERSION_CODES.M
                || Build.VERSION.SDK_INT &amp;lt; Build.VERSION_CODES.JELLY_BEAN_MR1) {
            // 1 android n以上  &amp;amp; android 4.1以下不存在此问题，无须处理
            return;
        }
        // 4.2不存在setDrawDuringWindowsAnimating，需要特殊处理
        if (Build.VERSION.SDK_INT &amp;lt; Build.VERSION_CODES.JELLY_BEAN_MR1) {
            handleDispatchDoneAnimating(view);
            return;
        }
        try {
            // 4.3及以上，反射setDrawDuringWindowsAnimating来实现动画过程中渲染
            ViewParent rootParent = view.getRootView().getParent();
            Method method = rootParent.getClass()
                    .getDeclaredMethod(&amp;quot;setDrawDuringWindowsAnimating&amp;quot;, boolean.class);
            method.setAccessible(true);
            method.invoke(rootParent, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * android4.2可以反射handleDispatchDoneAnimating来解决
     */
    private void handleDispatchDoneAnimating(View paramView) {
        try {
            ViewParent localViewParent = paramView.getRootView().getParent();
            Class localClass = localViewParent.getClass();
            Method localMethod = localClass.getDeclaredMethod(&amp;quot;handleDispatchDoneAnimating&amp;quot;);
            localMethod.setAccessible(true);
            localMethod.invoke(localViewParent);
        } catch (Exception localException) {
            localException.printStackTrace();
        }
    }&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;2.智能预取-提前化网络请求&lt;/strong&gt;  &lt;br /&gt;经过直出的改造之后，为了更快的渲染首屏，减少过程中涉及到的网络请求耗时，我们可以按照一定的策略和时机，提前从CDN中请求部分落地页html，缓存到本地，这样当用户点击查看新闻时，只需从缓存中加载即可&lt;/p&gt;
 &lt;blockquote&gt;手百预取服务架构图&lt;/blockquote&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwiS7?w=1799&amp;h=677" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;目前手百预取服务支撑着图文、图集、视频、广告等多个业务方，根据业务场景的不同，触发时机可以自定义，也可以遵循我们默认的刷新、滑停、点击等时机，此外，我们会对预取内容进行优先级排序（根据资源类型、触发时机），会动态的根据当前手机状态信息进行并发控制和流量控制，在一些降级场景中，server还可以通过云控的方式来控制是否预取以及预取的数量  &lt;br /&gt;  &lt;strong&gt;3.通用拦截-缓存共享、请求并行&lt;/strong&gt;  &lt;br /&gt;在落地页中，除了文本外，图片也是重要的组成部分。直出解决了文字展现的速度问题，但图片的加载渲染速度仍不理想，尤其是首屏中带有图片的文章，其首图的渲染速度才是真正的首屏时间点  &lt;br /&gt;传统Hybrid方案，前端页面通过端能力调用NA图片下载能力来缓存和渲染图片，虽然实现了客户端和前端图片缓存的共享，但由于JS执行时机较晚，且多次端能力调用存在效率问题，导致图片渲染延后  &lt;br /&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwvFK?w=664&amp;h=108" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;strong&gt;初步改进方案：&lt;/strong&gt;为了提升图片加载速度，减少JS调用耗时，改为纯H5请求图片，速度虽然有所提升，但是客户端和前端缓存无法共享，当点击图片调起NA图片查看器时，无法做到沉浸式效果，且仍需重复下载一次图片，造成流量浪费   &lt;br /&gt;  &lt;strong&gt;终极方案：&lt;/strong&gt;借由内核的shouldInterceptRequest回调，拦截落地页图片请求，由客户端调用NA图片下载框架进行下载，并以管道方式填充到内核的WebResourceResponse中  &lt;br /&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwvFO?w=1345&amp;h=325" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;此方案在满足图片渲染速度的同时，解耦了客户端和前端代码，客户端充当server角色，对图片进行请求和缓存控制，保证前端和客户端可以共用图片缓存，改造后的方案，非首图展现流程，页面不卡顿，首屏80分位值缩短80ms~150ms  &lt;br /&gt;效果如下（上图：优化前Hybrid方案；下图：优化后通用拦截方案）：  &lt;br /&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwvFQ?w=310&amp;h=640" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwvFR?w=310&amp;h=640" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;strong&gt;4.整体方案流程&lt;/strong&gt;  &lt;br /&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwvFX?w=1079&amp;h=713" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;strong&gt;（二）新的优化尝试&lt;/strong&gt;  &lt;br /&gt;  &lt;strong&gt;1.WebView预创建&lt;/strong&gt;  &lt;br /&gt;为了节省WebView的性能损耗，我们可以在合适时机提前创建好WebView，并存入缓存池，当页面需要显示内容时，直接从缓存池获取创建好的WebView，根据性能数据显示，WebView预创建可以提升首屏渲染时间200ms+  &lt;br /&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwvF9?w=502&amp;h=408" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;具体以Feed落地页为例，当用户进入手百并触发Feed吸顶操作后，我们会创建第一个WebView，当用户进入落地页后，会从缓存池中取出来渲染H5页面，为了不影响页面的加载速度，同时保证下次进入落地页缓存池中仍然有可用的WebView组件，我们会在每次页面加载完成（pageFinish）或者back退出落地页的时机，去触发预创建WebView的逻辑  &lt;br /&gt;由于WebView的初始化需要和context进行绑定，若想实现预创建的逻辑，需要保证context的一致性，常规做法我们考虑可以用fragment来实现承载H5页面的容器，这样context可以用外层的activity实例，但Fragment本身的切换流畅度存在一定问题，并且这样做限定了WebView预创建适用的场景。为此，我们找到了一种更加完美的替代方案，即MutableContextWrapper&lt;/p&gt;
 &lt;blockquote&gt;Special version of ContextWrapper that allows the base context to be modified after it is initially set. Change the base context for this ContextWrapper. All calls will then be delegated to the base context. Unlike ContextWrapper, the base context can be changed even after one is already set.  &lt;br /&gt;简单来说，就是一种新的context包装类，允许外部修改它的baseContext，并且所有ContextWrapper调用的方法都会代理到baseContext来执行&lt;/blockquote&gt;
 &lt;p&gt;下面是截取的一段预创建WebView的代码&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;/**
     * 创建WebView实例
     * 用了applicationContext
     */
    @DebugTrace
    public void prepareNewWebView() {
        if (mCachedWebViewStack.size() &amp;lt; CACHED_WEBVIEW_MAX_NUM) {
            mCachedWebViewStack.push(new WebView(new MutableContextWrapper(getAppContext())));
        }
    }
    /**
     * 从缓存池中获取合适的WebView
     * 
     * @param context activity context
     * @return WebView
     */
    private WebView acquireWebViewInternal(Context context) {
        // 为空，直接返回新实例
        if (mCachedWebViewStack == null || mCachedWebViewStack.isEmpty()) {
            return new WebView(context);
        }
        WebView webView = mCachedWebViewStack.pop();
        // webView不为空，则开始使用预创建的WebView,并且替换Context
        MutableContextWrapper contextWrapper = (MutableContextWrapper) webView.getContext();
        contextWrapper.setBaseContext(context);
        return webView;
    }&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;  &lt;strong&gt;2.NA组件懒加载&lt;/strong&gt;  &lt;br /&gt;a. WebView初始化完成，立刻loadUrl，无需等待框架onCreate或者OnResume结束  &lt;br /&gt;b. WebView初始完成后到页面首屏绘制完成之间，尽量减少UI线程的其他操作，繁忙的UI线程会拖慢WebView.loadUrl的速度&lt;/p&gt;
 &lt;blockquote&gt;具体到Feed落地页场景，由于我们的落地页包含两部分，WebView+NA评论组件，正常流程会在WebView初始化结束后，开始评论组件的初始化及评论数据的获取。由于此时评论的初始化仍处在onCreate的UI消息处理中，会严重延迟内核加载主文档的逻辑。考虑到用户进入落地页的时候，评论组件对用户来说并不可见，所以将评论组件的初始化延迟到页面的pageFinish时机或者firstScreenPaintFinished；  &lt;strong&gt;80分位性能提升60ms~100ms&lt;/strong&gt;
&lt;/blockquote&gt;
 &lt;p&gt;  &lt;strong&gt;3.内核优化&lt;/strong&gt;  &lt;br /&gt;a. 内核渲染优化：  &lt;br /&gt;内核中主要分为三个线程（IOThread、MainThread、ParserThread），首先IOThread会从网络端或者本地获取html数据，并把数据交给MainThread（渲染线程，十分繁忙，用于JS执行，页面布局等），为了保证MainThread不被阻塞，需要额外起一个后台线程（ParserThread）用来做html的解析工作。ParserThread每解析到落地页html中带有特殊class标记的一个div标签或者P标签（图中的first、second）时，就会触发一次MainThread的layout工作，并把layout后得到的高度与屏幕高度进行对比，如果当前layout高度已经大于屏幕高度，我们认为首屏内容已经完成布局，可以触发渲染上屏逻辑，不必等到整篇html全部解析完成再上屏，提前了首屏的渲染时间；  &lt;strong&gt;80分位下，内核的渲染优化可以提升首屏速度100ms~200ms&lt;/strong&gt;  &lt;br /&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwvGG?w=443&amp;h=602" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;b. 预加载JS：  &lt;br /&gt;预创建好WebView后，通过预加载JS（与内核约定好的JS内容，内核侧执行该JS时，只做初始化操作），触发WebView初始化逻辑，缩短后续加载url耗时；  &lt;strong&gt;80分位性能提升80ms左右&lt;/strong&gt;&lt;/p&gt;
 &lt;h2&gt;五、新的问题-流量和速度的平衡&lt;/h2&gt;
 &lt;p&gt;频繁预取会带来流量的浪费：预取的命中率虽然达到了90%以上，但有效率仅有15%  &lt;br /&gt;  &lt;strong&gt;解决思路：&lt;/strong&gt;  &lt;br /&gt;&amp;amp;nbsp&amp;amp;nbsp1.压缩预取的包大小，减少下行流量  &lt;br /&gt;&amp;amp;nbsp&amp;amp;nbsp2.少预取或者不预取  &lt;br /&gt;  &lt;strong&gt;（一）精简预取数据：&lt;/strong&gt;  &lt;br /&gt;图文：优化直出html中内联的css、icon等数据，数据大小减少约40%  &lt;br /&gt;  &lt;strong&gt;（二）后端智能预取：&lt;/strong&gt;  &lt;br /&gt;1) 图文：通过对图文资源进行评分，来决定4G是否需要预取，多组AB试验最优效果劣化9.5ms  &lt;br /&gt;2)视频：为了平衡性能和流量，在性能劣化可接受的范围内（视频起播时间劣化100ms），针对视频部分采用流量高峰期不预取的策略，减少视频总流量约7%，整体带宽峰值下降3%  &lt;br /&gt;  &lt;strong&gt;（三）AI智能预取&lt;/strong&gt;  &lt;br /&gt;通用用户操作行为，对Feed预取进行AI预测，减少无效预取的数量。&lt;/p&gt;
 &lt;h2&gt;六、总结&amp;amp;展望&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;（一）优化总结&lt;/strong&gt;  &lt;br /&gt;在总结之前，先来看下整体优化的前后效果对比（上图：优化前；下图：优化后）：  &lt;br /&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwvGP?w=310&amp;h=640" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwvGT?w=310&amp;h=640" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;可以看到，经过一系列的优化手段，落地页已经实现了秒开效果。回顾所做的事情，从分析用户反馈到定位性能瓶颈，再到各种优化尝试，发现所有类似的性能优化手段都可以从以下几点入手：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;提前做：包括预创建WebView和预取数据&lt;/li&gt;
  &lt;li&gt;并行做：包括图片直出&amp;amp;拦截加载，框架初始化阶段开启异步线程准备数据等&lt;/li&gt;
  &lt;li&gt;轻量化：对于前端来说，要尽量减少页面大小，删减不必要的JS和CSS，不仅可以缩短网络请求时间，还能提升内核解析时间&lt;/li&gt;
  &lt;li&gt;简单化：对于简单的信息展示页面，对内容动态性要求不高的场景，可以考虑使用直出替代hybrid，展示内容直接可渲染，无需JS异步加载&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;  &lt;img alt="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/bVbwvG3?w=1752&amp;h=635" title="&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;  &lt;strong&gt;（二）TODO&lt;/strong&gt;&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;页面的更新机制，目前方案仅适用于偏静态页面，对于动态性要求较高的业务，需要提供页面更新机制，保证每次显示的正确性&lt;/li&gt;
  &lt;li&gt;   &lt;strong&gt;开源之路：后续计划将我们总结下来的这套方案打包开源，前行之路必定坎坷，希望大家多多支持&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>android java html</category>
      <guid isPermaLink="true">https://itindex.net/detail/59967-%E7%99%BE%E5%BA%A6-app-android</guid>
      <pubDate>Sat, 17 Aug 2019 14:10:06 CST</pubDate>
    </item>
    <item>
      <title>掌握 Android 系统架构，看这一篇就够了！</title>
      <link>https://itindex.net/detail/59871-android-%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84</link>
      <description>&lt;div&gt;  &lt;p&gt;   &lt;strong&gt;Android系统庞大且错综复杂，今天小编将带领大家初探Android系统整体架构，一窥其全貌。&lt;/strong&gt;&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;    &lt;div&gt;&lt;/div&gt;    &lt;div&gt;     &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;   &lt;div&gt;&lt;/div&gt;&lt;/div&gt;  &lt;h2&gt;   &lt;strong&gt;引言&lt;/strong&gt;&lt;/h2&gt;  &lt;p&gt;本文作为Android系统架构的开篇，起到提纲挈领的作用，从系统整体架构角度概要讲解Android系统的核心技术点，带领大家初探Android系统全貌以及内部运作机制。虽然Android系统非常庞大且错综复杂，需要具备全面的技术栈，但整体架构设计清晰。Android底层内核空间以Linux Kernel作为基石，上层用户空间由Native系统库、虚拟机运行环境、框架层组成，通过系统调用(Syscall)连通系统的内核空间与用户空间。对于用户空间主要采用C++和Java代码编写，通过JNI技术打通用户空间的Java层和Native层(C++/C)，从而连通整个系统。&lt;/p&gt;  &lt;p&gt;为了能让大家整体上大致了解Android系统涉及的知识层面，先来看一张Google官方提供的经典分层架构图，从下往上依次分为Linux内核、HAL、系统Native库和Android运行时环境、Java框架层以及应用层这5层架构，其中每一层都包含大量的子模块或子系统。&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;    &lt;div&gt;&lt;/div&gt;    &lt;div&gt;     &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;   &lt;div&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;上图采用静态分层方式的架构划分，众所周知，程序代码是死的，系统运转是活的，各模块代码运行在不同的进程(线程)中，相互之间进行着各种错终复杂的信息传递与交互流，从这个角度来说此图并没能体现Android整个系统的内部架构、运行机理，以及各个模块之间是如何衔接与配合工作的。   &lt;strong&gt;为了更深入地掌握Android整个架构思想以及各个模块在Android系统所处的地位与价值，计划以Android系统启动过程为主线，以进程的视角来诠释Android M系统全貌&lt;/strong&gt;，全方位的深度剖析各个模块功能，争取各个击破。这样才能犹如庖丁解牛，解决、分析问题则能游刃有余。&lt;/p&gt;  &lt;h2&gt;   &lt;strong&gt;Android架构&lt;/strong&gt;&lt;/h2&gt;  &lt;p&gt;Google提供的5层架构图很经典，但为了更进一步透视Android系统架构，本文更多的是以进程的视角，以分层的架构来诠释Android系统的全貌，阐述Android内部的环环相扣的内在联系。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;系统启动架构图&lt;/strong&gt;&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;    &lt;div&gt;&lt;/div&gt;    &lt;div&gt;     &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;   &lt;div&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;   &lt;strong&gt;图解：&lt;/strong&gt;Android系统启动过程由上图从下往上的一个过程是由Boot Loader引导开机，然后依次进入 -&amp;gt; Kernel -&amp;gt; Native -&amp;gt; Framework -&amp;gt; App，接来下简要说说每个过程：&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;关于Loader层：&lt;/strong&gt;&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;Boot ROM: 当手机处于关机状态时，长按Power键开机，引导芯片开始从固化在 ROM里的预设代码开始执行，然后加载引导程序到 RAM；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;Boot Loader：这是启动Android系统之前的引导程序，主要是检查RAM，初始化硬件参数等功能。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;h4&gt;   &lt;strong&gt;2.1 Linux内核层&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;Android平台的基础是Linux内核，比如ART虚拟机最终调用底层Linux内核来执行功能。Linux内核的安全机制为Android提供相应的保障，也允许设备制造商为内核开发硬件驱动程序。&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;启动Kernel的swapper进程(pid=0)：该进程又称为idle进程, 系统初始化过程Kernel由无到有开创的第一个进程, 用于初始化进程管理、内存管理，加载Display,Camera Driver，Binder Driver等相关工作；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;启动kthreadd进程（pid=2）：是Linux系统的内核进程，会创建内核工作线程kworkder，软中断线程ksoftirqd，thermal等内核守护进程。 kthreadd进程是所有内核进程的鼻祖。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;h4&gt;   &lt;strong&gt;2.2 硬件抽象层 (HAL)&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;硬件抽象层 (HAL) 提供标准接口，HAL包含多个库模块，其中每个模块都为特定类型的硬件组件实现一组接口，比如WIFI/蓝牙模块，当框架API请求访问设备硬件时，Android系统将为该硬件加载相应的库模块。&lt;/p&gt;  &lt;h4&gt;   &lt;strong&gt;2.3 Android Runtime &amp;amp; 系统库&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;每个应用都在其自己的进程中运行，都有自己的虚拟机实例。ART通过执行DEX文件可在设备运行多个虚拟机，DEX文件是一种专为Android设计的字节码格式文件，经过优化，使用内存很少。ART主要功能包括：预先(AOT)和即时(JIT)编译，优化的垃圾回收(GC)，以及调试相关的支持。&lt;/p&gt;  &lt;p&gt;这里的Native系统库主要包括init孵化来的用户空间的守护进程、HAL层以及开机动画等。启动init进程(pid=1),是Linux系统的用户进程， init进程是所有用户进程的鼻祖。&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;init进程会孵化出ueventd、logd、healthd、installd、adbd、lmkd等用户守护进程；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;init进程还启动 servicemanager(binder服务管家)、 bootanim(开机动画)等重要服务&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;init进程孵化出Zygote进程，Zygote进程是Android系统的第一个Java进程(即虚拟机进程)， Zygote是所有Java进程的父进程，Zygote进程本身是由init进程孵化而来的。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;h4&gt;   &lt;strong&gt;2.4 Framework层&lt;/strong&gt;&lt;/h4&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;Zygote进程，是由init进程通过解析init.rc文件后fork生成的，Zygote进程主要包含：&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;加载ZygoteInit类，注册Zygote Socket服务端套接字&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;加载虚拟机&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;提前加载类preloadClasses&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;提前加载资源preloadResouces&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;System Server进程，是由Zygote进程fork而来， SystemServer是Zygote孵化的第一个进程，System Server负责启动和管理整个Java framework，包含ActivityManager，WindowManager，PackageManager，PowerManager等服务。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;Media Server进程，是由init进程fork而来，负责启动和管理整个C++     &lt;strong&gt;framework，包含AudioFlinger，Camera Service等服务。&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;h4&gt;   &lt;strong&gt;2.5 App层&lt;/strong&gt;&lt;/h4&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;Zygote进程孵化出的第一个App进程是Launcher，这是用户看到的桌面App；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;Zygote进程还会创建Browser，Phone，Email等App进程，每个App至少运行在一个进程上。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;所有的App进程都是由Zygote进程fork生成的。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;h4&gt;   &lt;strong&gt;2.6 Syscall &amp;amp;&amp;amp; JNI&lt;/strong&gt;&lt;/h4&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;Native与Kernel之间有一层系统调用(SysCall)层，见Linux系统调用(Syscall)原理;&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;Java层与Native(C/C++)层之间的纽带JNI，见Android JNI原理分析。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;h2&gt;   &lt;strong&gt;通信方式&lt;/strong&gt;&lt;/h2&gt;  &lt;p&gt;无论是Android系统，还是各种Linux衍生系统，各个组件、模块往往运行在各种不同的进程和线程内，这里就必然涉及进程/线程之间的通信。对于IPC(Inter-Process Communication, 进程间通信)，Linux现有管道、消息队列、共享内存、套接字、信号量、信号这些IPC机制，Android额外还有Binder IPC机制，Android OS中的Zygote进程的IPC采用的是Socket机制，在上层system server、media server以及上层App之间更多的是采用Binder IPC方式来完成跨进程间的通信。对于Android上层架构中，很多时候是在同一个进程的线程之间需要相互通信，例如同一个进程的主线程与工作线程之间的通信，往往采用的Handler消息机制。&lt;/p&gt;  &lt;p&gt;想深入理解Android内核层架构，必须先深入理解Linux现有的IPC机制；对于Android上层架构，则最常用的通信方式是Binder、Socket、Handler，当然也有少量其他的IPC方式，比如杀进程Process.killProcess()采用的是signal方式。下面说说Binder、Socket、Handler：&lt;/p&gt;  &lt;h4&gt;   &lt;strong&gt;3.1 Binder&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;Binder作为Android系统提供的一种IPC机制，无论从系统开发还是应用开发，都是Android系统中最重要的组成，也是最难理解的一块知识点，想了解为什么Android要采用Binder作为IPC机制？ 可查看这个知乎上的回答（   &lt;a href="https://links.jianshu.com/go?to=https%3A%2F%2Fwww.zhihu.com%2Fquestion%2F39440766%2Fanswer%2F89210950" rel="nofollow" target="_blank"&gt;https://www.zhihu.com/question/39440766/answer/89210950&lt;/a&gt;）。深入了解Binder机制，最好的方法便是阅读源码，借用Linux鼻祖Linus Torvalds曾说过的一句话：Read The Fucking Source Code。下面简要说说Binder IPC原理。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;Binder IPC原理&lt;/strong&gt;&lt;/p&gt;  &lt;p&gt;Binder通信采用c/s架构，从组件视角来说，包含Client、Server、ServiceManager以及binder驱动，其中ServiceManager用于管理系统中的各种服务。&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;    &lt;div&gt;&lt;/div&gt;    &lt;div&gt;     &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;   &lt;div&gt;&lt;/div&gt;&lt;/div&gt;  &lt;h4&gt;   &lt;strong&gt;3.2 Socket&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;Socket通信方式也是C/S架构，比Binder简单很多。在Android系统中采用Socket通信方式的主要有：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;zygote：用于孵化进程，system_server创建进程是通过socket向zygote进程发起请求；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;installd：用于安装App的守护进程，上层PackageManagerService很多实现最终都是交给它来完成；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;lmkd：lowmemorykiller的守护进程，Java层的LowMemoryKiller最终都是由lmkd来完成；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;adbd：这个也不用说，用于服务adb；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;logcatd:这个不用说，用于服务logcat；&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;vold：即volume Daemon，是存储类的守护进程，用于负责如USB、Sdcard等存储设备的事件处理。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;等等还有很多，这里不一一列举，Socket方式更多的用于Android framework层与native层之间的通信。Socket通信方式相对于binder比较简单，这里省略。&lt;/p&gt;  &lt;h4&gt;   &lt;strong&gt;3.3 Handler&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;Binder/Socket用于进程间通信，而Handler消息机制用于同进程的线程间通信，Handler消息机制是由一组MessageQueue、Message、Looper、Handler共同组成的，为了方便且称之为Handler消息机制。&lt;/p&gt;  &lt;p&gt;有人可能会疑惑，为何Binder/Socket用于进程间通信，能否用于线程间通信呢？答案是肯定，对于两个具有独立地址空间的进程通信都可以，当然也能用于共享内存空间的两个线程间通信，这就好比杀鸡用牛刀。接着可能还有人会疑惑，那handler消息机制能否用于进程间通信？答案是不能，Handler只能用于共享内存地址空间的两个线程间通信，即同进程的两个线程间通信。很多时候，Handler是工作线程向UI主线程发送消息，即App应用中只有主线程能更新UI，其他工作线程往往是完成相应工作后，通过Handler告知主线程需要做出相应地UI更新操作，Handler分发相应的消息给UI主线程去完成，如下图：&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;    &lt;div&gt;&lt;/div&gt;    &lt;div&gt;     &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;   &lt;div&gt;&lt;/div&gt;&lt;/div&gt;  &lt;p&gt;由于工作线程与主线程共享地址空间，即Handler实例对象mHandler位于线程间共享的内存堆上，工作线程与主线程都能直接使用该对象，只需要注意多线程的同步问题。工作线程通过mHandler向其成员变量MessageQueue中添加新Message，主线程一直处于loop()方法内，当收到新的Message时按照一定规则分发给相应的handleMessage()方法来处理。所以说，Handler消息机制用于同进程的线程间通信，其核心是线程间共享内存空间，而不同进程拥有不同的地址空间，也就不能用handler来实现进程间通信。&lt;/p&gt;  &lt;p&gt;上图只是Handler消息机制的一种处理流程，是不是只能工作线程向UI主线程发消息呢，其实不然，可以是UI线程向工作线程发送消息，也可以是多个工作线程之间通过handler发送消息。&lt;/p&gt;  &lt;p&gt;要理解framework层源码，掌握这3种基本的进程/线程间通信方式是非常有必要，当然Linux还有不少其他的IPC机制，比如共享内存、信号、信号量，在源码中也有体现，如果想全面彻底地掌握Android系统，还是需要对每一种IPC机制都有所了解。&lt;/p&gt;  &lt;h2&gt;   &lt;strong&gt;核心提纲&lt;/strong&gt;&lt;/h2&gt;  &lt;p&gt;小编对于Android从系统底层一路到上层都有自己的理解和沉淀，通过前面对系统启动的介绍，相信大家对Android系统有了一个整体观。接下来需抓核心、理思路，争取各个击破。后续将持续更新和完善整个大纲，不限于进程、内存、IO、系统服务架构以及分析实战等文章。&lt;/p&gt;  &lt;p&gt;当然本站有一些文章没来得及进一步加工，有时间根据大家的反馈，不断修正和完善所有文章，争取给文章，再进一步精简非核心代码，增加可视化图表以及文字的结论性分析。基于Android 6.0的源码，专注于分享Android系统原理、架构分析的原创文章。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;建议阅读群体：&lt;/strong&gt;适合于正从事或者有兴趣研究Android系统的工程师或者技术爱好者，也适合Android App高级工程师；对于尚未入门或者刚入门的App工程师阅读可能会有点困难，建议先阅读更基础的资料，再来阅读本站博客。&lt;/p&gt;  &lt;p&gt;看到Android整个系统架构是如此庞大的, 该问如何学习Android系统, 以下是我自己的Android的学习和研究论。&lt;/p&gt;  &lt;p&gt;   &lt;strong&gt;从整理上来列举一下Android系统的核心知识点概览：&lt;/strong&gt;   &lt;br /&gt;   &lt;strong&gt;需要这些关于Android系统核心知识加群免费获取    &lt;a href="https://links.jianshu.com/go?to=https%3A%2F%2Fjq.qq.com%2F%3F_wv%3D1027%26k%3D5gyv0JM" target="_blank"&gt;Android IOC架构设计&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;    &lt;div&gt;&lt;/div&gt;    &lt;div&gt;     &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;   &lt;div&gt;&lt;/div&gt;&lt;/div&gt;  &lt;h4&gt;   &lt;strong&gt;4.1 系统启动系列&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;   &lt;strong&gt;&lt;/strong&gt;&lt;/p&gt;  &lt;div&gt;   &lt;div&gt;    &lt;div&gt;&lt;/div&gt;    &lt;div&gt;     &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;   &lt;strong&gt;    &lt;div&gt;&lt;/div&gt;&lt;/strong&gt;&lt;/div&gt;  &lt;p&gt;&lt;/p&gt;  &lt;p&gt;Android系统中极其重要进程：init, zygote, system_server, servicemanager 进程:&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;th&gt;序号&lt;/th&gt;    &lt;th&gt;进程启动&lt;/th&gt;    &lt;th&gt;概述&lt;/th&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;1&lt;/td&gt;    &lt;td&gt;init进程&lt;/td&gt;    &lt;td&gt;Linux系统中用户空间的第一个进程, Init.main&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;2&lt;/td&gt;    &lt;td&gt;zygote进程&lt;/td&gt;    &lt;td&gt;所有Ａpp进程的父进程, ZygoteInit.main&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;3&lt;/td&gt;    &lt;td&gt;system_server进程(上篇)&lt;/td&gt;    &lt;td&gt;系统各大服务的载体, forkSystemServer过程&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;4&lt;/td&gt;    &lt;td&gt;system_server进程(下篇)&lt;/td&gt;    &lt;td&gt;系统各大服务的载体, SystemServer.main&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;5&lt;/td&gt;    &lt;td&gt;servicemanager进程&lt;/td&gt;    &lt;td&gt;binder服务的大管家, 守护进程循环运行在binder_loop&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;6&lt;/td&gt;    &lt;td&gt;App进程&lt;/td&gt;    &lt;td&gt;通过Process.start启动App进程, ActivityThread.main&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;再来看看守护进程(也就是进程名一般以d为后缀，比如logd，此处d是指daemon的简称), 下面介绍部分守护进程：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;debuggerd&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;installd&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;lmkd&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;logd&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;h4&gt;   &lt;strong&gt;4.2 系统稳定性系列&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;Android系统稳定性主要是异常崩溃(crash)和执行超时(timeout):&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;th&gt;序号&lt;/th&gt;    &lt;th&gt;文章名&lt;/th&gt;    &lt;th&gt;概述&lt;/th&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;1&lt;/td&gt;    &lt;td&gt;理解Android ANR的触发原理&lt;/td&gt;    &lt;td&gt;触发ANR的场景以及机理&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;2&lt;/td&gt;    &lt;td&gt;Input系统—ANR原理分析&lt;/td&gt;    &lt;td&gt;input触发ANR的原理&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;3&lt;/td&gt;    &lt;td&gt;理解Android ANR的信息收集过程&lt;/td&gt;    &lt;td&gt;AMS.appNotResponding过程分析,收集traces&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;4&lt;/td&gt;    &lt;td&gt;解读Java进程的Trace文件&lt;/td&gt;    &lt;td&gt;kill -3 信息收集过程&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;5&lt;/td&gt;    &lt;td&gt;Native进程之Trace原理&lt;/td&gt;    &lt;td&gt;debuggerd -b 信息收集过程&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;6&lt;/td&gt;    &lt;td&gt;WatchDog工作原理&lt;/td&gt;    &lt;td&gt;WatchDog触发机制&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;7&lt;/td&gt;    &lt;td&gt;理解Java Crash处理流程&lt;/td&gt;    &lt;td&gt;AMS.handleApplicationCrash过程分析&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;8&lt;/td&gt;    &lt;td&gt;理解Native Crash处理流程&lt;/td&gt;    &lt;td&gt;debuggerd守护进程&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;9&lt;/td&gt;    &lt;td&gt;global reference限制策略&lt;/td&gt;    &lt;td&gt;global reference&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;h4&gt;   &lt;strong&gt;4.3 Android进程系列&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;进程/线程是操作系统的魂，各种服务、组件、子系统都是依附于具体的进程实体。深入理解进程机制对于掌握Android系统整体架构和运转机制是非常有必要的，是系统工程师的基本功，下面列举进程相关的文章：&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;th&gt;序号&lt;/th&gt;    &lt;th&gt;文章名&lt;/th&gt;    &lt;th&gt;概述&lt;/th&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;1&lt;/td&gt;    &lt;td&gt;理解Android进程创建流程&lt;/td&gt;    &lt;td&gt;Process.start过程分析&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;2&lt;/td&gt;    &lt;td&gt;理解杀进程的实现原理&lt;/td&gt;    &lt;td&gt;Process.killProcess过程分析&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;3&lt;/td&gt;    &lt;td&gt;Android四大组件与进程启动的关系&lt;/td&gt;    &lt;td&gt;AMS.startProcessLocked过程分析组件与进程&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;4&lt;/td&gt;    &lt;td&gt;Android进程绝杀技--forceStop&lt;/td&gt;    &lt;td&gt;force-stop过程分析彻底移除组件与杀进程&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;5&lt;/td&gt;    &lt;td&gt;理解Android线程创建流程&lt;/td&gt;    &lt;td&gt;3种不同线程的创建过程&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;6&lt;/td&gt;    &lt;td&gt;彻底理解Android Binder通信架构&lt;/td&gt;    &lt;td&gt;以start-service为线,阐述进程间通信机理&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;7&lt;/td&gt;    &lt;td&gt;理解Binder线程池的管理&lt;/td&gt;    &lt;td&gt;Zygote fork的进程都默认开启binder线程池&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;8&lt;/td&gt;    &lt;td&gt;Android进程生命周期与ADJ&lt;/td&gt;    &lt;td&gt;进程adj, processState以及lmk&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;9&lt;/td&gt;    &lt;td&gt;Android LowMemoryKiller原理分析&lt;/td&gt;    &lt;td&gt;lmk原理分析&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;10&lt;/td&gt;    &lt;td&gt;进程优先级&lt;/td&gt;    &lt;td&gt;进程nice,thread priority以及scheduler&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;11&lt;/td&gt;    &lt;td&gt;Android进程调度之adj算法&lt;/td&gt;    &lt;td&gt;updateOomAdjLocked过程&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;12&lt;/td&gt;    &lt;td&gt;Android进程整理&lt;/td&gt;    &lt;td&gt;整理系统的所有进程/线程&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;h4&gt;   &lt;strong&gt;4.4 四大组件系列&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;对于App来说，Android应用的四大组件Activity，Service，Broadcast Receiver， Content Provider最为核心，接下分别展开介绍：&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;th&gt;序号&lt;/th&gt;    &lt;th&gt;文章名&lt;/th&gt;    &lt;th&gt;类别&lt;/th&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;1&lt;/td&gt;    &lt;td&gt;startActivity启动过程分析&lt;/td&gt;    &lt;td&gt;Activity&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;2&lt;/td&gt;    &lt;td&gt;简述Activity生命周期&lt;/td&gt;    &lt;td&gt;Activity&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;3&lt;/td&gt;    &lt;td&gt;startService启动过程分析&lt;/td&gt;    &lt;td&gt;Service&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;4&lt;/td&gt;    &lt;td&gt;bindService启动过程分析&lt;/td&gt;    &lt;td&gt;Service&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;5&lt;/td&gt;    &lt;td&gt;以Binder视角来看Service启动&lt;/td&gt;    &lt;td&gt;Service&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;6&lt;/td&gt;    &lt;td&gt;Android Broadcast广播机制分析&lt;/td&gt;    &lt;td&gt;Broadcast&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;7&lt;/td&gt;    &lt;td&gt;理解ContentProvider原理&lt;/td&gt;    &lt;td&gt;ContentProvider&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;8&lt;/td&gt;    &lt;td&gt;ContentProvider引用计数&lt;/td&gt;    &lt;td&gt;ContentProvider&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;9&lt;/td&gt;    &lt;td&gt;Activity与Service生命周期&lt;/td&gt;    &lt;td&gt;Activity&amp;amp;&amp;amp;Service&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;10&lt;/td&gt;    &lt;td&gt;简述Activity与Window关系&lt;/td&gt;    &lt;td&gt;Activity&amp;amp;&amp;amp;Window&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;11&lt;/td&gt;    &lt;td&gt;四大组件之综述&lt;/td&gt;    &lt;td&gt;AMS&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;12&lt;/td&gt;    &lt;td&gt;四大组件之ServiceRecord&lt;/td&gt;    &lt;td&gt;Service&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;13&lt;/td&gt;    &lt;td&gt;四大组件之BroadcastRecord&lt;/td&gt;    &lt;td&gt;Broadcast&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;14&lt;/td&gt;    &lt;td&gt;四大组件之ContentProviderRecord&lt;/td&gt;    &lt;td&gt;ContentProvider&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;15&lt;/td&gt;    &lt;td&gt;理解Android Context&lt;/td&gt;    &lt;td&gt;Context&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;16&lt;/td&gt;    &lt;td&gt;理解Application创建过程&lt;/td&gt;    &lt;td&gt;Application&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;17&lt;/td&gt;    &lt;td&gt;unbindService流程分析&lt;/td&gt;    &lt;td&gt;Service&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;18&lt;/td&gt;    &lt;td&gt;四大组件之ActivityRecord&lt;/td&gt;    &lt;td&gt;Activity&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;19&lt;/td&gt;    &lt;td&gt;AMS总结(一)&lt;/td&gt;    &lt;td&gt;AMS&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;h4&gt;   &lt;strong&gt;4.5 图形系统系列&lt;/strong&gt;&lt;/h4&gt;  &lt;p&gt;图形也是整个系统非常复杂且重要的一个系列，涉及WindowManager,SurfaceFlinger服务。&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;th&gt;序号&lt;/th&gt;    &lt;th&gt;文章名&lt;/th&gt;    &lt;th&gt;类别&lt;/th&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;1&lt;/td&gt;    &lt;td&gt;WindowManager启动篇&lt;/td&gt;    &lt;td&gt;Window&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;2&lt;/td&gt;    &lt;td&gt;WMS之启动窗口篇&lt;/td&gt;    &lt;td&gt;Window&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;3&lt;/td&gt;    &lt;td&gt;以Window视角来看&lt;/td&gt;    &lt;td&gt;Window&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;4&lt;/td&gt;    &lt;td&gt;Android图形系统概述&lt;/td&gt;    &lt;td&gt;SurfaceFlinger&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;5&lt;/td&gt;    &lt;td&gt;SurfaceFlinger启动篇&lt;/td&gt;    &lt;td&gt;SurfaceFlinger&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;6&lt;/td&gt;    &lt;td&gt;SurfaceFlinger绘图篇&lt;/td&gt;    &lt;td&gt;SurfaceFlinger&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;7&lt;/td&gt;    &lt;td&gt;Choreographer原理&lt;/td&gt;    &lt;td&gt;Choreographer&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;h2&gt;   &lt;strong&gt;结束语&lt;/strong&gt;&lt;/h2&gt;  &lt;p&gt;Android系统之博大精深，包括Linux内核、Native、虚拟机、Framework，通过系统调用连通内核与用户空间，通过JNI打通用户空间的Java层和Native层，通过Binder、Socket、Handler等打通跨进程、跨线程的信息交换。只有真正阅读并理解系统核心架构的设计，解决问题和设计方案才能做到心中无剑胜有剑，才能做到知其然知其所以然。当修炼到此，恭喜你对系统有了更高一个层次的理解，正如太极剑法，忘记了所有招式，也就练成了太极剑法。&lt;/p&gt;  &lt;p&gt;再回过头去看看那些API，看到的将不再是一行行代码、一个个接口的调用，而是各种信息的传递与交互工作，而是背后成千上万个小蝌蚪的动态执行流。记得《侠客行》里面的龙木二岛主终其一生也无法参透太玄经，石破天却短短数日练成绝世神功，究其根源是龙木二岛主以静态视角去解读太玄经，而石破天把墙壁的图案想象成无数游动的蝌蚪，最终成就绝世神功。一言以蔽之，程序代码是死的，系统运转是活的，要以动态视角去理解系统架构。&lt;/p&gt;  &lt;h4&gt;最后相关架构及资料领取方式：&lt;/h4&gt;  &lt;h5&gt;   &lt;strong&gt;点赞+加群免费获取    &lt;a href="https://links.jianshu.com/go?to=https%3A%2F%2Fjq.qq.com%2F%3F_wv%3D1027%26k%3D5gyv0JM" target="_blank"&gt;Android IOC架构设计&lt;/a&gt;&lt;/strong&gt;&lt;/h5&gt;  &lt;blockquote&gt;   &lt;p&gt;    &lt;strong&gt;加群     &lt;a href="https://links.jianshu.com/go?to=https%3A%2F%2Fjq.qq.com%2F%3F_wv%3D1027%26k%3D5gyv0JM" target="_blank"&gt;Android IOC架构设计&lt;/a&gt;领取获取往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发（ReactNative+Weex）微信小程序、Flutter全方面的Android进阶实践技术，群内还有技术大牛一起讨论交流解决问题。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>jianshu</category>
      <guid isPermaLink="true">https://itindex.net/detail/59871-android-%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84</guid>
      <pubDate>Sat, 27 Jul 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>Android NDK开发扫盲及最新CMake的编译使用 - 简书</title>
      <link>https://itindex.net/detail/59751-android-ndk-%E5%BC%80%E5%8F%91</link>
      <description>&lt;div&gt;    &lt;blockquote&gt;      &lt;p&gt;本篇文章旨在简介 Android 中        &lt;code&gt;NDK&lt;/code&gt;是什么以及重点讲解最新 Android Studio 编译工具        &lt;code&gt;CMake&lt;/code&gt;的使用&lt;/p&gt;&lt;/blockquote&gt;    &lt;h2&gt;1 NDK 简介&lt;/h2&gt;    &lt;p&gt;在介绍      &lt;code&gt;NDK&lt;/code&gt;之前还是首推 Android 官方      &lt;code&gt;NDK&lt;/code&gt;文档。      &lt;a href="https://link.jianshu.com?t=https://developer.android.com/ndk/guides/index.html" rel="nofollow" target="_blank"&gt;传送门&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;官方文档分别从以下几个方面介绍了      &lt;code&gt;NDK&lt;/code&gt;&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;        &lt;code&gt;NDK&lt;/code&gt;的基础概念&lt;/li&gt;      &lt;li&gt;如何编译        &lt;code&gt;NDK&lt;/code&gt;项目&lt;/li&gt;      &lt;li&gt;        &lt;code&gt;ABI&lt;/code&gt;是什么以及不同 CPU 指令集支持哪些        &lt;code&gt;ABI&lt;/code&gt;&lt;/li&gt;      &lt;li&gt;如何使用您自己及其他预建的库&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;本节将会对文档进行总结和补充。所以建议先浏览一遍文档，或者看完本篇文章再回头看一遍文档。&lt;/p&gt;    &lt;h3&gt;1.1 NDK 基础概念&lt;/h3&gt;    &lt;p&gt;首先先用简单的话分别解释下      &lt;code&gt;JNI&lt;/code&gt;、      &lt;code&gt;NDK&lt;/code&gt;， 以及分别和 Android 开发、c/c++ 开发的配合。在解释过程中会对      &lt;code&gt;Android.mk&lt;/code&gt;、      &lt;code&gt;Application.mk&lt;/code&gt;、      &lt;code&gt;ndk-build&lt;/code&gt;、      &lt;code&gt;CMake&lt;/code&gt;、      &lt;code&gt;CMakeList&lt;/code&gt;这些常见名词进行扫盲。&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;JNI&lt;/strong&gt;（Java Native Interface）：Java本地接口。是为了方便Java调用c、c++等本地代码所封装的一层接口（也是一个标准）。大家都知道，Java的优点是跨平台，但是作为优点的同时，其在本地交互的时候就编程了缺点。Java的跨平台特性导致其本地交互的能力不够强大，一些和操作系统相关的特性Java无法完成，于是Java提供了jni专门用于和本地代码交互，这样就增强了Java语言的本地交互能力。      &lt;a href="https://link.jianshu.com?t=http://blog.csdn.net/singwhatiwanna/article/details/9061545" rel="nofollow" target="_blank"&gt;上述部分文字摘自任玉刚的 Java JNI 介绍&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;NDK&lt;/strong&gt;（Native Development Kit） : 原生开发工具包，即帮助开发原生代码的一系列工具，包括但不限于编译工具、一些公共库、开发IDE等。&lt;/p&gt;    &lt;p&gt;      &lt;code&gt;NDK&lt;/code&gt;工具包中提供了完整的一套将 c/c++ 代码编译成静态/动态库的工具，而      &lt;code&gt;Android.mk&lt;/code&gt;和      &lt;code&gt;Application.mk&lt;/code&gt;你可以认为是描述编译参数和一些配置的文件。比如指定使用c++11还是c++14编译，会引用哪些共享库，并描述关系等，还会指定编译的      &lt;code&gt;abi&lt;/code&gt;。只有有了这些      &lt;code&gt;NDK&lt;/code&gt;中的编译工具才能准确的编译 c/c++ 代码。&lt;/p&gt;    &lt;p&gt;      &lt;code&gt;ndk-build&lt;/code&gt;文件是 Android NDK r4 中引入的一个 shell 脚本。其用途是调用正确的      &lt;code&gt;NDK&lt;/code&gt;构建脚本。其实最终还是会去调用      &lt;code&gt;NDK&lt;/code&gt;自己的编译工具。&lt;/p&gt;    &lt;p&gt;那      &lt;code&gt;CMake&lt;/code&gt;又是什么呢。脱离 Android 开发来看，c/c++ 的编译文件在不同平台是不一样的。Unix 下会使用      &lt;code&gt;makefile&lt;/code&gt;文件编译，Windows 下会使用      &lt;code&gt;project&lt;/code&gt;文件编译。而      &lt;code&gt;CMake&lt;/code&gt;则是一个跨平台的编译工具，它并不会直接编译出对象，而是根据自定义的语言规则（      &lt;code&gt;CMakeLists.txt&lt;/code&gt;）生成 对应      &lt;code&gt;makefile&lt;/code&gt;或      &lt;code&gt;project&lt;/code&gt;文件，然后再调用底层的编译。&lt;/p&gt;    &lt;p&gt;在Android Studio 2.2 之后，工具中增加了      &lt;code&gt;CMake&lt;/code&gt;的支持，你可以这么认为，在 Android Studio 2.2 之后你有2种选择来编译你写的 c/c++ 代码。一个是      &lt;code&gt;ndk-build&lt;/code&gt;+      &lt;code&gt;Android.mk&lt;/code&gt;+      &lt;code&gt;Application.mk&lt;/code&gt;组合，另一个是      &lt;code&gt;CMake&lt;/code&gt;+      &lt;code&gt;CMakeLists.txt&lt;/code&gt;组合。这2个组合与Android代码和c/c++代码无关，只是不同的构建脚本和构建命令。本篇文章主要会描述后者的组合。（也是Android现在主推的）&lt;/p&gt;    &lt;h3&gt;1.2 ABI 是什么&lt;/h3&gt;    &lt;p&gt;      &lt;code&gt;ABI&lt;/code&gt;（Application binary interface）应用程序二进制接口。不同的CPU 与指令集的每种组合都有定义的      &lt;code&gt;ABI&lt;/code&gt;(应用程序二进制接口)，一段程序只有遵循这个接口规范才能在该 CPU 上运行，所以同样的程序代码为了兼容多个不同的CPU，需要为不同的      &lt;code&gt;ABI&lt;/code&gt;构建不同的库文件。当然对于CPU来说，不同的架构并不意味着一定互不兼容。&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;armeabi设备只兼容armeabi；&lt;/li&gt;      &lt;li&gt;armeabi-v7a设备兼容armeabi-v7a、armeabi；&lt;/li&gt;      &lt;li&gt;arm64-v8a设备兼容arm64-v8a、armeabi-v7a、armeabi；&lt;/li&gt;      &lt;li&gt;X86设备兼容X86、armeabi；&lt;/li&gt;      &lt;li&gt;X86_64设备兼容X86_64、X86、armeabi；&lt;/li&gt;      &lt;li&gt;mips64设备兼容mips64、mips；&lt;/li&gt;      &lt;li&gt;mips只兼容mips；&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;具体的兼容问题可以参见这篇文章。      &lt;a href="https://link.jianshu.com?t=http://blog.coderclock.com/2017/05/07/android/Android-so-files-compatibility-and-adaptation/" rel="nofollow" target="_blank"&gt;Android SO文件的兼容和适配&lt;/a&gt;&lt;/p&gt;    &lt;p&gt;当我们开发 Android 应用的时候，由于 Java 代码运行在虚拟机上，所以我们从来没有关心过这方面的问题。但是当我们开发或者使用原生代码时就需要了解不同      &lt;code&gt;ABI&lt;/code&gt;以及为自己的程序选择接入不同      &lt;code&gt;ABI&lt;/code&gt;的库。（库越多，包越大，所以要有选择）&lt;/p&gt;    &lt;p&gt;下面我们来看下一共有哪些      &lt;code&gt;ABI&lt;/code&gt;以及对应的指令集&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;div&gt;&lt;/div&gt;        &lt;div&gt;          &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;      &lt;div&gt;ABI&lt;/div&gt;&lt;/div&gt;    &lt;h2&gt;2 CMake 的使用&lt;/h2&gt;    &lt;p&gt;这一节将重点介绍      &lt;code&gt;CMake&lt;/code&gt;的规则和使用，以及如何使用      &lt;code&gt;CMake&lt;/code&gt;编译自己及其他预建的库。&lt;/p&gt;    &lt;h3&gt;2.1 Hello world&lt;/h3&gt;    &lt;p&gt;我们通过一个Hello World项目来理解      &lt;code&gt;CMake&lt;/code&gt;&lt;/p&gt;    &lt;p&gt;首先创建一个新的包含原生代码的项目。在 New Project 时，勾选 Include C++ support&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;div&gt;&lt;/div&gt;        &lt;div&gt;          &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;      &lt;div&gt;New Project&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;项目创建好以后我们可以看到和普通Android项目有以下4个不同。&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;        &lt;code&gt;main&lt;/code&gt;下面增加了        &lt;code&gt;cpp&lt;/code&gt;目录，即放置 c/c++ 代码的地方&lt;/li&gt;      &lt;li&gt;module-level 的        &lt;code&gt;build.gradle&lt;/code&gt;有修改&lt;/li&gt;      &lt;li&gt;增加了        &lt;code&gt;CMakeLists.txt&lt;/code&gt;文件&lt;/li&gt;      &lt;li&gt;多了一个        &lt;code&gt;.externalNativeBuild&lt;/code&gt;目录&lt;/li&gt;&lt;/ol&gt;    &lt;div&gt;      &lt;div&gt;        &lt;div&gt;&lt;/div&gt;        &lt;div&gt;          &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;      &lt;div&gt;Difference&lt;/div&gt;&lt;/div&gt;    &lt;h4&gt;build.gradle&lt;/h4&gt;    &lt;pre&gt;      &lt;code&gt;android {
    ...
    defaultConfig {
        ...
        externalNativeBuild {
            cmake {
                cppFlags &amp;quot;-frtti -fexceptions&amp;quot;
                arguments &amp;quot;-DANDROID_ARM_NEON=TRUE&amp;quot;
            }
        }
    }
    buildTypes {
        ...
    }
    externalNativeBuild {
        cmake {
            path &amp;quot;CMakeLists.txt&amp;quot;
        }
    }
}
...&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;由于      &lt;code&gt;CMake&lt;/code&gt;的命令集成在了      &lt;code&gt;gradle&lt;/code&gt;-      &lt;code&gt;externalNativeBuild&lt;/code&gt;中，所以在      &lt;code&gt;gradle&lt;/code&gt;中有2个地方配置      &lt;code&gt;CMake&lt;/code&gt;。&lt;/p&gt;    &lt;p&gt;      &lt;code&gt;defaultConfig&lt;/code&gt;外面的      &lt;code&gt;externalNativeBuild - cmake&lt;/code&gt;，指明了      &lt;code&gt;CMakeList.txt&lt;/code&gt;的路径；      &lt;br /&gt;      &lt;code&gt;defaultConfig&lt;/code&gt;里面的      &lt;code&gt;externalNativeBuild - cmake&lt;/code&gt;，主要填写      &lt;code&gt;CMake&lt;/code&gt;的命令参数。即由      &lt;code&gt;arguments&lt;/code&gt;中的参数最后转化成一个可执行的      &lt;code&gt;CMake&lt;/code&gt;的命令，可以在      &lt;code&gt;.externalNativeBuild/cmake/debug/{abi}/cmake_build_command.txt&lt;/code&gt;中查到。如下&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;div&gt;&lt;/div&gt;        &lt;div&gt;          &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;      &lt;div&gt;cmake command&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;更多的可以填写的命令参数和含义可以参见      &lt;a href="https://link.jianshu.com?t=https://developer.android.com/ndk/guides/cmake.html" rel="nofollow" target="_blank"&gt;Android NDK-CMake文档&lt;/a&gt;&lt;/p&gt;    &lt;h4&gt;CMakeLists.txt&lt;/h4&gt;    &lt;p&gt;      &lt;code&gt;CMakeLists.txt&lt;/code&gt;中主要定义了哪些文件需要编译，以及和其他库的关系等。&lt;/p&gt;    &lt;p&gt;看下新项目中的      &lt;code&gt;CMakeLists.txt&lt;/code&gt;&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;cmake_minimum_required(VERSION 3.4.1)

# 编译出一个动态库 native-lib，源文件只有 src/main/cpp/native-lib.cpp
add_library( # Sets the name of the library.
             native-lib
             # Sets the library as a shared library.
             SHARED
             # Provides a relative path to your source file(s).
             src/main/cpp/native-lib.cpp )

# 找到预编译库 log_lib 并link到我们的动态库 native-lib中
find_library( # Sets the name of the path variable.
              log-lib
              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )
target_link_libraries( # Specifies the target library.
                       native-lib
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;这其实是一个最基本的      &lt;code&gt;CMakeLists.txt&lt;/code&gt;，其实      &lt;code&gt;CMakeLists.txt&lt;/code&gt;里面可以非常强大，比如自定义命令、查找文件、头文件包含、设置变量等等。建议结合      &lt;code&gt;CMake&lt;/code&gt;的      &lt;a href="https://link.jianshu.com?t=https://cmake.org/documentation/" rel="nofollow" target="_blank"&gt;官方文档&lt;/a&gt;使用。同时在这推荐一个中文翻译的简易的      &lt;a href="https://link.jianshu.com?t=https://www.zybuluo.com/khan-lau/note/254724" rel="nofollow" target="_blank"&gt;CMake手册&lt;/a&gt;&lt;/p&gt;    &lt;h3&gt;2.2 CMake 使用自己及其他预建的库&lt;/h3&gt;    &lt;p&gt;当你需要引入已有的静态库/动态库（FFMpeg）或者自己编译核心部分并提供出去时就需要考虑如何在      &lt;code&gt;CMake&lt;/code&gt;中使用自己及其他预建的库。&lt;/p&gt;    &lt;p&gt;Android NDK 官网的      &lt;a href="https://link.jianshu.com?t=https://developer.android.com/ndk/guides/libs.html" rel="nofollow" target="_blank"&gt;使用现有库&lt;/a&gt;的文档中还是使用      &lt;code&gt;ndk-build&lt;/code&gt;+      &lt;code&gt;Android.mk&lt;/code&gt;+      &lt;code&gt;Application.mk&lt;/code&gt;组合的说明文档。（其实官方文档中大部分都是的，并没有使用      &lt;code&gt;CMake&lt;/code&gt;）&lt;/p&gt;    &lt;p&gt;幸运的是， Github上的      &lt;a href="https://link.jianshu.com?t=https://github.com/googlesamples/android-ndk" rel="nofollow" target="_blank"&gt;官方示例&lt;/a&gt;里面有个项目      &lt;a href="https://link.jianshu.com?t=https://github.com/googlesamples/android-ndk/tree/master/hello-libs" rel="nofollow" target="_blank"&gt;hello-libs&lt;/a&gt;实现了如何创建出静态库/动态库，并引用它。现在我们把代码拉下来看下具体是如何实现的。&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;div&gt;&lt;/div&gt;        &lt;div&gt;          &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;      &lt;div&gt;hello-libs&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;我们先看下Github上的README介绍：&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;app - 从        &lt;code&gt;$project/distribution/&lt;/code&gt;中使用一个静态库和一个动态库&lt;/li&gt;      &lt;li&gt;gen-libs - 生成一个动态库和一个静态库并复制到        &lt;code&gt;$project/distribution/&lt;/code&gt;目录，你不需要再编译这个库，二进制文件已经保存在了项目中。当然，如果有需要你也可以编译自己的源码，只需要去掉        &lt;code&gt;setting.gradle&lt;/code&gt;和        &lt;code&gt;app/build.gradle&lt;/code&gt;中的注释，然后执行一次，接着注释回去，防止在 build 的过程中不受影响。&lt;/li&gt;&lt;/ul&gt;    &lt;h4&gt;我们采用自底向上的方式分析模块，先看下      &lt;code&gt;gen-libs&lt;/code&gt;模块。&lt;/h4&gt;    &lt;p&gt;      &lt;strong&gt;gen-libs/build.gradle&lt;/strong&gt;&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;android {
    ...
    defaultConfig {
        ...
        externalNativeBuild {
            cmake {
                arguments &amp;apos;-DANDROID_PLATFORM=android-9&amp;apos;,
                          &amp;apos;-DANDROID_TOOLCHAIN=clang&amp;apos;
                // explicitly build libs
                targets &amp;apos;gmath&amp;apos;, &amp;apos;gperf&amp;apos;
            }
        }
    }
    ...
}
...&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;查询      &lt;a href="https://link.jianshu.com?t=https://developer.android.com/ndk/guides/cmake.html" rel="nofollow" target="_blank"&gt;文档&lt;/a&gt;可以知道      &lt;code&gt;arguments&lt;/code&gt;中      &lt;code&gt;-DANDROID_PLATFORM&lt;/code&gt;代表编译的 android 平台，文档建议直接设置      &lt;code&gt;minSdkVersion&lt;/code&gt;就行了，所以这个参数可忽略。另一个参数      &lt;code&gt;-DANDROID_TOOLCHAIN=clang&lt;/code&gt;，      &lt;code&gt;CMake&lt;/code&gt;一共有2种编译工具链 -      &lt;code&gt;clang&lt;/code&gt;和      &lt;code&gt;gcc&lt;/code&gt;，      &lt;code&gt;gcc&lt;/code&gt;已经废弃，      &lt;code&gt;clang&lt;/code&gt;是默认的。&lt;/p&gt;    &lt;p&gt;      &lt;code&gt;targets &amp;apos;gmath&amp;apos;, &amp;apos;gperf&amp;apos;&lt;/code&gt;代表编译哪些项目。（不填就是都编译）&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;cpp/CMakeLists.txt&lt;/strong&gt;&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;cmake_minimum_required(VERSION 3.4.1)

set(CMAKE_VERBOSE_MAKEFILE on)

set(lib_src_DIR ${CMAKE_CURRENT_SOURCE_DIR})

set(lib_build_DIR $ENV{HOME}/tmp)
file(MAKE_DIRECTORY ${lib_build_DIR})

add_subdirectory(${lib_src_DIR}/gmath ${lib_build_DIR}/gmath)
add_subdirectory(${lib_src_DIR}/gperf ${lib_build_DIR}/gperf)&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;外层的      &lt;code&gt;CMakeLists&lt;/code&gt;里面核心就是      &lt;code&gt;add_subdirectory&lt;/code&gt;，查询      &lt;a href="https://link.jianshu.com?t=https://cmake.org/documentation/" rel="nofollow" target="_blank"&gt;CMake 官方文档&lt;/a&gt;可以知道这条命令的作用是为构建添加一个子路径。子路径中的      &lt;code&gt;CMakeLists.txt&lt;/code&gt;也会被执行。即会去分别执行      &lt;code&gt;gmath&lt;/code&gt;和      &lt;code&gt;gperf&lt;/code&gt;中的      &lt;code&gt;CMakeLists.txt&lt;/code&gt;&lt;/p&gt;    &lt;p&gt;      &lt;strong&gt;cpp/gmath/CMakeLists.txt&lt;/strong&gt;&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;cmake_minimum_required(VERSION 3.4.1)

set(CMAKE_VERBOSE_MAKEFILE on)

add_library(gmath STATIC src/gmath.c)

# copy out the lib binary... need to leave the static lib around to pass gradle check
set(distribution_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../distribution)
set_target_properties(gmath
                      PROPERTIES
                      ARCHIVE_OUTPUT_DIRECTORY
                      &amp;quot;${distribution_DIR}/gmath/lib/${ANDROID_ABI}&amp;quot;)

# copy out lib header file...
add_custom_command(TARGET gmath POST_BUILD
                   COMMAND &amp;quot;${CMAKE_COMMAND}&amp;quot; -E
                   copy &amp;quot;${CMAKE_CURRENT_SOURCE_DIR}/src/gmath.h&amp;quot;
                   &amp;quot;${distribution_DIR}/gmath/include/gmath.h&amp;quot;
#                   **** the following 2 lines are for potential future debug purpose ****
#                   COMMAND &amp;quot;${CMAKE_COMMAND}&amp;quot; -E
#                   remove_directory &amp;quot;${CMAKE_CURRENT_BINARY_DIR}&amp;quot;
                   COMMENT &amp;quot;Copying gmath to output directory&amp;quot;)&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;这个是其中一个静态库的      &lt;code&gt;CMakeLists.txt&lt;/code&gt;，另一个跟他很像。只是把      &lt;code&gt;STATIC&lt;/code&gt;改成了      &lt;code&gt;SHARED&lt;/code&gt;（动态库）。&lt;/p&gt;    &lt;p&gt;      &lt;code&gt;add_library(gmath STATIC src/gmath.c)&lt;/code&gt;之前用到过，编译出一个静态库，源文件是      &lt;code&gt;src/gmath.c&lt;/code&gt;&lt;/p&gt;    &lt;p&gt;      &lt;code&gt;set_target_properties&lt;/code&gt;命令的意思是设置目标的一些属性来改变它们构建的方式。这个命令中设置了      &lt;code&gt;gmath&lt;/code&gt;的      &lt;code&gt;ARCHIVE_OUTPUT_DIRECTORY&lt;/code&gt;属性。也就是改变了输出路径。&lt;/p&gt;    &lt;p&gt;      &lt;code&gt;add_custom_command&lt;/code&gt;命令是自定义命令。命令中把头文件也复制到了      &lt;code&gt;distribution_DIR&lt;/code&gt;中。&lt;/p&gt;    &lt;p&gt;以上就是一个静态库/动态库的编译过程。总结以下3点&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;编译静态库/动态库&lt;/li&gt;      &lt;li&gt;修改输出路径&lt;/li&gt;      &lt;li&gt;复制暴露的头文件&lt;/li&gt;&lt;/ol&gt;    &lt;h4&gt;接着，我们看下      &lt;code&gt;app&lt;/code&gt;模块是如何使用预建好的静态库/动态库的。&lt;/h4&gt;    &lt;p&gt;      &lt;strong&gt;app/src/main/cpp/CMakeLists.txt&lt;/strong&gt;&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;cmake_minimum_required(VERSION 3.4.1)

# configure import libs
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../distribution)

# 创建一个静态库 lib_gmath 直接引用libgmath.a
add_library(lib_gmath STATIC IMPORTED)
set_target_properties(lib_gmath PROPERTIES IMPORTED_LOCATION
    ${distribution_DIR}/gmath/lib/${ANDROID_ABI}/libgmath.a)

# 创建一个动态库 lib_gperf 直接引用libgperf.so
add_library(lib_gperf SHARED IMPORTED)
set_target_properties(lib_gperf PROPERTIES IMPORTED_LOCATION
    ${distribution_DIR}/gperf/lib/${ANDROID_ABI}/libgperf.so)

# build application&amp;apos;s shared lib
set(CMAKE_CXX_FLAGS &amp;quot;${CMAKE_CXX_FLAGS} -std=gnu++11&amp;quot;)

# 创建库 hello-libs
add_library(hello-libs SHARED
            hello-libs.cpp)

# 加入头文件
target_include_directories(hello-libs PRIVATE
                           ${distribution_DIR}/gmath/include
                           ${distribution_DIR}/gperf/include)

# hello-libs库链接上 lib_gmath 和 lib_gperf
target_link_libraries(hello-libs
                      android
                      lib_gmath
                      lib_gperf
                      log)&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;我将解释放在了注释中。可以看下基本上分成了4个步骤引入：&lt;/p&gt;    &lt;ol&gt;      &lt;li&gt;分别创建静态库/动态库，直接引用已经有的 .a 文件 或者 .so 文件&lt;/li&gt;      &lt;li&gt;创建自己应用的库        &lt;code&gt;hello-libs&lt;/code&gt;&lt;/li&gt;      &lt;li&gt;加入之前暴露头文件&lt;/li&gt;      &lt;li&gt;链接上静态库/动态库&lt;/li&gt;&lt;/ol&gt;    &lt;p&gt;还是很好理解的。编辑好并      &lt;code&gt;Sync&lt;/code&gt;后，你就可以发现      &lt;code&gt;hello-libs&lt;/code&gt;中的c/c++代码可以引用暴露的头文件调用内部方法了。&lt;/p&gt;    &lt;h2&gt;3 资料文献&lt;/h2&gt;    &lt;p&gt;首推      &lt;a href="https://link.jianshu.com?t=https://developer.android.com/ndk/guides/index.html" rel="nofollow" target="_blank"&gt;Android NDK 官方文档&lt;/a&gt;，虽然很多都不完整，但是绝对是必须看一遍的东西。&lt;/p&gt;    &lt;p&gt;当初次接触      &lt;code&gt;NDK&lt;/code&gt;开发又觉得新建的 Hello World 项目过于简单时。建议把      &lt;a href="https://link.jianshu.com?t=https://github.com/googlesamples/android-ndk" rel="nofollow" target="_blank"&gt;googlesamples - android-ndk&lt;/a&gt;项目拉下来。里面有多个实例参考，比官方文档完整很多。&lt;/p&gt;    &lt;div&gt;      &lt;div&gt;        &lt;div&gt;&lt;/div&gt;        &lt;div&gt;          &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;      &lt;div&gt;Google Samples&lt;/div&gt;&lt;/div&gt;    &lt;p&gt;当你发现示例里的一些NDK配置满足不了你的需求后，你就需要到      &lt;a href="https://link.jianshu.com?t=https://cmake.org/documentation/" rel="nofollow" target="_blank"&gt;CMake 官方文档&lt;/a&gt;去查询完整的支持的函数，同时这里也提供一个中文翻译的简易的      &lt;a href="https://link.jianshu.com?t=https://www.zybuluo.com/khan-lau/note/254724" rel="nofollow" target="_blank"&gt;CMake手册&lt;/a&gt;。&lt;/p&gt;    &lt;p&gt;以上文档资料仅为了解决 NDK 开发过程中编译配置问题，具体 c/c++ 的逻辑编写、jni等不在此范畴。&lt;/p&gt;    &lt;h2&gt;      &lt;strong&gt;        &lt;em&gt;彩蛋&lt;/em&gt;&lt;/strong&gt;&lt;/h2&gt;    &lt;p&gt;文末献上一组彩蛋，将      &lt;code&gt;CMake&lt;/code&gt;或者      &lt;code&gt;NDK&lt;/code&gt;开发过程中遇到的坑和小技巧以 Q&amp;amp;A 的方式列出。持续更新&lt;/p&gt;    &lt;h4&gt;Q1：怎么指定 C++标准？&lt;/h4&gt;    &lt;p&gt;A：在      &lt;code&gt;build_gradle&lt;/code&gt;中，配置      &lt;code&gt;cppFlags -std&lt;/code&gt;&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;externalNativeBuild {
  cmake {
    cppFlags &amp;quot;-frtti -fexceptions -std=c++14&amp;quot;
    arguments &amp;apos;-DANDROID_STL=c++_shared&amp;apos;
  }
}&lt;/code&gt;&lt;/pre&gt;    &lt;h4&gt;Q2：add_library 如何编译一个目录中所有源文件？&lt;/h4&gt;    &lt;p&gt;A： 使用      &lt;code&gt;aux_source_directory&lt;/code&gt;方法将路径列表全部放到一个变量中。&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;# 查找所有源码 并拼接到路径列表
aux_source_directory(${CMAKE_HOME_DIRECTORY}/src/api SRC_LIST)
aux_source_directory(${CMAKE_HOME_DIRECTORY}/src/core CORE_SRC_LIST)
list(APPEND SRC_LIST ${CORE_SRC_LIST})
add_library(native-lib SHARED ${SRC_LIST})&lt;/code&gt;&lt;/pre&gt;    &lt;h4&gt;Q3：怎么调试 CMakeLists.txt 中的代码？&lt;/h4&gt;    &lt;p&gt;A：使用      &lt;code&gt;message&lt;/code&gt;方法&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;cmake_minimum_required(VERSION 3.4.1)
message(STATUS &amp;quot;execute CMakeLists&amp;quot;)
...&lt;/code&gt;&lt;/pre&gt;    &lt;p&gt;然后运行后在      &lt;code&gt;.externalNativeBuild/cmake/debug/{abi}/cmake_build_output.txt&lt;/code&gt;中查看 log。&lt;/p&gt;    &lt;h4&gt;Q4：什么时候 CMakeLists.txt 里面会执行？&lt;/h4&gt;    &lt;p&gt;A：测试了下，好像在 sync 的时候会执行。执行一次后会生成      &lt;code&gt;makefile&lt;/code&gt;的文件缓存之类的东西放在      &lt;code&gt;externalNativeBuild&lt;/code&gt;中。所以如果      &lt;code&gt;CMakeLists.txt&lt;/code&gt;中没有修改的话再次同步好像是不会重新执行的。（或者删除      &lt;code&gt;.externalNativeBuild&lt;/code&gt;目录）&lt;/p&gt;    &lt;p&gt;真正编译的时候好像只是读取      &lt;code&gt;.externalNativeBuild&lt;/code&gt;目录中已经解析好的      &lt;code&gt;makefile&lt;/code&gt;去编译。不会再去执行      &lt;code&gt;CMakeLists.txt&lt;/code&gt;&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/59751-android-ndk-%E5%BC%80%E5%8F%91</guid>
      <pubDate>Thu, 27 Jun 2019 11:23:08 CST</pubDate>
    </item>
    <item>
      <title>Windows下编译ncnn的android端的库 - 迷若烟雨的专栏 - CSDN博客</title>
      <link>https://itindex.net/detail/59750-windows-%E7%BC%96%E8%AF%91-ncnn</link>
      <description>&lt;div&gt;    &lt;p&gt;      &lt;a href="https://github.com/Tencent/ncnn" rel="nofollow"&gt;ncnn&lt;/a&gt;是腾讯开源的一个为手机端极致优化的高性能神经网络前向计算框架，目前已在腾讯多款应用中使用。&lt;/p&gt;    &lt;p&gt;由于开发者使用的是linux类似的环境,因此只提供了build.sh用来构建android和iOS的库,但好在提供了CMakelist.txt文件,我们可以借助CMake进行跨平台的交叉编译。&lt;/p&gt;    &lt;p&gt;将以下代码存为build.bat文件,双击执行即可&lt;/p&gt;    &lt;pre&gt;@echo off
set ANDROID_NDK=D:/AndroidSDK/ndk-bundle

mkdir build_android
cd build_android
cmake -G &amp;quot;Unix Makefiles&amp;quot; -DCMAKE_TOOLCHAIN_FILE=&amp;quot;%ANDROID_NDK%/build/cmake/android.toolchain.cmake&amp;quot; ..\ -DCMAKE_MAKE_PROGRAM=&amp;quot;%ANDROID_NDK%/prebuilt/windows-x86_64/bin/make.exe&amp;quot; -DCMAKE_BUILD_TYPE=Release -DANDROID_ABI=&amp;quot;armeabi-v7a&amp;quot; -DANDROID_NATIVE_API_LEVEL=9
cmake --build .
cmake --build . --target install
cd ..
pause
&lt;/pre&gt;  &lt;div&gt;   &lt;br /&gt;&lt;/div&gt;    &lt;p&gt;其中ANDROID_NDK要换成你本机android ndk所在的目录，没有的话就去搜索下一个，最好15c版本及以上。&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/59750-windows-%E7%BC%96%E8%AF%91-ncnn</guid>
      <pubDate>Thu, 27 Jun 2019 00:15:58 CST</pubDate>
    </item>
    <item>
      <title>在Android手机上使用腾讯的ncnn实现图像分类 - 夜雨飘零 - CSDN博客</title>
      <link>https://itindex.net/detail/59737-android-%E6%89%8B%E6%9C%BA-%E8%85%BE%E8%AE%AF</link>
      <description>&lt;div&gt;  &lt;em&gt;前言&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;在之前笔者有介绍过《在Android设备上使用PaddleMobile实现图像分类》，使用的框架是百度开源的PaddleMobile。在本章中，笔者将会介绍使用腾讯的开源手机深度学习框架ncnn来实现在Android手机实现图像分类，这个框架开源时间比较长，相对稳定很多。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;ncnn的GitHub地址：https://github.com/Tencent/ncnn&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;使用Ubuntu编译ncnn库&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1、首先要下载和解压NDK。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;wget https://dl.google.com/android/repository/android-ndk-r17b-linux-x86_64.zip&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;unzip android-ndk-r17b-linux-x86_64.zip&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2、设置NDK环境变量，目录是NDK的解压目录。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;export NDK_ROOT=&amp;quot;/home/test/paddlepaddle/android-ndk-r17b&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;设置好之后，可以使用以下的命令查看配置情况。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;root@test:/home/test/paddlepaddle# echo $NDK_ROOT&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;/home/test/paddlepaddle/android-ndk-r17b&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3、安装cmake，需要安装较高版本的，笔者的cmake版本是3.11.2。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;下载cmake源码&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;wget https://cmake.org/files/v3.11/cmake-3.11.2.tar.gz&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;解压cmake源码&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;tar -zxvf cmake-3.11.2.tar.gz&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;进入到cmake源码根目录，并执行bootstrap。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;cd cmake-3.11.2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;./bootstrap&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;最后执行以下两条命令开始安装cmake。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;make&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;make install&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;安装完成之后，可以使用cmake --version是否安装成功。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;root@test:/home/test/paddlepaddle# cmake --version&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;cmake version 3.11.2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;CMake suite maintained and supported by Kitware (kitware.com/cmake).&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4、克隆ncnn源码。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;git clone https://github.com/Tencent/ncnn.git&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5、编译源码。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# 进入到ncnn源码根目录下&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;cd ncnn&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# 创建一个新的文件夹&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;mkdir -p build-android-armv7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# 进入到该文件夹中&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;cd build-android-armv7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# 执行编译命令&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    -DANDROID_ABI=&amp;quot;armeabi-v7a&amp;quot; -DANDROID_ARM_NEON=ON \&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    -DANDROID_PLATFORM=android-14 ..&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# 这里笔者使用4个行程并行编译&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;make -j4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;make install&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;9&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;10&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;11&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;12&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;13&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;9&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;10&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;11&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;12&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;13&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6、编译完成，会在build-android-armv7目录下生成一个install目录，我们编译得到的文件都在该文件夹下：&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;include 调用ncnn所需的头文件，该文件夹会存放在Android项目的src/main/cpp目录下；&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;lib 编译得到的ncnn库libncnn.a，之后会存放在Android项目的src/main/jniLibs/armeabi-v7a/libncnn.a&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;转换预测模型&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1、克隆Caffe源码。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;git clone https://github.com/BVLC/caffe.git&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2、编译Caffe源码。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# 切换到Caffe目录&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;cd caffe&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# 在当前目录执行cmake&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;cmake .&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# 使用4个线程编译&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;make -j4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;make install&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3、升级Caffe模型。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# 把需要转换的模型复制到caffe/tools，并切入到该目录&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;cd tools&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# 升级Caffe模型&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;./upgrade_net_proto_text mobilenet_v2_deploy.prototxt mobilenet_v2_deploy_new.prototxt&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;./upgrade_net_proto_binary mobilenet_v2.caffemodel mobilenet_v2_new.caffemodel&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4、检查模型配置文件，因为只能一张一张图片预测，所以输入要设置为dim: 1。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;name: &amp;quot;MOBILENET_V2&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;layer {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;  name: &amp;quot;input&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;  type: &amp;quot;Input&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;  top: &amp;quot;data&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;  input_param {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    shape {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;      dim: 1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;      dim: 3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;      dim: 224&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;      dim: 224&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;  }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;}&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;9&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;10&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;11&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;12&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;13&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;14&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5、切换到ncnn的根目录，就是我们上一部分克隆的ncnn源码。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;cd ncnn/&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6、在根目录下编译ncnn源码。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;mkdir -p build&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;cd build&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;cmake ..&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;make -j4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;make install&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7、把新的Caffe模型转换成NCNN模型。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# 经过上一步，会生产一个tools，我们进入到以下目录&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;cd tools/caffe/&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# 把已经升级的网络定义文件和权重文件复制到当目录，并执行以下命令&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;./caffe2ncnn mobilenet_v2_deploy_new.prototxt mobilenet_v2_new.caffemodel mobilenet_v2.param mobilenet_v2.bin&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8、对象模型参数进行加密，这样就算别人反编译我们的apk也用不了我们的模型文件。把上一步获得的mobilenet_v2.param、mobilenet_v2.bin复制到该目录的上一个目录，也就是tools目录。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# 切换到上一个目录&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;cd ../&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# 执行命令之后会生成mobilenet_v2.param、mobilenet_v2.id.h、mobilenet_v2.mem.h&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;./ncnn2mem mobilenet_v2.param mobilenet_v2.bin mobilenet_v2.id.h mobilenet_v2.mem.h&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;经过上面的步骤，得到的文件中，以下文件时需要的：&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;mobilenet_v2.param.bin 网络的模型参数；&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;mobilenet_v2.bin 网络的权重；&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;mobilenet_v2.id.h 在预测图片的时候使用到。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;开发Android项目&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;我们在Android Studio上创建一个NCNN1的项目，别忘了选择C++支持。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;其他的可以直接默认就可以了，在这里要注意选择C++11支持。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;在main目录下创建assets目录，并复制以下目录到该目录：&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;mobilenet_v2.param.bin 上一步获取网络的模型参数；&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;mobilenet_v2.bin 上一步获取网络的权重；&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;synset.txt label对应的名称，下载地址：https://github.com/shicai/MobileNet-Caffe/blob/master/synset.txt。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;在cpp目录下复制在使用Ubuntu编译NCNN库部分编译得到的include文件夹，包括里面的C++头文件。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;把mobilenet_v2.id.h复制到cpp目录下。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;在main目录下创建jniLibs/armeabi-v7a/目录，并把使用Ubuntu编译NCNN库部分编译得到的libncnn.a复制到该目录。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;在cpp目录下创建一个C++文件，并编写以下代码，这段代码是用于加载模型和预测图片的：&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;#include &amp;lt;android/bitmap.h&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;#include &amp;lt;android/log.h&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;#include &amp;lt;jni.h&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;#include &amp;lt;string&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;#include &amp;lt;vector&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;// ncnn&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;#include &amp;quot;include/net.h&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;#include &amp;quot;mobilenet_v2.id.h&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;#include &amp;lt;sys/time.h&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;#include &amp;lt;unistd.h&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;static ncnn::UnlockedPoolAllocator g_blob_pool_allocator;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;static ncnn::PoolAllocator g_workspace_pool_allocator;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;static ncnn::Mat ncnn_param;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;static ncnn::Mat ncnn_bin;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;static ncnn::Net ncnn_net;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;extern &amp;quot;C&amp;quot; {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;// public native boolean Init(byte[] param, byte[] bin, byte[] words);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;JNIEXPORT jboolean JNICALL&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;Java_com_example_ncnn1_NcnnJni_Init(JNIEnv *env, jobject thiz, jbyteArray param, jbyteArray bin) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    // init param&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        int len = env-&amp;gt;GetArrayLength(param);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        ncnn_param.create(len, (size_t) 1u);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        env-&amp;gt;GetByteArrayRegion(param, 0, len, (jbyte *) ncnn_param);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        int ret = ncnn_net.load_param((const unsigned char *) ncnn_param);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        __android_log_print(ANDROID_LOG_DEBUG, &amp;quot;NcnnJni&amp;quot;, &amp;quot;load_param %d %d&amp;quot;, ret, len);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    // init bin&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        int len = env-&amp;gt;GetArrayLength(bin);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        ncnn_bin.create(len, (size_t) 1u);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        env-&amp;gt;GetByteArrayRegion(bin, 0, len, (jbyte *) ncnn_bin);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        int ret = ncnn_net.load_model((const unsigned char *) ncnn_bin);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        __android_log_print(ANDROID_LOG_DEBUG, &amp;quot;NcnnJni&amp;quot;, &amp;quot;load_model %d %d&amp;quot;, ret, len);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    ncnn::Option opt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    opt.lightmode = true;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    opt.num_threads = 4;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    opt.blob_allocator = &amp;amp;g_blob_pool_allocator;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    opt.workspace_allocator = &amp;amp;g_workspace_pool_allocator;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    ncnn::set_default_option(opt);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    return JNI_TRUE;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;}&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;// public native String Detect(Bitmap bitmap);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;JNIEXPORT jfloatArray JNICALL Java_com_example_ncnn1_NcnnJni_Detect(JNIEnv* env, jobject thiz, jobject bitmap)&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;{&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    // ncnn from bitmap&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    ncnn::Mat in;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        AndroidBitmapInfo info;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        AndroidBitmap_getInfo(env, bitmap, &amp;amp;info);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        int width = info.width;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        int height = info.height;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            return NULL;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        void* indata;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        AndroidBitmap_lockPixels(env, bitmap, &amp;amp;indata);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        // 把像素转换成data，并指定通道顺序&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        in = ncnn::Mat::from_pixels((const unsigned char*)indata, ncnn::Mat::PIXEL_RGBA2BGR, width, height);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        AndroidBitmap_unlockPixels(env, bitmap);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    // ncnn_net&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    std::vector&amp;lt;float&amp;gt; cls_scores;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        // 减去均值和乘上比例&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        const float mean_vals[3] = {103.94f, 116.78f, 123.68f};&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        const float scale[3] = {0.017f, 0.017f, 0.017f};&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        in.substract_mean_normalize(mean_vals, scale);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        ncnn::Extractor ex = ncnn_net.create_extractor();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        // 如果时不加密是使用ex.input(&amp;quot;data&amp;quot;, in);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        ex.input(mobilenet_v2_param_id::BLOB_data, in);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        ncnn::Mat out;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        // 如果时不加密是使用ex.extract(&amp;quot;prob&amp;quot;, out);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        ex.extract(mobilenet_v2_param_id::BLOB_prob, out);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        int output_size = out.w;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        jfloat *output[output_size];&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        for (int j = 0; j &amp;lt; out.w; j++) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            output[j] = &amp;amp;out[j];&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        jfloatArray jOutputData = env-&amp;gt;NewFloatArray(output_size);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        if (jOutputData == nullptr) return nullptr;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        env-&amp;gt;SetFloatArrayRegion(jOutputData, 0, output_size,&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                                 reinterpret_cast&amp;lt;const jfloat *&amp;gt;(*output));  // copy&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        return jOutputData;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;}&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;}&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;9&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;10&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;11&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;12&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;13&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;14&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;15&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;16&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;17&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;18&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;19&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;20&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;21&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;22&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;23&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;24&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;25&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;26&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;27&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;28&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;29&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;30&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;31&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;32&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;33&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;34&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;35&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;36&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;37&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;38&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;39&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;40&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;41&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;42&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;43&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;44&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;45&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;46&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;47&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;48&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;49&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;50&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;51&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;52&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;53&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;54&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;55&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;56&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;57&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;58&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;59&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;60&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;61&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;62&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;63&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;64&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;65&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;66&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;67&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;68&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;69&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;70&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;71&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;72&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;73&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;74&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;75&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;76&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;77&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;78&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;79&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;80&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;81&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;82&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;83&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;84&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;85&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;86&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;87&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;88&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;89&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;90&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;91&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;92&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;93&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;94&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;95&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;96&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;97&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;98&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;99&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;100&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;101&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;102&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;103&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;104&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;105&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;106&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;9&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;10&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;11&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;12&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;13&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;14&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;15&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;16&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;17&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;18&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;19&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;20&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;21&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;22&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;23&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;24&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;25&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;26&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;27&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;28&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;29&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;30&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;31&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;32&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;33&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;34&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;35&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;36&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;37&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;38&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;39&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;40&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;41&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;42&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;43&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;44&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;45&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;46&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;47&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;48&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;49&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;50&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;51&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;52&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;53&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;54&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;55&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;56&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;57&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;58&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;59&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;60&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;61&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;62&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;63&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;64&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;65&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;66&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;67&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;68&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;69&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;70&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;71&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;72&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;73&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;74&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;75&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;76&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;77&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;78&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;79&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;80&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;81&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;82&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;83&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;84&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;85&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;86&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;87&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;88&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;89&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;90&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;91&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;92&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;93&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;94&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;95&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;96&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;97&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;98&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;99&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;100&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;101&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;102&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;103&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;104&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;105&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;106&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;在项目包com.example.ncnn1下，修改MainActivity.java中的代码，修改如下：&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;package com.example.ncnn1;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.Manifest;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.app.Activity;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.content.Intent;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.content.pm.PackageManager;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.content.res.AssetManager;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.graphics.Bitmap;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.graphics.BitmapFactory;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.net.Uri;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.os.Bundle;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.support.annotation.NonNull;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.support.annotation.Nullable;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.support.v4.app.ActivityCompat;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.support.v4.content.ContextCompat;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.text.method.ScrollingMovementMethod;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.util.Log;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.view.View;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.widget.Button;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.widget.ImageView;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.widget.TextView;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.widget.Toast;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import com.bumptech.glide.Glide;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import com.bumptech.glide.load.engine.DiskCacheStrategy;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import com.bumptech.glide.request.RequestOptions;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import java.io.BufferedReader;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import java.io.FileNotFoundException;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import java.io.IOException;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import java.io.InputStream;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import java.io.InputStreamReader;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import java.util.ArrayList;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import java.util.Arrays;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import java.util.List;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;public class MainActivity extends Activity {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private static final String TAG = MainActivity.class.getName();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private static final int USE_PHOTO = 1001;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private String camera_image_path;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private ImageView show_image;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private TextView result_text;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private boolean load_result = false;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private int[] ddims = {1, 3, 224, 224};&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private int model_index = 1;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private List&amp;lt;String&amp;gt; resultLabel = new ArrayList&amp;lt;&amp;gt;();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private NcnnJni squeezencnn = new NcnnJni();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    @Override&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    public void onCreate(Bundle savedInstanceState) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        super.onCreate(savedInstanceState);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        setContentView(R.layout.activity_main);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        try {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            initSqueezeNcnn();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        } catch (IOException e) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            Log.e(&amp;quot;MainActivity&amp;quot;, &amp;quot;initSqueezeNcnn error&amp;quot;);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        init_view();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        readCacheLabelFromLocalFile();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private void initSqueezeNcnn() throws IOException {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        byte[] param = null;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        byte[] bin = null;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            InputStream assetsInputStream = getAssets().open(&amp;quot;mobilenet_v2.param.bin&amp;quot;);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            int available = assetsInputStream.available();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            param = new byte[available];&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            int byteCode = assetsInputStream.read(param);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            assetsInputStream.close();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            InputStream assetsInputStream = getAssets().open(&amp;quot;mobilenet_v2.bin&amp;quot;);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            int available = assetsInputStream.available();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            bin = new byte[available];&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            int byteCode = assetsInputStream.read(bin);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            assetsInputStream.close();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        load_result = squeezencnn.Init(param, bin);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        Log.d(&amp;quot;load model&amp;quot;, &amp;quot;result:&amp;quot; + load_result);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    // initialize view&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private void init_view() {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        request_permissions();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        show_image = (ImageView) findViewById(R.id.show_image);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        result_text = (TextView) findViewById(R.id.result_text);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        result_text.setMovementMethod(ScrollingMovementMethod.getInstance());&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        Button use_photo = (Button) findViewById(R.id.use_photo);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        // use photo click&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        use_photo.setOnClickListener(new View.OnClickListener() {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            @Override&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            public void onClick(View view) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                if (!load_result) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                    Toast.makeText(MainActivity.this, &amp;quot;never load model&amp;quot;, Toast.LENGTH_SHORT).show();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                    return;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                PhotoUtil.use_photo(MainActivity.this, USE_PHOTO);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        });&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;	// load label&amp;apos;s name&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private void readCacheLabelFromLocalFile() {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        try {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            AssetManager assetManager = getApplicationContext().getAssets();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            BufferedReader reader = new BufferedReader(new InputStreamReader(assetManager.open(&amp;quot;synset.txt&amp;quot;)));&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            String readLine = null;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            while ((readLine = reader.readLine()) != null) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                resultLabel.add(readLine);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            reader.close();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        } catch (Exception e) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            Log.e(&amp;quot;labelCache&amp;quot;, &amp;quot;error &amp;quot; + e);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    @Override&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        String image_path;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        RequestOptions options = new RequestOptions().skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        if (resultCode == Activity.RESULT_OK) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            switch (requestCode) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                case USE_PHOTO:&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                    if (data == null) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                        Log.w(TAG, &amp;quot;user photo data is null&amp;quot;);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                        return;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                    Uri image_uri = data.getData();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                    Glide.with(MainActivity.this).load(image_uri).apply(options).into(show_image);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                    // get image path from uri&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                    image_path = PhotoUtil.get_path_from_URI(MainActivity.this, image_uri);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                    // predict image&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                    predict_image(image_path);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                    break;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    //  predict image&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private void predict_image(String image_path) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        // picture to float array&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        Bitmap bmp = PhotoUtil.getScaleBitmap(image_path);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        Bitmap rgba = bmp.copy(Bitmap.Config.ARGB_8888, true);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        // resize to 227x227&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        Bitmap input_bmp = Bitmap.createScaledBitmap(rgba, ddims[2], ddims[3], false);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        try {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            // Data format conversion takes too long&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            // Log.d(&amp;quot;inputData&amp;quot;, Arrays.toString(inputData));&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            long start = System.currentTimeMillis();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            // get predict result&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            float[] result = squeezencnn.Detect(input_bmp);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            long end = System.currentTimeMillis();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            Log.d(TAG, &amp;quot;origin predict result:&amp;quot; + Arrays.toString(result));&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            long time = end - start;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            Log.d(&amp;quot;result length&amp;quot;, String.valueOf(result.length));&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            // show predict result and time&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            int r = get_max_result(result);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            String show_text = &amp;quot;result：&amp;quot; + r + &amp;quot;\nname：&amp;quot; + resultLabel.get(r) + &amp;quot;\nprobability：&amp;quot; + result[r] + &amp;quot;\ntime：&amp;quot; + time + &amp;quot;ms&amp;quot;;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            result_text.setText(show_text);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        } catch (Exception e) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            e.printStackTrace();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    // get max probability label&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private int get_max_result(float[] result) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        float probability = result[0];&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        int r = 0;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        for (int i = 0; i &amp;lt; result.length; i++) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            if (probability &amp;lt; result[i]) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                probability = result[i];&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                r = i;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        return r;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    // request permissions&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    private void request_permissions() {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        List&amp;lt;String&amp;gt; permissionList = new ArrayList&amp;lt;&amp;gt;();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            permissionList.add(Manifest.permission.CAMERA);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            permissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        // if list is not empty will request permissions&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        if (!permissionList.isEmpty()) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            ActivityCompat.requestPermissions(this, permissionList.toArray(new String[permissionList.size()]), 1);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    @Override&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        super.onRequestPermissionsResult(requestCode, permissions, grantResults);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        switch (requestCode) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            case 1:&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                if (grantResults.length &amp;gt; 0) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                    for (int i = 0; i &amp;lt; grantResults.length; i++) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                        int grantResult = grantResults[i];&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                        if (grantResult == PackageManager.PERMISSION_DENIED) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                            String s = permissions[i];&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                            Toast.makeText(this, s + &amp;quot; permission was denied&amp;quot;, Toast.LENGTH_SHORT).show();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                break;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;}&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;9&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;10&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;11&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;12&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;13&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;14&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;15&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;16&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;17&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;18&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;19&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;20&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;21&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;22&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;23&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;24&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;25&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;26&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;27&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;28&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;29&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;30&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;31&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;32&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;33&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;34&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;35&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;36&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;37&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;38&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;39&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;40&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;41&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;42&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;43&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;44&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;45&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;46&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;47&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;48&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;49&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;50&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;51&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;52&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;53&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;54&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;55&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;56&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;57&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;58&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;59&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;60&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;61&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;62&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;63&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;64&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;65&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;66&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;67&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;68&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;69&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;70&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;71&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;72&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;73&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;74&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;75&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;76&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;77&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;78&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;79&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;80&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;81&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;82&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;83&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;84&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;85&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;86&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;87&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;88&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;89&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;90&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;91&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;92&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;93&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;94&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;95&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;96&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;97&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;98&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;99&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;100&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;101&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;102&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;103&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;104&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;105&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;106&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;107&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;108&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;109&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;110&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;111&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;112&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;113&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;114&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;115&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;116&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;117&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;118&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;119&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;120&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;121&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;122&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;123&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;124&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;125&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;126&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;127&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;128&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;129&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;130&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;131&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;132&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;133&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;134&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;135&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;136&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;137&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;138&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;139&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;140&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;141&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;142&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;143&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;144&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;145&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;146&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;147&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;148&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;149&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;150&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;151&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;152&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;153&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;154&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;155&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;156&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;157&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;158&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;159&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;160&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;161&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;162&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;163&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;164&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;165&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;166&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;167&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;168&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;169&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;170&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;171&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;172&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;173&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;174&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;175&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;176&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;177&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;178&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;179&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;180&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;181&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;182&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;183&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;184&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;185&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;186&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;187&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;188&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;189&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;190&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;191&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;192&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;193&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;194&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;195&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;196&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;197&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;198&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;199&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;200&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;201&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;202&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;203&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;204&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;205&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;206&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;207&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;208&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;209&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;210&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;211&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;212&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;213&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;214&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;215&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;216&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;217&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;218&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;219&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;220&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;221&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;222&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;223&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;224&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;225&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;226&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;9&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;10&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;11&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;12&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;13&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;14&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;15&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;16&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;17&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;18&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;19&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;20&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;21&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;22&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;23&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;24&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;25&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;26&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;27&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;28&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;29&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;30&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;31&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;32&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;33&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;34&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;35&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;36&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;37&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;38&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;39&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;40&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;41&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;42&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;43&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;44&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;45&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;46&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;47&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;48&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;49&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;50&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;51&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;52&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;53&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;54&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;55&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;56&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;57&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;58&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;59&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;60&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;61&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;62&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;63&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;64&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;65&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;66&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;67&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;68&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;69&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;70&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;71&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;72&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;73&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;74&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;75&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;76&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;77&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;78&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;79&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;80&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;81&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;82&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;83&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;84&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;85&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;86&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;87&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;88&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;89&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;90&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;91&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;92&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;93&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;94&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;95&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;96&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;97&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;98&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;99&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;100&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;101&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;102&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;103&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;104&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;105&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;106&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;107&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;108&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;109&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;110&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;111&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;112&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;113&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;114&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;115&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;116&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;117&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;118&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;119&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;120&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;121&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;122&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;123&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;124&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;125&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;126&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;127&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;128&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;129&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;130&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;131&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;132&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;133&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;134&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;135&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;136&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;137&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;138&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;139&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;140&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;141&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;142&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;143&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;144&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;145&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;146&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;147&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;148&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;149&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;150&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;151&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;152&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;153&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;154&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;155&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;156&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;157&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;158&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;159&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;160&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;161&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;162&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;163&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;164&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;165&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;166&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;167&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;168&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;169&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;170&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;171&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;172&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;173&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;174&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;175&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;176&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;177&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;178&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;179&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;180&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;181&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;182&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;183&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;184&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;185&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;186&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;187&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;188&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;189&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;190&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;191&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;192&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;193&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;194&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;195&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;196&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;197&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;198&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;199&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;200&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;201&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;202&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;203&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;204&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;205&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;206&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;207&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;208&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;209&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;210&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;211&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;212&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;213&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;214&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;215&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;216&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;217&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;218&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;219&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;220&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;221&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;222&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;223&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;224&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;225&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;226&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;同样在项目的包com.example.ncnn1下，创建一个NcnnJni.java类，用于提供JNI接口，代码如下：&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;package com.example.ncnn1;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.graphics.Bitmap;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;public class NcnnJni&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;{&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    public native boolean Init(byte[] param, byte[] bin);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    public native float[] Detect(Bitmap bitmap);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    static {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        System.loadLibrary(&amp;quot;ncnn_jni&amp;quot;);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;}&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;9&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;10&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;11&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;12&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;13&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;14&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;9&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;10&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;11&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;12&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;13&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;14&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;还是在项目的包com.example.ncnn1下，创建一个PhotoUtil.java类，这个是图片的工具类，代码如下：&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;package com.example.ncnn1;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.app.Activity;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.content.Context;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.content.Intent;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.database.Cursor;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.graphics.Bitmap;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.graphics.BitmapFactory;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.net.Uri;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import android.provider.MediaStore;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;import java.nio.FloatBuffer;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;public class PhotoUtil {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    // get picture in photo&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    public static void use_photo(Activity activity, int requestCode) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        Intent intent = new Intent(Intent.ACTION_PICK);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        intent.setType(&amp;quot;image/*&amp;quot;);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        activity.startActivityForResult(intent, requestCode);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    // get photo from Uri&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    public static String get_path_from_URI(Context context, Uri uri) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        String result;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        if (cursor == null) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            result = uri.getPath();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        } else {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            cursor.moveToFirst();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            result = cursor.getString(idx);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            cursor.close();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        return result;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    // compress picture&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    public static Bitmap getScaleBitmap(String filePath) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        BitmapFactory.Options opt = new BitmapFactory.Options();&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        opt.inJustDecodeBounds = true;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        BitmapFactory.decodeFile(filePath, opt);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        int bmpWidth = opt.outWidth;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        int bmpHeight = opt.outHeight;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        int maxSize = 500;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        // compress picture with inSampleSize&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        opt.inSampleSize = 1;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        while (true) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            if (bmpWidth / opt.inSampleSize &amp;lt; maxSize || bmpHeight / opt.inSampleSize &amp;lt; maxSize) {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                break;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            opt.inSampleSize *= 2;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        opt.inJustDecodeBounds = false;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        return BitmapFactory.decodeFile(filePath, opt);&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;}&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;9&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;10&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;11&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;12&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;13&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;14&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;15&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;16&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;17&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;18&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;19&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;20&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;21&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;22&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;23&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;24&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;25&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;26&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;27&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;28&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;29&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;30&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;31&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;32&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;33&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;34&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;35&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;36&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;37&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;38&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;39&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;40&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;41&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;42&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;43&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;44&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;45&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;46&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;47&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;48&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;49&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;50&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;51&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;52&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;53&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;54&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;55&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;56&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;57&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;58&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;59&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;9&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;10&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;11&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;12&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;13&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;14&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;15&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;16&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;17&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;18&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;19&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;20&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;21&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;22&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;23&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;24&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;25&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;26&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;27&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;28&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;29&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;30&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;31&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;32&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;33&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;34&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;35&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;36&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;37&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;38&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;39&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;40&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;41&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;42&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;43&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;44&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;45&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;46&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;47&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;48&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;49&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;50&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;51&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;52&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;53&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;54&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;55&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;56&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;57&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;58&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;59&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;修改启动页面的布局，修改如下：&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;&amp;lt;RelativeLayout xmlns:android=&amp;quot;http://schemas.android.com/apk/res/android&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    xmlns:app=&amp;quot;http://schemas.android.com/apk/res-auto&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    xmlns:tools=&amp;quot;http://schemas.android.com/tools&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    android:layout_width=&amp;quot;match_parent&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    android:layout_height=&amp;quot;match_parent&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    tools:context=&amp;quot;.MainActivity&amp;quot;&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    &amp;lt;LinearLayout&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:id=&amp;quot;@+id/btn_ll&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:layout_alignParentBottom=&amp;quot;true&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:layout_width=&amp;quot;match_parent&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:layout_height=&amp;quot;wrap_content&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:orientation=&amp;quot;horizontal&amp;quot;&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        &amp;lt;Button&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            android:id=&amp;quot;@+id/use_photo&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            android:layout_weight=&amp;quot;1&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            android:layout_width=&amp;quot;0dp&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            android:layout_height=&amp;quot;wrap_content&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            android:text=&amp;quot;相册&amp;quot; /&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    &amp;lt;/LinearLayout&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    &amp;lt;TextView&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:layout_above=&amp;quot;@id/btn_ll&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:id=&amp;quot;@+id/result_text&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:textSize=&amp;quot;16sp&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:layout_width=&amp;quot;match_parent&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:hint=&amp;quot;预测结果会在这里显示&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:layout_height=&amp;quot;100dp&amp;quot; /&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    &amp;lt;ImageView&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:layout_alignParentTop=&amp;quot;true&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:layout_above=&amp;quot;@id/result_text&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:id=&amp;quot;@+id/show_image&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:layout_width=&amp;quot;match_parent&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        android:layout_height=&amp;quot;match_parent&amp;quot; /&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;&amp;lt;/RelativeLayout&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;9&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;10&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;11&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;12&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;13&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;14&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;15&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;16&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;17&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;18&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;19&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;20&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;21&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;22&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;23&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;24&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;25&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;26&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;27&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;28&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;29&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;30&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;31&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;32&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;33&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;34&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;35&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;36&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;37&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;38&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;39&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;40&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;修改APP目录下的CMakeLists.txt文件，修改如下：&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# For more information about using CMake with Android Studio, read the&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# documentation: https://d.android.com/studio/projects/add-native-code.html&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# Sets the minimum version of CMake required to build the native library.&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;cmake_minimum_required(VERSION 3.4.1)&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# Creates and names a library, sets it as either STATIC&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# or SHARED, and provides the relative paths to its source code.&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# You can define multiple libraries, and CMake builds them for you.&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# Gradle automatically packages shared libraries with your APK.&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;set(ncnn_lib ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libncnn.a)&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;add_library (ncnn_lib STATIC IMPORTED)&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;set_target_properties(ncnn_lib PROPERTIES IMPORTED_LOCATION ${ncnn_lib})&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;add_library( # Sets the name of the library.&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;             ncnn_jni&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;             # Sets the library as a shared library.&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;             SHARED&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;             # Provides a relative path to your source file(s).&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;             src/main/cpp/ncnn_jni.cpp )&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# Searches for a specified prebuilt library and stores the path as a&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# variable. Because CMake includes system libraries in the search path by&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# default, you only need to specify the name of the public NDK library&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# you want to add. CMake verifies that the library exists before&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# completing its build.&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;find_library( # Sets the name of the path variable.&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;              log-lib&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;              # Specifies the name of the NDK library that&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;              # you want CMake to locate.&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;              log )&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# Specifies libraries CMake should link to your target library. You&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# can link multiple libraries, such as libraries you define in this&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;# build script, prebuilt third-party libraries, or system libraries.&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;target_link_libraries( # Specifies the target library.&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                       ncnn_jni&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                       ncnn_lib&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                       jnigraphics&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                       # Links the target library to the log library&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                       # included in the NDK.&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                       ${log-lib} )&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;9&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;10&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;11&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;12&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;13&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;14&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;15&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;16&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;17&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;18&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;19&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;20&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;21&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;22&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;23&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;24&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;25&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;26&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;27&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;28&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;29&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;30&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;31&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;32&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;33&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;34&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;35&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;36&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;37&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;38&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;39&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;40&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;41&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;42&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;43&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;44&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;45&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;46&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;47&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;48&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;49&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;50&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;51&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;修改APP目录下的build.gradle文件，修改如下：&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;apply plugin: &amp;apos;com.android.application&amp;apos;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;android {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    compileSdkVersion 28&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    defaultConfig {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        applicationId &amp;quot;com.example.ncnn1&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        minSdkVersion 21&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        targetSdkVersion 28&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        versionCode 1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        versionName &amp;quot;1.0&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        testInstrumentationRunner &amp;quot;android.support.test.runner.AndroidJUnitRunner&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        externalNativeBuild {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            cmake {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                cppFlags &amp;quot;-std=c++11 -fopenmp&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;                abiFilters &amp;quot;armeabi-v7a&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    buildTypes {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        release {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            minifyEnabled false&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            proguardFiles getDefaultProguardFile(&amp;apos;proguard-android.txt&amp;apos;), &amp;apos;proguard-rules.pro&amp;apos;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    externalNativeBuild {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        cmake {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            path &amp;quot;CMakeLists.txt&amp;quot;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    sourceSets {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        main {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            jniLibs.srcDirs = [&amp;quot;src/main/jniLibs&amp;quot;]&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;            jni.srcDirs = [&amp;apos;src/cpp&amp;apos;]&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;        }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    }&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;}&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;dependencies {&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    implementation fileTree(dir: &amp;apos;libs&amp;apos;, include: [&amp;apos;*.jar&amp;apos;])&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    implementation &amp;apos;com.android.support:appcompat-v7:28.0.0-rc02&amp;apos;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    implementation &amp;apos;com.android.support.constraint:constraint-layout:1.1.3&amp;apos;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    testImplementation &amp;apos;junit:junit:4.12&amp;apos;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    implementation &amp;apos;com.github.bumptech.glide:glide:4.3.1&amp;apos;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    androidTestImplementation &amp;apos;com.android.support.test:runner:1.0.2&amp;apos;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;    androidTestImplementation &amp;apos;com.android.support.test.espresso:espresso-core:3.0.2&amp;apos;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;}&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;3&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;4&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;5&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;6&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;7&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;8&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;9&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;10&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;11&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;12&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;13&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;14&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;15&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;16&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;17&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;18&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;19&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;20&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;21&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;22&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;23&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;24&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;25&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;26&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;27&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;28&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;29&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;30&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;31&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;32&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;33&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;34&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;35&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;36&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;37&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;38&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;39&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;40&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;41&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;42&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;43&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;44&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;45&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;46&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;47&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;最后别忘了在配置文件中添加权限。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;&amp;lt;uses-permission android:name=&amp;quot;android.permission.READ_EXTERNAL_STORAGE&amp;quot;/&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;&amp;lt;uses-permission android:name=&amp;quot;android.permission.WRITE_EXTERNAL_STORAGE&amp;quot;/&amp;gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;1&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;2&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;最后的效果图如下：&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;代码传送门： 上面已经几乎包括所有的代码了，为了读者方便直接使用，可以在这里下载项目源代码。&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;   &lt;br /&gt;&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;参考资料&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;https://github.com/BVLC/caffe&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;https://github.com/Tencent/ncnn/wiki/how-to-use-ncnn-with-alexnet&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;https://github.com/Tencent/ncnn/wiki/how-to-build&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;https://github.com/Tencent/ncnn/tree/master/examples/squeezencnn&lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;--------------------- &lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;作者：夜雨飘零1 &lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;来源：CSDN &lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;原文：https://blog.csdn.net/qq_33200967/article/details/82421089 &lt;/em&gt;&lt;/div&gt; &lt;div&gt;  &lt;em&gt;版权声明：本文为博主原创文章，转载请附上博文链接！&lt;/em&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/59737-android-%E6%89%8B%E6%9C%BA-%E8%85%BE%E8%AE%AF</guid>
      <pubDate>Mon, 24 Jun 2019 22:05:23 CST</pubDate>
    </item>
    <item>
      <title>Android开发优化的几点建议</title>
      <link>https://itindex.net/detail/59710-android-%E5%BC%80%E5%8F%91-%E4%BC%98%E5%8C%96</link>
      <description>&lt;p&gt;安卓开发大军浩浩荡荡，经过近十年的发展，Android技术优化日异月新，如今Android 9.0 已经发布，Android系统性能也已经非常流畅，可以在体验上完全媲美iOS。  &lt;br /&gt;但是，到了各大厂商手里，改源码、自定义系统，使得Android原生系统变得鱼龙混杂，然后到了不同层次的开发工程师手里，因为技术水平的参差不齐，即使很多手机在跑分软件性能非常高，打开应用依然存在卡顿现象。另外，随着产品内容迭代，功能越来越复杂，UI页面也越来越丰富，也成为流畅运行的一种阻碍。综上所述，对APP进行性能优化已成为开发者该有的一种综合素质，也是开发者能够完成高质量应用程序作品的保证。&lt;/p&gt;
 &lt;p&gt;在Android应用优化方面，我们主要从以下4个方面进行优化：&lt;/p&gt;
 &lt;ol&gt;
  &lt;li&gt;稳定（内存溢出、崩溃）&lt;/li&gt;
  &lt;li&gt;流畅（卡顿）&lt;/li&gt;
  &lt;li&gt;耗损（耗电、流量、网络）&lt;/li&gt;
  &lt;li&gt;安装包（APK瘦身）&lt;/li&gt;
&lt;/ol&gt;
 &lt;h2&gt;内存优化&lt;/h2&gt;
 &lt;p&gt;由于Android应用的沙箱机制，每个应用所分配的内存大小是有限度的，内存太低就会触发LMK(Low Memory Killer)机制，进而会出现闪退现象。如果要对内存进行优化，就需要先搞懂java的内存是如何分配和回收的，关于这方面，可以重点参考下面的内容：  &lt;br /&gt;  &lt;a href="https://blog.csdn.net/csdn_aiyang/article/details/72876272" rel="nofollow noreferrer"&gt;Java 垃圾回收器的GC机制，看这一篇就够了 &lt;/a&gt;  &lt;br /&gt;  &lt;a href="https://www.jianshu.com/p/9f5c6f206a84" rel="nofollow noreferrer"&gt;Android 内存泄漏常见案例及分析&lt;/a&gt;  &lt;br /&gt;  &lt;a href="https://www.cnblogs.com/daqiang5566/p/6145671.html" rel="nofollow noreferrer"&gt;Android应用内存泄漏的定位、分析与解决策略&lt;/a&gt;&lt;/p&gt;
 &lt;h3&gt;分析工具&lt;/h3&gt;
 &lt;h4&gt;Memory Monitor 工具&lt;/h4&gt;
 &lt;p&gt;Memory Monitor是Android Studio自带的一个内存监视工具，它可以很好地帮助我们进行内存实时分析。通过点击Android Studio右下角的Memory Monitor标签，打开工具可以看见较浅蓝色代表free的内存，而深色的部分代表使用的内存从内存变换的走势图变换，可以判断关于内存的使用状态，例如当内存持续增高时，可能发生内存泄漏；当内存突然减少时，可能发生GC等。&lt;/p&gt;
 &lt;h4&gt;Memory Analyzer工具&lt;/h4&gt;
 &lt;p&gt;MAT 是一个快速，功能丰富的 Java Heap 分析工具，通过分析 Java 进程的内存快照 HPROF 分析，从众多的对象中分析，快速计算出在内存中对象占用的大小，查看哪些对象不能被垃圾收集器回收，并可以通过视图直观地查看可能造成这种结果的对象。&lt;/p&gt;
 &lt;h4&gt;LeakCanary工具&lt;/h4&gt;
 &lt;p&gt;LeakCanary是一个内存监测工具，该工具是Square公司出品的，所谓Square出品必属精品，LeakCanary的官方地址为  &lt;a href="https://github.com/square/leakcanar" rel="nofollow noreferrer"&gt;https://github.com/square/lea...&lt;/a&gt;，我们可以在Gradle里引用它。&lt;/p&gt;
 &lt;h4&gt;Android Lint 工具&lt;/h4&gt;
 &lt;p&gt;Android Lint 是Android Sutido种集成的一个Android代码提示工具，它可以给布局、代码提供非常强大的帮助。如果在布局文件中写了三层冗余的LinearLayout布局，就会在编辑器右边看到提示。当然这个是一个简单的举例，Lint的功能非常强大，大家应该养成写完代码查看Lint的习惯，这不仅让你及时发现代码种隐藏的一些问题，更能让你养成良好的代码风格，要知道，这些Lint提示可都是Google大牛们汗水合智慧的结晶。&lt;/p&gt;
 &lt;h3&gt;其他建议&lt;/h3&gt;
 &lt;p&gt;在Android应用开发中，影响稳定性的原因很多，比如内存使用不合理、代码异常场景考虑不周全、代码逻辑不合理等，都会对应用的稳定性造成影响。  &lt;br /&gt;其中最常见的两个场景是：Crash 和 ANR，这两个错误将会使得程序无法使用。所以做好Crash监控，把崩溃信息、异常信息收集记录起来，以便后续分析；合理使用主线程处理业务，不要在主线程中做耗时操作，防止ANR程序无响应发生。  &lt;br /&gt;具体可以参考下面的文章链接：  &lt;br /&gt;  &lt;a href="https://blog.csdn.net/zhangbijun1230/article/details/80739233" rel="nofollow noreferrer"&gt;Android系统稳定性问题总结&lt;/a&gt;&lt;/p&gt;
 &lt;h2&gt;交互优化&lt;/h2&gt;
 &lt;p&gt;交互是与用户体验最直接的方面，交互场景大概可以分为四个部分：UI 绘制、应用启动、页面跳转、事件响应。对于上面四个方面，大致可以从以下两个方面来进行优化：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;界面绘制：主要原因是绘制的层级深、页面复杂、刷新不合理，由于这些原因导致卡顿的场景更多出现在 UI 和启动后的初始界面以及跳转到页面的绘制上。&lt;/li&gt;
  &lt;li&gt;数据处理：导致这种卡顿场景的原因是数据处理量太大，一般分为三种情况，一是数据在处理 UI 线程，二是数据处理占用 CPU 高，导致主线程拿不到时间片，三是内存增加导致 GC 频繁，从而引起卡顿。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;我们知道，Android的绘制需要经过onMeasure、onLayout、onDraw等几个步骤，所以布局的层级越深、元素越多、耗时也就越长。还有就是Android 系统每隔 16ms 发出 VSYNC 信号，触发对 UI 进行渲染，如果每次渲染都成功，这样就能够达到流畅的画面所需的 60FPS。如果某个操作花费的时间是 24ms ，系统在得到 VSYNC 信号时就无法正常进行正常渲染，这样就发生了丢帧现象。&lt;/p&gt;
 &lt;p&gt;之所以出现卡顿现象，是因为有两个原因：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;绘制任务太重，绘制一帧内容耗时太长&lt;/li&gt;
  &lt;li&gt;主线程太忙，根据系统传递过来的 VSYNC 信号来时还没准备好数据导致丢帧&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;基于问题产生的原因，我们可以从以下几个方面进行优化：&lt;/p&gt;
 &lt;h4&gt;布局优化&lt;/h4&gt;
 &lt;p&gt;在Android种系统对View进行测量、布局和绘制时，都是通过对View数的遍历来进行操作的。如果一个View数的高度太高就会严重影响测量、布局和绘制的速度。Google也在其API文档中建议View高度不宜哦过10层。现在版本种Google使用RelativeLayout替代LineraLayout作为默认根布局，目的就是降低LineraLayout嵌套产生布局树的高度，从而提高UI渲染的效率。  &lt;br /&gt;在布局优化方面，我们可以从以下几个方面进行优化：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;布局复用，使用   &lt;code&gt;&amp;lt;include&amp;gt;&lt;/code&gt;标签重用layout；&lt;/li&gt;
  &lt;li&gt;提高显示速度，使用   &lt;code&gt;&amp;lt;ViewStub&amp;gt;&lt;/code&gt;延迟View加载；&lt;/li&gt;
  &lt;li&gt;减少层级，使用   &lt;code&gt;&amp;lt;merge&amp;gt;&lt;/code&gt;标签替换父级布局；&lt;/li&gt;
  &lt;li&gt;注意使用wrap_content，会增加measure计算成本；&lt;/li&gt;
  &lt;li&gt;删除控件中无用属性；&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;渲染优化&lt;/h4&gt;
 &lt;p&gt;过度绘制是指在屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次重叠的 UI 结构中，如果不可见的 UI 也在做绘制的操作，就会导致某些像素区域被绘制了多次，从而浪费了多余的 CPU 以及 GPU 资源。我们可以通过开启手机的过渡绘制功能来检测页面是否被过度绘制。&lt;/p&gt;
 &lt;p&gt;为了避免过度绘制，我们可以从以下几个方面进行优化：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;布局上的优化，移除 XML 中非必须的背景，移除 Window 默认的背景、按需显示占位背景图片。&lt;/li&gt;
  &lt;li&gt;自定义View优化，使用 canvas.clipRect()来帮助系统识别那些可见的区域，只有在这个区域内才会被绘制。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;启动优化&lt;/h4&gt;
 &lt;p&gt;应用一般都有闪屏页，优化闪屏页的 UI 布局，可以通过 Profile GPU Rendering 检测丢帧情况。  &lt;br /&gt;也可以通过启动加载逻辑优化。可以采用分布加载、异步加载、延期加载策略来提高应用启动速度。  &lt;br /&gt;数据准备。数据初始化分析，加载数据可以考虑用线程初始化等策略。&lt;/p&gt;
 &lt;h4&gt;刷新优化&lt;/h4&gt;
 &lt;p&gt;Android开发中，通常是异步操作页面的，因此需要可以从刷新优化上来优化应用，主要有两个原则：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;减少刷新次数；&lt;/li&gt;
  &lt;li&gt;缩小刷新区域；&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;动画优化&lt;/h4&gt;
 &lt;p&gt;在实现动画效果时，需要根据不同场景选择合适的动画框架来实现。有些情况下，可以用硬件加速方式来提供流畅度。&lt;/p&gt;
 &lt;h2&gt;耗电优化&lt;/h2&gt;
 &lt;p&gt;在移动设备中，电池的重要性不言而喻，没有电什么都干不成。对于操作系统和设备开发商来说，耗电优化一致没有停止，去追求更长的待机时间，而对于一款应用来说，并不是可以忽略电量使用问题，特别是那些被归为“电池杀手”的应用，最终的结果是被卸载。因此，应用开发者在实现需求的同时，需要尽量减少电量的消耗。&lt;/p&gt;
 &lt;p&gt;在 Android5.0 以前，在应用中测试电量消耗比较麻烦，也不准确，5.0 之后专门引入了一个获取设备上电量消耗信息的 API，即Battery Historian。Battery Historian 是一款由 Google 提供的 Android 系统电量分析工具，和Systrace 一样，是一款图形化数据分析工具，直观地展示出手机的电量消耗过程，通过输入电量分析文件，显示消耗情况，最后提供一些可供参考电量优化的方法。&lt;/p&gt;
 &lt;h3&gt;网络优化&lt;/h3&gt;
 &lt;p&gt;对于网络的优化，可以从以下几个方面着手进行：&lt;/p&gt;
 &lt;h4&gt;图片网络优化&lt;/h4&gt;
 &lt;p&gt;例如，针对网络情况，返回不同的图片数据，一种是高清大图，一种是正常图片，一种是缩略小图。当用户处于wifi下给控件设置高清大图，当4g或者3g模式下加载正常图片，当弱网条件下加载缩略图。&lt;/p&gt;
 &lt;h4&gt;网络数据优化&lt;/h4&gt;
 &lt;p&gt;移动端获取网络数据优化可以从以下几点着手：&lt;/p&gt;
 &lt;ul&gt;  &lt;li&gt;连接复用：节省连接建立时间，如开启 keep-alive。&lt;/li&gt;&lt;/ul&gt;
 &lt;p&gt;对于Android来说默认情况下HttpURLConnection和HttpClient都开启了keep-alive。只是2.2之前HttpURLConnection存在影响连接池的Bug，具体可见：Android HttpURLConnection及HttpClient选择&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;请求合并：即将多个请求合并为一个进行请求，比较常见的就是网页中的CSS Image Sprites。如果某个页面内请求过多，也可以考虑做一定的请求合并。&lt;/li&gt;
  &lt;li&gt;减少请求数据的大小：对于post请求，body可以做gzip压缩的，header也可以做数据压缩。返回数据的body也可以做gzip压缩，body数据体积可以缩小到原来的30%左右。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h4&gt;异常拦截优化&lt;/h4&gt;
 &lt;p&gt;在获取数据的流程中，访问接口和解析数据时都有可能会出错，我们可以通过拦截器在这两层拦截错误。&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;在访问接口时，我们不用设置拦截器，因为一旦出现错误，Retrofit会自动抛出异常。比如，常见请求异常404，500，503等等。&lt;/li&gt;
  &lt;li&gt;在解析数据时，我们设置一个拦截器，判断Result里面的code是否为成功，如果不成功，则要根据与服务器约定好的错误码来抛出对应的异常。比如，token失效，禁用同账号登陆多台设备，缺少参数，参数传递异常等等。&lt;/li&gt;
&lt;/ul&gt;
 &lt;h2&gt;APK瘦身&lt;/h2&gt;
 &lt;p&gt;应用安装包大小对应用使用没有影响，但应用的安装包越大，用户下载的门槛越高，特别是在移动网络情况下，用户在下载应用时，对安装包大小的要求更高，因此，减小安装包大小可以让更多用户愿意下载和体验产品。&lt;/p&gt;
 &lt;p&gt;在Android Studio工具栏里，打开build–&amp;gt;Analyze APK, 选择要分析的APK包 ，可以看到apk的相关信息，如下所示：  &lt;br /&gt;  &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://segmentfault.com/img/remote/1460000019516977?w=1253&amp;h=235" title="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;"&gt;&lt;/img&gt;  &lt;br /&gt;Android的apk主要有以下信息构成：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;assets文件夹。存放一些配置文件、资源文件，assets不会自动生成对应的 ID，而是通过 AssetManager 类的接口获取。&lt;/li&gt;
  &lt;li&gt;res。res 是 resource 的缩写，这个目录存放资源文件，会自动生成对应的 ID 并映射到 .R 文件中，访问直接使用资源ID。&lt;/li&gt;
  &lt;li&gt;META-INF。保存应用的签名信息，签名信息可以验证 APK 文件的完整性。&lt;/li&gt;
  &lt;li&gt;AndroidManifest.xml。这个文件用来描述 Android 应用的配置信息，一些组件的注册信息、可使用权限等。&lt;/li&gt;
  &lt;li&gt;classes.dex。Dalvik 字节码程序，让 Dalvik 虚拟机可执行，一般情况下，Android 应用在打包时通过Android SDK 中的 dx 工具将 Java 字节码转换为 Dalvik 字节码。&lt;/li&gt;
  &lt;li&gt;resources.arsc。记录着资源文件和资源 ID 之间的映射关系，用来根据资源 ID 寻找资源。&lt;/li&gt;
&lt;/ul&gt;
 &lt;p&gt;基于上面的组成部分，那么优化也可以从以下几个方面着手：&lt;/p&gt;
 &lt;ul&gt;
  &lt;li&gt;代码混淆。使用proGuard 代码混淆器工具，它包括压缩、优化、混淆等功能。&lt;/li&gt;
  &lt;li&gt;资源优化。比如使用 Android Lint 删除冗余资源，资源文件最少化等。&lt;/li&gt;
  &lt;li&gt;图片优化。比如利用 AAPT 工具对 PNG 格式的图片做压缩处理，降低图片色彩位数等。&lt;/li&gt;
  &lt;li&gt;避免重复功能的库，使用 WebP图片格式等。&lt;/li&gt;
  &lt;li&gt;插件化，比如功能模块放在服务器上，按需下载，可以减少安装包大小。&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>android</category>
      <guid isPermaLink="true">https://itindex.net/detail/59710-android-%E5%BC%80%E5%8F%91-%E4%BC%98%E5%8C%96</guid>
      <pubDate>Tue, 18 Jun 2019 21:46:52 CST</pubDate>
    </item>
    <item>
      <title>Google 认为禁止华为使用 Android 会危及到美国国家安全</title>
      <link>https://itindex.net/detail/59672-google-%E5%8D%8E%E4%B8%BA-android</link>
      <description>以美国国家安全的名义，Google 告诉特朗普政府它应该继续向华为提供 Android 相关服务。因为出口管制禁令，Google 被禁止授权华为使用 Google Play 等 Android 核心服务。Google 认为这将迫使华为创建 Android 分支版本，不再包含 Google 服务（国行版本就是如此），其中之一是自动扫描恶意程序的  Google Play Protect。这意味着华为向全世界销售的智能手机将运行没有 Google 安全功能的 Android 手机， &lt;a href="https://www.theverge.com/2019/6/7/18656163/google-huawei-android-security-ban-claims" target="_blank"&gt;这将会降低其安全性更容易被入侵&lt;/a&gt;。如果美国人向使用这些手机的人发送敏感信息，无论有没有端对端加密，信息都更容易被窃取。因此 Google 认为美国的国家安全受到了威胁。 &lt;p&gt;  &lt;img height="120" src="https://img.solidot.org//0/446/liiLIZF8Uh6yM.jpg"&gt;&lt;/img&gt;&lt;/p&gt; &lt;div&gt;
  &lt;a href="http://feeds.feedburner.com/~ff/solidot?a=FJizeLnXHho:jy3fKlOTtfY:yIl2AUoC8zA"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/solidot?d=yIl2AUoC8zA"&gt;&lt;/img&gt;&lt;/a&gt;   &lt;a href="http://feeds.feedburner.com/~ff/solidot?a=FJizeLnXHho:jy3fKlOTtfY:7Q72WNTAKBA"&gt;   &lt;img border="0" src="http://feeds.feedburner.com/~ff/solidot?d=7Q72WNTAKBA"&gt;&lt;/img&gt;&lt;/a&gt;
&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/59672-google-%E5%8D%8E%E4%B8%BA-android</guid>
      <pubDate>Fri, 07 Jun 2019 23:40:06 CST</pubDate>
    </item>
    <item>
      <title>Google Glass 企业版第二代发布，支持 Android 移动设备管理</title>
      <link>https://itindex.net/detail/59616-google-glass-%E4%BC%81%E4%B8%9A</link>
      <description>&lt;p&gt;  &lt;img alt="" src="http://img.qdaily.com/article/article_show/20190522074002iHTl19nLxJdtVrDf.jpg?imageMogr2/auto-orient/thumbnail/!640x380r/gravity/Center/crop/640x380/ignore-error/1"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;Google Glass 研发又有了新进展，周一 Google 发布了第二代企业版智能眼镜（Google Glass Enterprise Edition 2），售价为 999 美元。Google Glass 最早于 2013 年推出，当时面向普通消费者。&lt;/p&gt; &lt;p&gt;它利用微型投影仪将计算机生成的图像发送到用户眼中，图像看起来像是浮动在现实世界之中。最终因为原装 Google Glass 价格高昂和内置相机引发的隐私问题，针对消费者的最早 AR 设备宣告失败。&lt;/p&gt; &lt;p&gt;两年后 Google 推出了全新的 Google Glass，它将这款新产品重新定位为面向企业，为外科医生、工厂工人和其他专业人士使用的工具。企业版 Google Glass 升级了原有配置，更好的处理器，更高清的摄像头。如果用户不用来拍摄的话，续航时间能保持在 8 小时左右。第一代企业版的重量基本和传统眼镜相当，并仅在一边眼镜上方安装了小型投影仪。&lt;/p&gt; &lt;p&gt;第二代 Google Glass 和上一代外观设计上没有太大变化，不过依然在充电速度、芯片、镜架和移动设备管理方面进行了升级。&lt;/p&gt; &lt;div&gt;
      &lt;img alt="" src="http://img.qdaily.com/uploads/20190522074104kypXrDmwTbsL7g2l.jpg-w600"&gt;&lt;/img&gt;
&lt;/div&gt; &lt;p&gt;它会在功能更强大的高通 Snapdragon XR1 平台上运行，这一芯片专门针对 AR 和 VR 设计。Google 表示，凭借 XR1 的功能，新款智能眼镜将可以加入“计算机视觉和先进的机器学习功能”。&lt;/p&gt; &lt;p&gt;  &lt;img alt="" src="http://img.qdaily.com/uploads/20160725026790Msgaji5TilWhj7z4.jpg-w600"&gt;&lt;/img&gt;&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;Google 还和史密斯光学公司合作开发出新的镜架，看起来和普通眼镜差不多，这意味着它相对其他智能眼镜而言，佩戴起来更为轻便。此外，电池续航时间和其他组件也有所改进。&lt;/p&gt; &lt;p&gt;这款新眼镜是基于 Android 平台开发，因此用户可以在移动设备上配置应用和服务。企业可以更便于分发给员工使用，并且能够管理员工的使用方式。一个例子是当设备遗失时，就会被设置为禁用状态。&lt;/p&gt; &lt;p&gt;第二代 Google Glass 企业版面向物流、制造和医疗等服务行业，主要客户包括 H.B 富勒、萨特健康、德国邮政 DHL 集团和 AGCO 等 50 多家公司。根据业务和 Google 签订合同的公司，他们能以更低的价格购买新产品。&lt;/p&gt; &lt;p&gt;之前有传言认为这款产品对标产品正是微软 HoLolens 2，后者于今年 2 月份推出，3500 美元的售价比 Google 新产品高出不少。不过现在看起来 Google Glass 企业版第二代似乎竞争力低一点，毕竟后者可以通过双眼查看，而且因为美国军方这个大订单，还加入了夜视仪功能。&lt;/p&gt; &lt;p&gt;去年 11 月，微软拿下美国陆军价值 4.8 亿的合同，包括首批订购 2500 个 HoloLens 眼镜，最终需求预计会达到 10 万个。而在微软推出 HoloLens 的 3 年里，本身也就卖掉 5 万个智能眼镜。&lt;/p&gt; &lt;p&gt;Google 没有透露初代 Google Glass 企业版的销售情况，来自独立产品分析机构   &lt;a href="http://Forrester Research" rel="nofollow" target="_blank"&gt;Forrester Research &lt;/a&gt;的预计是到 2025 年它将贡献 20 亿美元销售额。&lt;/p&gt; &lt;p&gt;尽管新款 Google Glass 进行了不少升级，研发团队已经从 Google X 调整到AR/VR 部门，瞄准的消费对象还是企业和专业人士。普通消费者如果需要一款 AR 设备，面对 999 美元的定价，考虑购买的可能性并不算大。&lt;/p&gt; &lt;p&gt;题图来自：Google&lt;/p&gt; &lt;div&gt;&lt;/div&gt; &lt;div&gt;
      &lt;img alt="" src="http://img.qdaily.com/uploads/20171222175448DuIFGKx5VU4XcYEf.png-w600"&gt;&lt;/img&gt;
&lt;/div&gt; &lt;p&gt;我们做了一个壁纸应用，给你的手机加点好奇心。去 App 商店搜   &lt;a href="http://m.notch.qdaily.com/mobile/downloads.html"&gt;好奇怪&lt;/a&gt; 下载吧。  &lt;br /&gt;&lt;/p&gt;
      &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/59616-google-glass-%E4%BC%81%E4%B8%9A</guid>
      <pubDate>Thu, 23 May 2019 01:34:43 CST</pubDate>
    </item>
    <item>
      <title>福利 | Android 将迎海量更新、Pixel 家族再添新成员：Google I/O 2019 前瞻</title>
      <link>https://itindex.net/detail/59550-%E7%A6%8F%E5%88%A9-android-%E8%BF%8E%E6%B5%B7</link>
      <description>&lt;p&gt;今年的 Google I/O 大会已近在眼前，名曰「开发者大会」，这场一年一度的盛事对用户、开发者乃至整个 Android 生态的走向都有着重要的指导意义。那么这次的 I/O 大会又会有哪些可能的看点呢？&lt;/p&gt; &lt;h2&gt;&lt;/h2&gt; &lt;h2&gt;如何科学地关注 I/O 大会&lt;/h2&gt; &lt;p&gt;今年 I/O 大会的召开地点依然定在 Google 加州总部山景城海岸线圆形剧场，大会召开时间为当地时间 5 月 7 日至 9 日。而整个大会的「开胃菜」、同时也是大多数用户最为关注的   &lt;strong&gt;Google Keynote 则会在北京时间 5 月 8 日凌晨 1:00 拉开帷幕&lt;/strong&gt;。&lt;/p&gt; &lt;p&gt;如果你的时间和精力都比较充足，届时可以前往活动的官方 YouTube 直播频道或通过国内频道进行收看（当然，你也可以关注少数派当天晚些时候放出的 Keynote 回顾）。&lt;/p&gt; &lt;img alt="" src="https://cdn.sspai.com/2019/05/06/cd8016859153e5ab8a99a60bb5012db3.png"&gt;&lt;/img&gt;国内转播来源 | 图：谷歌开发者 &lt;p&gt;而如果你对 I/O 大会除 Keynote 以外的其他会议日程感兴趣，Google I/O 2019 配套应用就是必不可少的跟会神器了。&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;&lt;/p&gt; &lt;p&gt;今年 Google 为 I/O 2019 配套应用带来了不少新东西，我们也可以提前在这款应用上窥见 Android 设计语言上的新动向 —— 去年是 Material Design 2，今年则是暗色模式（Dark Mode）。在 Google I/O 2019 应用的设置中新增了一个 选择主题 的功能，这个功能在 Android 9 上提供了「明/暗/遵循省电模式」三种主题策略，在 Android Q Beta 2 中则是「明/暗/系统默认」。结合最近越来越多的 Google 应用都加入了暗色模式这一事实，显然 Android Q 对全局暗色模式的支持也已经是板上钉钉的事了。&lt;/p&gt; &lt;img alt="" src="https://cdn.sspai.com/2019/05/06/d5fc313c9b3ceba823ae042d25f4054e.png"&gt;&lt;/img&gt;I/O 2019 配套应用 &lt;p&gt;当然，Android 在设计上的变化我们下文再做展开，回到「跟会」这件事情上，对大多数不能到现场参加 I/O 大会的朋友来说，Google I/O 2019 其实也是即时跟进大会日程和关键动态的利器。&lt;/p&gt; &lt;p&gt;更新后的配套应用在往年基础上加入了几项贴心又实用的小功能：比如首页的 Announcement 小卡片，这里会第一时间推送一些大会上的关键信息发布动态，就算没时间看完所有场次的直播，我们也能在 Google I/O 2019 应用首页的公告更新中看到当下发布会的亮点内容；另外议程详情界面这一次还加入了针对单个活动的日历通知功能，如果你对某几个方面的产品特别感兴趣，不妨打开 Google I/O 2019 大致浏览或搜索一下会议日程、找到你想重点关注的那几场，然后点击日历小图标加入日历提醒即可。&lt;/p&gt; &lt;h2&gt;&lt;/h2&gt; &lt;h2&gt;今年的 I/O 有什么看点&lt;/h2&gt; &lt;p&gt;那么就大会本身来说，我们在今年的 I/O 上又会看到哪些新东西呢？&lt;/p&gt; &lt;h3&gt;&lt;/h3&gt; &lt;h3&gt;Android Q 的新改动&lt;/h3&gt; &lt;p&gt;根据 Google 在 Android Q Beta 1 放出时公布的时间线和往年惯例，本次 I/O 大会势必会放出 Android Q 的第三个 Beta 测试版本。 &lt;/p&gt; &lt;img alt="" src="https://cdn.sspai.com/2019/05/06/af38c858a0820bf5f2e2496b0bbc9acd.png"&gt;&lt;/img&gt;Android Q 测试时间线 &lt;p&gt;在现有的测试版本中，Google 已经带来了不少可圈可点的新特性。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;比如更加严格的隐私和权限管理&lt;/strong&gt;。Android Q Beta 1 在以往「允许/拒绝」式简单授权的基础上新增了「仅使用时允许」这一权限控制维度，同时在首次运行那些没有适配运行时权限（runtime permission）的老应用时首先弹出权限控制窗口来方便用户进行管理。&lt;/p&gt; &lt;img alt="" src="https://cdn.sspai.com/2019/05/06/3e62d6e4c2ee8924cc3db6c6ab3117d8.png" width="450"&gt;&lt;/img&gt;权限管理会有更多新变化吗？ &lt;p&gt;另外，个性化广告推荐、用量数据分享等涉及到用户隐私数据的设置项目在 Android Q 中也被提到了更加显眼的位置。显然，Google 在 Android Q 上想要讲的如何加强隐私和权限控制的故事，5 月 8 日凌晨放出的 Beta 3，或许也会对这部分改动进行更加清晰的阐释和完善。&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;再比如对 Android 9 现有手势操作的改进&lt;/strong&gt;。很多用户都对 Android 9 引入的「药丸」Home 键手势颇有微词，Google 去年也承诺将在往后的版本中继续对原生 Android 的手势功能进行打磨。&lt;/p&gt; &lt;p&gt;在 Android Q 中我们会看到新的手势系统吗？尽管在目前的测试版本中我们在这方面几乎看不到任何变化，但答案还是很有可能的：此前 XDA 的开发者已经通过 adb 指令的方式开启过好几种隐藏于当前测试版本当中的手势操作，包括完全取消「返回」按键的纯 Home 键手势方案、类似 iOS 的「底部手势+侧边缘划动返回」方案等等。&lt;/p&gt; &lt;img alt="" src="https://cdn.sspai.com/2019/05/06/2cca528ff9d3af2a59008b8e8a6ff5f3.gif"&gt;&lt;/img&gt;其中一个正在测试的手势方案 | 图：XDA &lt;p&gt;Android Q 最终会采用哪一种手势方案，I/O 大会或许会给出答案。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;当然，在用户和开发者社区中呼声同样不小的全局暗色模式也是 Android Q 的一大亮点更新&lt;/strong&gt;。去年年底 Google 在   &lt;a href="https://sspai.com/post/48333" target="_blank"&gt;开发者峰会&lt;/a&gt; 上首次公开承认了 OLED 屏幕技术、暗色主题和电量消耗这三者之间的微妙关联，为 Android 系统和白得耀眼的 Material Design 2 带来暗色模式的进程就此开启。&lt;/p&gt; &lt;img alt="" src="https://cdn.sspai.com/2019/05/06/a64e1ddc89c1f972d2c0cb7bb7b87dda.png"&gt;&lt;/img&gt;Google 自家应用在明/暗主题下的耗电对比 &lt;p&gt;虽然目前 Google 还没有在 Beta 2 中为 Android Q 加入正式的暗色模式开关，考虑到 Google 大部分自家应用都已加入了暗色主题，这一功能的跳票可能性很小，两天后的 I/O 大会上，我们应该能够看到全局暗色模式的正式亮相。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;  &lt;strong&gt;有些遗憾的是，早前我们颇为看好的&lt;/strong&gt;  &lt;strong&gt;分区存储（Scoped Storage）特性要&lt;/strong&gt;  &lt;strong&gt;不幸「跳票」了&lt;/strong&gt;。从 Beta 3 开始，应用只要没有面向 Q 适配，就将默认采用与旧版本 Android 系统相同的存储机制，而非此前我们所   &lt;a href="https://sspai.com/post/53888" target="_blank"&gt;介绍&lt;/a&gt; 过的「沙盒」处理。&lt;/p&gt; &lt;img alt="" src="https://cdn.sspai.com/2019/05/06/c063226186ad633e778150fab8c02e7b.png" width="400"&gt;&lt;/img&gt;「沙盒」机制作用下的存储空间更加干净 &lt;p&gt;从当前的测试版本来看，这一特性延迟上线其实也是可以理解的：分区存储机制会对那些没来得及适配的应用造成不小的冲击，导致包括用户数据丢失、基础功能失灵等一系列影响，那些存储行为不规范的应用在这一机制下甚至无法正常运行；另一方面，分区存储机制和早期的存储重定向应用一样，也存在着影响性能的   &lt;a href="https://issuetracker.google.com/issues/130261278" target="_blank"&gt;bug&lt;/a&gt;，Google 可能还需要一些时间来好好打磨。&lt;/p&gt; &lt;img alt="" src="https://cdn.sspai.com/2019/05/06/886b8623703ee3908462ebb6fc33aed8.png"&gt;&lt;/img&gt;气泡通知与改进后的分享菜单 &lt;p&gt;除了上述特性，Android Q 还将加强了对单色摄像头的支持、引入单独存储图片深度数据的DDF（动态深度格式）架构同时默认开启最新的 TLS 1.3 协议。此前我们介绍过的气泡通知、分享菜单改进等特性在 Beta 3 版本中应该也会得到进一步完善。  &lt;br /&gt;&lt;/p&gt; &lt;h3&gt;&lt;/h3&gt; &lt;h3&gt;Pixel 家族的新成员&lt;/h3&gt; &lt;p&gt;从去年年底开始，有关入门版 Pixel（彼时还叫「Pixel Lite」）的流言便时有传出，去年曾   &lt;a href="https://rozetked.me/articles/2732-fotografii-google-pixel-3xl" target="_blank"&gt;准确爆料 &lt;/a&gt;过 Pixel 3 XL 的俄罗斯网站   &lt;em&gt;Rozetked.me &lt;/em&gt;更直接   &lt;a href="https://rozetked.me/articles/3751-eksklyuziv-pixel-3-lite-sargo-v-nashih-rukah-s-jack-3-5-i-snapdragon-670" target="_blank"&gt;放&lt;/a&gt;  &lt;a href="https://rozetked.me/articles/3751-eksklyuziv-pixel-3-lite-sargo-v-nashih-rukah-s-jack-3-5-i-snapdragon-670" target="_blank"&gt;出&lt;/a&gt; 过入门级 Pixel 的开发机实物照片。&lt;/p&gt; &lt;p&gt;而后随着 Android Q 测试进程的不断推进，关于入门版 Pixel 新机的曝光也不断浮出水面。从外媒最近公布的信息来看，Google 可能会在 I/O 大会上公布 Pixel 3a/3a XL 两款入门机型，Pixel 3a 在外观上具有与 Pixel 3 相似的设计风格，但相较后者还是有很多降级之处：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;机身背面依旧是单摄像头、撞色设计，但不再使用金属和玻璃的拼接工艺，而采用了纯塑料材质。&lt;/li&gt;&lt;/ul&gt; &lt;ul&gt;  &lt;li&gt;屏幕的边框稍稍有些变宽。&lt;/li&gt;&lt;/ul&gt; &lt;ul&gt;  &lt;li&gt;前置的下扬声器也挪到了机身底部，顶部的听筒到底是不是兼任扬声器还不得而知。&lt;/li&gt;&lt;/ul&gt; &lt;ul&gt;  &lt;li&gt;机身顶部多了 3.5 mm 的耳机插孔，不知是因为机身空间有多还是阉割了防水特性。&lt;/li&gt;&lt;/ul&gt; &lt;img alt="" src="https://cdn.sspai.com/2019/05/06/e69052e21329aebe825bf821374146b4.jpg"&gt;&lt;/img&gt;图：Onleaks x 91Mobiles &lt;p&gt;最近曝光的零售   &lt;a href="https://youtu.be/dp-y3GJND_0" target="_blank"&gt;包装盒&lt;/a&gt; 也提供了更为丰富的细节：本次新机除了经典的黑白二色，还多了一款名为 Purple-ish 的淡紫配色。&lt;/p&gt; &lt;img alt="" src="https://cdn.sspai.com/2019/05/06/a07760530aa517ca49332618c3e8245e.png"&gt;&lt;/img&gt;Pixel 3a XL 零售包装 | 图：AndroidPolice &lt;p&gt;结合 Google Play 开发者控制台泄露出的   &lt;a href="https://9to5google.com/2019/04/05/pixel-3a-reveal-google-play/" target="_blank"&gt;配置&lt;/a&gt;  &lt;a href="https://9to5google.com/2019/04/05/pixel-3a-reveal-google-play/" target="_blank"&gt;信息&lt;/a&gt;，两款新机的硬件参数整理如下：  &lt;br /&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;Pixel 3a&lt;/strong&gt;：2220×1080 5.56英寸 LG OLED显示屏、高通骁龙 670 处理器、4GB RAM、 32G起步的存储空间、与 Pixel 3 相同的前置 800万像素镜头和 1200万像素后置镜头、电池 2915mAh（约 3000 mAh左右）、 USB C配 18W快充、3.5mm 耳机孔、Active Edge、Titan M安全芯片以及 eSIM 支持。&lt;/li&gt;&lt;/ul&gt; &lt;ul&gt;  &lt;li&gt;   &lt;strong&gt;Pixel 3a XL&lt;/strong&gt;：配置与 Pixel 3a 大体上一致，但屏幕尺寸为 6 英寸，分辨率 2160×1080，存储空间 64GB起步，处理器疑似升级为高通骁龙 710，电池也拓展到 3700mAh 左右。&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;在最让人纠结的价格环节，Google 以往在 Pixel 旗舰机型上给出的定价往往让人望而却步，此次作为入门版的 Pixel 3a/3a XL 则下探到 399 美元/479 美元起售，折合人民币约 2690  元/3230 元。&lt;/p&gt; &lt;p&gt;原汁原味的 Android 系统，经典拼接设计的个性化机身，虽然配置略降但摄像头表现毫不缩水的 Pixel 中端机，能够打开你的钱包吗？&lt;/p&gt; &lt;h3&gt;&lt;/h3&gt; &lt;h3&gt;还有什么值得期待&lt;/h3&gt; &lt;p&gt;除了新系统和新手机，你还希望在 I/O 大会上看到哪些新东西呢？&lt;/p&gt; &lt;p&gt;就我个人而言，  &lt;strong&gt;Google Assistant 的官方简体中文支持&lt;/strong&gt;应该是愿望清单的头一项。就在不久前 Google 为 iOS 版 Google Assistant 应用推送了简体中文支持，少数派也第一时间进行了   &lt;a href="https://sspai.com/post/53796" target="_blank"&gt;评测&lt;/a&gt;。&lt;/p&gt; &lt;img alt="" src="https://cdn.sspai.com/2019/05/06/472e482575cd228abd3878609c397f6d.png"&gt;&lt;/img&gt;iOS 已经用上了简体中文版 Google 助理 &lt;p&gt;无论从功能还是从本地化程度来说，简体中文下的 Google Assistant 都已经相当出色，要是本次 I/O 大会 Google 能向 Android 和其他硬件平台（包括 Google Home 系列产品）全面开放简体中文支持，势必又会为我们开启一个崭新的世界。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;此前传闻中 Google 或许会  &lt;strong&gt;通过 Play 应用商店来推送 Android 系统更新&lt;/strong&gt;的消息也格外值得我们关注：当下已经有很多包括 Google 自己在内的 OEM 厂商选择即将系统内置应用放在 Play 应用商店中更新，因为这样做的好处很明显，相较系统而言更新迭代需求更频繁的应用显然不适合与系统更新绑定在一起。&lt;/p&gt; &lt;p&gt;而常年在安全漏洞与安全更新补丁的「拉锯战」中经受洗礼的 Google，这一次似乎还想进一步让 Android 系统的更新也彻底摆脱周期和规律的束缚：外媒 9to5Google 不仅通过对 Play 应用商店的拆包挖掘到了通过 Play 应用商店进行 Android 系统更新的相关字符串，此前也已经有 Reddit 网友分享了它们通过 Play 应用商店进行 Android Q 测试版本更新的   &lt;a href="https://www.reddit.com/r/GooglePixel/comments/bdjiok/just_got_an_update_on_my_pixel_3_like_updating/" target="_blank"&gt;经历&lt;/a&gt;。&lt;/p&gt; &lt;img alt="" src="https://cdn.sspai.com/2019/05/06/bd3c58d3c6c89fdd54990520f782b82d.png" width="400"&gt;&lt;/img&gt;通过应用商店更新 Android 系统 | 图：Reddit &lt;p&gt;其他 OEM 厂商是否可以通过 Play 应用商店推送自家的系统更新呢？在更多围绕这项技术的细节正式披露前还很难说，一切还是要等到 I/O 大会见分晓。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;最后，根据 9to5Google 编辑早前对 Fuchsia 代码的挖掘成果，  &lt;strong&gt;Google 很有可能会在本次 I/O 大会上发布一款智能家居新品 Nest Hub Max&lt;/strong&gt;。&lt;/p&gt; &lt;img alt="" src="https://cdn.sspai.com/2019/05/06/d326fa62be8b5920afb89a94057f0d1d.jpg"&gt;&lt;/img&gt;早前 Google Store 官网意外上线的 Nest Home Hub 页面 &lt;p&gt;综合代码信息和早前意外上线 Google Store 官网的页面来看，Nest Hub Max 将会是一款搭载 2GB 运存、10.1 英寸 LCD 显示屏和索尼 IMX 277「Nest」相机的智能显示设备。  &lt;br /&gt;&lt;/p&gt; &lt;p&gt;不过这款产品的命名显然更加有趣 —— Nest 往后会成为类似 Pixel、但专注智能家居领域的独立子品牌吗？如果不是，这条产品线与当前的 Google Home 系列产品又将各自扮演怎样的角色？&lt;/p&gt; &lt;h2&gt;&lt;/h2&gt; &lt;h2&gt;小结&lt;/h2&gt; &lt;p&gt;欢迎你在评论区说说：本次 Google I/O，你最期待哪个软件/硬件的发布？并简单说一说原因。我们将从中选择 5 位幸运儿各送出由 Google 官方提供的纪念 T 恤一件。&lt;/p&gt; &lt;img alt="" src="https://cdn.sspai.com/2019/05/06/9c6deea655b2eff5e4342e3e491d9e3d.jpg" width="400"&gt;&lt;/img&gt;夏天到了，把 Google 穿在身上当一个骄傲的谷粉吧！ &lt;p&gt;感谢 Google 官方  &lt;a href="https://weibo.com/googlev?profile_ftype=1&amp;is_all=1#_0" target="_blank"&gt; @Google 黑板报&lt;/a&gt; 提供的奖品，除了少数派，你也可以关注   &lt;a href="https://weibo.com/googlev?profile_ftype=1&amp;is_all=1#_0" target="_blank"&gt;@Google 黑板报&lt;/a&gt; 以及微博   &lt;a href="https://s.weibo.com/weibo/%23%E8%B0%B7%E6%AD%8Ci%2Fo%E5%A4%A7%E4%BC%9A%23" target="_blank"&gt;#谷歌I/O大会#&lt;/a&gt; 话题获取更多关于 Google I/O 2019 的最新动态。&lt;/p&gt; &lt;p&gt;本文由 @  &lt;a href="https://sspai.com/user/642980/updates" target="_blank"&gt;路中南&lt;/a&gt; 和 @  &lt;a href="https://sspai.com/user/696946/updates" target="_blank"&gt;Clyde&lt;/a&gt; 联合撰写。&lt;/p&gt; &lt;div&gt;  &lt;hr&gt;&lt;/hr&gt;&lt;/div&gt; &lt;p&gt;  &lt;strong&gt;关联阅读：&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://sspai.com/post/53269" target="_blank"&gt;Android Q 第一个 Beta 版发布，这是值得你关注的 6 大变化 | 具透&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://sspai.com/post/53888" target="_blank"&gt;「沙箱」默认开启，Pixel 3 双卡双待……Android Q Beta 2 都有这些新变化 | 具透&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://sspai.com/post/53518" target="_blank"&gt;无需第三方应用就能管好流氓应用，这可能是 Android Q 最重要的新功能 | 具透&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;  &lt;strong&gt;参考链接：&lt;/strong&gt;&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;   &lt;a href="https://wx.mut.one/MEfV0uqR" target="_blank"&gt;观看 Google I/O‘19 的正确姿势 (°∀°)ﾉ&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://9to5google.com/2019/05/03/google-io-2019-expectations/" target="_blank"&gt;What to expect at Google I/O 2019: Pixel 3a, Nest Hub Max, Android 10 Q, Assistant, and more&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://9to5google.com/2019/04/05/pixel-3a-nest-hub-max-google-store/" target="_blank"&gt;Pixel 3a, Nest Hub Max, and much more appear on refreshed Google Store&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://9to5google.com/2018/11/16/alleged-pixel-3-lite-leaked-images/" target="_blank"&gt;Alleged photos of Pixel 3 Lite aka ‘Sargo’ leak w/ headphone jack, Snapdragon 670 [Gallery]&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.xda-developers.com/google-pixel-3a-pixel-3a-xl-names-revealed/" target="_blank"&gt;The mid-range Google Pixels may launch as the Google Pixel 3a and Pixel 3a XL&lt;/a&gt;&lt;/li&gt;  &lt;li&gt;   &lt;a href="https://www.androidpolice.com/2019/05/01/photos-of-pixel-3a-packaging-leak-possible-us-price-points-rumored/" target="_blank"&gt;Photos of Pixel 3a packaging leak, possible US price points rumored&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt; &lt;p&gt;&amp;gt; 下载少数派   &lt;a href="https://sspai.com/page/client"&gt;客户端&lt;/a&gt;、关注   &lt;a href="http://sspai.com/s/KEPQ"&gt;少数派公众号&lt;/a&gt; ，第一时间了解科技圈的新鲜事 &lt;/p&gt; &lt;p&gt;&amp;gt; 特惠、好用的硬件产品，尽在   &lt;a href="https://sspai.com/post/https-//shop549593764.taobao.com/?spm=a230r.7195193.1997079397.2.2ddc7e0bPqKQHc"&gt;少数派 sspai 官方店铺 &lt;/a&gt;&lt;/p&gt; &lt;p&gt;  &lt;br /&gt;&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/59550-%E7%A6%8F%E5%88%A9-android-%E8%BF%8E%E6%B5%B7</guid>
      <pubDate>Mon, 06 May 2019 16:09:46 CST</pubDate>
    </item>
    <item>
      <title>Android 轻松解决内存泄漏</title>
      <link>https://itindex.net/detail/59541-android-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F</link>
      <description>&lt;div&gt;  &lt;h2&gt;基础知识&lt;/h2&gt;  &lt;h3&gt;Java 的内存分配简述&lt;/h3&gt;  &lt;div&gt;   &lt;div&gt;    &lt;div&gt;&lt;/div&gt;    &lt;div&gt;     &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;   &lt;div&gt;image.png&lt;/div&gt;&lt;/div&gt;  &lt;ul&gt;   &lt;li&gt;方法区（non-heap）：编译时就分配好，在程序整个运行期间都存在。它主要存放静态数据和常量；&lt;/li&gt;   &lt;li&gt;栈区：当方法执行时，会在栈区内存中创建方法体内部的局部变量，方法结束后自动释放内存；&lt;/li&gt;   &lt;li&gt;堆区（heap）：通常用来存放 new 出来的对象。由 GC 负责回收。&lt;/li&gt;&lt;/ul&gt;  &lt;h3&gt;Java四种不同的引用类型&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;强引用（Strong Reference）:JVM 宁愿抛出 OOM，也不会让 GC 回收存在强引用的对象。&lt;/li&gt;   &lt;li&gt;软引用（Soft Reference） ：    &lt;code&gt;一个对象只具有软引用，在内存不足时&lt;/code&gt;，这个对象才会被 GC 回收。&lt;/li&gt;   &lt;li&gt;弱引用（weak Reference）：    &lt;code&gt;在 GC 时，如果一个对象只存在弱引用，那么它将会被回收&lt;/code&gt;。&lt;/li&gt;   &lt;li&gt;虚引用（Phantom Reference）：任何时候都可以被 GC 回收，当垃圾回收器准备回收一个对象时，如果发现它还有虚引用，就会在回收对象的内存之前，把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否存在该对象的虚引用，来了解这个对象是否将要被回收。可以用来作为 GC 回收 Object 的标志。&lt;/li&gt;&lt;/ul&gt;  &lt;blockquote&gt;   &lt;p&gt;与 Android 中的差异：在 2.3 以后版本中，即使内存够用，Android 系统会优先将 SoftReference 的对象提前回收掉, 其他和 Java 中是一样的。&lt;/p&gt;   &lt;p&gt;因此谷歌官方建议用    &lt;code&gt;LruCache&lt;/code&gt;(least recentlly use 最少最近使用算法)。会将内存控制在一定的大小内, 超出最大值时会自动回收, 这个最大值开发者自己定。&lt;/p&gt;&lt;/blockquote&gt;  &lt;h2&gt;什么是内存泄漏？&lt;/h2&gt;  &lt;ul&gt;   &lt;li&gt;对于 C++ 来说，内存泄漏就是 new 出来的对象没有 delete，俗称野指针；&lt;/li&gt;   &lt;li&gt;而对于 java 而言，就是存放在堆上的 Object 无法被 GC 正常回收。&lt;/li&gt;&lt;/ul&gt;  &lt;h2&gt;内存泄漏根本原因&lt;/h2&gt;  &lt;p&gt;   &lt;code&gt;长生命周期的对象&lt;/code&gt;持有   &lt;code&gt;短生命周期对象**强/软引用**&lt;/code&gt;,导致本应该被回收的短生命周期的对象却无法被正常回收。&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;例如在单例模式中，我们常常在获取单例对象时需要传一个 Context 。单例对象是一个长生命周期的对象（应用程序结束时才终结），而如果我们传递的是某一个 Activity 作为 context,那么这个 Activity 就会因为引用被持有而无法销毁，从而导致内存泄漏。&lt;/p&gt;&lt;/blockquote&gt;  &lt;h2&gt;内存泄漏的危害&lt;/h2&gt;  &lt;ul&gt;   &lt;li&gt;运行性能的问题: Android在运行的时候，如果内存泄漏将导致其他组件可用的内存变少，一方面会使得GC的频率加剧，在发生GC的时候，所有进程都必须进行等待，GC的频率越多，从而用户越容易感知到卡顿。另一方面，内存变少，将可能使得系统会额外分配给你一些内存，而影响整个系统的运行状况。&lt;/li&gt;   &lt;li&gt;运行崩溃问题: 内存泄露是内存溢出(OOM)的重要原因之一，会导致 Crash。如果应用程序在消耗光了所有的可用堆空间，那么再试图在堆上分配新对象时就会引起 OOM(Out Of Memory Error) 异常，此时应用程序就会崩溃退出。&lt;/li&gt;&lt;/ul&gt;  &lt;h2&gt;内存泄漏的典型案例&lt;/h2&gt;  &lt;h3&gt;永远的单例（Singleton）&lt;/h3&gt;  &lt;p&gt;由于单例模式的静态特性，使得它的生命周期和我们的应用一样长，一不小心让单例无限制的持有 Activity 的强引用就会导致内存泄漏。&lt;/p&gt;  &lt;h4&gt;解决方案&lt;/h4&gt;  &lt;ul&gt;   &lt;li&gt;把传入的 Context 改为同应用生命周期一样长的 Application 中的 Context。&lt;/li&gt;   &lt;li&gt;通过重写 Application，提供 getContext 方法,那样就不需要在获取单例时传入 context。&lt;/li&gt;&lt;/ul&gt;  &lt;pre&gt;   &lt;code&gt;public class BaseApplication extends Application{
    private static ApplicationContext sContext;
    @Override
    public void onCreate(){
        super.onCreate();
        sContext = getApplicationContext();
    }
    public static Context getApplicationContext(){
        return sContext;
    }
}&lt;/code&gt;&lt;/pre&gt;  &lt;h3&gt;Handler引发的内存泄漏&lt;/h3&gt;  &lt;p&gt;由于 Handler 属于 TLS（Thread Local Storage）变量，导致它的生命周期和 Activity 不一致。因此通过 Handler 来更新 UI 一般很难保证跟 View 或者 Activity 的生命周期一致，故很容易导致无法正确释放。&lt;/p&gt;  &lt;p&gt;例如：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;public class HandlerBadActivity extends AppCompatActivity {
    private final Handler handler = new Handler(){//非静态内部类，持有外部类的强引用
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_bad);
        // 延迟 5min 发送一个消息
        handler.postDelayed(new Runnable() {
        //内部会将该 Runable 封装为一个 Message 对象，同时将 Message.target 赋值为 handler
            @Override
            public void run() {
                //do something
            }
        }, 1000 * 60 * 5);
        this.finish();
    }
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;上面的代码中发送了了一个延时 5 分钟执行的 Message，当该 Activity 退出的时候，延时任务（Message）还在主线程的 MessageQueue 中等待，此时的   &lt;strong&gt;    &lt;code&gt;Message 持有 Handler 的强引用&lt;/code&gt;&lt;/strong&gt;（创建时通过 Message.target 进行指定），并且由于   &lt;strong&gt;    &lt;code&gt;Handler 是 HandlerBadActivity 的非静态内部类&lt;/code&gt;&lt;/strong&gt;，所以   &lt;strong&gt;    &lt;code&gt;Handler 会持有一个指向 HandlerBadActivity 的强引用&lt;/code&gt;&lt;/strong&gt;，所以虽然此时 HandlerBadActivity 调用了 finish 也无法进行内存回收，造成内存泄漏。&lt;/p&gt;  &lt;h4&gt;解决方法&lt;/h4&gt;  &lt;p&gt;   &lt;code&gt;将 Handler 声明为静态内部类&lt;/code&gt;，但是要注意   &lt;code&gt;**如果用到 Context 等外部类的 非static 对象，还是应该使用 ApplicationContext 或者通过弱引用来持有这些外部对象**&lt;/code&gt;。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;public class HandlerGoodActivity extends AppCompatActivity {
    private static final class MyHandler extends Handler{//声明为静态内部类（避免持有外部类的强引用）
        private final WeakReference&amp;lt;HandlerGoodActivity&amp;gt; mActivity;
        public MyHandler(HandlerGoodActivity activity){
            this.mActivity = new WeakReference&amp;lt;HandlerGoodActivity&amp;gt;(activity);//使用弱引用
        }
        @Override
        public void handleMessage(Message msg) {
            HandlerGoodActivity activity = mActivity.get();
            if (activity == null || activity.isFinishing() || activity.isDestroyed()) {//判断 activity 是否为空，以及是否正在被销毁、或者已经被销毁
              removeCallbacksAndMessages(null);
              return;
            }
            // do something
        }
    }
    private final MyHandler myHandler = new MyHandler(this);
}&lt;/code&gt;&lt;/pre&gt;  &lt;h3&gt;慎用 static 成员变量&lt;/h3&gt;  &lt;p&gt;   &lt;code&gt;static 修饰的变量位于内存的方法区，其生命周期与 App 的生命周期一致&lt;/code&gt;。 这必然会导致一系列问题，如果你的 app 进程设计上是长驻内存的，那即使 app 切到后台，这部分内存也不会被释放。&lt;/p&gt;  &lt;h4&gt;解决方法&lt;/h4&gt;  &lt;p&gt;不要在类初始化时初始化静态成员，也就是可以考虑懒加载。架构设计上要思考是否真的有必要这样做，尽量避免。如果架构需要这么设计，那么此对象的生命周期你有责任管理起来。&lt;/p&gt;  &lt;p&gt;当然，Application 的 context 不是万能的，所以也不能随便乱用，对于有些地方则必须使用 Activity 的 Context，对于Application，Service，Activity三者的Context的应用场景如下：&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;th&gt;功能&lt;/th&gt;    &lt;th&gt;Application&lt;/th&gt;    &lt;th&gt;Service&lt;/th&gt;    &lt;th&gt;Activity&lt;/th&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;Start an Activity&lt;/td&gt;    &lt;td&gt;NO1&lt;/td&gt;    &lt;td&gt;NO1&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;Show a Dialog&lt;/td&gt;    &lt;td&gt;NO&lt;/td&gt;    &lt;td&gt;NO&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;Layout Inflation&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;Start an Service&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;Bind an Service&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;Send a Broadcast&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;Register BroadcastReceiver&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;Load Resource Values&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;    &lt;td&gt;YES&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;blockquote&gt;   &lt;ul&gt;    &lt;li&gt;NO1 表示 Application 和 Service 可以启动一个 Activity，不过     &lt;code&gt;需要创建一个新的 task 任务队列&lt;/code&gt;。&lt;/li&gt;    &lt;li&gt;对于 Dialog 而言，只有在 Activity 中才能创建。&lt;/li&gt;&lt;/ul&gt;&lt;/blockquote&gt;  &lt;h3&gt;使用系统服务引发的内存泄漏&lt;/h3&gt;  &lt;p&gt;为了方便我们使用一些常见的系统服务，Activity 做了一些封装。比如说，可以通过   &lt;code&gt;getPackageManager&lt;/code&gt;在 Activtiy 中获取   &lt;code&gt;PackageManagerService&lt;/code&gt;，但是，里面实际上调用了 Activity 对应的 ContextImpl 中的 getPackageManager 方法&lt;/p&gt;  &lt;p&gt;   &lt;code&gt;ContextWrapper#getPackageManager&lt;/code&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;@Override
public PackageManager getPackageManager() {
    return mBase.getPackageManager();
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;   &lt;code&gt;ContextImpl#getPackageManager&lt;/code&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;@Override
public PackageManager getPackageManager() {
    if (mPackageManager != null) {
        return mPackageManager;
    }
    IPackageManager pm = ActivityThread.getPackageManager();
    if (pm != null) {
        // Doesn&amp;apos;t matter if we make more than one instance.
        return (mPackageManager = new ApplicationPackageManager(this, pm));//创建 ApplicationPackageManager
    }
    return null;
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;   &lt;code&gt;ApplicationPackageManager#ApplicationPackageManager&lt;/code&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;ApplicationPackageManager(ContextImpl context,
                          IPackageManager pm) {
    mContext = context;//保存 ContextImpl 的强引用
    mPM = pm;
}

private UserManagerService(Context context, PackageManagerService pm,
        Object packagesLock, File dataDir) {
    mContext = context;//持有外部 Context 引用
    mPm = pm;
    //代码省略
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;   &lt;code&gt;PackageManagerService#PackageManagerService&lt;/code&gt;&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;public class PackageManagerService extends IPackageManager.Stub {
    static UserManagerService sUserManager;//持有 UMS 静态引用
    public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
          sUserManager = new UserManagerService(context, this, mPackages);//初始化 UMS
        }
}&lt;/code&gt;&lt;/pre&gt;  &lt;blockquote&gt;   &lt;p&gt;遇到的内存泄漏问题是因为在 Activity 中调用了 getPackageManger 方法获取 PMS ，该方法调用的是 ContextImpl，此时如果ContextImpl 中 PackageManager 为 null，就会创建一个 PackageManger（ContextImpl 会将自己传递进去，而 ContextImpl 的 mOuterContext 为 Activity），创建 PackageManager 实际上会创建 PackageManagerService（简称 PMS），而 PMS 的构造方法中会创建一个 UserManger（UserManger 初始化之后会持有 ContextImpl 的强引用）。&lt;/p&gt;   &lt;p&gt;只要 PMS 的 class 未被销毁，那么就会一直引用着 UserManger ，进而导致其关联到的资源无法正常释放。&lt;/p&gt;&lt;/blockquote&gt;  &lt;h4&gt;解决办法&lt;/h4&gt;  &lt;blockquote&gt;   &lt;p&gt;    &lt;code&gt;将getPackageManager()&lt;/code&gt;改为    &lt;code&gt;getApplication()#getPackageManager()&lt;/code&gt;。这样引用的就是 Application Context，而非 Activity 了。&lt;/p&gt;&lt;/blockquote&gt;  &lt;h3&gt;远离非静态内部类和匿名类&lt;/h3&gt;  &lt;p&gt;因为使用非静态内部类和匿名类都会默认持有外部类的引用，如果生命周期不一致，就会导致内存泄漏。&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;public class NestedClassLeakActivity extends AppCompatActivity {

    class InnerClass {//非静态内部类

    }

    private static InnerClass sInner;//指向非静态内部类的静态引用

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_nested_class);
        if (sInner == null) {
           sInner = new InnerClass();//创建非静态内部类的实例
        }
    }
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;非静态内部类默认会持有外部类的引用，而外部类中又有一个该非静态内部类的静态实例，该静态实例的生命周期和应用的一样长，而静态实例又持有 Activity 的引用，因此导致 Activity 的内存资源不能正常回收。&lt;/p&gt;  &lt;h4&gt;解决方法&lt;/h4&gt;  &lt;p&gt;将该内部类设为静态内部类 也可以将该内部类抽取出来封装成一个单例&lt;/p&gt;  &lt;h3&gt;集合引发的内存泄漏&lt;/h3&gt;  &lt;p&gt;我们通常会把一些对象的引用加入到集合容器（比如ArrayList）中，当我们不再需要该对象时（通常会调用 remove 方法），并没有把它的引用从集合中清理掉（其中的一种情况就是 remove 方法没有将不再需要的引用赋值为 null），下面以 ArrayList 的 remove 方法为例&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;public E remove( int index) {
    // 数组越界检查
    RangeCheck(index);
    modCount++;
    // 取出要删除位置的元素，供返回使用
    E oldValue = (E) elementData[index];
   // 计算数组要复制的数量
    int numMoved = size - index - 1;
   // 数组复制，就是将index之后的元素往前移动一个位置
    if (numMoved &amp;gt; 0)
       System. arraycopy(elementData, index+1, elementData, index,
                      numMoved);
   // 将数组最后一个元素置空（因为删除了一个元素，然后index后面的元素都向前移动了，所以最后一个就没用了），好让gc尽快回收
    elementData[--size ] = null; // Let gc do its work
    return oldValue;
}&lt;/code&gt;&lt;/pre&gt;  &lt;h3&gt;WebView 引发的内存泄漏&lt;/h3&gt;  &lt;p&gt;WebView 解析网页时会申请   &lt;code&gt;Native堆内存&lt;/code&gt;用于保存页面元素，当页面较复杂时会有很大的内存占用。如果页面包含图片，内存占用会更严重。并且打开新页面时，为了能快速回退，   &lt;code&gt;之前页面占用的内存也不会释放&lt;/code&gt;。有时浏览十几个网页，都会占用几百兆的内存。这样加载网页较多时，会导致系统不堪重负，最终强制关闭应用，也就是出现应用闪退或重启。&lt;/p&gt;  &lt;p&gt;由于占用的都是   &lt;code&gt;Native 堆内存&lt;/code&gt;，所以   &lt;code&gt;实际占用的内存大小不会显示在常用的 DDMS Heap 工具中&lt;/code&gt;（ DMS Heap 工具看到的只是Java虚拟机分配的内存，即使Native堆内存已经占用了几百兆，这里显示的还只是几兆或十几兆）。只有使用 adb shell 中的一些命令比如 dumpsys meminfo 包名，或者在程序中使用   &lt;code&gt;Debug.getNativeHeapSize()&lt;/code&gt;才能看到 Native 堆内存信息。&lt;/p&gt;  &lt;blockquote&gt;   &lt;p&gt;据说由于 WebView 的一个 BUG，即使它所在的 Activity(或者Service) 结束也就是 onDestroy() 之后，或者直接调用 WebView.destroy()之后，它所占用这些内存也不会被释放。&lt;/p&gt;&lt;/blockquote&gt;  &lt;h4&gt;解决方法&lt;/h4&gt;  &lt;p&gt;把使用了 WebView 的 Activity (或者 Service) 放在单独的进程里。&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;系统在检测到应用占用内存过大有可能被系统干掉&lt;/li&gt;   &lt;li&gt;也可以在它所在的 Activity(或者 Service) 结束后，调用 System.exit(0)，主动Kill掉进程。由于系统的内存分配是以进程为准的，进程关闭后，系统会自动回收所有内存。&lt;/li&gt;&lt;/ul&gt;  &lt;blockquote&gt;   &lt;p&gt;使用 WebView 的页面（Activity），在生命周期结束页面退出（onDestory）的时候，主动调用WebView.onPause()==以及==WebView.destory()以便让系统释放 WebView 相关资源。&lt;/p&gt;&lt;/blockquote&gt;  &lt;h3&gt;其他常见的引起内存泄漏原因&lt;/h3&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;p&gt;Android 3.0 以下，Bitmap 在不使用的时候没有使用 recycle() 释放内存。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;     &lt;code&gt;非静态内部类的静态实例&lt;/code&gt;容易造成内存泄漏：即一个类中如果你不能够控制它其中内部类的生命周期（譬如Activity中的一些特殊Handler等），则尽量使用静态类和弱引用来处理（譬如ViewRoot的实现）。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;     &lt;code&gt;警惕线程未终止造成的内存泄露&lt;/code&gt;；譬如在 Activity 中关联了一个生命周期超过 Activity 的 Thread，在退出 Activity 时切记结束线程。&lt;/p&gt;    &lt;blockquote&gt;     &lt;p&gt;一个典型的例子就是 HandlerThread 的 run 方法。该方法在这里是一个死循环，它不会自己结束，线程的生命周期超过了 Activity 生命周期，我们必须手动在 Activity 的销毁方法中中调用 thread.getLooper().quit() 才不会泄露。&lt;/p&gt;&lt;/blockquote&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;     &lt;code&gt;对象的注册与反注册没有成对出现&lt;/code&gt;造成的内存泄露；譬如注册广播接收器、注册观察者（典型的譬如数据库的监听）等。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;     &lt;code&gt;创建与关闭没有成对出现造成的泄露&lt;/code&gt;；譬如Cursor资源必须手动关闭，WebView必须手动销毁，流等对象必须手动关闭等。&lt;/p&gt;&lt;/li&gt;   &lt;li&gt;    &lt;p&gt;避免代码设计模式的错误造成内存泄露；譬如循环引用，A 持有 B，B 持有 C，C 持有 A，这样的设计谁都得不到释放。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;h4&gt;【附】相关架构及资料&lt;/h4&gt;  &lt;blockquote&gt;   &lt;p&gt;    &lt;strong&gt;加群     &lt;a href="https://links.jianshu.com/go?to=https%3A%2F%2Fjq.qq.com%2F%3F_wv%3D1027%26k%3D5gyv0JM" target="_blank"&gt;Android IOC架构设计&lt;/a&gt;领取获取往期Android高级架构资料、源码、笔记、视频。高级UI、性能优化、架构师课程、NDK、混合式开发（ReactNative+Weex）微信小程序、Flutter全方面的Android进阶实践技术，群内还有技术大牛一起讨论交流解决问题。&lt;/strong&gt;&lt;/p&gt;&lt;/blockquote&gt;  &lt;div&gt;   &lt;div&gt;    &lt;div&gt;&lt;/div&gt;    &lt;div&gt;     &lt;img&gt;&lt;/img&gt;&lt;/div&gt;&lt;/div&gt;   &lt;div&gt;Android高级技术大纲&lt;/div&gt;&lt;/div&gt;  &lt;h3&gt;   &lt;strong&gt;领取方式：&lt;/strong&gt;&lt;/h3&gt;  &lt;h5&gt;   &lt;strong&gt;点赞+加群免费获取    &lt;a href="https://links.jianshu.com/go?to=https%3A%2F%2Fjq.qq.com%2F%3F_wv%3D1027%26k%3D5gyv0JM" target="_blank"&gt;Android IOC架构设计&lt;/a&gt;&lt;/strong&gt;&lt;/h5&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>jianshu</category>
      <guid isPermaLink="true">https://itindex.net/detail/59541-android-%E5%86%85%E5%AD%98%E6%B3%84%E6%BC%8F</guid>
      <pubDate>Thu, 02 May 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>彻底理解 Android 应用无响应机制</title>
      <link>https://itindex.net/detail/59467-%E7%90%86%E8%A7%A3-android-%E5%BA%94%E7%94%A8</link>
      <description>&lt;div&gt;  &lt;h3&gt;引言&lt;/h3&gt;  &lt;p&gt;不论从事安卓应用开发，还是安卓系统研发，应该都遇到应用无响应（ANR，Application Not Responding）问题，当应用程序一段时间无法及时响应，则会弹出ANR对话框，让用户选择继续等待，还是强制关闭。&lt;/p&gt;  &lt;p&gt;绝大多数人对ANR的了解仅停留在主线程耗时或CPU繁忙会导致ANR。面试过无数的候选人，几乎没有人能真正从系统级去梳理清晰ANR的来龙去脉，比如有哪些路径会引发ANR? 有没有可能主线程不耗时也出现ANR？如何更好的调试ANR?&lt;/p&gt;  &lt;p&gt;如果没有深入研究过Android Framework的源代码，是难以形成对ANR有一个全面、正确的理解。研究系统源码以及工作实践后提炼而来，以图文并茂的方式跟大家讲解，相信定能帮忙大家加深对ANR的理解。&lt;/p&gt;  &lt;h3&gt;ANR触发机制&lt;/h3&gt;  &lt;p&gt;对于知识学习的过程，要知其然知其所以然，才能做到庖丁解牛般游刃有余。要深入理解ANR，就需要从根上去找寻答案，那就是ANR是如何触发的？&lt;/p&gt;  &lt;p&gt;ANR是一套监控Android应用响应是否及时的机制，可以把发生ANR比作是引爆炸弹，那么整个流程包含三部分组成：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;埋定时炸弹：中控系统(system_server进程)启动倒计时，在规定时间内如果目标(应用进程)没有干完所有的活，则中控系统会定向炸毁(杀进程)目标。&lt;/li&gt;   &lt;li&gt;拆炸弹：在规定的时间内干完工地的所有活，并及时向中控系统报告完成，请求解除定时炸弹，则幸免于难。&lt;/li&gt;   &lt;li&gt;引爆炸弹：中控系统立即封装现场，抓取快照，搜集目标执行慢的罪证(traces)，便于后续的案件侦破(调试分析)，最后是炸毁目标。&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;常见的ANR有service、broadcast、provider以及input，更多细节详见理解Android ANR的触发原理，http://gityuan.com/2016/07/02/android-anr，接下来本文以图文形式分别讲解。&lt;/p&gt;  &lt;h4&gt;service超时机制&lt;/h4&gt;  &lt;p&gt;下面来看看埋炸弹与拆炸弹在整个服务启动(startService)过程所处的环节。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="service_anr" src="http://itindex.net/images/android-anr/service_anr.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;图解1：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;客户端(App进程)向中控系统(system_server进程)发起启动服务的请求&lt;/li&gt;   &lt;li&gt;中控系统派出一名空闲的通信员(binder_1线程)接收该请求，紧接着向组件管家(ActivityManager线程)发送消息，埋下定时炸弹&lt;/li&gt;   &lt;li&gt;通讯员1号(binder_1)通知工地(service所在进程)的通信员准备开始干活&lt;/li&gt;   &lt;li&gt;通讯员3号(binder_3)收到任务后转交给包工头(main主线程)，加入包工头的任务队列(MessageQueue)&lt;/li&gt;   &lt;li&gt;包工头经过一番努力干完活(完成service启动的生命周期)，然后等待SharedPreferences(简称SP)的持久化；&lt;/li&gt;   &lt;li&gt;包工头在SP执行完成后，立刻向中控系统汇报工作已完成&lt;/li&gt;   &lt;li&gt;中控系统的通讯员2号(binder_2)收到包工头的完工汇报后，立刻拆除炸弹。如果在炸弹倒计时结束前拆除炸弹则相安无事，否则会引发爆炸(触发ANR)&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;更多细节详见startService启动过程分析，http://gityuan.com/2016/03/06/start-service&lt;/p&gt;  &lt;h4&gt;broadcast超时机制&lt;/h4&gt;  &lt;p&gt;broadcast跟service超时机制大抵相同，对于静态注册的广播在超时检测过程需要检测SP，如下图所示。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="broadcast_anr" src="http://itindex.net/images/android-anr/broadcast_anr.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;图解2：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;客户端(App进程)向中控系统(system_server进程)发起发送广播的请求&lt;/li&gt;   &lt;li&gt;中控系统派出一名空闲的通信员(binder_1)接收该请求转交给组件管家(ActivityManager线程)&lt;/li&gt;   &lt;li&gt;组件管家执行任务(processNextBroadcast方法)的过程埋下定时炸弹&lt;/li&gt;   &lt;li&gt;组件管家通知工地(receiver所在进程)的通信员准备开始干活&lt;/li&gt;   &lt;li&gt;通讯员3号(binder_3)收到任务后转交给包工头(main主线程)，加入包工头的任务队列(MessageQueue)&lt;/li&gt;   &lt;li&gt;包工头经过一番努力干完活(完成receiver启动的生命周期)，发现当前进程还有SP正在执行写入文件的操作，便将向中控系统汇报的任务交给SP工人(queued-work-looper线程)&lt;/li&gt;   &lt;li&gt;SP工人历经艰辛终于完成SP数据的持久化工作，便可以向中控系统汇报工作完成&lt;/li&gt;   &lt;li&gt;中控系统的通讯员2号(binder_2)收到包工头的完工汇报后，立刻拆除炸弹。如果在倒计时结束前拆除炸弹则相安无事，否则会引发爆炸(触发ANR)&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;（说明：SP从8.0开始采用名叫“queued-work-looper”的handler线程，在老版本采用newSingleThreadExecutor创建的单线程的线程池）&lt;/p&gt;  &lt;p&gt;如果是动态广播，或者静态广播没有正在执行持久化操作的SP任务，则不需要经过“queued-work-looper”线程中转，而是直接向中控系统汇报，流程更为简单，如下图所示：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="broadcast_anr_2" src="http://itindex.net/images/android-anr/broadcast_anr_2.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;可见，只有XML静态注册的广播超时检测过程会考虑是否有SP尚未完成，动态广播并不受其影响。SP的apply将修改的数据项更新到内存，然后再异步同步数据到磁盘文件，因此很多地方会推荐在主线程调用采用apply方式，避免阻塞主线程，但静态广播超时检测过程需要SP全部持久化到磁盘，如果过度使用apply会增大应用ANR的概率，更多细节详见http://gityuan.com/2017/06/18/SharedPreferences&lt;/p&gt;  &lt;p&gt;Google这样设计的初衷是针对静态广播的场景下，保障进程被杀之前一定能完成SP的数据持久化。因为在向中控系统汇报广播接收者工作执行完成前，该进程的优先级为Foreground级别，高优先级下进程不但不会被杀，而且能分配到更多的CPU时间片，加速完成SP持久化。&lt;/p&gt;  &lt;p&gt;更多细节详见Android Broadcast广播机制分析，http://gityuan.com/2016/06/04/broadcast-receiver&lt;/p&gt;  &lt;h4&gt;provider超时机制&lt;/h4&gt;  &lt;p&gt;provider的超时是在provider进程首次启动的时候才会检测，当provider进程已启动的场景，再次请求provider并不会触发provider超时。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="provider_anr" src="http://itindex.net/images/android-anr/provider_anr.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;图解3：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;客户端(App进程)向中控系统(system_server进程)发起获取内容提供者的请求&lt;/li&gt;   &lt;li&gt;中控系统派出一名空闲的通信员(binder_1)接收该请求，检测到内容提供者尚未启动，则先通过zygote孵化新进程&lt;/li&gt;   &lt;li&gt;新孵化的provider进程向中控系统注册自己的存在&lt;/li&gt;   &lt;li&gt;中控系统的通信员2号接收到该信息后，向组件管家(ActivityManager线程)发送消息，埋下炸弹&lt;/li&gt;   &lt;li&gt;通信员2号通知工地(provider进程)的通信员准备开始干活&lt;/li&gt;   &lt;li&gt;通讯员4号(binder_4)收到任务后转交给包工头(main主线程)，加入包工头的任务队列(MessageQueue)&lt;/li&gt;   &lt;li&gt;包工头经过一番努力干完活(完成provider的安装工作)后向中控系统汇报工作已完成&lt;/li&gt;   &lt;li&gt;中控系统的通讯员3号(binder_3)收到包工头的完工汇报后，立刻拆除炸弹。如果在倒计时结束前拆除炸弹则相安无事，否则会引发爆炸(触发ANR)&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;更多细节详见理解ContentProvider原理，http://gityuan.com/2016/07/30/content-provider&lt;/p&gt;  &lt;h4&gt;inpu超时机制&lt;/h4&gt;  &lt;p&gt;input的超时检测机制跟service、broadcast、provider截然不同，为了更好的理解input过程先来介绍两个重要线程的相关工作：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;InputReader线程负责通过EventHub(监听目录/dev/input)读取输入事件，一旦监听到输入事件则放入到InputDispatcher的mInBoundQueue队列，并通知其处理该事件；&lt;/li&gt;   &lt;li&gt;InputDispatcher线程负责将接收到的输入事件分发给目标应用窗口，分发过程使用到3个事件队列：    &lt;ul&gt;     &lt;li&gt;mInBoundQueue用于记录InputReader发送过来的输入事件；&lt;/li&gt;     &lt;li&gt;outBoundQueue用于记录即将分发给目标应用窗口的输入事件；&lt;/li&gt;     &lt;li&gt;waitQueue用于记录已分发给目标应用，且应用尚未处理完成的输入事件；&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;input的超时机制并非时间到了一定就会爆炸，而是处理后续上报事件的过程才会去检测是否该爆炸，所以更相信是扫雷的过程，具体如下图所示。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="input_anr" src="http://itindex.net/images/android-anr/input_anr.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;图解4：&lt;/p&gt;  &lt;ol&gt;   &lt;li&gt;InputReader线程通过EventHub监听底层上报的输入事件，一旦收到输入事件则将其放至mInBoundQueue队列，并唤醒InputDispatcher线程&lt;/li&gt;   &lt;li&gt;InputDispatcher开始分发输入事件，设置埋雷的起点时间。先检测是否有正在处理的事件(mPendingEvent)，如果没有则取出mInBoundQueue队头的事件，并将其赋值给mPendingEvent，且重置ANR的timeout；否则不会从mInBoundQueue中取出事件，也不会重置timeout。然后检查窗口是否就绪(checkWindowReadyForMoreInputLocked)，满足以下任一情况，则会进入扫雷状态(检测前一个正在处理的事件是否超时)，终止本轮事件分发，否则继续执行步骤3。    &lt;ul&gt;     &lt;li&gt;对于按键类型的输入事件，则outboundQueue或者waitQueue不为空，&lt;/li&gt;     &lt;li&gt;对于非按键的输入事件，则waitQueue不为空，且等待队头时间超时500ms&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;   &lt;li&gt;当应用窗口准备就绪，则将mPendingEvent转移到outBoundQueue队列&lt;/li&gt;   &lt;li&gt;当outBoundQueue不为空，且应用管道对端连接状态正常，则将数据从outboundQueue中取出事件，放入waitQueue队列&lt;/li&gt;   &lt;li&gt;InputDispatcher通过socket告知目标应用所在进程可以准备开始干活&lt;/li&gt;   &lt;li&gt;App在初始化时默认已创建跟中控系统双向通信的socketpair，此时App的包工头(main线程)收到输入事件后，会层层转发到目标窗口来处理&lt;/li&gt;   &lt;li&gt;包工头完成工作后，会通过socket向中控系统汇报工作完成，则中控系统会将该事件从waitQueue队列中移除。&lt;/li&gt;&lt;/ol&gt;  &lt;p&gt;input超时机制为什么是扫雷，而非定时爆炸呢？是由于对于input来说即便某次事件执行时间超过timeout时长，只要用户后续在没有再生成输入事件，则不会触发ANR。
这里的扫雷是指当前输入系统中正在处理着某个耗时事件的前提下，后续的每一次input事件都会检测前一个正在处理的事件是否超时（进入扫雷状态），检测当前的时间距离上次输入事件分发时间点是否超过timeout时长。如果前一个输入事件，则会重置ANR的timeout，从而不会爆炸。&lt;/p&gt;  &lt;p&gt;更多细节详见Input系统-ANR原理分析，http://gityuan.com/2017/01/01/input-anr&lt;/p&gt;  &lt;h3&gt;ANR超时阈值&lt;/h3&gt;  &lt;p&gt;不同组件的超时阈值各有不同，关于service、broadcast、contentprovider以及input的超时阈值如下表：&lt;/p&gt;  &lt;p&gt;   &lt;img alt="anr_timeout" src="http://itindex.net/images/android-anr/anr_timeout.jpg"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;h4&gt;前台与后台服务的区别&lt;/h4&gt;  &lt;p&gt;系统对前台服务启动的超时为20s，而后台服务超时为200s，那么系统是如何区别前台还是后台服务呢？来看看ActiveServices的核心逻辑：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;ComponentName startServiceLocked(...) {
    final boolean callerFg;
    if (caller != null) {
        final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller);
        callerFg = callerApp.setSchedGroup != ProcessList.SCHED_GROUP_BACKGROUND;
    } else {
        callerFg = true;
    }
    ...
    ComponentName cmp = startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
    return cmp;
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;在startService过程根据发起方进程callerApp所属的进程调度组来决定被启动的服务是属于前台还是后台。当发起方进程不等于ProcessList.SCHED_GROUP_BACKGROUND(后台进程组)则认为是前台服务，否则为后台服务，并标记在ServiceRecord的成员变量createdFromFg。&lt;/p&gt;  &lt;p&gt;什么进程属于SCHED_GROUP_BACKGROUND调度组呢？进程调度组大体可分为TOP、前台、后台，进程优先级（Adj）和进程调度组（SCHED_GROUP）算法较为复杂，其对应关系可粗略理解为Adj等于0的进程属于Top进程组，Adj等于100或者200的进程属于前台进程组，Adj大于200的进程属于后台进程组。关于Adj的含义见下表，简单来说就是Adj&amp;gt;200的进程对用户来说基本是无感知，主要是做一些后台工作，故后台服务拥有更长的超时阈值，同时后台服务属于后台进程调度组，相比前台服务属于前台进程调度组，分配更少的CPU时间片。&lt;/p&gt;  &lt;p&gt;   &lt;img alt="adj" src="http://itindex.net/images/android-anr/adj.png"&gt;&lt;/img&gt;&lt;/p&gt;  &lt;p&gt;关于细节详见解读Android进程优先级ADJ算法，http://gityuan.com/2018/05/19/android-process-adj&lt;/p&gt;  &lt;p&gt;   &lt;code&gt;前台服务准确来说，是指由处于前台进程调度组的进程发起的服务&lt;/code&gt;。这跟常说的fg-service服务有所不同，fg-service是指挂有前台通知的服务。&lt;/p&gt;  &lt;h4&gt;前台与后台广播超时&lt;/h4&gt;  &lt;p&gt;前台广播超时为10s，后台广播超时为60s，那么如何区分前台和后台广播呢？来看看AMS的核心逻辑：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;BroadcastQueue broadcastQueueForIntent(Intent intent) {
    final boolean isFg = (intent.getFlags() &amp;amp; Intent.FLAG_RECEIVER_FOREGROUND) != 0;
    return (isFg) ? mFgBroadcastQueue : mBgBroadcastQueue;
}

mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
        &amp;quot;foreground&amp;quot;, BROADCAST_FG_TIMEOUT, false);
mBgBroadcastQueue = new BroadcastQueue(this, mHandler,
        &amp;quot;background&amp;quot;, BROADCAST_BG_TIMEOUT, true);&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;根据发送广播sendBroadcast(Intent intent)中的intent的flags是否包含FLAG_RECEIVER_FOREGROUND来决定把该广播是放入前台广播队列或者后台广播队列，前台广播队列的超时为10s，后台广播队列的超时为60s，默认情况下广播是放入后台广播队列，除非指明加上FLAG_RECEIVER_FOREGROUND标识。&lt;/p&gt;  &lt;p&gt;后台广播比前台广播拥有更长的超时阈值，同时在广播分发过程遇到后台service的启动(mDelayBehindServices)会延迟分发广播，等待service的完成，因为等待service而导致的广播ANR会被忽略掉；后台广播属于后台进程调度组，而前台广播属于前台进程调度组。简而言之，后台广播更不容易发生ANR，同时执行的速度也会更慢。&lt;/p&gt;  &lt;p&gt;另外，只有串行处理的广播才有超时机制，因为接收者是串行处理的，前一个receiver处理慢，会影响后一个receiver；并行广播通过一个循环一次性向所有的receiver分发广播事件，所以不存在彼此影响的问题，则没有广播超时。&lt;/p&gt;  &lt;p&gt;   &lt;code&gt;前台广播准确来说，是指位于前台广播队列的广播&lt;/code&gt;。&lt;/p&gt;  &lt;h4&gt;前台与后台ANR&lt;/h4&gt;  &lt;p&gt;除了前台服务，前台广播，还有前台ANR可能会让你云里雾里的，来看看其中核心逻辑：&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;final void appNotResponding(...) {
    ...
    synchronized (mService) {
        isSilentANR = !showBackground &amp;amp;&amp;amp; !isInterestingForBackgroundTraces(app);
        ...
    }
    ...
    File tracesFile = ActivityManagerService.dumpStackTraces(
            true, firstPids,
            (isSilentANR) ? null : processCpuTracker,
            (isSilentANR) ? null : lastPids,
            nativePids);

    synchronized (mService) {
        if (isSilentANR) {
            app.kill(&amp;quot;bg anr&amp;quot;, true);
            return;
        }
        ...
        
        //弹出ANR选择的对话框
        Message msg = Message.obtain();
        msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
        msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem);
        mService.mUiHandler.sendMessage(msg);
    }
}&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;决定是前台或者后台ANR取决于该应用发生ANR时对用户是否可感知，比如拥有当前前台可见的activity的进程，或者拥有前台通知的fg-service的进程，这些是用户可感知的场景，发生ANR对用户体验影响比较大，故需要弹框让用户决定是否退出还是等待，如果直接杀掉这类应用会给用户造成莫名其妙的闪退。&lt;/p&gt;  &lt;p&gt;后台ANR相比前台ANR，只抓取发生无响应进程的trace，也不会收集CPU信息，并且会在后台直接杀掉该无响应的进程，不会弹框提示用户。&lt;/p&gt;  &lt;p&gt;   &lt;code&gt;前台ANR准确来说，是指对用户可感知的进程发生的ANR&lt;/code&gt;。&lt;/p&gt;  &lt;h3&gt;ANR爆炸现场&lt;/h3&gt;  &lt;p&gt;对于service、broadcast、provider、input发生ANR后，中控系统会马上去抓取现场的信息，用于调试分析。收集的信息包括如下：&lt;/p&gt;  &lt;ul&gt;   &lt;li&gt;将am_anr信息输出到EventLog，也就是说ANR触发的时间点最接近的就是EventLog中输出的am_anr信息&lt;/li&gt;   &lt;li&gt;收集以下重要进程的各个线程调用栈trace信息，保存在data/anr/traces.txt文件    &lt;ul&gt;     &lt;li&gt;当前发生ANR的进程，system_server进程以及所有persistent进程&lt;/li&gt;     &lt;li&gt;audioserver, cameraserver, mediaserver, surfaceflinger等重要的native进程&lt;/li&gt;     &lt;li&gt;CPU使用率排名前5的进程&lt;/li&gt;&lt;/ul&gt;&lt;/li&gt;   &lt;li&gt;将发生ANR的reason以及CPU使用情况信息输出到main log&lt;/li&gt;   &lt;li&gt;将traces文件和CPU使用情况信息保存到dropbox，即data/system/dropbox目录&lt;/li&gt;   &lt;li&gt;对用户可感知的进程则弹出ANR对话框告知用户，对用户不可感知的进程发生ANR则直接杀掉&lt;/li&gt;&lt;/ul&gt;  &lt;p&gt;整个ANR信息收集过程比较耗时，其中抓取进程的trace信息，每抓取一个等待200ms，可见persistent越多，等待时间越长。关于抓取trace命令，对于Java进程可通过在adb shell环境下执行kill -3 [pid]可抓取相应pid的调用栈；对于Native进程在adb shell环境下执行debuggerd -b [pid]可抓取相应pid的调用栈。对于ANR问题发生后的蛛丝马迹(trace)在traces.txt和dropbox目录中保存记录。更多细节详见理解Android ANR的信息收集过程，http://gityuan.com/2016/12/02/app-not-response。&lt;/p&gt;  &lt;p&gt;有了现场信息，可以调试分析，先定位发生ANR时间点，然后查看trace信息，接着分析是否有耗时的message、binder调用，锁的竞争，CPU资源的抢占，以及结合具体场景的上下文来分析，调试手段就需要针对前面说到的message、binder、锁等资源从系统角度细化更多debug信息，这里不再展开，后续再以ANR案例来讲解。&lt;/p&gt;  &lt;p&gt;作为应用开发者应让主线程尽量只做UI相关的操作，避免耗时操作，比如过度复杂的UI绘制，网络操作，文件IO操作；避免主线程跟工作线程发生锁的竞争，减少系统耗时binder的调用，谨慎使用sharePreference，注意主线程执行provider query操作。简而言之，尽可能减少主线程的负载，让其空闲待命，以期可随时响应用户的操作。&lt;/p&gt;  &lt;h4&gt;回答&lt;/h4&gt;  &lt;p&gt;最后，来回答文章开头的提问，有哪些路径会引发ANR? 答应是从埋下定时炸弹到拆炸弹之间的任何一个或多个路径执行慢都会导致ANR（以service为例），可以是service的生命周期的回调方法(比如onStartCommand)执行慢，可以是主线程的消息队列存在其他耗时消息让service回调方法迟迟得不到执行，可以是SP操作执行慢，可以是system_server进程的binder线程繁忙而导致没有及时收到拆炸弹的指令。另外ActivityManager线程也可能阻塞，出现的现象就是前台服务执行时间有可能超过10s，但并不会出现ANR。&lt;/p&gt;  &lt;p&gt;发生ANR时从trace来看主线程却处于空闲状态或者停留在非耗时代码的原因有哪些？可以是抓取trace过于耗时而错过现场，可以是主线程消息队列堆积大量消息而最后抓取快照一刻只是瞬时状态，可以是广播的“queued-work-looper”一直在处理SP操作。&lt;/p&gt;  &lt;p&gt;本文的知识源自对Android系统源码的研究以及工作实践中提炼而来，Android达摩院独家武功秘籍分享给大家，希望能升大家对提对ANR的理解。&lt;/p&gt;  &lt;hr&gt;&lt;/hr&gt;  &lt;strong&gt;微信公众号&lt;/strong&gt;  &lt;a href="http://gityuan.com/images/about-me/damoyuan_logo.png" target="_blank"&gt;   &lt;strong&gt;Android达摩院&lt;/strong&gt;&lt;/a&gt;  &lt;strong&gt;| 微博&lt;/strong&gt;  &lt;a href="http://weibo.com/gityuan" target="_blank"&gt;   &lt;strong&gt;weibo.com/gityuan&lt;/strong&gt;&lt;/a&gt;  &lt;strong&gt;| 博客&lt;/strong&gt;  &lt;a href="http://gityuan.com/talk/" target="_blank"&gt;   &lt;strong&gt;留言区交流&lt;/strong&gt;&lt;/a&gt;  &lt;img alt="android-damoyuan" src="http://itindex.net/images/about-me/damoyuan_logo.png"&gt;&lt;/img&gt;  &lt;hr&gt;&lt;/hr&gt;  &lt;ul&gt;   &lt;li&gt;    &lt;a href="http://itindex.net/2019/03/20/android_future/" target="_blank" title="Android&amp;#25216;&amp;#26415;&amp;#26550;&amp;#26500;&amp;#28436;&amp;#36827;&amp;#19982;&amp;#26410;&amp;#26469;"&gt;上一篇     &lt;br /&gt;Android技术架构演进与未来&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>dev</category>
      <guid isPermaLink="true">https://itindex.net/detail/59467-%E7%90%86%E8%A7%A3-android-%E5%BA%94%E7%94%A8</guid>
      <pubDate>Tue, 16 Apr 2019 00:00:00 CST</pubDate>
    </item>
    <item>
      <title>html5选择摄像头 android - Enable rear camera with HTML5 - Stack Overflow</title>
      <link>https://itindex.net/detail/59326-html5-%E9%80%89%E6%8B%A9-%E6%91%84%E5%83%8F%E5%A4%B4</link>
      <description>&lt;div&gt;    &lt;p&gt;Check out    &lt;a href="https://simpl.info/getusermedia/sources/"&gt;https://simpl.info/getusermedia/sources/&lt;/a&gt; that shows how you can select sources using&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;MediaStreamTrack.getSources(gotSources);&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;You can then select the source and pass it in as optional into getUserMedia&lt;/p&gt;  &lt;pre&gt;   &lt;code&gt;var constraints = {
  audio: {
    optional: [{sourceId: audioSource}]
  },
  video: {
    optional: [{sourceId: videoSource}]
  }
};
navigator.getUserMedia(constraints, successCallback, errorCallback);&lt;/code&gt;&lt;/pre&gt;  &lt;p&gt;It is now fully available in Stable Chrome and mobile (As of v30)&lt;/p&gt;  &lt;p&gt;详见源码：&lt;/p&gt;  &lt;p&gt;https://github.com/samdutton/simpl/blob/gh-pages/getusermedia/sources/js/main.js   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;html：&lt;/p&gt;  &lt;table&gt;   &lt;tr&gt;    &lt;td&gt;&amp;lt;div id=&amp;quot;container&amp;quot;&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;     &lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;  &amp;lt;h1&amp;gt;&amp;lt;a href=&amp;quot;     &lt;a href="https://simpl.info/index.html" rel="noreferrer noopener" target="_blank"&gt;../../index.html&lt;/a&gt;&amp;quot; title=&amp;quot;simpl.info home page&amp;quot;&amp;gt;simpl.info&amp;lt;/a&amp;gt; MediaStreamTrack.getSources&amp;lt;/h1&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;     &lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;  &amp;lt;div class=&amp;quot;select&amp;quot;&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;    &amp;lt;label for=&amp;quot;audioSource&amp;quot;&amp;gt;Audio source: &amp;lt;/label&amp;gt;&amp;lt;select id=&amp;quot;audioSource&amp;quot;&amp;gt;&amp;lt;/select&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;  &amp;lt;/div&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;     &lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;  &amp;lt;div class=&amp;quot;select&amp;quot;&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;    &amp;lt;label for=&amp;quot;videoSource&amp;quot;&amp;gt;Video source: &amp;lt;/label&amp;gt;&amp;lt;select id=&amp;quot;videoSource&amp;quot;&amp;gt;&amp;lt;/select&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;  &amp;lt;/div&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;     &lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;  &amp;lt;video muted autoplay&amp;gt;&amp;lt;/video&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;     &lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;  &amp;lt;script src=&amp;quot;     &lt;a href="https://simpl.info/getusermedia/sources/js/main.js" rel="noreferrer noopener" target="_blank"&gt;js/main.js&lt;/a&gt;&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;     &lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;  &amp;lt;p&amp;gt;This demo requires Chrome 30 or later.&amp;lt;/p&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;     &lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;  &amp;lt;p&amp;gt;For more information, see &amp;lt;a href=&amp;quot;     &lt;a href="https://www.html5rocks.com/en/tutorials/getusermedia/intro/" rel="noreferrer noopener" target="_blank"&gt;https://www.html5rocks.com/en/tutorials/getusermedia/intro/&lt;/a&gt;&amp;quot; title=&amp;quot;Media capture article by Eric Bidelman on HTML5 Rocks&amp;quot;&amp;gt;Capturing Audio &amp;amp;amp; Video in HTML5&amp;lt;/a&amp;gt; on HTML5 Rocks.&amp;lt;/p&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;     &lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;&amp;lt;a href=&amp;quot;     &lt;a href="https://github.com/samdutton/simpl/blob/gh-pages/getusermedia/sources/js/main.js" rel="noreferrer noopener" target="_blank"&gt;https://github.com/samdutton/simpl/blob/gh-pages/getusermedia/sources/js/main.js&lt;/a&gt;&amp;quot; title=&amp;quot;View source for this page on GitHub&amp;quot; id=&amp;quot;viewSource&amp;quot;&amp;gt;View source on GitHub&amp;lt;/a&amp;gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;     &lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;   &lt;tr&gt;    &lt;td&gt;&lt;/td&gt;    &lt;td&gt;&amp;lt;/div&amp;gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;  &lt;p&gt;   &lt;br /&gt;&lt;/p&gt;  &lt;p&gt;Last time i developped that code, soo here is the version which i use : you call directly the function whichCamera in your code and you specefic which camera &amp;quot;user&amp;quot;,&amp;quot;environment&amp;quot; or &amp;quot;computer&amp;quot;&amp;apos;if you are runing in a computer)&lt;/p&gt;    &lt;pre&gt;      &lt;code&gt;`//----------------------------------------------------------------------
//  whichCamera(Type)
//    For smartphone or tablet :
//     Start the type={user,environment} camera.
//    For computer it&amp;apos;s simple :
//      type = &amp;quot;computer&amp;quot;.
//----------------------------------------------------------------------
var streamSrc, cameraType;
function whichCamera(type){

  var cameraFacing;
  cameraType = type;
  if( type == &amp;quot;user&amp;quot;)
    cameraFacing = 0;
  else if( type == &amp;quot;environment&amp;quot;)
    cameraFacing = 1;
  else if( type == &amp;quot;computer&amp;quot;){
    cameraFacing = 2;
  }
  console.log(type+&amp;quot; index : &amp;quot;+cameraFacing);

  //  Here we list all media devices, in order to choose between
  //  the front and the rear camera.
  //      videoDevices[0] : user Camera
  //      videoDevices[1] : environment Camera
  //  Then set the video resolution.
  navigator.mediaDevices.enumerateDevices()
  .then(devices =&amp;gt; {
    var videoDevices, videoDeviceIndex, constraints;
    //  Initialize the array wich will contain all video resources IDs.
    //  Most of devices have two video resources (Front &amp;amp; Rear Camera).
    videoDevices = [0,0];
    //  Simple index to browse the videa resources array (videoDevices).
    videoDeviceIndex = 0;
    //  devices.forEach(), this function will detect all media resources (Audio, Video) of the device
    //  where we run the application.
    devices.forEach(function(device) {
      console.log(device.kind + &amp;quot;: &amp;quot; + device.label +
        &amp;quot; id = &amp;quot; + device.deviceId);
      // If the kind of the media resource is video,
      if (device.kind == &amp;quot;videoinput&amp;quot;) {
        //  then we save it on the array videoDevices.
        videoDevices[videoDeviceIndex++] =  device.deviceId;
        console.log(device.deviceId+&amp;quot; = &amp;quot;+videoDevices[videoDeviceIndex-1]);
      }
    });
    console.log(&amp;quot;Camera facing =&amp;quot;+cameraFacing+&amp;quot; ID = &amp;quot;+videoDevices[videoDeviceIndex-1]);

    // Here we specified which camera we start,
    //  videoDevices[0] : Front Camera
    //  videoDevices[1] : Back Camera
    if( cameraFacing != &amp;quot;computer&amp;quot;){
      constraints = { deviceId: { exact: videoDevices[cameraFacing]  }};
      return navigator.mediaDevices.getUserMedia({ video:
                                                          constraints,
                                                          width: { min: 1280, ideal: 1600, max: 1920 },
                                                          height: { min: 720, ideal: 1200, max: 1080 }
                                                  }
                                                );
    }else
      return navigator.mediaDevices.getUserMedia({ video: true });
    })
    //  Then we retrieve the link to the video stream.
    .then(stream =&amp;gt; {
      if (window.webkitURL) {
        video.src = window.webkitURL.createObjectURL(stream);
        localMediaStream = stream;
        console.log(localMediaStream +&amp;quot; = &amp;quot;+ stream)
      } else if (video.mozSrcObject !== undefined) {
        video.mozSrcObject = stream;
        console.log(video.mozSrcObject +&amp;quot; = &amp;quot;+ stream)
      } else if (video.srcObject !== undefined) {
        video.srcObject = stream;
        console.log(video.srcObject +&amp;quot; = &amp;quot;+ stream)
      } else {
        video.src = stream;
        console.log(video.src +&amp;quot; = &amp;quot;+ stream)
      }
      streamSrc = stream;
    })
    .catch(e =&amp;gt; console.error(e));

}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/59326-html5-%E9%80%89%E6%8B%A9-%E6%91%84%E5%83%8F%E5%A4%B4</guid>
      <pubDate>Sun, 03 Mar 2019 11:20:50 CST</pubDate>
    </item>
    <item>
      <title>Ncnn使用详解(2)——Android端 - DmrfCoder的博客 - CSDN博客</title>
      <link>https://itindex.net/detail/59288-ncnn-android-dmrfcoder</link>
      <description>&lt;h1&gt;摘要&lt;/h1&gt; &lt;p&gt;本片文章基于你已经完成了  &lt;a href="https://blog.csdn.net/qq_36982160/article/details/79929869" rel="nofollow" target="_blank"&gt;这篇文章&lt;/a&gt;的学习，主要介绍如何将写好的c代码应用到Android项目中。&lt;/p&gt; &lt;h1&gt;  &lt;a name="t1"&gt;&lt;/a&gt;环境说明&lt;/h1&gt; &lt;p&gt;系统：Ubuntu16.04   &lt;br /&gt;软件：Android Studio&lt;/p&gt; &lt;h1&gt;  &lt;a name="t2"&gt;&lt;/a&gt;前期准备之  &lt;a href="https://www.baidu.com/s?wd=ndk&amp;tn=24004469_oem_dg&amp;rsv_dl=gh_pl_sl_csd" target="_blank"&gt;ndk&lt;/a&gt;安装&lt;/h1&gt; &lt;p&gt;在正式开始前我们需要先下载安装ndk，这里介绍一种简单高效的方式，打开Android Studio，然后依次点击File-&amp;gt;Settings-&amp;gt;Appearance&amp;amp;Behavior-&amp;gt;System Settings-&amp;gt;Android SDK，然后在SDK Tools下找到ndk，然后选中，点击apply就可以自动下载安装了，如图：   &lt;br /&gt;  &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdn.net/20180413161729327?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2OTgyMTYw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" title=""&gt;&lt;/img&gt;  &lt;br /&gt;完成之后在你的sdk目录下会多出一个ndk-bundle的包，这就是你ndk的路径，类似下图：   &lt;br /&gt;  &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdn.net/2018041316184860?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2OTgyMTYw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" title=""&gt;&lt;/img&gt;  &lt;br /&gt;至此，ndk已经安装完毕了，下一步是配置ndk的环境变量：   &lt;br /&gt;首先打开profile：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;sudo vim /etc/profile&lt;/code&gt;  &lt;ul&gt;   &lt;li&gt;1&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt; &lt;p&gt;打开后在profile文件的末尾加上：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;export NDK_HOME=sdkroot/ndk-bundle
PATH=$NDK_HOME:$PATH&lt;/code&gt;  &lt;ul&gt;   &lt;li&gt;1&lt;/li&gt;   &lt;li&gt;2&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt; &lt;p&gt;sdkroot是你的sdk目录，每个人的不一样，视情况而定，下面是我的配置  &lt;a href="https://www.baidu.com/s?wd=%E6%88%AA%E5%9B%BE&amp;tn=24004469_oem_dg&amp;rsv_dl=gh_pl_sl_csd" target="_blank"&gt;截图&lt;/a&gt;：   &lt;br /&gt;  &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdn.net/20171030113606747?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzY5ODIxNjA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" title=""&gt;&lt;/img&gt;  &lt;br /&gt;添加完成后保存退出，使用以下命令使配置的环境变量生效：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;source /etc/profile&lt;/code&gt;  &lt;ul&gt;   &lt;li&gt;1&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt; &lt;p&gt;验证ndk是否配置成功：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;ndk-build -v&lt;/code&gt;  &lt;ul&gt;   &lt;li&gt;1&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt; &lt;p&gt;出现类似以下输出即说明ndk配置成功：   &lt;br /&gt;  &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdn.net/20171030113528511?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzY5ODIxNjA=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" title=""&gt;&lt;/img&gt;&lt;/p&gt; &lt;h1&gt;  &lt;a name="t3"&gt;&lt;/a&gt;编译ncnn sdk&lt;/h1&gt; &lt;p&gt;我们需要将ncnn打包，这样我们才能在android ndk的代码中使用include&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;mkdir build-android
cd build-android
cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
    -DANDROID_ABI=&amp;quot;armeabi-v7a&amp;quot; -DANDROID_ARM_NEON=ON \
    -DANDROID_PLATFORM=android-14 ..
make
make install&lt;/code&gt;  &lt;ul&gt;   &lt;li&gt;1&lt;/li&gt;   &lt;li&gt;2&lt;/li&gt;   &lt;li&gt;3&lt;/li&gt;   &lt;li&gt;4&lt;/li&gt;   &lt;li&gt;5&lt;/li&gt;   &lt;li&gt;6&lt;/li&gt;   &lt;li&gt;7&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt; &lt;p&gt;参数说明：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;ANDROID_ABI 是架构名字，&amp;quot;armeabi-v7a&amp;quot; 支持绝大部分手机硬件

ANDROID_ARM_NEON 是否使用 NEON 指令集，设为 ON 支持绝大部分手机硬件

ANDROID_PLATFORM 指定最低系统版本，&amp;quot;android-14&amp;quot; 就是 android-4.0&lt;/code&gt;  &lt;ul&gt;   &lt;li&gt;1&lt;/li&gt;   &lt;li&gt;2&lt;/li&gt;   &lt;li&gt;3&lt;/li&gt;   &lt;li&gt;4&lt;/li&gt;   &lt;li&gt;5&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt; &lt;p&gt;你可以根据自己的需要设置自己的参数，详见  &lt;a href="https://github.com/Tencent/ncnn/wiki/cmake-%E6%89%93%E5%8C%85-android-sdk" rel="nofollow" target="_blank"&gt;这里&lt;/a&gt;   &lt;br /&gt;完成后你就可以在ncnn/build-android下找到install了，大概如图：   &lt;br /&gt;  &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdn.net/20180413162620477?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2OTgyMTYw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" title=""&gt;&lt;/img&gt;  &lt;br /&gt;install下有include和lib两个文件夹，这两个文件夹下的东西后面会用到。&lt;/p&gt; &lt;h1&gt;  &lt;a name="t4"&gt;&lt;/a&gt;进行ndk开发&lt;/h1&gt; &lt;p&gt;Android可以通过ndk-build和cmake两种方式来编译c，而且官方比较推荐的是cmake的方式，但是我用cmake试了好长时间一直报各种诡异的错误，应该是我还没有学到ncnn in ndk with cmake的正确打开方式，所以这里介绍一下使用ndk-build这种方式编译c，步骤如下：   &lt;br /&gt;我这里新建了一个android 的demo项目，项目结构如下：   &lt;br /&gt;  &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdn.net/20180413163646377?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2OTgyMTYw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" title=""&gt;&lt;/img&gt;   &lt;br /&gt;主要是assets文件夹下放置你的bin和param文件，jni文件夹下放置你的cpp和两个mk文件，具体内容下面会介绍（可以直接在对应位置新建这两个文件夹），然后要修改你的app gradle文件：   &lt;br /&gt;  &lt;img alt="&amp;#36825;&amp;#37324;&amp;#20889;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdn.net/20180413163947939?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2OTgyMTYw/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70" title=""&gt;&lt;/img&gt;  &lt;br /&gt;对应的内容你可以根据自己的情况修改，然后配置两个mk文件：   &lt;br /&gt;* Android.mk&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;LOCAL_PATH := $(call my-dir)

#把这个路径改成你自己刚才编译的install路径，用全路径！
NCNN_INSTALL_PATH := ncnn-master/build-android/install

include $(CLEAR_VARS)
LOCAL_MODULE := ncnn
LOCAL_SRC_FILES := $(NCNN_INSTALL_PATH)/lib/libncnn.a
include $(PREBUILT_STATIC_LIBRARY)

include $(CLEAR_VARS)

LOCAL_MODULE := demo
#这个是你的cpp文件
LOCAL_SRC_FILES := demo.cpp

LOCAL_C_INCLUDES := $(NCNN_INSTALL_PATH)/include

LOCAL_STATIC_LIBRARIES := ncnn

LOCAL_CFLAGS := -O2 -fvisibility=hidden -fomit-frame-pointer -fstrict-aliasing -ffunction-sections -fdata-sections -ffast-math
LOCAL_CPPFLAGS := -O2 -fvisibility=hidden -fvisibility-inlines-hidden -fomit-frame-pointer -fstrict-aliasing -ffunction-sections -fdata-sections -ffast-math
LOCAL_LDFLAGS += -Wl,--gc-sections

LOCAL_CFLAGS += -fopenmp
LOCAL_CPPFLAGS += -fopenmp
LOCAL_LDFLAGS += -fopenmp

LOCAL_LDLIBS := -lz -llog -ljnigraphics

include $(BUILD_SHARED_LIBRARY)
&lt;/code&gt;  &lt;ul&gt;   &lt;li&gt;1&lt;/li&gt;   &lt;li&gt;2&lt;/li&gt;   &lt;li&gt;3&lt;/li&gt;   &lt;li&gt;4&lt;/li&gt;   &lt;li&gt;5&lt;/li&gt;   &lt;li&gt;6&lt;/li&gt;   &lt;li&gt;7&lt;/li&gt;   &lt;li&gt;8&lt;/li&gt;   &lt;li&gt;9&lt;/li&gt;   &lt;li&gt;10&lt;/li&gt;   &lt;li&gt;11&lt;/li&gt;   &lt;li&gt;12&lt;/li&gt;   &lt;li&gt;13&lt;/li&gt;   &lt;li&gt;14&lt;/li&gt;   &lt;li&gt;15&lt;/li&gt;   &lt;li&gt;16&lt;/li&gt;   &lt;li&gt;17&lt;/li&gt;   &lt;li&gt;18&lt;/li&gt;   &lt;li&gt;19&lt;/li&gt;   &lt;li&gt;20&lt;/li&gt;   &lt;li&gt;21&lt;/li&gt;   &lt;li&gt;22&lt;/li&gt;   &lt;li&gt;23&lt;/li&gt;   &lt;li&gt;24&lt;/li&gt;   &lt;li&gt;25&lt;/li&gt;   &lt;li&gt;26&lt;/li&gt;   &lt;li&gt;27&lt;/li&gt;   &lt;li&gt;28&lt;/li&gt;   &lt;li&gt;29&lt;/li&gt;   &lt;li&gt;30&lt;/li&gt;   &lt;li&gt;31&lt;/li&gt;   &lt;li&gt;32&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt; &lt;ul&gt;  &lt;li&gt;Application.mk&lt;/li&gt;&lt;/ul&gt; &lt;pre&gt;  &lt;code&gt;
# APP_STL := stlport_static
APP_STL := gnustl_static
# APP_ABI := armeabi armeabi-v7a
APP_ABI := armeabi-v7a
APP_PLATFORM := android-9
#NDK_TOOLCHAIN_VERSION := 4.9
&lt;/code&gt;  &lt;ul&gt;   &lt;li&gt;1&lt;/li&gt;   &lt;li&gt;2&lt;/li&gt;   &lt;li&gt;3&lt;/li&gt;   &lt;li&gt;4&lt;/li&gt;   &lt;li&gt;5&lt;/li&gt;   &lt;li&gt;6&lt;/li&gt;   &lt;li&gt;7&lt;/li&gt;   &lt;li&gt;8&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt; &lt;p&gt;这两个.mk文件我的建议是复制粘贴到你的项目里，只改动必要的文件路径，其余的参数别动，除非你直到你改的意思是什么～   &lt;br /&gt;因为ndk的原理是使用  &lt;a href="https://www.baidu.com/s?wd=java%E6%8E%A5%E5%8F%A3&amp;tn=24004469_oem_dg&amp;rsv_dl=gh_pl_sl_csd" target="_blank"&gt;java接口&lt;/a&gt;调用c代码，所以我们需要进行java接口的编写，给出一个示例代码：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt;public class Ncnn
{
    public native boolean InitNcnn(String gestureDetectionModelPath);


    public native  void Detect(float[] i,float[] q,float []scores,int[] a);


    static {
        System.loadLibrary(&amp;quot;demo&amp;quot;);
    }



}
&lt;/code&gt;  &lt;ul&gt;   &lt;li&gt;1&lt;/li&gt;   &lt;li&gt;2&lt;/li&gt;   &lt;li&gt;3&lt;/li&gt;   &lt;li&gt;4&lt;/li&gt;   &lt;li&gt;5&lt;/li&gt;   &lt;li&gt;6&lt;/li&gt;   &lt;li&gt;7&lt;/li&gt;   &lt;li&gt;8&lt;/li&gt;   &lt;li&gt;9&lt;/li&gt;   &lt;li&gt;10&lt;/li&gt;   &lt;li&gt;11&lt;/li&gt;   &lt;li&gt;12&lt;/li&gt;   &lt;li&gt;13&lt;/li&gt;   &lt;li&gt;14&lt;/li&gt;   &lt;li&gt;15&lt;/li&gt;   &lt;li&gt;16&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt; &lt;p&gt;一共两个方法，一个是初始化，一个是执行预测，需要注意的是初始化方法调用的时候需要传入一个二进制文件路径的参数，大概思路是把bin和param文件拷贝到手机上然后让c代码读取，这里给出模板代码：&lt;/p&gt; &lt;pre&gt;  &lt;code&gt; private void IniteNcnn() throws IOException {


        ncnn = new Ncnn();//实例化上面的java接口类


        try {
            copyBigDataToSD(&amp;quot;demo.bin&amp;quot;);

            copyBigDataToSD(&amp;quot;demo.param&amp;quot;);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //模型初始化
        File sdDir = Environment.getExternalStorageDirectory();//获取跟目录
        String sdPath = sdDir.toString() + &amp;quot;/gesturencnn/&amp;quot;;
        boolean b = ncnn.InitNcnn(sdPath);


    }


    private void copyBigDataToSD(String strOutFileName) throws IOException {

        File sdDir = Environment.getExternalStorageDirectory();
        File file = new File(sdDir.toString() + &amp;quot;/demo/&amp;quot;);
        if (!file.exists()) {
            file.mkdir();
        }

        String tmpFile = sdDir.toString() + &amp;quot;/demo/&amp;quot; + strOutFileName;
        File f = new File(tmpFile);
        if (f.exists()) {
            return;
        }
        InputStream myInput;
        java.io.OutputStream myOutput = new FileOutputStream(sdDir.toString() + &amp;quot;/demo/&amp;quot; + strOutFileName);
        myInput = context.getAssets().open(strOutFileName);
        byte[] buffer = new byte[1024];
        int length = myInput.read(buffer);
        while (length &amp;gt; 0) {
            myOutput.write(buffer, 0, length);
            length = myInput.read(buffer);
        }
        myOutput.flush();
        myInput.close();
        myOutput.close();

    }

&lt;/code&gt;  &lt;ul&gt;   &lt;li&gt;1&lt;/li&gt;   &lt;li&gt;2&lt;/li&gt;   &lt;li&gt;3&lt;/li&gt;   &lt;li&gt;4&lt;/li&gt;   &lt;li&gt;5&lt;/li&gt;   &lt;li&gt;6&lt;/li&gt;   &lt;li&gt;7&lt;/li&gt;   &lt;li&gt;8&lt;/li&gt;   &lt;li&gt;9&lt;/li&gt;   &lt;li&gt;10&lt;/li&gt;   &lt;li&gt;11&lt;/li&gt;   &lt;li&gt;12&lt;/li&gt;   &lt;li&gt;13&lt;/li&gt;   &lt;li&gt;14&lt;/li&gt;   &lt;li&gt;15&lt;/li&gt;   &lt;li&gt;16&lt;/li&gt;   &lt;li&gt;17&lt;/li&gt;   &lt;li&gt;18&lt;/li&gt;   &lt;li&gt;19&lt;/li&gt;   &lt;li&gt;20&lt;/li&gt;   &lt;li&gt;21&lt;/li&gt;   &lt;li&gt;22&lt;/li&gt;   &lt;li&gt;23&lt;/li&gt;   &lt;li&gt;24&lt;/li&gt;   &lt;li&gt;25&lt;/li&gt;   &lt;li&gt;26&lt;/li&gt;   &lt;li&gt;27&lt;/li&gt;   &lt;li&gt;28&lt;/li&gt;   &lt;li&gt;29&lt;/li&gt;   &lt;li&gt;30&lt;/li&gt;   &lt;li&gt;31&lt;/li&gt;   &lt;li&gt;32&lt;/li&gt;   &lt;li&gt;33&lt;/li&gt;   &lt;li&gt;34&lt;/li&gt;   &lt;li&gt;35&lt;/li&gt;   &lt;li&gt;36&lt;/li&gt;   &lt;li&gt;37&lt;/li&gt;   &lt;li&gt;38&lt;/li&gt;   &lt;li&gt;39&lt;/li&gt;   &lt;li&gt;40&lt;/li&gt;   &lt;li&gt;41&lt;/li&gt;   &lt;li&gt;42&lt;/li&gt;   &lt;li&gt;43&lt;/li&gt;   &lt;li&gt;44&lt;/li&gt;   &lt;li&gt;45&lt;/li&gt;   &lt;li&gt;46&lt;/li&gt;   &lt;li&gt;47&lt;/li&gt;   &lt;li&gt;48&lt;/li&gt;   &lt;li&gt;49&lt;/li&gt;   &lt;li&gt;50&lt;/li&gt;   &lt;li&gt;51&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt; &lt;p&gt;直接在你需要的地方调用IniteNcnn（）就可以了，需要注意的是要  &lt;strong&gt;进行内存卡读取权限的申请&lt;/strong&gt;哦&lt;/p&gt; &lt;p&gt;然后将你上一篇教程写的demo.cpp放在jni目录下，对里面的代码进行必要的修改，主要需要实现模型的初始化和执行预测两个函数，初始化这里给出一个模板，至于执行预测的函数直接写一个对应函数然后调用你之前写好的c代码就可以了：&lt;/p&gt; &lt;ul&gt;  &lt;li&gt;初始化    &lt;br /&gt;这个函数是通用的，建议全部复制粘贴，具体对应的java接口代码后面会介绍&lt;/li&gt;&lt;/ul&gt; &lt;pre&gt;  &lt;code&gt;
extern &amp;quot;C&amp;quot;{

#注意把这里的函数名改成你自己对应的，一定不能错！
JNIEXPORT jboolean JNICALL
Java_com_example_dmrf_JniClass_Ncnn_InitNcnn(JNIEnv *env, jobject instance,
                                                                    jstring DetectionModelPath_) {


    const char *DetectionModelPath = env-&amp;gt;GetStringUTFChars(DetectionModelPath_, 0);
    if (NULL == DetectionModelPath) {
        return false;
    }

    string tModelDir = DetectionModelPath;
    string tLastChar = tModelDir.substr(tModelDir.length() - 1, 1);


    if (&amp;quot;\\&amp;quot; == tLastChar) {
        tModelDir = tModelDir.substr(0, tModelDir.length() - 1) + &amp;quot;/&amp;quot;;
    } else if (tLastChar != &amp;quot;/&amp;quot;) {
        tModelDir += &amp;quot;/&amp;quot;;
    }


    std::vector&amp;lt;std::string&amp;gt; param_files;
    param_files.resize(1);
    param_files[0] = tModelDir + &amp;quot;/demo.param&amp;quot;;

    std::vector&amp;lt;std::string&amp;gt; bin_files;
    bin_files.resize(1);
    bin_files[0] = tModelDir + &amp;quot;/demo.bin&amp;quot;;


    squeezenet.load_param(param_files[0].data());
    squeezenet.load_model(bin_files[0].data());

    env-&amp;gt;ReleaseStringUTFChars(DetectionModelPath_, DetectionModelPath);

    return true;
}


&lt;/code&gt;  &lt;ul&gt;   &lt;li&gt;1&lt;/li&gt;   &lt;li&gt;2&lt;/li&gt;   &lt;li&gt;3&lt;/li&gt;   &lt;li&gt;4&lt;/li&gt;   &lt;li&gt;5&lt;/li&gt;   &lt;li&gt;6&lt;/li&gt;   &lt;li&gt;7&lt;/li&gt;   &lt;li&gt;8&lt;/li&gt;   &lt;li&gt;9&lt;/li&gt;   &lt;li&gt;10&lt;/li&gt;   &lt;li&gt;11&lt;/li&gt;   &lt;li&gt;12&lt;/li&gt;   &lt;li&gt;13&lt;/li&gt;   &lt;li&gt;14&lt;/li&gt;   &lt;li&gt;15&lt;/li&gt;   &lt;li&gt;16&lt;/li&gt;   &lt;li&gt;17&lt;/li&gt;   &lt;li&gt;18&lt;/li&gt;   &lt;li&gt;19&lt;/li&gt;   &lt;li&gt;20&lt;/li&gt;   &lt;li&gt;21&lt;/li&gt;   &lt;li&gt;22&lt;/li&gt;   &lt;li&gt;23&lt;/li&gt;   &lt;li&gt;24&lt;/li&gt;   &lt;li&gt;25&lt;/li&gt;   &lt;li&gt;26&lt;/li&gt;   &lt;li&gt;27&lt;/li&gt;   &lt;li&gt;28&lt;/li&gt;   &lt;li&gt;29&lt;/li&gt;   &lt;li&gt;30&lt;/li&gt;   &lt;li&gt;31&lt;/li&gt;   &lt;li&gt;32&lt;/li&gt;   &lt;li&gt;33&lt;/li&gt;   &lt;li&gt;34&lt;/li&gt;   &lt;li&gt;35&lt;/li&gt;   &lt;li&gt;36&lt;/li&gt;   &lt;li&gt;37&lt;/li&gt;   &lt;li&gt;38&lt;/li&gt;   &lt;li&gt;39&lt;/li&gt;   &lt;li&gt;40&lt;/li&gt;   &lt;li&gt;41&lt;/li&gt;   &lt;li&gt;42&lt;/li&gt;   &lt;li&gt;43&lt;/li&gt;   &lt;li&gt;44&lt;/li&gt;&lt;/ul&gt;&lt;/pre&gt; &lt;p&gt;至此所有代码已经编写完毕，然后就可以build了，步骤如下：   &lt;br /&gt;cd到src/main/jni目录下，执行ndk-build，然后就会生成.so文件，然后你就可以干你的后序工作了。&lt;/p&gt;&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/59288-ncnn-android-dmrfcoder</guid>
      <pubDate>Sun, 17 Feb 2019 17:43:13 CST</pubDate>
    </item>
    <item>
      <title>MobileNetSSD通过Ncnn前向推理框架在Android端的使用--Cmake编译(目标检测 objection detection)补充篇章(多目标也可以显示) - Che_Hongshu - CSDN博客</title>
      <link>https://itindex.net/detail/59266-mobilenetssd-ncnn-%E6%8E%A8%E7%90%86</link>
      <description>&lt;div&gt;    &lt;h2&gt;      &lt;a&gt;&lt;/a&gt;一、前言&lt;/h2&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;a href="https://blog.csdn.net/qq_33431368/article/details/84674318" rel="nofollow"&gt;安装win10+linux-Ubuntu16.04的双系统（超细致）&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;        &lt;a href="https://blog.csdn.net/qq_33431368/article/details/84717053" rel="nofollow"&gt;ubuntu16.04+caffe＋CUDA10.0+cudnn7.4+opencv2.4.9.1+python2.7　（超超细致）&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;        &lt;a href="https://blog.csdn.net/qq_33431368/article/details/84866166" rel="nofollow"&gt;Caffe_ssd安装以及利用VOC2012,VOC2007数据集测试VGG_SSD网络&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;        &lt;a href="https://blog.csdn.net/qq_33431368/article/details/84977194" rel="nofollow"&gt;Caffe实现MobileNetSSD以及各个文件的具体解释，利用自己的数据集dataset训练MobileNetSSD建立模型&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;p&gt;推荐先把下面这两篇看完再来看这补充的一篇&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;        &lt;a href="https://blog.csdn.net/qq_33431368/article/details/84990390" rel="nofollow"&gt;MobileNetSSD通过Ncnn前向推理框架在PC端的使用(目标检测 objection detection)&lt;/a&gt;&lt;/li&gt;      &lt;li&gt;        &lt;a href="https://blog.csdn.net/qq_33431368/article/details/85009758" rel="nofollow"&gt;MobileNetSSD通过Ncnn前向推理框架在Android端的使用–Cmake编译(目标检测 objection detection)&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;    &lt;h2&gt;      &lt;a&gt;&lt;/a&gt;二、需要修改NDK部分代码以及java部分代码即可(下面有具体板块解释修改)&lt;/h2&gt;    &lt;p&gt;MobileNetssd.cpp文件修改在后面的Detect函数&lt;/p&gt;    &lt;pre&gt;// public native String Detect(Bitmap bitmap);
JNIEXPORT jfloatArray JNICALL Java_com_example_che_mobilenetssd_1demo_MobileNetssd_Detect(JNIEnv* env, jobject thiz, jobject bitmap)
{
    // ncnn from bitmap
    ncnn::Mat in;
    {
        AndroidBitmapInfo info;
        AndroidBitmap_getInfo(env, bitmap, &amp;amp;info);
//        int origin_w = info.width;
//        int origin_h = info.height;
//        int width = 300;
//        int height = 300;
        int width = info.width;
        int height = info.height;
        if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)
            return NULL;

        void* indata;
        AndroidBitmap_lockPixels(env, bitmap, &amp;amp;indata);
        // 把像素转换成data，并指定通道顺序
        // 因为图像预处理每个网络层输入的数据格式不一样一般为300*300 128*128等等所以这类需要一个resize的操作可以在cpp中写，也可以是java读入图片时有个resize操作
//      in = ncnn::Mat::from_pixels_resize((const unsigned char*)indata, ncnn::Mat::PIXEL_RGBA2RGB, origin_w, origin_h, width, height);

        in = ncnn::Mat::from_pixels((const unsigned char*)indata, ncnn::Mat::PIXEL_RGBA2RGB, width, height);

        // 下面一行为debug代码
        //__android_log_print(ANDROID_LOG_DEBUG, &amp;quot;MobilenetssdJniIn&amp;quot;, &amp;quot;Mobilenetssd_predict_has_input1, in.w: %d; in.h: %d&amp;quot;, in.w, in.h);
        AndroidBitmap_unlockPixels(env, bitmap);
    }

    // ncnn_net
    std::vector&amp;lt;float&amp;gt; cls_scores;
    {
        // 减去均值和乘上比例（这个数据和前面的归一化图片预处理形式一一对应）
        const float mean_vals[3] = {127.5f, 127.5f, 127.5f};
        const float scale[3] = {0.007843f, 0.007843f, 0.007843f};

        in.substract_mean_normalize(mean_vals, scale);// 归一化

        ncnn::Extractor ex = ncnn_net.create_extractor();//前向传播

        // 如果不加密是使用ex.input(&amp;quot;data&amp;quot;, in);
        // BLOB_data在id.h文件中可见，相当于datainput网络层的id
        ex.input(MobileNetSSD_deploy_param_id::BLOB_data, in);
        //ex.set_num_threads(4); 和上面一样一个对象

        ncnn::Mat out;
        // 如果时不加密是使用ex.extract(&amp;quot;prob&amp;quot;, out);
        //BLOB_detection_out.h文件中可见，相当于dataout网络层的id,输出检测的结果数据
        ex.extract(MobileNetSSD_deploy_param_id::BLOB_detection_out, out);

        int output_wsize = out.w;
        int output_hsize = out.h;

        //输出整理
        jfloat *output[output_wsize * output_hsize];   // float类型
        for(int i = 0; i&amp;lt; out.h; i++) {
            for (int j = 0; j &amp;lt; out.w; j++) {
                output[i*output_wsize + j] = &amp;amp;out.row(i)[j];
            }
        }
        //建立float数组 长度为 output_wsize * output_hsize,如果只是ouput_size相当于只有一行的out的数据那就是一个object检测数据
        jfloatArray jOutputData = env-&amp;gt;NewFloatArray(output_wsize * output_hsize);
        if (jOutputData == nullptr) return nullptr;
        env-&amp;gt;SetFloatArrayRegion(jOutputData, 0,  output_wsize * output_hsize,
                                 reinterpret_cast&amp;lt;const jfloat *&amp;gt;(*output));
        return jOutputData;
    }
}
   &lt;br /&gt;&lt;/pre&gt;    &lt;p&gt;MainActivity.java修改后后为&lt;/p&gt;    &lt;pre&gt;package com.example.che.mobilenetssd_demo;

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;


public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getName();
    private static final int USE_PHOTO = 1001;
    private String camera_image_path;
    private ImageView show_image;
    private TextView result_text;
    private boolean load_result = false;
    private int[] ddims = {1, 3, 300, 300}; //这里的维度的值要和train model的input 一一对应
    private int model_index = 1;
    private List&amp;lt;String&amp;gt; resultLabel = new ArrayList&amp;lt;&amp;gt;();
    private MobileNetssd mobileNetssd = new MobileNetssd(); //java接口实例化　下面直接利用java函数调用NDK c++函数

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try
        {
            initMobileNetSSD();
        } catch (IOException e) {
            Log.e(&amp;quot;MainActivity&amp;quot;, &amp;quot;initMobileNetSSD error&amp;quot;);
        }
        init_view();
        readCacheLabelFromLocalFile();
}

    /**
     *
     * MobileNetssd初始化，也就是把model文件进行加载
     */
    private void initMobileNetSSD() throws IOException {
        byte[] param = null;
        byte[] bin = null;
        {
            //用io流读取二进制文件，最后存入到byte[]数组中
            InputStream assetsInputStream = getAssets().open(&amp;quot;MobileNetSSD_deploy.param.bin&amp;quot;);// param：  网络结构文件
            int available = assetsInputStream.available();
            param = new byte[available];
            int byteCode = assetsInputStream.read(param);
            assetsInputStream.close();
        }
        {
            //用io流读取二进制文件，最后存入到byte上，转换为int型
            InputStream assetsInputStream = getAssets().open(&amp;quot;MobileNetSSD_deploy.bin&amp;quot;);//bin：   model文件
            int available = assetsInputStream.available();
            bin = new byte[available];
            int byteCode = assetsInputStream.read(bin);
            assetsInputStream.close();
        }

        load_result = mobileNetssd.Init(param, bin);// 再将文件传入java的NDK接口(c++ 代码中的init接口 )
        Log.d(&amp;quot;load model&amp;quot;, &amp;quot;MobileNetSSD_load_model_result:&amp;quot; + load_result);
    }


    // initialize view
    private void init_view() {
        request_permissions();
        show_image = (ImageView) findViewById(R.id.show_image);
        result_text = (TextView) findViewById(R.id.result_text);
        result_text.setMovementMethod(ScrollingMovementMethod.getInstance());
        Button use_photo = (Button) findViewById(R.id.use_photo);
        // use photo click
        use_photo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!load_result) {
                    Toast.makeText(MainActivity.this, &amp;quot;never load model&amp;quot;, Toast.LENGTH_SHORT).show();
                    return;
                }
                PhotoUtil.use_photo(MainActivity.this, USE_PHOTO);
            }
        });
    }

    // load label&amp;apos;s name
    private void readCacheLabelFromLocalFile() {
        try {
            AssetManager assetManager = getApplicationContext().getAssets();
            BufferedReader reader = new BufferedReader(new InputStreamReader(assetManager.open(&amp;quot;words.txt&amp;quot;)));//这里是label的文件
            String readLine = null;
            while ((readLine = reader.readLine()) != null) {
                resultLabel.add(readLine);
            }
            reader.close();
        } catch (Exception e) {
            Log.e(&amp;quot;labelCache&amp;quot;, &amp;quot;error &amp;quot; + e);
        }
    }


    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        String image_path;
        RequestOptions options = new RequestOptions().skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE);
        if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {
                case USE_PHOTO:
                    if (data == null) {
                        Log.w(TAG, &amp;quot;user photo data is null&amp;quot;);
                        return;
                    }
                    Uri image_uri = data.getData();

                    //Glide.with(MainActivity.this).load(image_uri).apply(options).into(show_image);

                    // get image path from uri
                    image_path = PhotoUtil.get_path_from_URI(MainActivity.this, image_uri);
                    // predict image
                    predict_image(image_path);
                    break;
            }
        }
    }

    //  predict image
    private void predict_image(String image_path) {
        // picture to float array
        Bitmap bmp = PhotoUtil.getScaleBitmap(image_path);
        Bitmap rgba = bmp.copy(Bitmap.Config.ARGB_8888, true);
        // resize
        Bitmap input_bmp = Bitmap.createScaledBitmap(rgba, ddims[2], ddims[3], false);
        try {
            // Data format conversion takes too long
            // Log.d(&amp;quot;inputData&amp;quot;, Arrays.toString(inputData));
            long start = System.currentTimeMillis();
            // get predict result
            float[] result = mobileNetssd.Detect(input_bmp);
            // time end
            long end = System.currentTimeMillis();
            Log.d(TAG, &amp;quot;origin predict result:&amp;quot; + Arrays.toString(result));
            long time = end - start;
            Log.d(&amp;quot;result length&amp;quot;, &amp;quot;length of result: &amp;quot; + String.valueOf(result.length));
            // show predict result and time
            // float[] r = get_max_result(result);

            String show_text = &amp;quot;result：&amp;quot; + Arrays.toString(result) + &amp;quot;\nname：&amp;quot; + resultLabel.get((int) result[0]) + &amp;quot;\nprobability：&amp;quot; + result[1] + &amp;quot;\ntime：&amp;quot; + time + &amp;quot;ms&amp;quot; ;
            result_text.setText(show_text);

            // 画布配置
            Canvas canvas = new Canvas(rgba);
            //图像上画矩形
            Paint paint = new Paint();
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.STROKE);//不填充
            paint.setStrokeWidth(5); //线的宽度


            float get_finalresult[][] = TwoArry(result);
            Log.d(&amp;quot;zhuanhuan&amp;quot;,get_finalresult+&amp;quot;&amp;quot;);
            int object_num = 0;
            int num = result.length/6;// number of object
            //continue to draw rect
            for(object_num = 0; object_num &amp;lt; num; object_num++){
                Log.d(TAG, &amp;quot;haha :&amp;quot; + Arrays.toString(get_finalresult));
                // 画框
                paint.setColor(Color.RED);
                paint.setStyle(Paint.Style.STROKE);//不填充
                paint.setStrokeWidth(5); //线的宽度
                canvas.drawRect(get_finalresult[object_num][2] * rgba.getWidth(), get_finalresult[object_num][3] * rgba.getHeight(),
                        get_finalresult[object_num][4] * rgba.getWidth(), get_finalresult[object_num][5] * rgba.getHeight(), paint);

                paint.setColor(Color.YELLOW);
                paint.setStyle(Paint.Style.FILL);//不填充
                paint.setStrokeWidth(1); //线的宽度
                canvas.drawText(resultLabel.get((int) get_finalresult[object_num][0]) + &amp;quot;\n&amp;quot; + get_finalresult[object_num][1],
                        get_finalresult[object_num][2]*rgba.getWidth(),get_finalresult[object_num][3]*rgba.getHeight(),paint);
            }

            show_image.setImageBitmap(rgba);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //一维数组转化为二维数组(自己新写的)
    public static float[][] TwoArry(float[] inputfloat){
        int n = inputfloat.length;
        int num = inputfloat.length/6;
        float[][] outputfloat = new float[num][6];
        int k = 0;
        for(int i = 0; i &amp;lt; num ; i++)
        {
            int j = 0;

            while(j&amp;lt;6)
            {
                outputfloat[i][j] =  inputfloat[k];
                k++;
                j++;
            }

        }

        return outputfloat;
    }

    /*
    // get max probability label
    private float[] get_max_result(float[] result) {
        int num_rs = result.length / 6;
        float maxProp = result[1];
        int maxI = 0;
        for(int i = 1; i&amp;lt;num_rs;i++){
            if(maxProp&amp;lt;result[i*6+1]){
                maxProp = result[i*6+1];
                maxI = i;
            }
        }
        float[] ret = {0,0,0,0,0,0};
        for(int j=0;j&amp;lt;6;j++){
            ret[j] = result[maxI*6 + j];
        }
        return ret;
    }
    */
    // request permissions(add)
    private void request_permissions() {
        List&amp;lt;String&amp;gt; permissionList = new ArrayList&amp;lt;&amp;gt;();
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            permissionList.add(Manifest.permission.CAMERA);
        }
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            permissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
        }
        // if list is not empty will request permissions
        if (!permissionList.isEmpty()) {
            ActivityCompat.requestPermissions(this, permissionList.toArray(new String[permissionList.size()]), 1);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1:
                if (grantResults.length &amp;gt; 0) {
                    for (int i = 0; i &amp;lt; grantResults.length; i++) {
                        int grantResult = grantResults[i];
                        if (grantResult == PackageManager.PERMISSION_DENIED) {
                            String s = permissions[i];
                            Toast.makeText(this, s + &amp;quot;permission was denied&amp;quot;, Toast.LENGTH_SHORT).show();
                        }
                    }
                }
                break;
        }
    }



}
   &lt;br /&gt;&lt;/pre&gt;    &lt;h2&gt;      &lt;a&gt;&lt;/a&gt;三、运行结果&lt;/h2&gt;    &lt;p&gt;      &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20181215223415794.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNDMxMzY4,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;      &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20181215223519778.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNDMxMzY4,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;      &lt;img alt="&amp;#22312;&amp;#36825;&amp;#37324;&amp;#25554;&amp;#20837;&amp;#22270;&amp;#29255;&amp;#25551;&amp;#36848;" src="https://img-blog.csdnimg.cn/20181215223543145.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNDMxMzY4,size_16,color_FFFFFF,t_70"&gt;&lt;/img&gt;&lt;/p&gt;    &lt;h2&gt;      &lt;a&gt;&lt;/a&gt;具体解释&lt;/h2&gt;    &lt;p&gt;.cpp端不需要过多解释给出的代码有相应的注释      &lt;br /&gt;.java中需要解释一些      &lt;br /&gt;首先通过前向推理最后输出的数据格式为      &lt;br /&gt;6位，　label+概率(置信度)+左＋上＋右＋下      &lt;br /&gt;当然最后你可以在上图中也发现为小数，其实他是为对应的长或宽的比例      &lt;br /&gt;比如上图result 中的现实的数据前六位      &lt;br /&gt;13.0 0.99939597 0.40525293 0.18877083 0.839892715 0.943314&lt;/p&gt;    &lt;ul&gt;      &lt;li&gt;13.0就是对应所有label之前在label文件中定义过的,0为backgroud以此列推&lt;/li&gt;      &lt;li&gt;0.999就为概率&lt;/li&gt;      &lt;li&gt;0.4则为对应　宽长×0.4的位置&lt;/li&gt;      &lt;li&gt;0.188 则对应于　高长×0.188的位置&lt;/li&gt;      &lt;li&gt;以此列推，这个时候再对应于前面的代码做一下消化就没问题了&lt;/li&gt;      &lt;li&gt;所以处理这种数据首先需要每6个做一下区分，所以也就有了下面我自己写的 public static float[][] TwoArry(float[] inputfloat)函数，回过头看一下一切舒服了        &lt;br /&gt;这边我又加入了一些颜色和框的粗细的改变以及框的旁边的txt文本label的添加&lt;/li&gt;&lt;/ul&gt;    &lt;h2&gt;      &lt;a&gt;&lt;/a&gt;代码地址&lt;/h2&gt;    &lt;p&gt;已在我的github上，希望大家给个star follow      &lt;br /&gt;      &lt;a href="https://github.com/chehongshu/ncnnforandroid_objectiondetection_Mobilenetssd/tree/master/MobileNetSSD_demo" rel="nofollow"&gt;https://github.com/chehongshu/ncnnforandroid_objectiondetection_Mobilenetssd/tree/master/MobileNetSSD_demo&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
    &lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category />
      <guid isPermaLink="true">https://itindex.net/detail/59266-mobilenetssd-ncnn-%E6%8E%A8%E7%90%86</guid>
      <pubDate>Fri, 01 Feb 2019 09:02:48 CST</pubDate>
    </item>
    <item>
      <title>Android取证：使用ADB和DD对文件系统做镜像</title>
      <link>https://itindex.net/detail/59204-android-adb-dd</link>
      <description>&lt;p&gt;  &lt;img alt="AndroidForensic.jpg" height="313" src="https://image.3001.net/images/20181231/1546219326_5c296f3e97828.jpg!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;从本文开始我将为大家带来一系列与数字取证相关的文章，并将重点关注移动设备方面的取证技术。在这篇文章中，我将为大家分享一些关于我对Android设备镜像采集的想法。在Android设备上，有两种我们可以执行的镜像采集类型：&lt;/strong&gt;&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;实时采集：在正在运行的设备上执行。通常，分析人员会使用各种工具获取root权限，并使用DD提取镜像；&lt;/p&gt;
  &lt;p&gt;死采集：在设备上执行启动到另一个状态。例如，如果设备安装了ClockwordMod，则分析人员可以重启设备恢复并获取root shell。&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;p&gt;在本文中我将为大家展示如何执行Android data分区的实时采集。&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;注意：&lt;/strong&gt;为了执行过程的顺利，设备必须已获取root权限。&lt;/p&gt;
 &lt;h2&gt;Root Android&lt;/h2&gt;
 &lt;p&gt;如今，root Android设备已成为一种非常普遍的现象，在我的取证工作中我经常会碰到已root的手机设备。此外，在某些情况下为了提取某些数据，取证人员必须要自己root设备。Root的过程特定于每个手机型号，Android版本以及内部版本号，因此想要成功root必须根据你的手机型号找到合适的工具。&lt;/p&gt;
 &lt;p&gt;大多数现代Android手机都可以使用一款名为KingoRoot的工具获取root权限，如果你的手机并不适用（bootloader锁定，Knox等），你可以在  &lt;a href="https://xda-developers.com/"&gt;XDA Developers&lt;/a&gt;上寻求帮助。&lt;/p&gt;
 &lt;p&gt;Android root软件有时会使用恶意软件重新打包一些可能不需要的程序，这可能会改变文件系统，必须在分析过程中进行过滤。因此，为了安全起见我建议大家，只在官方方法不起作用时再使用这些软件。&lt;/p&gt;
 &lt;h2&gt;镜像 /data 分区&lt;/h2&gt;
 &lt;p&gt;我们将使用“dd”工具来完成我们的工作。默认情况下，“dd”在Android的“/system/bin”目录下。&lt;/p&gt;
 &lt;p&gt;为了限制设备文件系统的更改，镜像将使用NetCat创建的隧道传输到工作站。&lt;/p&gt;
 &lt;p&gt;因此，root后的第一步必须先安装Busybox（  &lt;a href="https://www.appsapk.com/busybox-app/"&gt;在这里下载&lt;/a&gt;），这是一个集成了三百多个最常用Linux命令和工具的软件。&lt;/p&gt;
 &lt;p&gt;下载busybox Apk后，使用adb命令在设备上进行安装：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;adb -d install BusyBox.apk&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;然后，连接到手机并检查root访问权限：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;adb -d shell
ls /data
su
ls /data&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;我们使用  &lt;strong&gt;ls /data&lt;/strong&gt;来测试我们是否可以访问受保护的目录。&lt;/p&gt;
 &lt;p&gt;第一次运行它时，应该会出现失败的情况。我们使用  &lt;strong&gt;su&lt;/strong&gt;命令将用户切换到root。然后，再次使用  &lt;strong&gt;ls /data&lt;/strong&gt;命令进行测试看现在我们是否可以访问受保护的目录。&lt;/p&gt;
 &lt;p&gt;接下来，我们需要检查设备上已安装的分区：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;root@VF-895N:/ # mount
rootfs / rootfs ro,relatime 0 0
tmpfs /dev tmpfs rw,seclabel,nosuid,relatime,size=450904k,nr_inodes=112726,mode=755 0 0
devpts /dev/pts devpts rw,seclabel,relatime,mode=600 0 0
none /dev/cpuctl cgroup rw,relatime,cpu 0 0
adb /dev/usb-ffs/adb functionfs rw,relatime 0 0
proc /proc proc rw,relatime 0 0
sysfs /sys sysfs rw,seclabel,relatime 0 0
selinuxfs /sys/fs/selinux selinuxfs rw,relatime 0 0
debugfs /sys/kernel/debug debugfs rw,relatime 0 0
none /sys/fs/cgroup tmpfs rw,seclabel,relatime,size=450904k,nr_inodes=112726,mode=750,gid=1000 0 0
none /acct cgroup rw,relatime,cpuacct 0 0
tmpfs /mnt/asec tmpfs rw,seclabel,relatime,size=450904k,nr_inodes=112726,mode=755,gid=1000 0 0
tmpfs /mnt/obb tmpfs rw,seclabel,relatime,size=450904k,nr_inodes=112726,mode=755,gid=1000 0 0
/dev/block/bootdevice/by-name/system /system ext4 ro,seclabel,relatime,discard,data=ordered 0 0
/dev/block/bootdevice/by-name/userdata /data ext4 rw,seclabel,nosuid,nodev,relatime,discard,noauto_da_alloc,data=ordered 0 0
/dev/block/bootdevice/by-name/cache /cache ext4 rw,seclabel,nosuid,nodev,relatime,data=ordered 0 0
/dev/block/bootdevice/by-name/persist /persist ext4 rw,seclabel,nosuid,nodev,relatime,data=ordered 0 0
/dev/block/bootdevice/by-name/tctpersist /tctpersist ext4 rw,seclabel,nosuid,nodev,relatime,data=ordered 0 0
/dev/block/bootdevice/by-name/modem /firmware vfat ro,context=u:object_r:firmware_file:s0,relatime,uid=1000,gid=1000,fmask=0337,dmask=0227,codepage=437,iocharset=iso8859-1,shortname=lower,errors=remount-ro 0 0
/dev/fuse /storage/uicc1 fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
/dev/fuse /storage/uicc0 fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
/dev/fuse /mnt/shell/emulated fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
/dev/fuse /storage/usbotg fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
/dev/fuse /storage/sdcard0 fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0
/dev/block/vold/179:65 /mnt/media_rw/sdcard1 vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1023,gid=1023,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
/dev/block/vold/179:65 /mnt/secure/asec vfat rw,dirsync,nosuid,nodev,noexec,relatime,uid=1023,gid=1023,fmask=0007,dmask=0007,allow_utime=0020,codepage=437,iocharset=iso8859-1,shortname=mixed,utf8,errors=remount-ro 0 0
/dev/fuse /storage/sdcard1 fuse rw,nosuid,nodev,relatime,user_id=1023,group_id=1023,default_permissions,allow_other 0 0&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;我们重点来关注data分区，在本例中为“  &lt;strong&gt;/dev/block/bootdevice/by-name/userdata&lt;/strong&gt;”。&lt;/p&gt;
 &lt;p&gt;接下来，我们需要设置工作站和移动设备之间的连接路由，转发端口为8888。&lt;/p&gt;
 &lt;p&gt;在工作站上运行：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;adb forward tcp:8888 tcp:8888&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;现在让我们使用“dd”启动镜像过程并使用netcat管道传输数据。&lt;/p&gt;
 &lt;p&gt;在手机设备的root shell下运行：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;root@VF-895N:/ #dd if=/dev/block/bootdevice/by-name/userdata | busybox nc -l -p 8888&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;并在取证工作站上运行：&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;nc 127.0.0.1 8888 &amp;gt; android_data.dd&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;镜像过程完成后，将如下图所示：&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image-1.png" height="72" src="https://image.3001.net/images/20181231/1546219374_5c296f6ed0eba.png!small" width="550"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;然后，我们可以使用sleuthkit或Autopsy对镜像进行分析。&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="image-2.png" height="194" src="https://image.3001.net/images/20181231/1546219387_5c296f7b303e5.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;*参考来源：   &lt;a href="https://www.andreafortuna.org/dfir/android-forensics-imaging-android-file-system-using-adb-and-dd/"&gt;andreafortuna&lt;/a&gt;，FB小编secist编译，转载请注明来自FreeBuf.COM&lt;/strong&gt;    &lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>终端安全 adb Android取证</category>
      <guid isPermaLink="true">https://itindex.net/detail/59204-android-adb-dd</guid>
      <pubDate>Thu, 10 Jan 2019 15:00:34 CST</pubDate>
    </item>
    <item>
      <title>技术分享 | 看我如何使用TheFatRat黑掉你的Android手机</title>
      <link>https://itindex.net/detail/59001-%E6%8A%80%E6%9C%AF-%E5%88%86%E4%BA%AB-thefatrat</link>
      <description>&lt;p&gt;  &lt;strong&gt;严正声明：本文仅限于技术讨论和教育目的，严禁用于其他用途。&lt;/strong&gt;&lt;/p&gt;
 &lt;h2&gt;前言&lt;/h2&gt;
 &lt;p&gt;  &lt;strong&gt;在这篇文章中，我们将教大家如何使用TheFatRat这款工具。这款工具可以帮大家构建后门程序，以及执行后渗透利用攻击等等。在主流Payload的帮助下，这款工具可以生成质量非常高的恶意软件，而这些恶意软件可以在Windows、   &lt;a href="https://gbhackers.com/vault-7-leaks-cia-android-hacking-tool-highrise-steals-data-from-compromised-android-phones-via-sms-wikileaks/"&gt;Android&lt;/a&gt;和macOS平台上运行。&lt;/strong&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="TheFatRat" height="462.0175438596491" src="https://image.3001.net/images/20181110/1541819127_5be64af76d2f6.png!small" width="690"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;值得一提的是，TheFatRat所生成的恶意软件能够  &lt;a href="https://gbhackers.com/bypass-antivirus-using-payload/"&gt;绕过反病毒产品&lt;/a&gt;，而这将允许攻击者拿到Meterpreter会话。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;h2&gt;自动化Metasploit功能&lt;/h2&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;blockquote&gt;  &lt;p&gt;1. 针对Windows、Linux、Mac和Android创建后门；&lt;/p&gt;
  &lt;p&gt;2. 后门可绕过反病毒产品；&lt;/p&gt;
  &lt;p&gt;3. 检测/开启Metasploit服务；&lt;/p&gt;
  &lt;p&gt;4. 针对Windows、Linux、Android和macOS创建meterpreterreverse_tcp Payload；&lt;/p&gt;
  &lt;p&gt;5. 开启多个meterpreter reverse_tcp监听器；&lt;/p&gt;
  &lt;p&gt;6. Searchsploit快速搜索；&lt;/p&gt;
  &lt;p&gt;7. 绕过反病毒产品；&lt;/p&gt;
  &lt;p&gt;8. 使用其他技术创建后门；&lt;/p&gt;
  &lt;p&gt;9. 自动运行监听器脚本；&lt;/p&gt;
&lt;/blockquote&gt;
 &lt;h2&gt;工具下载&lt;/h2&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;下载地址：【  &lt;a href="https://github.com/Screetsec/TheFatRat"&gt;GitHub传送门&lt;/a&gt;】&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;下载命令：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;   &lt;p&gt;git clone     &lt;a href="https://github.com/Screetsec/TheFatRat.git"&gt;https://github.com/Screetsec/TheFatRat.git&lt;/a&gt;&lt;/p&gt;   &lt;p&gt;cd TheFatRat&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;接下来，我们需要提供执行权限并运行setup.sh。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;pre&gt;  &lt;code&gt;chmod +x setup.sh &amp;amp;&amp;amp; ./setup.sh&lt;/code&gt;&lt;/pre&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;安装过程大概需要10到15分钟，程序会自动检测缺失的组件，并自动完成依赖安装。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;安装完成之后，程序会显示一个Payload创建列表：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="Payload&amp;#21019;&amp;#24314;&amp;#21015;&amp;#34920;" src="https://image.3001.net/images/20181110/1541819155_5be64b139293a.png!small"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;由于这篇文章主要介绍如何在原始APK文件中加入后门，所以我们需要选择第5个选项。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#36873;&amp;#25321;&amp;#31532;5&amp;#20010;&amp;#36873;&amp;#39033;" src="https://image.3001.net/images/20181110/1541819163_5be64b1b7412c.png!small"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;与此同时，我们还需要下载一个Android应用程序安装文件-APK文件，然后输入文件路径。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;完成之后，我们要选择需要的Meterpreter，这里选择reverse_tcp。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="reverse_tcp" src="https://image.3001.net/images/20181110/1541819174_5be64b2696a01.png!small"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;接下来，你需要选择APK创建工具：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="APK&amp;#21019;&amp;#24314;&amp;#24037;&amp;#20855;" src="https://image.3001.net/images/20181110/1541819187_5be64b330cf01.png!small"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="APK&amp;#21019;&amp;#24314;&amp;#24037;&amp;#20855;" src="https://image.3001.net/images/20181110/1541819187_5be64b33a3a07.png!small"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;在APK的构建过程中，它会对原始APK进行反编译，并插入Payload，然后重新编译新的应用程序。它会使用混淆方法来嵌入Payload，并添加数字签名。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;创建完成之后，你就可以把APK文件发送给目标手机了。接下来，我们需要通过msfconsole来设置设置哦我们的meterpreter会话。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="meterpreter&amp;#20250;&amp;#35805;" src="https://image.3001.net/images/20181110/1541819204_5be64b448ada6.png!small"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;安装完成之后，你将会拿到meterpreter会话，然后完成设备的控制操作。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#35774;&amp;#22791;&amp;#30340;&amp;#25511;&amp;#21046;&amp;#25805;&amp;#20316;" src="https://image.3001.net/images/20181110/1541819212_5be64b4c969d9.png!small"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;输入“help”命令能够查看所有可执行的命令：&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#21487;&amp;#25191;&amp;#34892;&amp;#30340;&amp;#21629;&amp;#20196;" src="https://image.3001.net/images/20181110/1541819224_5be64b58362c8.png!small"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;你可以导出通话记录、通讯录、短信消息，伸直还可以远程截屏。&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;img alt="&amp;#36828;&amp;#31243;&amp;#25130;&amp;#23631;" src="https://image.3001.net/images/20181110/1541819235_5be64b631bc18.png!small"&gt;&lt;/img&gt;&lt;/p&gt;
 &lt;p&gt;&lt;/p&gt;
 &lt;p&gt;  &lt;strong&gt;* 参考来源：   &lt;a href="https://gbhackers.com/android-rat-kali-linux-tutorial/"&gt;gbhackers&lt;/a&gt;，FB小编Alpha_h4ck编译，转载请注明来自FreeBuf.COM&lt;/strong&gt;&lt;/p&gt;
&lt;div&gt; &lt;a href="https://itindex.net/"  title="IT 资讯"&gt;&lt;img src="https://itindex.net/images/iconWarning.gif" title="IT 资讯" border="0"/&gt; &lt;/a&gt;</description>
      <category>终端安全 Android TheFatRat</category>
      <guid isPermaLink="true">https://itindex.net/detail/59001-%E6%8A%80%E6%9C%AF-%E5%88%86%E4%BA%AB-thefatrat</guid>
      <pubDate>Sat, 24 Nov 2018 13:00:29 CST</pubDate>
    </item>
  </channel>
</rss>

