<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.8.5">Jekyll</generator><link href="https://rockyqu.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://rockyqu.github.io/" rel="alternate" type="text/html" /><updated>2019-07-20T05:17:45+00:00</updated><id>https://rockyqu.github.io/feed.xml</id><title type="html">Qu</title><subtitle>Android Engineer</subtitle><author><name>RockyQu</name><email>250533855@qq.com</email></author><entry><title type="html">RecyclerView 回收复用机制原理浅析</title><link href="https://rockyqu.github.io/recyclerview/2019/07/18/Android-RecyclerView-Multiplexing.html" rel="alternate" type="text/html" title="RecyclerView 回收复用机制原理浅析" /><published>2019-07-18T00:00:00+00:00</published><updated>2019-07-18T00:00:00+00:00</updated><id>https://rockyqu.github.io/recyclerview/2019/07/18/Android%20RecyclerView%20Multiplexing</id><content type="html" xml:base="https://rockyqu.github.io/recyclerview/2019/07/18/Android-RecyclerView-Multiplexing.html">&lt;hr /&gt;

&lt;h2 id=&quot;0x00&quot;&gt;0x00&lt;/h2&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x01&quot;&gt;0x01&lt;/h2&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x02&quot;&gt;0x02&lt;/h2&gt;</content><author><name>RockyQu</name><email>250533855@qq.com</email></author><category term="RecyclerView" /><summary type="html">RecyclerView 回收复用机制原理浅析</summary></entry><entry><title type="html">Android 使用 so 文件存储私密数据，并增加签名防盗机制</title><link href="https://rockyqu.github.io/android%20ndk/2018/11/18/Android-NDK-DecryptKey.html" rel="alternate" type="text/html" title="Android 使用 so 文件存储私密数据，并增加签名防盗机制" /><published>2018-11-18T00:00:00+00:00</published><updated>2018-11-18T00:00:00+00:00</updated><id>https://rockyqu.github.io/android%20ndk/2018/11/18/Android%20NDK%20DecryptKey</id><content type="html" xml:base="https://rockyqu.github.io/android%20ndk/2018/11/18/Android-NDK-DecryptKey.html">&lt;hr /&gt;

&lt;h2 id=&quot;0x00-实际项目中引出的一些需求问题&quot;&gt;0x00 实际项目中引出的一些需求问题&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;有时你需要在客户端存放一些保密的数据，比如某些授权 Key ，如果直接写在 Java 中，会很容易被反编译看到，那么我们可以把这些数据存在 so 文件中，来增加反编译难度，并且增加 APP 签名防盗机制来防止别人盗用 so 文件。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x01-一些准备工作&quot;&gt;0x01 一些准备工作&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;配置 NDK 开发环境，看这里 → &lt;a href=&quot;https://rockycoder.cn/android%20ndk/2018/01/18/Android-Studio-JNI-Exercise.html&quot;&gt;Android Studio NDK 开发安装配置&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;有必要的了解一下 NDK 开发基础（此例子使用的是 CMake）、最好学习一下 C/C++ 基础，不然代码看起来很费劲&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x02-编写配置文件javacc-代码&quot;&gt;0x02 编写配置文件、Java、C/C++ 代码&lt;/h2&gt;
&lt;p&gt;1、新建 JNIKey.class 并声明 native 方法&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public class JNIKey {

    static {
        System.loadLibrary(&quot;Key&quot;);
    }

    /**
     * 初始化并判断当前 APP 是否为合法应用，只需调用一次
     *
     * @return 返回 true 则初始化成功并当前 APP 为合法应用
     */
    public static native boolean init();

    /**
     * 获取 Key
     *
     * @return return key
     */
    public static native String getKey();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;2、Build → Rebuild Project 生成 class 文件，生成目录一般在如下目录&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;JNIKey\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes\me\key\protection\
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;3、根据生成的 class 文件生成 .h 文件，进入 Terminal 命令窗口输入以下命令&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;javah -d jni -classpath D:\Android\Workspace\JNIKeyProtection\Key\build\intermediates\javac\debug\compileDebugJavaWithJavac\classes me.key.protection.JNIKey
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;注意路径不要写错了，生成的 .h 文件里包含自动生成的一些方法，方法名称一一应对 Java native 方法，如果在 .cpp 代码里用的是动态注册的方式，这步可以忽略不做，因为动态注册方法名可以随便写，具体看 .cpp 里的代码&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;4、在 src\main 目录下新建 cpp 目录，新建 Key.cpp 文件在这里编写 C++ 代码，.cpp 表示 C++ 文件, .c 表示 C 文件&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/image/2018-11-18/2018-11-18_1.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;5、在 Module 根目录下新建 CmakeLists.txt 文件，配置 JNI 相关参数&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# 指定编译器版本
cmake_minimum_required(VERSION 3.4.1)

# 存放生成 so 库的目录
# set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI})

# 配置 so 库信息
add_library(

        # 生成的 so 库名称，此处生成的 so 文件名称是libKey.so
        Key

        # STATIC：静态库，是目标文件的归档文件，在链接其它目标的时候使用
        # SHARED：动态库，会被动态链接，在运行时被加载
        # MODULE：模块库，是不会被链接到其它目标中的插件，但是可能会在运行时使用dlopen-系列的函数动态链接
        SHARED

        # 资源文件，可以多个，资源路径是相对路径，相对于本CMakeLists.txt所在目录
        src/main/cpp/Key.cpp)

# 依赖 NDK 中的 log 日志库
find_library(
        log-lib
        log)

# 关联 log 库到本地库。如果你本地的库（DecryptKey）想要调用log库的方法，那么就需要配置这个属性
target_link_libraries(

        # 目标库
        Key

        # 依赖库
        ${log-lib})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;blockquote&gt;
  &lt;p&gt;注意是 Module 根目录，不是 Project 根目录&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;6、修改对应 Module 的 build.gradle 文件添加配置参数&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;defaultConfig 标签里添加如下配置&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;externalNativeBuild {
        cmake {
            cppFlags &quot;-frtti -fexceptions&quot;
        }
    }

    // 平台架构支持
    ndk {
        abiFilters 'arm64-v8a', 'armeabi', 'armeabi-v7a', 'x86', 'x86_64'
    }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;android 标签里添加如下配置&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;externalNativeBuild {
    cmake {
        path &quot;CMakeLists.txt&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;7、在 cpp 文件夹下新建 Key.cpp 文件里面放 c++ 代码&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;完整代码 → &lt;a href=&quot;https://github.com/RockyQu/JNIKeyProtection/blob/master/JNIKey/src/main/cpp/Key.cpp&quot;&gt;Key.cpp&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x03-调用生成的-so-文件&quot;&gt;0x03 调用生成的 .so 文件&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;默认 .so 文件生成目录，复制出来放到 libs 目录下，即可使用&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{项目目录}\JNIKey\build\intermediates\cmake\debug\obj\
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;你可以对生成的库文件封装一个 Jar 包，这样 .so 就不必放在特定的包下，Jar 包的生成必须要 Module 里，默认 Jar 包生成目录&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;{项目目录}\JNIKey\build\intermediates\intermediate-jars\debug\classes.jar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x04-完整开源项目--jnikeyprotection&quot;&gt;0x04 完整开源项目 → &lt;a href=&quot;https://github.com/RockyQu/JNIKeyProtection&quot;&gt;JNIKeyProtection&lt;/a&gt;&lt;/h2&gt;</content><author><name>RockyQu</name><email>250533855@qq.com</email></author><category term="AndroidStudio NDK" /><category term="NDK" /><category term="JNI" /><summary type="html">使用 .so 文件来存储 APP 静态私密数据，并且增加身份验证，防止 .so 被恶意调用</summary></entry><entry><title type="html">使用 FFmpeg 生成 ts 切片并使用 AES-128 加密</title><link href="https://rockyqu.github.io/ffmpeg/2018/10/26/Generate-encrypted-video.html" rel="alternate" type="text/html" title="使用 FFmpeg 生成 ts 切片并使用 AES-128 加密" /><published>2018-10-26T00:00:00+00:00</published><updated>2018-10-26T00:00:00+00:00</updated><id>https://rockyqu.github.io/ffmpeg/2018/10/26/Generate%20encrypted%20video</id><content type="html" xml:base="https://rockyqu.github.io/ffmpeg/2018/10/26/Generate-encrypted-video.html">&lt;hr /&gt;

&lt;h2 id=&quot;0x00-基本流程&quot;&gt;0x00 基本流程&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;目的：使用 FFmpeg 将一个 MP4 视频文件切割成多个 ts 切片，并在生成 ts 切片过程中对每一个切片使用 AES-128 加密，最后生成一个包含解密 Key 的 m3u8 视频播放索引文件。这些被加密的 ts 切片无法在其他地方播放，必须使用 m3u8 文件包含的解密 key 才可以。&lt;/li&gt;
  &lt;li&gt;工具：需要在 PC 配置 FFmpeg 环境，通过 Dos 命令行生成 ts 切片 + m3u8 配置文件，加密 key 文件需要配置 OpenSSL 来生成加密文件或者直接随便手写一段字符串也可以，区别是手写的是明文加密，使用 OpenSSL 生成的加密 key 文件是看不到明文的。&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x01-环境配置&quot;&gt;0x01 环境配置&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;FFmpeg &lt;a href=&quot;https://github.com/FFmpeg/FFmpeg&quot;&gt;GitHub&lt;/a&gt; &lt;a href=&quot;https://ffmpeg.org&quot;&gt;官网&lt;/a&gt;&lt;br /&gt;
下载地址 &lt;a href=&quot;https://ffmpeg.zeranoe.com/builds/&quot;&gt;https://ffmpeg.zeranoe.com/builds/&lt;/a&gt; 点击 [Download Build] 下载，也可以选择其他不同的版本&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;版本说明&lt;br /&gt;
1、Dev 是开发版本，里面包含有库文件（.lib）和头文件（.h），但是没有 exe 文件。&lt;br /&gt;
2、Shared 文件夹里面有 ffmpeg.exe、ffplay.exe、ffprobe.exe，除此之外还有一些dll文件，比如说avcodec-58.dll、avdevice-58.dll等。它的exe文件比较小，运行时需要调用dll的功能。&lt;br /&gt;
3、Static 文件夹里面只有三个 exe，dll文件被集成在 exe 里面了，所以文件比较大。我们使用这个版本就可以。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;配置 FFmpeg
下载完成解压进入 bin 目录，将 bin 目录配置到系统环境变量 Path 里即可
&lt;img src=&quot;/assets/image/2018-10-26/2018-10-26-Generate encrypted video 1.png&quot; alt=&quot;1&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;/assets/image/2018-10-26/2018-10-26-Generate encrypted video 2.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;配置 OpenSSL 这个是用来专门生成密码的，如果你是手写的可以不用&lt;br /&gt;
下载地址 &lt;a href=&quot;https://slproweb.com/products/Win32OpenSSL.html&quot;&gt;https://slproweb.com/products/Win32OpenSSL.html&lt;/a&gt;&lt;br /&gt;
安装成功后使用同样的方法，将 bin 目录配置到系统环境变量。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x02-使用-openssl-生成密钥&quot;&gt;0x02 使用 OpenSSL 生成密钥&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;生成密钥文件
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openssl rand 16 &amp;gt; [你的密钥文件路径] 例 D:\encrypt.key
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;生成 IV
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;openssl rand -hex 16
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x03-使用-ffmpeg-生成加密文件&quot;&gt;0x03 使用 FFmpeg 生成加密文件&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;一行命令同时生成 ts 切片，m3u8 索引播放文件并加密&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ffmpeg -y -i [原始视频文件路径] -c:v libx264 -c:a copy -f hls -hls_time 180 -hls_list_size 0 -hls_key_info_file [密钥文件路径] -hls_playlist_type vod -hls_segment_filename [切片文件路径] [索引文件路径]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;以上命令的部分参数说明&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;-y&lt;/td&gt;
      &lt;td&gt;直接覆盖已经存在的输出文件&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;-c&lt;/td&gt;
      &lt;td&gt;指定输入输出的解码编码器&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;-f&lt;/td&gt;
      &lt;td&gt;强制设置输入输出的文件格式，默认情况下ffmpeg会根据文件后缀名判断文件格式&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;-hls_time&lt;/td&gt;
      &lt;td&gt;指定生成 ts 视频切片的时间长度s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;-hls_list_size 0&lt;/td&gt;
      &lt;td&gt;索引播放列表的最大列数 默认5，0 为不限制&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;-hls_playlist_type vod&lt;/td&gt;
      &lt;td&gt;表示当前的视频流并不是一个直播流，而是点播流&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;-hls_segment_filename&lt;/td&gt;
      &lt;td&gt;输出 ts m3u8 文件路径&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;[原始文件路径] 例 D:\test.mp4&lt;/li&gt;
  &lt;li&gt;[密钥文件路径] 文件后缀名需为 .keyinfo 否则可能会找不到解密文件，例 D:\key.keyinfo&lt;/li&gt;
  &lt;li&gt;[切片文件路径] 例 D:\test_%03d.ts&lt;/li&gt;
  &lt;li&gt;[索引文件路径] 例 D:\test.m3u8&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;0x04-相关参数与格式说明&quot;&gt;0x04 相关参数与格式说明&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;m3u8 部分字段意义&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;#EXTM3U&lt;/td&gt;
      &lt;td&gt;每个M3U文件第一行必须是这个tag&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;#EXTINF:&lt;duration&gt;,&lt;title&gt;&lt;/title&gt;&lt;/duration&gt;&lt;/td&gt;
      &lt;td&gt;duration表示持续的时间（秒）必须是整数，如果版本在3以上可以是浮点数&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;#EXTINF&lt;/td&gt;
      &lt;td&gt;指定每个媒体段(ts)的持续时间，这个仅对其后面的URI有效，每两个媒体段URI间被这个tag分隔开&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;#EXT-X-TARGETDURATION&lt;/td&gt;
      &lt;td&gt;指定最大的媒体段时间长（秒）。所以#EXTINF中指定的时间长度必须小于或是等于这个最大值。这个tag在整个PlayList文件中只能出现一次（在嵌套的情况下，一般有真正ts url的m3u8才会出现该tag）  格式如下：#EXT-X-TARGETDURATION:&lt;s&gt;：s表示最大的秒数&lt;/s&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;#EXT-X-MEDIA-SEQUENCE&lt;/td&gt;
      &lt;td&gt;每一个media URI 在 PlayList中只有唯一的序号，相邻之间序号+1&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;#EXT-X-KEY&lt;/td&gt;
      &lt;td&gt;表示怎么对media segments进行解码。其作用范围是下次该tag出现前的所有media URI  格式如下：#EXT-X-KEY:&lt;attribute-list&gt;：NONE 或者 AES-128。如果是NONE，则URI以及IV属性必须不存在，如果是AES-128(Advanced Encryption Standard)，则URI必须存在，IV可以不存在。对于AES-128的情况，keytag和URI属性共同表示了一个key文件，通过URI可以获得这个key，如果没有IV（Initialization Vector）,则使用序列号作为IV进行编解码，将序列号的高位赋到16个字节的buffer中，左边补0；如果有IV，则将改值当成16个字节的16进制数。&lt;/attribute-list&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;#EXT-X-ALLOW-CACHE：&lt;/td&gt;
      &lt;td&gt;是否允许做cache，这个可以在PlayList文件中任意地方出现，并且最多出现一次，作用效果是所有的媒体段 格式如下：#EXT-X-ALLOW-CACHE:YES、NO&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;ul&gt;
  &lt;li&gt;key.keyinfo 文件规则，该文件内容一共为三行&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;第一行&lt;/td&gt;
      &lt;td&gt;decrypt.key 文件路径&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;第二行&lt;/td&gt;
      &lt;td&gt;加密 encrypt.key 文件路径&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;第三行&lt;/td&gt;
      &lt;td&gt;IV 非必须&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;解密与加密 key 文件可以使用同一个&lt;/p&gt;</content><author><name>RockyQu</name><email>250533855@qq.com</email></author><category term="FFmpeg" /><category term="encrypt" /><category term="video" /><summary type="html">使用 FFmpeg 生成 ts 切片并使用 AES-128 加密</summary></entry><entry><title type="html">2017 Google IO Architecture Components 架构组件 Room 使用教程</title><link href="https://rockyqu.github.io/architecture%20components/2018/06/02/2017-Google-IO-Architecture-Components-Room.html" rel="alternate" type="text/html" title="2017 Google IO Architecture Components 架构组件 Room 使用教程" /><published>2018-06-02T00:00:00+00:00</published><updated>2018-06-02T00:00:00+00:00</updated><id>https://rockyqu.github.io/architecture%20components/2018/06/02/2017%20Google%20IO%20Architecture%20Components%20Room</id><content type="html" xml:base="https://rockyqu.github.io/architecture%20components/2018/06/02/2017-Google-IO-Architecture-Components-Room.html">&lt;hr /&gt;

&lt;h2 id=&quot;0x00&quot;&gt;0x00&lt;/h2&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x01&quot;&gt;0x01&lt;/h2&gt;</content><author><name>RockyQu</name><email>250533855@qq.com</email></author><category term="Google IO" /><category term="Architecture Components" /><category term="Room" /><summary type="html">讲解 Architecture Components 架构组件 Room 教程并如何更好的引入到自己的架构中</summary></entry><entry><title type="html">Android 4.0 以上版本特性整理</title><link href="https://rockyqu.github.io/android/2018/05/27/Android-Version-Features.html" rel="alternate" type="text/html" title="Android 4.0 以上版本特性整理" /><published>2018-05-27T00:00:00+00:00</published><updated>2018-05-27T00:00:00+00:00</updated><id>https://rockyqu.github.io/android/2018/05/27/Android%20Version%20Features</id><content type="html" xml:base="https://rockyqu.github.io/android/2018/05/27/Android-Version-Features.html">&lt;hr /&gt;

&lt;h2 id=&quot;0x00-90&quot;&gt;0x00 9.0&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;室内 WIFI 定位&lt;/li&gt;
  &lt;li&gt;异形屏幕支持&lt;/li&gt;
  &lt;li&gt;通道设置、广播以及免打扰&lt;/li&gt;
  &lt;li&gt;多相机支持和相机更新&lt;/li&gt;
  &lt;li&gt;新的图片解码&lt;/li&gt;
  &lt;li&gt;动画&lt;/li&gt;
  &lt;li&gt;表单自动填充&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x01-80&quot;&gt;0x01 8.0&lt;/h2&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;固定快捷方式和小部件&lt;/li&gt;
  &lt;li&gt;固定快捷方式和小部件&lt;/li&gt;
  &lt;li&gt;Background Execution Limits&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x02-70&quot;&gt;0x02 7.0&lt;/h2&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;支持 VR&lt;/li&gt;
  &lt;li&gt;App快捷菜单&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x03-60&quot;&gt;0x03 6.0&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;运行时请求权限&lt;/li&gt;
  &lt;li&gt;取消支持 Apache HTTP 客户端&lt;/li&gt;
  &lt;li&gt;支持文本选择&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x04-50&quot;&gt;0x04 5.0&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;支持 OpenGL ES3.1&lt;/li&gt;
  &lt;li&gt;Android Runtime (ART) 默认运行平台设置&lt;/li&gt;
  &lt;li&gt;引入 Material Design 设计&lt;/li&gt;
  &lt;li&gt;RecyclerView 组件&lt;/li&gt;
  &lt;li&gt;支持 Android NDK 中的 64位&lt;/li&gt;
  &lt;li&gt;JobScheduler&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x05-40&quot;&gt;0x05 4.0&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;支持 OpenGL ES3.0&lt;/li&gt;
  &lt;li&gt;人脸识别解锁&lt;/li&gt;
  &lt;li&gt;添加转场动画&lt;/li&gt;
  &lt;li&gt;添加全屏沉浸模式&lt;/li&gt;
  &lt;li&gt;低功耗传感器&lt;/li&gt;
&lt;/ul&gt;</content><author><name>RockyQu</name><email>250533855@qq.com</email></author><category term="Android" /><summary type="html">Android 4.0 以上重要版本特性整理</summary></entry><entry><title type="html">Android 开发中导致 ANR 的一些注意事项小结</title><link href="https://rockyqu.github.io/android/2018/05/22/Android-Application-Not-Responding.html" rel="alternate" type="text/html" title="Android 开发中导致 ANR 的一些注意事项小结" /><published>2018-05-22T00:00:00+00:00</published><updated>2018-05-22T00:00:00+00:00</updated><id>https://rockyqu.github.io/android/2018/05/22/Android%20Application%20Not%20Responding</id><content type="html" xml:base="https://rockyqu.github.io/android/2018/05/22/Android-Application-Not-Responding.html">&lt;hr /&gt;

&lt;h2 id=&quot;0x00-触发-anr-条件&quot;&gt;0x00 触发 ANR 条件&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;ANR 全称 “Application Not Responding”，即“应用程序无响应“。系统会向用户显示一个对话框，用户可以选择“等待”而让程序继续运行，或“强制关闭”。App 的响应能力是由 Activity Manager 和 Window Manager 系统服务来监控的&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;输入事件5s内没被处理（键盘输入, 触摸屏幕）&lt;/li&gt;
  &lt;li&gt;BroadcastReceiver 在规定时间内没处理完(前台广播为10s，后台广播为60s)&lt;/li&gt;
  &lt;li&gt;Service (前台服务为10s，后台服务为60s)&lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x01-导出-anr-文件&quot;&gt;0x01 导出 ANR 文件&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;发生 ANR 后，系统会收集 ANR trace 日志信息，日志文件会被保存到 /data/anr/traces.txt 中，你可以使用 adb 提取此文件&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;检查是否已经配置 adb，在 Android Studio Terminal 窗口输入 adb 回车，未配置会显示如下信息
&lt;img src=&quot;/assets/image/2018-05-22/2018-05-22-Android Application Not Responding 1.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;配置 adb，新建一个名称为 Android 的系统变量，变量值写入你自己 SDK
&lt;img src=&quot;/assets/image/2018-05-22/2018-05-22-Android Application Not Responding 2.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;在系统环境变量 Path 下添加 %Android%
&lt;img src=&quot;/assets/image/2018-05-22/2018-05-22-Android Application Not Responding 3.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;重启 AS 再次在 Terminal 窗口输入 adb 查看，成功后如下图
&lt;img src=&quot;/assets/image/2018-05-22/2018-05-22-Android Application Not Responding 4.png&quot; alt=&quot;4&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;查看是否存在 traces.txt 文件
&lt;img src=&quot;/assets/image/2018-05-22/2018-05-22-Android Application Not Responding 7.png&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;导出 traces.txt 文件，如果设备没有 root 需使用如下命令，先复制到 SDCard，再导出就可以了
&lt;img src=&quot;/assets/image/2018-05-22/2018-05-22-Android Application Not Responding 5.png&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x02-分析-anr-问题&quot;&gt;0x02 分析 ANR 问题&lt;/h2&gt;
&lt;p&gt;具体问题需要具体分析，这个例子是在主线程中强行 Sleep 的 ANR 日志，如下图，会找到类似 java.lang.Thread.sleep 的信息
&lt;img src=&quot;/assets/image/2018-05-22/2018-05-22-Android Application Not Responding 6.png&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x03-降低-anr-概率-参考&quot;&gt;0x03 降低 ANR 概率 &lt;a href=&quot;https://www.jianshu.com/p/fa962a5fd939&quot;&gt;参考&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;主线程读取数据：母庸质疑在主线程中不能读取网络数据，但系统是允许主线程从数据库或者其他地方获取数据，但这种操作 ANR 风险很大，也会造成掉帧等，影响用户体验
    &lt;ul&gt;
      &lt;li&gt;避免在主线程进行数据库操作，由其是大量比较耗时的查询等操作，这个我有亲身经历，比如在进入 Activity 的 onCreate 里面读取较大的文件会明显感觉到卡顿，所以请放到异步里执行&lt;/li&gt;
      &lt;li&gt;慎用 SharedPreference ，避免存储超大的 Value &lt;a href=&quot;http://weishu.me/2016/10/13/sharedpreference-advices/&quot;&gt;详细讲解&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;不要在 BroadcastReciever 的 onRecieve() 方法中干过多的活，尤其应用在后台的时候。一种解决方案是直接开的异步线程执行，但此时应用可能在后台，系统优先级较低，进程很容易被系统杀死，所以可以选择开个 IntentService 去执行相应操作，即使是后台 Service 也会提高进程优先级，降低被杀可能性&lt;/li&gt;
  &lt;li&gt;各个组件的生命周期函数都不应该有太耗时的操作，即使对于后台 Service 或者 ContentProvider 来讲，应用在后台运行时候其 onCreate()时候不会有用户输入引起事件无响应 ANR，但其执行时间过长也会引起 Service 的 ANR 和 ContentProvider 的 ANR&lt;/li&gt;
  &lt;li&gt;尽量避免主线程的被锁的情况，在一些同步的操作主线程有可能被锁，需要等待其他线程释放相应锁才能继续执行，这样会有一定的 ANR 风险，对于这种情况有时也可以用异步线程来执行相应的逻辑。如果主线程被死锁了基本就等于要发生 ANR 了&lt;/li&gt;
&lt;/ul&gt;</content><author><name>RockyQu</name><email>250533855@qq.com</email></author><category term="ANR" /><summary type="html">总结 ANR 类型、如何导致的 ANR、如何避免、解决办法</summary></entry><entry><title type="html">Android 5.0 以下版本出现 java.lang.NoClassDefFoundError</title><link href="https://rockyqu.github.io/multidex/2018/05/16/Android-NoClassDefFoundError.html" rel="alternate" type="text/html" title="Android 5.0 以下版本出现 java.lang.NoClassDefFoundError" /><published>2018-05-16T00:00:00+00:00</published><updated>2018-05-16T00:00:00+00:00</updated><id>https://rockyqu.github.io/multidex/2018/05/16/Android%20NoClassDefFoundError</id><content type="html" xml:base="https://rockyqu.github.io/multidex/2018/05/16/Android-NoClassDefFoundError.html">&lt;hr /&gt;

&lt;h2 id=&quot;0x00-前言&quot;&gt;0x00 前言&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;版本兼容性测试发现在 Android 5.0 以下出现 NoClassDefFoundError，这个错误一般是由于超出 64K 方法造成，此时需要做分包处理，常用方法是引入官方的 MultiDex 即可解决大部份，以下为基础步骤&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;引入-multidex-步骤&quot;&gt;引入 MultiDex 步骤&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;引入依赖库
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;compile 'com.android.support:multidex:1.0.3'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;App build.gradle 开启 Multidex 开关
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;multiDexEnabled true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;重写 Application.attachBaseContext() 方法加入
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;MultiDex.install(this);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;以上方法基本能解决大部分问题，但是在 Android 5.0 以下系统版本中依然可能出现类似 NoClassDefFoundError 的错误，那么请往下看&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x01-64k-的由来-参考&quot;&gt;0x01 64K 的由来 &lt;a href=&quot;https://developer.android.com/studio/build/multidex&quot;&gt;参考&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;Android 应用 (APK) 文件包含 Dalvik Executable (DEX) 文件形式的可执行字节码文件，其中包含用来运行您的应用的已编译代码。Android 系统安装一个应用的时候，有一步是对 DEX 进行优化，这个过程有一个专门的工具来处理，叫 DexOpt。DexOpt 的执行过程是在第一次加载 DEX 文件的时候执行的。这个过程会生成一个 OptimisedDex 文件。执行 OptimisedDex 的效率会比直接执行 DEX 文件的效率要高很多。但是在早期的 Android 系统中，即 Android 5.0 以下的版本，DexOpt 会把每一个类的方法 id 检索起来，存在一个链表结构里面。但是这个链表的长度是用一个 short 类型来保存的，导致了方法 id 的数目不能够超过 65536 个。当一个项目足够大的时候，显然这个方法数的上限是不够的。尽管在新版本的 Android 系统中，修复了这个问题，但是我们仍然需要对低版本的 Android 系统做兼容。
为了解决方法数超限的问题，需要将该 DEX 文件拆成两个或多个，为此谷歌官方推出了 MultiDex 兼容包，配合 AndroidStudio 实现了一个 APK 包含多个 DEX 的功能。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;Android 5.0 之前版本的 Dalvik 可执行文件分包支持
Android 5.0（API 级别 21）之前的平台版本使用 Dalvik 运行时来执行应用代码。默认情况下，Dalvik 限制应用的每个 APK 只能使用单个 classes.dex 字节码文件。要想绕过这一限制，您可以使用 Dalvik 可执行文件分包支持库，它会成为您的应用主要 DEX 文件的一部分，然后管理对其他 DEX 文件及其所包含代码的访问。
    &lt;blockquote&gt;
      &lt;p&gt;注：如果您的项目配置时所面向的 Dalvik 可执行文件分包使用的是 minSdkVersion 20 或更低版本，并且您将其部署到运行 Android 4.4（API 级别 20）或更低版本的目标设备上，则 Android Studio 会停用 Instant Run。&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;Android 5.0 及更高版本的 Dalvik 可执行文件分包支持
Android 5.0（API 级别 21）及更高版本使用名为 ART 的运行时，后者原生支持从 APK 文件加载多个 DEX 文件。ART 在应用安装时执行预编译，扫描 classesN.dex 文件，并将它们编译成单个 .oat 文件，供 Android 设备执行。因此，如果您的 minSdkVersion 为 21 或更高值，则不需要 Dalvik 可执行文件分包支持库。
    &lt;blockquote&gt;
      &lt;p&gt;注：如果将应用的 minSdkVersion 设置为 21 或更高值，使用 Instant Run 时，Android Studio 会自动将应用配置为进行 Dalvik 可执行文件分包。由于 Instant Run 仅适用于调试版本的应用，您仍需配置发布构建进行 Dalvik 可执行文件分包，以规避 64K 限制。&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;0x02-声明主-dex-文件中需要的类&quot;&gt;0x02 声明主 DEX 文件中需要的类&lt;/h2&gt;
&lt;p&gt;正常情况是不应出现此类情况的，因为构建工具能识别这些代码路径，但可能在代码路径可见性较低（如使用的库具有复杂的依赖项）时出现。因此，可以使用 multiDexKeepProguard 属性声明它们，以手动将这些其他类指定为主 DEX 文件中的必需项。如果类multiDexKeepProguard 文件中匹配，则该类会添加至主 DEX 文件。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;multiDexKeepProguard
请在主 APP 目录下创建一个名为 multidex-config.pro 的文件，在 multiDexKeepProguard 中指定的这个件，该文件的语法与 Proguard 相同，例:-keep com.example.MyClass.class
    &lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;android {
  buildTypes {
      release {
          multiDexKeepProguard file('multidex-config.pro')
      }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
  &lt;p&gt;注：官网的这行代码是这样写的 multiDexKeepProguard ‘multidex-config.pro’ 但是发现找不到文件，改成 multiDexKeepProguard file(‘multidex-config.pro’) 就可以了&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;0x03-实际项目中的应用&quot;&gt;0x03 实际项目中的应用&lt;/h2&gt;
&lt;p&gt;我的项目在引入 MultiDex 依然报如下错误，可能是这个类的实例是使用 Java newInstance 来创建对象导致的
&lt;img src=&quot;/assets/image/2018-05-16/2018-05-16-Android NoClassDefFoundError 1.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;使用 multiDexKeepProguard 添加 keep 这个类就行了&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;-keep class me.mvp.demo.app.AppConfiguration
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;文件在这里 &lt;a href=&quot;https://github.com/RockyQu/MVPFrames/blob/rx/App/multidex-config.pro&quot;&gt;multidex-config.pro&lt;/a&gt;&lt;/p&gt;</content><author><name>RockyQu</name><email>250533855@qq.com</email></author><category term="NoClassDefFoundError" /><category term="MultiDex" /><summary type="html">记一次由 NoClassDefFoundError 引出的 MultiDex 分包问题</summary></entry><entry><title type="html">Java synchronized 用法小结</title><link href="https://rockyqu.github.io/java/2018/05/11/Java-Synchronized-Example.html" rel="alternate" type="text/html" title="Java synchronized 用法小结" /><published>2018-05-11T00:00:00+00:00</published><updated>2018-05-11T00:00:00+00:00</updated><id>https://rockyqu.github.io/java/2018/05/11/Java%20Synchronized%20Example</id><content type="html" xml:base="https://rockyqu.github.io/java/2018/05/11/Java-Synchronized-Example.html">&lt;hr /&gt;

&lt;h2 id=&quot;0x00-前言&quot;&gt;0x00 前言&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;synchronized 是 Java语言的关键字，当它用来修饰一个方法或者一个代码块的时候，能够保证在同一时刻最多只有一个线程执行该段代码，而这段代码也被称为临界区。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;0x01-几种应用方式&quot;&gt;0x01 几种应用方式&lt;/h2&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;修饰类，其作用的范围是synchronized后面括号括起来的部分，作用主的对象是这个类的所有对象&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;0x02-修饰代码块&quot;&gt;0x02 修饰代码块&lt;/h2&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class SyncRunnable implements Runnable {

    public void run() {
        synchronized (this) {// 可以去掉此行代码，查看不同的结果
            for (int i = 0; i &amp;lt; 3; i++) {
                try {
                    Logg.e(i);
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;调用代码&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SyncRunnable runnable = new SyncRunnable();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
thread2.start();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;运行结果
&lt;img src=&quot;/assets/image/2018-05-11/2018-05-11-Java synchronized Example 1.png&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;

&lt;p&gt;把上面的调用代码改成如下代码，发现 thread3 和 thread4 同时在执行。这是因为 synchronized 只锁定对象，每个对象只有一个锁（lock）与之相关联这时会有两把锁分别锁定 runnable1 对象和 runnable2 对象，而这两把锁是互不干扰的，不形成互斥，所以两个线程可以同时执行。&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SyncRunnable runnable1 = new SyncRunnable();
SyncRunnable runnable2 = new SyncRunnable();
Thread thread3 = new Thread(runnable1);
Thread thread4 = new Thread(runnable2);
thread3.start();
thread4.start();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;指定某个对象加锁&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;class Counter {

    String name;
    int count;

    public Counter(String name, int count) {
        this.name = name;
        this.count = count;
    }

    public void add(int count) {
        this.count += count;
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void sub(int count) {
        this.count -= count;
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public int getCount() {
        return count;
    }
}

class CounterOperator implements Runnable {

    private final Counter counter;

    public CounterOperator(Counter counter) {
        this.counter = counter;
    }

    public void run() {
        synchronized (counter) {// 可以去掉此行代码，查看不同的结果
            counter.add(500);
            counter.sub(500);
            Logg.e(Thread.currentThread().getName() + &quot;:&quot; + counter.getCount());
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;调用代码&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Counter account = new Counter(&quot;Counter&quot;, 1000);
CounterOperator counterOperator = new CounterOperator(account);
for (int i = 0; i &amp;lt; 5; i++) {
    Thread thread = new Thread(counterOperator, &quot;Thread&quot; + i);
    thread.start();
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;在 CounterOperator 类中的 run 方法里，我们用 synchronized 给 Counter 对象加锁。这时，当一个线程访问 Counter 对象时，其他试图访问 Counter 对象的线程将会阻塞，直到该线程访问 Counter 对象结束。也就是说谁拿到那个锁谁就可以运行它所控制的那段代码。当没有明确的对象作为锁，只是想让一段代码同步时，可以创建一个特殊的对象来充当锁&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;byte[] lock = new byte[0]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;注意创建这种类型锁时可以使用 byte[] lock = new byte[0] 零长度的 byte 数组 Object lock = new Object() 更经济&lt;/p&gt;

&lt;h2 id=&quot;0x03-修饰方法&quot;&gt;0x03 修饰方法&lt;/h2&gt;
&lt;p&gt;将最开始的 run() 方法改成如下代码，既是修饰一个方法，修饰方法和修饰代码块是等价的，只是作用范围不一样，修饰代码块是大括号括起来的范围，而修饰方法范围是整个函数&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;public synchronized void run() {
    for (int i = 0; i &amp;lt; 3; i++) {
        try {
            Logg.e(i);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;方法修饰需要注意几点&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;synchronized 关键字不能继承。如果在父类中的某个方法使用了 synchronized 关键字，而在子类中覆盖了这个方法，在子类中的这个方法默认情况下并不是同步的，而必须在子类的这个方法中也加上 synchronized 才可以。或者还可以在子类方法中调用父类中相应的方法，这样虽然子类中的方法不是同步的，但子类调用了父类的同步方法，因此，子类的方法也就相当于同步了&lt;/li&gt;
  &lt;li&gt;在定义接口方法时不能使用 synchronized 关键字&lt;/li&gt;
  &lt;li&gt;构造方法不能使用 synchronized 关键字，但可以使用 synchronized 代码块来进行同步&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;修饰静态方法&quot;&gt;修饰静态方法&lt;/h3&gt;
&lt;p&gt;静态方法是属于类的而不属于对象的。同样 synchronized 修饰的静态方法锁定的是这个类的所有对象&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;static class SyncStaticRunnable implements Runnable {

    public synchronized void run() {
        method();
    }

    public synchronized static void method() {
        for (int i = 0; i &amp;lt; 3; i++) {
            try {
                Logg.e(i);
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;调用代码&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;SyncStaticRunnable runnable1 = new SyncStaticRunnable();
SyncStaticRunnable runnable2 = new SyncStaticRunnable();
Thread thread3 = new Thread(runnable1);
Thread thread4 = new Thread(runnable2);
thread3.start();
thread4.start();
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;runnable1 和 runnable2 是两个对象，但在 thread3 和 thread4 并发执行时却保持了线程同步。这是因为 run 中调用了静态方法，而静态方法是属于类的，所以相当于用了同一把锁&lt;/p&gt;

&lt;h2 id=&quot;0x04-修饰类&quot;&gt;0x04 修饰类&lt;/h2&gt;
&lt;p&gt;把刚才上面的类修改一下如下代码&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;static class SyncStaticRunnable implements Runnable {

    public synchronized void run() {
        method();
    }

    public static void method() {
    	synchronized(SyncStaticRunnable.class) {
            for (int i = 0; i &amp;lt; 3; i++) {
                try {
                    Logg.e(i);
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;效果是一样的，synchronized 作用于一个类时，类所有对象用的是同一把锁&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x05-总结&quot;&gt;0x05 总结&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;synchronized 关键字加在方法上或对象上，如果它作用的对象是非静态的，则它取得的锁是对象。如果 synchronized 作用的对象是一个静态方法或一个类，则它取得的锁是对类，该类所有的对象同一把锁&lt;/li&gt;
  &lt;li&gt;每个对象只有一个锁与之相关联，谁拿到这个锁谁就可以运行它所控制的那段代码&lt;/li&gt;
  &lt;li&gt;实现同步是要很大的系统开销作为代价的，甚至可能造成死锁，所以尽量避免无谓的同步控制&lt;/li&gt;
&lt;/ul&gt;</content><author><name>RockyQu</name><email>250533855@qq.com</email></author><category term="Java" /><category term="synchronized" /><category term="线程同步" /><summary type="html">Java 线程同步 synchronized 即同步锁使用方法总结</summary></entry><entry><title type="html">Android View 绘制流程分析</title><link href="https://rockyqu.github.io/android%20source/2018/05/03/Android-View-Drawing.html" rel="alternate" type="text/html" title="Android View 绘制流程分析" /><published>2018-05-03T00:00:00+00:00</published><updated>2018-05-03T00:00:00+00:00</updated><id>https://rockyqu.github.io/android%20source/2018/05/03/Android%20View%20Drawing</id><content type="html" xml:base="https://rockyqu.github.io/android%20source/2018/05/03/Android-View-Drawing.html">&lt;hr /&gt;

&lt;h2 id=&quot;0x00-前言&quot;&gt;0x00 前言&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;此篇博文讨论的技术是在 XML 里声明的 View 是如何最终显示到页面上的。并且你可能需要查看一些 Android Open Source，这里推举一个在线看源码的网站 &lt;a href=&quot;http://androidxref.com/&quot;&gt;androidxref&lt;/a&gt;，所展示的源码来自 &lt;a href=&quot;http://androidxref.com/8.0.0_r4/&quot;&gt;Android 8.0&lt;/a&gt;。因为涉及知识点多，所以此篇幅非常长，请耐心阅读~&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x01-整理中&quot;&gt;0x01 整理中………………………&lt;/h2&gt;

&lt;hr /&gt;</content><author><name>RockyQu</name><email>250533855@qq.com</email></author><category term="View" /><category term="绘制流程" /><summary type="html">结合源码分析从一个 View 被声明到 XML 开始到最终是如何显示到页面上的</summary></entry><entry><title type="html">使用 Ubuntu 编译 ijkplayer 源码</title><link href="https://rockyqu.github.io/ijkplayer/2018/04/18/Compile-ijkplayer.html" rel="alternate" type="text/html" title="使用 Ubuntu 编译 ijkplayer 源码" /><published>2018-04-18T00:00:00+00:00</published><updated>2018-04-18T00:00:00+00:00</updated><id>https://rockyqu.github.io/ijkplayer/2018/04/18/Compile%20ijkplayer</id><content type="html" xml:base="https://rockyqu.github.io/ijkplayer/2018/04/18/Compile-ijkplayer.html">&lt;hr /&gt;

&lt;h2 id=&quot;0x00-安装-ubuntu&quot;&gt;0x00 安装 Ubuntu&lt;/h2&gt;
&lt;blockquote&gt;
  &lt;p&gt;我用的是 &lt;a href=&quot;https://www.virtualbox.org/&quot;&gt;Oracle VM VirtualBox&lt;/a&gt; 虚拟机来安装 &lt;a href=&quot;https://www.ubuntu.com/download&quot;&gt;Ubuntu 64位&lt;/a&gt;，不会对已安装的系统造成什么影响。在新建的虚拟机时配置内存要选用大一点的，第一次我安装全是默认项，卡的要死，建议分配内存 4G，硬盘 30G 以上&lt;/p&gt;
&lt;/blockquote&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x01-配置相关工具&quot;&gt;0x01 配置相关工具&lt;/h2&gt;

&lt;h3 id=&quot;1配置-ndk&quot;&gt;1.配置 NDK&lt;/h3&gt;

&lt;p&gt;下载好的 NDK 解压 &lt;a href=&quot;https://dl.google.com/android/repository/android-ndk-r14b-linux-x86_64.zip&quot;&gt;NDK Download&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/image/2018-04-18/2018-04-18-Compile ijkplayer 5.png&quot; alt=&quot;5&quot; /&gt;&lt;/p&gt;

&lt;p&gt;转到 Home 目录下，配置 NDK 路径，类似 Java 配置 JDK，很多教程 Android SDK 也要配置，其实是不用的
&lt;img src=&quot;/assets/image/2018-04-18/2018-04-18-Compile ijkplayer 6.png&quot; alt=&quot;6&quot; /&gt;&lt;/p&gt;

&lt;p&gt;键盘 Ctrl + H 显示隐藏文件，找到 .bashrc 文件&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/image/2018-04-18/2018-04-18-Compile ijkplayer 7.png&quot; alt=&quot;7&quot; /&gt;&lt;/p&gt;

&lt;p&gt;将如下代码添加到文件末尾，保存并关闭，路径要写你自己的 NDK 路径&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ANDROID_NDK=/home/qu/Downloads/android-ndk-r14b
export PATH=${PATH}:ANDROID_NDK
export ANDROID_NDK
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;你可以用命令来测试一下是否成功&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;adb -version
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;至此 NDK 配置结束了&lt;/p&gt;

&lt;h3 id=&quot;2编译前准备&quot;&gt;2.编译前准备&lt;/h3&gt;

&lt;p&gt;安装一些软件&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install git
sudo apt-get install yasm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;下载 ijkplayer 项目&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/Bilibili/ijkplayer.git ijkplayer-android
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;简单了解一下 ijkplayer 目录结构
&lt;img src=&quot;/assets/image/2018-04-18/2018-04-18-Compile ijkplayer 10.png&quot; alt=&quot;10&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;3开始编译&quot;&gt;3.开始编译&lt;/h3&gt;

&lt;p&gt;进入 ijkplayer-android 目录&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd ijkplayer-android
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果你的代码不是最新的版本，最新版本号请查看 GitHub&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git checkout -B latest k0.8.8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;开始初始化&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./init-android.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;需要支持 Https 协议的需要执行如下命令&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./init-android-openssl.sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;进入 contrib 目录&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd android/contrib
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;编译各个平台的编译 openssl&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./compile-openssl.sh clean
./compile-openssl.sh all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;编译 ffmpeg 解码库&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./compile-ffmpeg.sh clean
./compile-ffmpeg.sh all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;返回上一目录&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;cd ..
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;获取 ijkplayer 的项目&lt;/p&gt;
&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./compile-ijk.sh all
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;至此所有编译结束了，你会在这个目录下看到如下文件&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/image/2018-04-18/2018-04-18-Compile ijkplayer 8.png&quot; alt=&quot;8&quot; /&gt;&lt;/p&gt;

&lt;p&gt;最后复制出来，用 Android Studio 打开项目即可运行查看效果&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/image/2018-04-18/2018-04-18-Compile ijkplayer 9.png&quot; alt=&quot;9&quot; /&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x02-总结我遇到的一些问题&quot;&gt;0x02 总结我遇到的一些问题&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;h3 id=&quot;报错不能为虚拟电脑--xxx-打开一个新任务&quot;&gt;报错：不能为虚拟电脑  XXX 打开一个新任务&lt;/h3&gt;
    &lt;p&gt;此时需要通过安装 &lt;a href=&quot;https://www.virtualbox.org/wiki/Downloads&quot;&gt;Oracle VM VirtualBox Extension Pack&lt;/a&gt; 扩展包解决此问题&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&quot;/assets/image/2018-04-18/2018-04-18-Compile ijkplayer 1.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;安装菜单目录 管理 → 全局设定 → 扩展&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/image/2018-04-18/2018-04-18-Compile ijkplayer 2.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;h3 id=&quot;请用-ndk-r14b-版本&quot;&gt;请用 ndk r14b 版本&lt;/h3&gt;
    &lt;p&gt;开始用的版本过低编译失败，又下了个最新的也失败，最后找到 r14b 版本，编译成功了&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;h3 id=&quot;提示-you-need-the-ndkr10e-or-later&quot;&gt;提示 You need the NDKr10e or later&lt;/h3&gt;
    &lt;p&gt;检查 NDK 路径配置是否正确，或者 NDK 版本过高过低都会造成此问题&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;h3 id=&quot;提示-errorfailed-to-create-tool-chain&quot;&gt;提示 ERROR:failed to create tool chain&lt;/h3&gt;
    &lt;p&gt;通过安装 apt-get install python 解决此问题&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;h3 id=&quot;安装下载时异常缓慢&quot;&gt;安装下载时异常缓慢&lt;/h3&gt;
    &lt;p&gt;推举一个免费 VPN 支持 Ubuntu 版的 &lt;a href=&quot;https://github.com/getlantern/lantern&quot;&gt;Lantern&lt;/a&gt;，使用如下命令安装&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sudo apt install gdebi-core
sudo gdebi lantern.deb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;0x03-编译完成的项目地址&quot;&gt;0x03 编译完成的项目地址&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/RockyQu/ijkplayer&quot;&gt;https://github.com/RockyQu/ijkplayer&lt;/a&gt;&lt;/p&gt;</content><author><name>RockyQu</name><email>250533855@qq.com</email></author><category term="ijkplayer" /><summary type="html">基于版本 0.8.8</summary></entry></feed>