【移动支付】关于视频直播技术,葡京网上娱乐场你想要知道的都在此地了(三)编码和打包

本文已授权微信公众号:鸿洋(hongyangAndroid)原创头阵

至于直播的技能小说不少,成体系的不多。大家将用七篇小说,更系统化地介绍当下大热的视频直播各环节的关键技术,帮忙录像直播创业者们更周全、长远地打听录像直播技术,更好地技术选型。

供销社的种类代码比较多,每一遍调试改动java文件后要将近2分钟才能跑起来,实在受不住。在网上找了一大堆配置参数也一向不很明确的效果,
尝试使用instant
run效果也不怎样,然后又尝试采纳freeline编译速度还足以只是不平稳,每回败北后全量编译很开支时间,既然没有好的方案就融洽尝尝做。

视频编码是视频直播技术多元作品的第三篇,是本种类一个不胜关键的局地,是运动支付必修的根基学科,本篇作品从理论到执行一网打尽主流编码器。

花色地址:
https://github.com/typ0520/fastdex

一旦把所有流媒体比喻成一个物流系统,那么编解码就是其中配货和装货的经过,这些历程相当主要,它的速度和收缩比对物流系统的含义非凡大,影响物流连串的总体进程和基金。同样,对流媒体传输来说,编码也要命重大,它的编码性能、编码速度和编码压缩比会直接影响所有流媒体传输的用户体验和传导费用。

注: 本文对gradle task做的表明都创造在闭馆instant run的前提下

本体系文章大纲之类,想复习从前小说的直白点击直达链接:

注: 本文所有的代码、gradle职分名、职责输出路径、全部使用debug这么些buildType作说明

优化构建速度首先要求找到那几个环节造成构建速度这么慢,把上面的代码放进app/build.gradle里把日子用度领先50ms的职责时间打印出来

 public class BuildTimeListener implements TaskExecutionListener, BuildListener {
    private Clock clock
    private times = []

    @Override
    void beforeExecute(Task task) {
        clock = new org.gradle.util.Clock()
    }

    @Override
    void afterExecute(Task task, TaskState taskState) {
        def ms = clock.timeInMs
        times.add([ms, task.path])

        //task.project.logger.warn "${task.path} spend ${ms}ms"
    }

    @Override
    void buildFinished(BuildResult result) {
        println "Task spend time:"
        for (time in times) {
            if (time[0] >= 50) {
                printf "%7sms  %s\n", time
            }
        }
    }

    ......
}

project.gradle.addListener(new BuildTimeListener())

执行./gradlew assembleDebug,经过漫长的等候得到以下输出

Total time: 1 mins 39.566 secs
Task spend time:
     69ms  :app:prepareComAndroidSupportAnimatedVectorDrawable2340Library
    448ms  :app:prepareComAndroidSupportAppcompatV72340Library
     57ms  :app:prepareComAndroidSupportDesign2340Library
     55ms  :app:prepareComAndroidSupportSupportV42340Library
     84ms  :app:prepareComFacebookFrescoImagepipeline110Library
     69ms  :app:prepareComSquareupLeakcanaryLeakcanaryAndroid14Beta2Library
     60ms  :app:prepareOrgXutilsXutils3336Library
     68ms  :app:compileDebugRenderscript
    265ms  :app:processDebugManifest
   1517ms  :app:mergeDebugResources
    766ms  :app:processDebugResources
   2897ms  :app:compileDebugJavaWithJavac
   3117ms  :app:transformClassesWithJarMergingForDebug
   7899ms  :app:transformClassesWithMultidexlistForDebug
  65327ms  :app:transformClassesWithDexForDebug
    151ms  :app:transformNative_libsWithMergeJniLibsForDebug
    442ms  :app:transformResourcesWithMergeJavaResForDebug
   2616ms  :app:packageDebug
    123ms  :app:zipalignDebug

从地点的输出可以发现总的构建时间为100秒左右(上边的输出不是根据真的的实践各样输出的),transformClassesWithDexForDebug职分是最慢的损耗了65秒,它就是大家要求重视优化的天职,首先讲下构建进度中器重职务的效率,方便了然前边的hook点

mergeDebugResources职分的出力是解压所有的aar包输出到app/build/intermediates/exploded-aar,并且把富有的资源文件合并到app/build/intermediates/res/merged/debug目录里

processDebugManifest职分是把所有aar包里的AndroidManifest.xml中的节点,合并到品种的AndroidManifest.xml中,并根据app/build.gradle中当前buildType的manifestPlaceholders配置内容替换manifest文件中的占位符,最终输出到app/build/intermediates/manifests/full/debug/AndroidManifest.xml

processDebugResources的作用

  • 1、调用aapt生成项目和所有aar看重的R.java,输出到app/build/generated/source/r/debug目录
  • 2、生成资源索引文件app/build/intermediates/res/resources-debug.ap_
  • 3、把符号表输出到app/build/intermediates/symbols/debug/R.txt

compileDebugJavaWithJavac以此职务是用来把java文件编译成class文件,输出的不二法门是app/build/intermediates/classes/debug
编译的输入目录有

  • 1、项目源码目录,默许路径是app/src/main/java,可以通过sourceSets的dsl配置,允许有四个(打印project.android.sourceSets.main.java.srcDirs可以查阅当前有所的源码路径,具体配置可以参照android-doc
  • 2、app/build/generated/source/aidl
  • 3、app/build/generated/source/buildConfig
  • 4、app/build/generated/source/apt(继承javax.annotation.processing.AbstractProcessor做动态代码生成的局地库,输出在那些目录,具体可以参见Butterknife

    Tinker)的代码

transformClassesWithJarMergingForDebug的法力是把compileDebugJavaWithJavac职务的输出app/build/intermediates/classes/debug,和app/build/intermediates/exploded-aar中具有的classes.jar和libs里的jar包作为输入,合并起来输出到app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar,大家在开发中凭借第三方库的时候偶然报duplicate
entry:xxx 的荒谬,就是因为在集合的长河中在差别jar包里发现了同等路线的类

transformClassesWithMultidexlistForDebug以此职分开支的年华也很长将近8秒,它有七个效率

  • 1、扫描项目标AndroidManifest.xml文件和剖析类之间的信赖性关系,统计出那一个类必须放在第八个dex里面,最终把分析的结果写到app/build/intermediates/multi-dex/debug/maindexlist.txt文件之中
  • 2、生成混淆配置项输出到app/build/intermediates/multi-dex/debug/manifest_keep.txt文件里

种类里的代码入口是manifest中application节点的性质android.name配置的一而再自Application的类,在android5.0在先的版本系统只会加载一个dex(classes.dex),classes2.dex
…….classesN.dex
一般是利用android.support.multidex.MultiDex加载的,所以一旦输入的Application类不在classes.dex里5.0之下肯定会挂掉,别的当入口Application看重的类不在classes.dex时起头化的时候也会因为类找不到而挂掉,还有假如混淆的时候类名变掉了也会因为对应持续而挂掉,综上所述就是这几个职责的功用

transformClassesWithDexForDebug本条职务的功力是把带有所有class文件的jar包转换为dex,class文件更多变换的越慢
输入的jar包路径是app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar
输出dex的目录是build/intermediates/transforms/dex/debug/folders/1000/1f/main

***只顾编写gradle插件时若是必要动用方面这么些途径不要硬编码的方法写死,最好从Android
gradle api中去赢得路径,幸免未来发生变化

组成方面的那一个新闻主要要求优化的是transformClassesWithDexForDebug以此义务,我的笔触是第四回全量打包进行完transformClassesWithDexForDebug义务后把转变的dex缓存下来,并且在举办那个职分前对眼前享有的java源文件做快照,未来补丁打包的时候经过当前具备的java文件音讯和以前的快照做相比较,找出转变的java文件进而赢得那几个class文件发生变化,然后把app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar中尚无变化的class移除掉,仅把变化class送去生成dex,然后选用一种热修复方案把那一个dex当做补丁dex加载进来,有思路了后头就是打下种种技术点

==============================

(一)采集

怎么获得transformClassesWithDexForDebug职分履行前后的生命周期

参考了Tinker品类的代码,找到下面的完毕

public class ImmutableDexTransform extends Transform {
    Project project
    DexTransform dexTransform
    def variant

    ......

    @Override
    void transform(TransformInvocation transformInvocation) throws TransformException, IOException, InterruptedException {
        def outputProvider = transformInvocation.getOutputProvider()
        //dex的输出目录
        File outputDir = outputProvider.getContentLocation("main", dexTransform.getOutputTypes(), dexTransform.getScopes(), Format.DIRECTORY);
        if (outputDir.exists()) {
            outputDir.delete()
        }
        println("===执行transform前清空dex输出目录: ${project.projectDir.toPath().relativize(outputDir.toPath())}")
        dexTransform.transform(transformInvocation)
        if (outputDir.exists()) {
            println("===执行transform后dex输出目录不是空的: ${project.projectDir.toPath().relativize(outputDir.toPath())}")
            outputDir.listFiles().each {
                println("===执行transform后: ${it.name}")
            }
        }
    }
}

project.getGradle().getTaskGraph().addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
    @Override
    public void graphPopulated(TaskExecutionGraph taskGraph) {
        for (Task task : taskGraph.getAllTasks()) {
            if (task instanceof TransformTask && task.name.toLowerCase().contains(variant.name.toLowerCase())) {

                if (((TransformTask) task).getTransform() instanceof DexTransform && !(((TransformTask) task).getTransform() instanceof ImmutableDexTransform)) {
                    project.logger.warn("find dex transform. transform class: " + task.transform.getClass() + " . task name: " + task.name)

                    DexTransform dexTransform = task.transform
                    ImmutableDexTransform hookDexTransform = new ImmutableDexTransform(project,
                            variant, dexTransform)
                    project.logger.info("variant name: " + variant.name)

                    Field field = TransformTask.class.getDeclaredField("transform")
                    field.setAccessible(true)
                    field.set(task, hookDexTransform)
                    project.logger.warn("transform class after hook: " + task.transform.getClass())
                    break;
                }
            }
        }
    }
});

把地点的代码放进app/build.gradle执行./gradlew assembleDebug

:app:transformClassesWithMultidexlistForDebug
ProGuard, version 5.2.1
Reading program jar [/Users/tong/Projects/fastdex/app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar]
Reading library jar [/Users/tong/Applications/android-sdk-macosx/build-tools/23.0.1/lib/shrinkedAndroid.jar]
Preparing output jar [/Users/tong/Projects/fastdex/app/build/intermediates/multi-dex/debug/componentClasses.jar]
  Copying resources from program jar [/Users/tong/Projects/fastdex/app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar]
:app:transformClassesWithDexForDebug
===执行transform前清空dex输出目录: build/intermediates/transforms/dex/debug/folders/1000/1f/main
......
===执行transform后dex输出目录不是空的: build/intermediates/transforms/dex/debug/folders/1000/1f/main
===执行transform后: classes.dex

从地点的日记输出注脚那一个hook点是有效的,在全量打包时举行transform前可以对java源码做快照,执行完将来把dex缓存下来;在补丁打包举办transform从前比较快照移除没有生成的class,执行完以后合并缓存的dex放进dex输出目录

==============================

(二)处理

如何做快照与对待快照并得到变化的class列表

举行上面的代码可以博得具有的类型源码目录

project.android.sourceSets.main.java.srcDirs.each { srcDir->
    println("==srcDir: ${srcDir}")
}

sample工程并未配备sourceSets,因而输出的是app/src/main/java

给源码目录做快照,直接通过文件复制的章程,把所有的srcDir目录下的java文件复制到快照目录下(那里有个坑,不要采用project.copy
{}它会使文件的lastModified值爆发变化,直接运用流copy并且要用源文件的lastModified覆盖目的文件的lastModified)

由此java文件的长度和上次修改时间八个要素相比较可以得知同一个文本是否爆发变化,通过快照目录没有某个文件而当前目录有某个文件可以识破伸张了文本,通过快照目录有某个文件不过当前目录没有可以得知删除文件(为了效用可以不处理删除,仅造成缓存里有好几用不到的类而已)
举个例子来说假设项目源码的路径为/Users/tong/fastdex/app/src/main/java,做快照时把那一个目录复制到/Users/tong/fastdex/app/build/fastdex/snapshoot下,当前快照里的文件树为

└── com
    └── dx168
        └── fastdex
            └── sample
                ├── CustomView.java
                ├── MainActivity.java
                └── SampleApplication.java

若是当前源码路径的内容暴发变化,当前的公文树为

└── com
    └── dx168
        └── fastdex
            └── sample
                ├── CustomView.java
                ├── MainActivity.java(内容已经被修改)
                ├── New.java
                └── SampleApplication.java

通过文件遍历相比较可以获取那个转变的相对路径列表

  • com/dx168/fastdex/sample/MainActivity.java
  • com/dx168/fastdex/sample/New.java

由此那一个列表进而可以得知变化的class有

  • com/dx168/fastdex/sample/MainActivity.class
  • com/dx168/fastdex/sample/New.class

唯独java文件编译的时候若是有其中类还会有其余的片段class输出,比如拿R文件做下编译,它的编译输出如下

➜  sample git:(master) ls
R.java
➜  sample git:(master) javac R.java 
➜  sample git:(master) ls
R$attr.class      R$dimen.class     R$id.class        R$layout.class    R$string.class    R$styleable.class R.java
R$color.class     R$drawable.class  R$integer.class   R$mipmap.class    R$style.class     R.class
➜  sample git:(master) 

其余假设应用了butterknife,还会生成binder类,比如编译MainActivity.java时生成了
com/dx168/fastdex/sample/MainActivity$$ViewBinder.class

整合方面几点可以得到具有变化class的分外方式

  • com/dx168/fastdex/sample/MainActivity.class
  • com/dx168/fastdex/sample/MainActivity$*.class
  • com/dx168/fastdex/sample/New.class
  • com/dx168/fastdex/sample/New$*.class

有了地方的同盟形式就可以在补丁打包进行transform前把app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar中并未转变的class全部移除掉

project.copy {
    from project.zipTree(combinedJar)
        for (String pattern : patterns) {
            include pattern
        }
    }
    into tmpDir
}
project.ant.zip(baseDir: tmpDir, destFile: patchJar)

然后就足以应用patchJar作为输入jar生成补丁dex

注:
那种映射方案一经翻开了模糊就对应不上了,需要分析混淆将来暴发的mapping文件才能缓解,但是大家也从未须求在打开混淆的buildType下做开发支出调试,所以暂时可以不做那些事情

==============================
有了补丁dex,就可以挑选一种热修复方案把补丁dex加载进来,那里方案有某些种,为了不难直接拔取android.support.multidex.MultiDex以dex插桩的办法来加载,只必要把dex根据google标准(classes.dex、classes2.dex、classesN.dex)排列好就行了,那里有七个技术点

鉴于patch.dex和缓存下来dex里面有双重的类,当加载引用了重复类的类时会造成pre-verify的失实,具体请参考QQ空间社团写的安卓App热补丁动态修复技术介绍
,那篇小说详细分析了造成pre-verify错误的因由,小说里给的化解方案是往所有引用被修复类的类中插入一段代码,并且被插入的那段代码所在的类的dex必须是一个独立的dex,那个dex大家先行准备好,叫做fastdex-runtime.dex,它的代码结构是

└── com
    └── dx168
        └── fastdex
            └── runtime
                ├── FastdexApplication.java
                ├── antilazyload
                │   └── AntilazyLoad.java
                └── multidex
                    ├── MultiDex.java
                    ├── MultiDexApplication.java
                    ├── MultiDexExtractor.java
                    └── ZipUtil.java

AntilazyLoad.java就是在注入时被引用的类
MultiDex.java是用来加载classes2.dex –
classesN.dex的包,为了以防万一项目并未依赖MultiDex,所以把MultiDex的代码copy到了大家的package下
法斯特dexApplication.java的法力前面在说

整合大家的品种须求在全量打包前把app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar中兼有的花色代码的class整体动态插入代码(第三方库由于不在我们的修复范围内为此为了功效忽略掉),具体的做法是往所有的构造方法中添加对com.dx168.fastdex.runtime.antilazyload.AntilazyLoad的看重,如下边的代码所示

//source class:
public class MainActivity {
}

==>

//dest class:
import com.dx168.fastdex.runtime.antilazyload.AntilazyLoad;
public class MainActivity {
    public MainActivity() {
        System.out.println(Antilazyload.str);
    }
}

动态往class文件中插入代码应用的是asm,我把做测试的时候找到的一部分有关材料和代码都放到了github上面点我翻看,代码相比较六只贴出来一部分,具体请查看ClassInject.groovy

 private static class MyClassVisitor extends ClassVisitor {
    public MyClassVisitor(ClassVisitor classVisitor) {
        super(Opcodes.ASM5, classVisitor);
    }

    @Override
    public MethodVisitor visitMethod(int access,
                                     String name,
                                     String desc,
                                     String signature,
                                     String[] exceptions) {
        //判断是否是构造方法
        if ("<init>".equals(name)) {
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
            MethodVisitor newMethod = new AsmMethodVisit(mv);
            return newMethod;
        } else {
            return super.visitMethod(access, name, desc, signature, exceptions);
        }
    }
}

static class AsmMethodVisit extends MethodVisitor {
    public AsmMethodVisit(MethodVisitor mv) {
        super(Opcodes.ASM5, mv);
    }

    @Override
    public void visitInsn(int opcode) {
        if (opcode == Opcodes.RETURN) {
            //访问java/lang/System的静态常量out
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            //访问AntilazyLoad的静态变量
            mv.visitFieldInsn(GETSTATIC, "com/dx168/fastdex/runtime/antilazyload/AntilazyLoad", "str", "Ljava/lang/String;");
            //调用out的println打印AntilazyLoad.str的值
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }
        super.visitInsn(opcode);
    }
}

===============
拍卖完pre-verify问题,接下去又并发坑了,当补丁dex打好后只要缓存的dex有三个(classes.dex
classes2.dex),那么合并dex后的一一就是
fastdex-runtime.dex 、patch.dex、classes.dex 、classes2.dex
(patch.dex必须放在缓存的dex以前才能被修复)

fastdex-runtime.dex  => classes.dex
patch.dex            => classes2.dex
classes.dex          => classes3.dex
classes2.dex         => classes4.dex

在讲解transformClassesWithMultidexlistForDebug义务时有说经过序入口Application的问题,假诺patch.dex中不分包入口Application,apk启动的时候一定会报类找不到的不当,那么怎么解决那一个问题呢

    1. 先是个方案:
      transformClassesWithMultidexlistForDebug任务中输出的maindexlist.txt中有所的class都插手patch.dex的浮动
    1. 第三种方案:
      对品种的入口Application做代理,并把这几个代理类放在第三个dex里面,项目标dex依照顺序放在前边

率先种方案方案由于必须让maindexlist.txt中大量的类插足了补丁的浮动,与事先尽量裁减class文件参预dex生成的考虑是相争持的,功能相对于第七个方案相比较低,其余一个缘由是无能为力担保项目标Application中采纳了MultiDex;

其次种方案并未上述问题,可是一旦项目代码中有应用getApplication()做强转就会出问题(参考issue#2),instant
run也会有雷同的问题,它的做法是hook系统的api运行期把Application还原回来,所以强转就不会有题目了,请参见MonkeyPatcher.java(需求翻墙才能打开,如果看不住就参照FastdexApplication.java的monkeyPatchApplication方法)

归纳最后拔取了第二种方案以下是fastdex-runtime.dex中代理Application的代码

public class FastdexApplication extends Application {
    public static final String LOG_TAG = "Fastdex";
    private Application realApplication;

    //从manifest文件的meta_data中获取真正的项目Application类
    private String getOriginApplicationName(Context context) {
        ApplicationInfo appInfo = null;
        try {
            appInfo = context.getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        String msg = appInfo.metaData.getString("FASTDEX_ORIGIN_APPLICATION_CLASSNAME");
        return msg;
    }

    private void createRealApplication(Context context) {
        String applicationClass = getOriginApplicationName(context);
        if (applicationClass != null) {
            Log.d(LOG_TAG, new StringBuilder().append("About to create real application of class name = ").append(applicationClass).toString());

            try {
                Class realClass = Class.forName(applicationClass);
                Constructor constructor = realClass.getConstructor(new Class[0]);
                this.realApplication = ((Application) constructor.newInstance(new Object[0]));
                Log.v(LOG_TAG, new StringBuilder().append("Created real app instance successfully :").append(this.realApplication).toString());
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        } else {
            this.realApplication = new Application();
        }
    }

    protected void attachBaseContext(Context context) {
        super.attachBaseContext(context);
        MultiDex.install(context);
        createRealApplication(context);

        if (this.realApplication != null)
            try {
                Method attachBaseContext = ContextWrapper.class
                        .getDeclaredMethod("attachBaseContext", new Class[]{Context.class});

                attachBaseContext.setAccessible(true);
                attachBaseContext.invoke(this.realApplication, new Object[]{context});
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
    }

    public void onCreate() {
        super.onCreate();

        if (this.realApplication != null) {
            this.realApplication.onCreate();
        }
    }
    ......
}

根据以前的职分表达生成manifest文件的职责是processDebugManifest,大家只须要在那么些职务执行完事后做拍卖,成立一个贯彻类为FastdexManifestTask的职责,焦点代码如下

def ns = new Namespace("http://schemas.android.com/apk/res/android", "android")
def xml = new XmlParser().parse(new InputStreamReader(new FileInputStream(manifestPath), "utf-8"))
def application = xml.application[0]
if (application) {
    QName nameAttr = new QName("http://schemas.android.com/apk/res/android", 'name', 'android');
    def applicationName = application.attribute(nameAttr)
    if (applicationName == null || applicationName.isEmpty()) {
        applicationName = "android.app.Application"
    }
    //替换application的android.name节点
    application.attributes().put(nameAttr, "com.dx168.fastdex.runtime.FastdexApplication")
    def metaDataTags = application['meta-data']
    // remove any old FASTDEX_ORIGIN_APPLICATION_CLASSNAME elements
    def originApplicationName = metaDataTags.findAll {
        it.attributes()[ns.name].equals(FASTDEX_ORIGIN_APPLICATION_CLASSNAME)
    }.each {
        it.parent().remove(it)
    }
    // Add the new FASTDEX_ORIGIN_APPLICATION_CLASSNAME element
    //把原来的Application写入到meta-data中
    application.appendNode('meta-data', [(ns.name): FASTDEX_ORIGIN_APPLICATION_CLASSNAME, (ns.value): applicationName])
    // Write the manifest file
    def printer = new XmlNodePrinter(new PrintWriter(manifestPath, "utf-8"))
    printer.preserveWhitespace = true
    printer.print(xml)
}
File manifestFile = new File(manifestPath)
if (manifestFile.exists()) {
    File buildDir = FastdexUtils.getBuildDir(project,variantName)
    FileUtils.copyFileUsingStream(manifestFile, new File(buildDir,MANIFEST_XML))
    project.logger.error("fastdex gen AndroidManifest.xml in ${MANIFEST_XML}")
}

选用下边的代码把那一个职分加进去并保管在processDebugManifest义务履行落成后实施

project.afterEvaluate {
    android.applicationVariants.all { variant ->
        def variantOutput = variant.outputs.first()
        def variantName = variant.name.capitalize()

        //替换项目的Application为com.dx168.fastdex.runtime.FastdexApplication
        FastdexManifestTask manifestTask = project.tasks.create("fastdexProcess${variantName}Manifest", FastdexManifestTask)
        manifestTask.manifestPath = variantOutput.processManifest.manifestOutputFile
        manifestTask.variantName = variantName
        manifestTask.mustRunAfter variantOutput.processManifest

        variantOutput.processResources.dependsOn manifestTask
    }
}

拍卖完之后manifest文件application节点android.name属性的值就变成了com.dx168.fastdex.runtime.FastdexApplication,并且把本来项目标Application的名字写入到meta-data中,用来运行期给法斯特dexApplication去读取

<meta-data android:name="FASTDEX_ORIGIN_APPLICATION_CLASSNAME" android:value="com.dx168.fastdex.sample.SampleApplication"/>

==============================

(三)编码和包裹

开发完以上成效后做下边的两遍打包做时间比较(其实只做一遍并不是太标准,做几十次测试取时间的平均值那样才最准)
  • 1、删除build目录第两次全量打包(不开启fastdex)

    BUILD SUCCESSFUL
    
      Total time: 1 mins 46.678 secs
      Task spend time:
        437ms  :app:prepareComAndroidSupportAppcompatV72340Library
         50ms  :app:prepareComAndroidSupportDesign2340Library
         66ms  :app:prepareComAndroidSupportSupportV42340Library
         75ms  :app:prepareComFacebookFrescoImagepipeline110Library
         56ms  :app:prepareOrgXutilsXutils3336Library
        870ms  :app:mergeDebugResources
         93ms  :app:processDebugManifest
        777ms  :app:processDebugResources
       1200ms  :app:compileDebugJavaWithJavac
       3643ms  :app:transformClassesWithJarMergingForDebug
       5520ms  :app:transformClassesWithMultidexlistForDebug
      61770ms  :app:transformClassesWithDexForDebug
         99ms  :app:transformNative_libsWithMergeJniLibsForDebug
        332ms  :app:transformResourcesWithMergeJavaResForDebug
       2083ms  :app:packageDebug
        202ms  :app:zipalignDebug
    
  • 2、删除build目录第三遍全量打包(开启fastdex)

    BUILD SUCCESSFUL
    
      Total time: 1 mins 57.764 secs
      Task spend time:
        106ms  :app:prepareComAndroidSupportAnimatedVectorDrawable2340Library
        107ms  :runtime:transformClassesAndResourcesWithSyncLibJarsForDebug
        416ms  :app:prepareComAndroidSupportAppcompatV72340Library
         67ms  :app:prepareComAndroidSupportSupportV42340Library
         76ms  :app:prepareComFacebookFrescoImagepipeline110Library
         53ms  :app:prepareOrgXutilsXutils3336Library
        111ms  :app:processDebugManifest
        929ms  :app:mergeDebugResources
        697ms  :app:processDebugResources
       1227ms  :app:compileDebugJavaWithJavac
       3237ms  :app:transformClassesWithJarMergingForDebug
       6225ms  :app:transformClassesWithMultidexlistForDebug
      78990ms  :app:transformClassesWithDexForDebug
        122ms  :app:transformNative_libsWithMergeJniLibsForDebug
        379ms  :app:transformResourcesWithMergeJavaResForDebug
       2050ms  :app:packageDebug
         77ms  :app:zipalignDebug
    
  • 3、在打开fastdex第两回全量打包已毕后,关掉fastdex修改sample工程的MainActivity.java

    BUILD SUCCESSFUL
    
    Total time: 1 mins 05.394 secs
    Task spend time:
       52ms  :app:mergeDebugResources
     2583ms  :app:compileDebugJavaWithJavac
    60718ms  :app:transformClassesWithDexForDebug
      101ms  :app:transformNative_libsWithMergeJniLibsForDebug
      369ms  :app:transformResourcesWithMergeJavaResForDebug
     2057ms  :app:packageDebug
       75ms  :app:zipalignDebug
    
  • 4、在打开fastdex第两回全量打包完毕后,依然开启fastdex修改sample工程的MainActivity.java

    BUILD SUCCESSFUL
    
      Total time: 16.5 secs
      Task spend time:
        142ms  :app:processDebugManifest
       1339ms  :app:compileDebugJavaWithJavac
       3291ms  :app:transformClassesWithJarMergingForDebug
       4865ms  :app:transformClassesWithMultidexlistForDebug
       1005ms  :app:transformClassesWithDexForDebug
       2112ms  :app:packageDebug
         76ms  :app:zipalignDebug
    
打包编号 总时间 transform时间
1 1 mins 46.678s 61770 ms
2 1 mins 57.764s 78990 ms
3 1 mins 05.394s 60718 ms
4 16.5s 1005 ms

由此1和2相对而言发现,开启fastdex进行第三遍全量的打包时的小时花费比不开启多了10秒左右,这些重大是流入代码和IO上的付出

透过2和3相比较发现,开启fastdex进行补丁打包时的时光花费比不开启快了60秒左右,那就是期待已久的构建速度啊\_

==============================
刚激动一会就尼玛报了一个错误,当修改activity_main.xml时往里面扩大一个控件

<TextView
    android:id="@+id/tv2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

打出来的包启动的时候就径直crash掉了

Caused by: java.lang.IllegalStateException: 
Required view 'end_padder' with ID 2131493007 for field 'tv1' was not found.
If this view is optional add '@Nullable' (fields) or '@Optional' (methods) annotation.
     at butterknife.internal.Finder.findRequiredView(Finder.java:51)
     at com.dx168.fastdex.sample.CustomView$$ViewBinder.bind(CustomView$$ViewBinder.java:17)
     at com.dx168.fastdex.sample.CustomView$$ViewBinder.bind(CustomView$$ViewBinder.java:12)
     at butterknife.ButterKnife.bind(ButterKnife.java:187)
     at butterknife.ButterKnife.bind(ButterKnife.java:133) 
     at com.dx168.fastdex.sample.CustomView.<init>(CustomView.java:20) 
     ......
     at dalvik.system.NativeStart.main(Native Method) 

错误音信里的趣味是为CustomView的tv1字段,寻找id=2131493007的view时从不找到,先反编译报错的apk,�找到报错的地点CustomView$$ViewBinder.bind

public class CustomView$$ViewBinder<T extends CustomView>
        implements ViewBinder<T>
{
    public CustomView$$ViewBinder()
    {
        System.out.println(AntilazyLoad.str);
    }

    public Unbinder bind(Finder paramFinder, T paramT, Object paramObject)
    {
        InnerUnbinder localInnerUnbinder = createUnbinder(paramT);
        paramT.tv1 = ((TextView)paramFinder.castView((View)paramFinder
                .findRequiredView(paramObject, 2131493007, "field 'tv1'"), 2131493007, "field 'tv1'"));
        paramT.tv3 = ((TextView)paramFinder.castView((View)paramFinder
                .findRequiredView(paramObject, 2131493008, "field 'tv3'"), 2131493008, "field 'tv3'"));
        return localInnerUnbinder;
    }
    ......
}

CustomView$$ViewBinder这些类是ButterKnife动态生成的,这一个值的发源是CustomView的tv1字段上边的笺注,CustomView.class反编译后如下

public class CustomView extends LinearLayout 
{
    @BindView(2131493007)
    TextView tv1;
    @BindView(2131493008)
    TextView tv3;

    public CustomView(Context paramContext, AttributeSet paramAttributeSet)
    {
        super(paramContext, paramAttributeSet);
        inflate(paramContext, 2130968632, this);
        ButterKnife.bind(this);
        this.tv3.setText(2131099697);
        MainActivity.aa();
        System.out.println(AntilazyLoad.str);
    }
}

总的来看那里是不是觉得奇怪,CustomView的源码明明是

public class CustomView extends LinearLayout {
    @BindView(R.id.tv1)  TextView tv1;
    @BindView(R.id.tv3)  TextView tv3;

    public CustomView(Context context, AttributeSet attrs) {
        super(context, attrs);
        inflate(context,R.layout.view_custom,this);
        ButterKnife.bind(this);

        tv3.setText(R.string.s3);
        MainActivity.aa();
    }
}

�在编译将来R.id.tv1怎么就改成数字2131493007了吧,原因是java编译器做了一个属性优化,固然发现源文件引用的是一个包括final描述符的常量,会一向做值copy

反编译最终三遍编译成功时的R.class结果如下(
app/build/intermediates/classes/debug/com/dx168/fastdex/sample/R.class)

public static final R {
    public static final class id {
        ......

        public static final int tv1 = 2131493008;
        public static final int tv2 = 2131492977;
        public static final int tv3 = 2131493009;

        ......

        public id() {
        }
    }
}

经过分析,当全量打包时R.id.tv1 =
2131493007,由于R文件中的id都是final的,所以引用R.id.tv1的地点都被轮换为它对应的值2131493007了;当在activity_layout.xml中添加名字为tv2的控件,然后开展补丁打包时R.id.tv1的值变成了2131493008,而缓存的dex对应节点的值依旧2131493007,所以在查找id为2131493007对应的控件时因为找不到而挂掉

本人的率先个想法是一旦在实践完processDebugResources任务后,把R文件里id类的所有字段的final描述符去掉就足以把值copy那么些编译优化绕过去
=>

public static final R {
    public static final class id {
        ......

        public static int tv1 = 2131493008;
        public static int tv2 = 2131492977;
        public static int tv3 = 2131493009;

        ......

        public id() {
        }
    }
}

解除未来在实践compileDebugJavaWithJavac时编译出错了

2.png

阴差阳错的缘故是表明只可以引用带final描述符的常量,除此之外switch语句的case也不可以不引用常量,具体请查看oracle对常量表明式的说明

假诺运用这一个方案,对id的引用就不可以拔取常量表明式,像ButterKnife那样的view看重注入的框架都不可能用了,限制性太大那几个想法就废弃了

再有一个思路就是修改aapt的源码�,使很多次封装时名字同样id的值保持一致,这些一定能解决可是工作量太大了就从未有过如此做,之后采用了一个折中的办法,就是每一回把项目中的所有类(除去第三方库)都踏足dex的成形,固然缓解了那几个问题但成效一下子下滑好多,需求将近40秒才能跑起来仍然很慢

==============================
那一个题材苦恼了许久,直到tinker开源后阅读它的源码TinkerResourceIdTask.groovy时,发现它们也赶上了平等的问题,并有了一个缓解方案,大家的情状和tinker场景在这一个题材上是一模一样的,直接照抄代码就化解了那几个问题,首要的事体说三次,感谢tinker、感谢tinker、感谢tinker!!

tinker的缓解方案是,打补丁时依据用户配置的resourceMapping文件(每一次构建成功后输出的app/build/intermediates/symbols/debug/R.txt),生成public.xml和ids.xml然后放进app/build/intermediates/res/merged/debug/values目录里,aapt在处理的时候会基于文件里的安插规则去变通,具体那块的规律请看锤子科技董事长老罗的文章Android应用程序资源的编译和包裹进程分析(在内部搜索public.xml)那其间有详实的认证

同上并组成大家的场合,第三回全量打包成功之后把app/build/intermediates/symbols/debug/R.txt缓存下来,补丁打包在履行processResources任务前,根据缓存的符号表R.txt去生成public.xml和ids.xml然后放进app/build/intermediates/res/merged/debug/values目录里,这样同样名字的id前后的一遍构建值就能保持一致了,代码如下FastdexResourceIdTask.groovy

public class FastdexResourceIdTask extends DefaultTask {
    static final String RESOURCE_PUBLIC_XML = "public.xml"
    static final String RESOURCE_IDX_XML = "idx.xml"

    String resDir
    String variantName

    @TaskAction
    def applyResourceId() {
        File buildDir = FastdexUtils.getBuildDir(project,variantName)
        String resourceMappingFile = new File(buildDir,Constant.R_TXT)
        // Parse the public.xml and ids.xml
        if (!FileUtils.isLegalFile(resourceMappingFile)) {
            project.logger.error("==fastdex apply resource mapping file ${resourceMappingFile} is illegal, just ignore")
            return
        }
        File idsXmlFile = new File(buildDir,RESOURCE_IDX_XML)
        File publicXmlFile = new File(buildDir,RESOURCE_PUBLIC_XML)
        if (FileUtils.isLegalFile(idsXmlFile) && FileUtils.isLegalFile(publicXmlFile)) {
            project.logger.error("==fastdex public xml file and ids xml file already exist, just ignore")
            return
        }
        String idsXml = resDir + "/values/ids.xml";
        String publicXml = resDir + "/values/public.xml";
        FileUtils.deleteFile(idsXml);
        FileUtils.deleteFile(publicXml);
        List<String> resourceDirectoryList = new ArrayList<String>()
        resourceDirectoryList.add(resDir)

        project.logger.error("==fastdex we build ${project.getName()} apk with apply resource mapping file ${resourceMappingFile}")
        Map<RDotTxtEntry.RType, Set<RDotTxtEntry>> rTypeResourceMap = PatchUtil.readRTxt(resourceMappingFile)

        AaptResourceCollector aaptResourceCollector = AaptUtil.collectResource(resourceDirectoryList, rTypeResourceMap)
        PatchUtil.generatePublicResourceXml(aaptResourceCollector, idsXml, publicXml)
        File publicFile = new File(publicXml)

        if (publicFile.exists()) {
            FileUtils.copyFileUsingStream(publicFile, publicXmlFile)
            project.logger.error("==fastdex gen resource public.xml in ${RESOURCE_PUBLIC_XML}")
        }
        File idxFile = new File(idsXml)
        if (idxFile.exists()) {
            FileUtils.copyFileUsingStream(idxFile, idsXmlFile)
            project.logger.error("==fastdex gen resource idx.xml in ${RESOURCE_IDX_XML}")
        }
    }
}

project.afterEvaluate {
    android.applicationVariants.all { variant ->
        def variantOutput = variant.outputs.first()
        def variantName = variant.name.capitalize()

        //保持补丁打包时R文件中相同的节点和第一次打包时的值保持一致
        FastdexResourceIdTask applyResourceTask = project.tasks.create("fastdexProcess${variantName}ResourceId", com.dx168.fastdex.build.task.FastdexResourceIdTask)
        applyResourceTask.resDir = variantOutput.processResources.resDir
        applyResourceTask.variantName = variantName
        variantOutput.processResources.dependsOn applyResourceTask
    }
}

若果项目中的资源越发多,第四遍补丁打包生成public.xml和ids.xml时会占用部分岁月,最好做五次缓存,未来的补丁打包直接动用缓存的public.xml和ids.xml**

==============================
解决了上边的原理性问题后,接下去继续做优化,下边有讲到*
transformClassesWithMultidexlistForDebug*任务的效益,由于使用了隔离Application的做法,所有的花色代码都不在classes.dex中,这么些用来分析那一个项目中的类要求放在classes.dex的职务就从未意思了,直接禁掉它

project.afterEvaluate {
    android.applicationVariants.all { variant ->
        def variantName = variant.name.capitalize()

        def multidexlistTask = null
        try {
            multidexlistTask = project.tasks.getByName("transformClassesWithMultidexlistFor${variantName}")
        } catch (Throwable e) {
            //没有开启multiDexEnabled的情况下,会报这个任务找不到的异常
        }
        if (multidexlistTask != null) {
            multidexlistTask.enabled = false
        }
    }
}

禁掉以后,执行./gradle assembleDebug,在构建进程中挂掉了

:app:transformClassesWithMultidexlistForDebug SKIPPED
:app:transformClassesWithDexForDebug
Running dex in-process requires build tools 23.0.2.
For faster builds update this project to use the latest build tools.
UNEXPECTED TOP-LEVEL ERROR:
java.io.FileNotFoundException: /Users/tong/Projects/fastdex/app/build/intermediates/multi-dex/debug/maindexlist.txt (No such file or directory)
      at java.io.FileInputStream.open0(Native Method)
      at java.io.FileInputStream.open(FileInputStream.java:195)
      at java.io.FileInputStream.<init>(FileInputStream.java:138)
      at java.io.FileInputStream.<init>(FileInputStream.java:93)
      at java.io.FileReader.<init>(FileReader.java:58)
      at com.android.dx.command.dexer.Main.readPathsFromFile(Main.java:436)
      at com.android.dx.command.dexer.Main.runMultiDex(Main.java:361)
      at com.android.dx.command.dexer.Main.run(Main.java:275)
      at com.android.dx.command.dexer.Main.main(Main.java:245)
      at com.android.dx.command.Main.main(Main.java:106)
:app:transformClassesWithDexForDebug FAILED

FAILURE: Build failed with an exception.
......
BUILD FAILED

从上边的日记的首先行发现transformClassesWithMultidexlistForDebug任务真正禁止掉了,前边跟着一个SKIPPED的出口,可是执行transformClassesWithDexForDebug任务时报app/build/intermediates/multi-dex/debug/maindexlist.txt
(No such file or directory)
,原因是transformClassesWithDexForDebug义务会检讨那个文件是否存在,既然那样就在推行transformClassesWithDexForDebug义务前创办一个空文件,看是不是还会报错,代码如下

public class FastdexCreateMaindexlistFileTask extends DefaultTask {
    def applicationVariant

    @TaskAction
    void createFile() {
        if (applicationVariant != null) {
            File maindexlistFile = applicationVariant.getVariantData().getScope().getMainDexListFile()
            File parentFile = maindexlistFile.getParentFile()
            if (!parentFile.exists()) {
                parentFile.mkdirs()
            }

            if (!maindexlistFile.exists() || maindexlistFile.isDirectory()) {
                maindexlistFile.createNewFile()
            }
        }
    }
}

project.afterEvaluate {
    android.applicationVariants.all { variant ->
        def variantName = variant.name.capitalize()

        def multidexlistTask = null
        try {
            multidexlistTask = project.tasks.getByName("transformClassesWithMultidexlistFor${variantName}")
        } catch (Throwable e) {
            //没有开启multiDexEnabled的情况下,会报这个任务找不到的异常
        }
        if (multidexlistTask != null) {
            FastdexCreateMaindexlistFileTask createFileTask = project.tasks.create("fastdexCreate${variantName}MaindexlistFileTask", FastdexCreateMaindexlistFileTask)
            createFileTask.applicationVariant = variant

            multidexlistTask.dependsOn createFileTask
            multidexlistTask.enabled = false
        }
    }
}

双重执行./gradle assembleDebug

:app:transformClassesWithJarMergingForDebug UP-TO-DATE
:app:collectDebugMultiDexComponents UP-TO-DATE
:app:fastdexCreateDebugMaindexlistFileTask
:app:transformClassesWithMultidexlistForDebug SKIPPED
:app:transformClassesWithDexForDebug UP-TO-DATE
:app:mergeDebugJniLibFolders UP-TO-DATE
:app:transformNative_libsWithMergeJniLibsForDebug UP-TO-DATE
:app:processDebugJavaRes UP-TO-DATE
:app:transformResourcesWithMergeJavaResForDebug UP-TO-DATE
:app:validateConfigSigning
:app:packageDebug UP-TO-DATE
:app:zipalignDebug UP-TO-DATE
:app:assembleDebug UP-TO-DATE

BUILD SUCCESSFUL

Total time: 16.201 secs

本次构建成功验证创立空文件的那种格局可行

=========

咱俩集团的门类在运用的经过中,发现补丁打包时尽管只改了一个java类,但构建时举办compileDebugJavaWithJavac职责仍旧花了13秒

BUILD SUCCESSFUL

Total time: 28.222 secs
Task spend time:
    554ms  :app:processDebugManifest
    127ms  :app:mergeDebugResources
   3266ms  :app:processDebugResources
  13621ms  :app:compileDebugJavaWithJavac
   3654ms  :app:transformClassesWithJarMergingForDebug
   1354ms  :app:transformClassesWithDexForDebug
    315ms  :app:transformNative_libsWithMergeJniLibsForDebug
    220ms  :app:transformResourcesWithMergeJavaResForDebug
   2684ms  :app:packageDebug

经过分析由于我们利用了butterknife和tinker,那三个里面都用到了javax.annotation.processing.AbstractProcessor那个接口做代码动态变化,所以项目中的java文件假如过多,挨个扫描所有的java文件同时做操作会促成大量的时日浪费,其实她们每一次变更的代码几乎都是千篇一律的,因而如若补丁打包时能把那几个职务换成自己的落到实处,仅编译和快照相比较变化的java文件,并把结果输出到app/build/intermediates/classes/debug,覆盖原来的class,能大大进步功效,部分代码如下,详情看FastdexCustomJavacTask.groovy

public class FastdexCustomJavacTask extends DefaultTask {
    ......

    @TaskAction
    void compile() {
        ......
        File androidJar = new File("${project.android.getSdkDirectory()}/platforms/${project.android.getCompileSdkVersion()}/android.jar")
        File classpathJar = FastdexUtils.getInjectedJarFile(project,variantName)
        project.logger.error("==fastdex androidJar: ${androidJar}")
        project.logger.error("==fastdex classpath: ${classpathJar}")
        project.ant.javac(
                srcdir: patchJavaFileDir,
                source: '1.7',
                target: '1.7',
                encoding: 'UTF-8',
                destdir: patchClassesFileDir,
                bootclasspath: androidJar,
                classpath: classpathJar
        )
        compileTask.enabled = false
        File classesDir = applicationVariant.getVariantData().getScope().getJavaOutputDir()
        Files.walkFileTree(patchClassesFileDir.toPath(),new SimpleFileVisitor<Path>(){
            @Override
            FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Path relativePath = patchClassesFileDir.toPath().relativize(file)
                File destFile = new File(classesDir,relativePath.toString())
                FileUtils.copyFileUsingStream(file.toFile(),destFile)
                return FileVisitResult.CONTINUE
            }
        })
    }
}
project.afterEvaluate {
    android.applicationVariants.all { variant ->
        def variantName = variant.name.capitalize()
        Task compileTask = project.tasks.getByName("compile${variantName}JavaWithJavac")
        Task customJavacTask = project.tasks.create("fastdexCustomCompile${variantName}JavaWithJavac", com.dx168.fastdex.build.task.FastdexCustomJavacTask)
        customJavacTask.applicationVariant = variant
        customJavacTask.variantName = variantName
        customJavacTask.compileTask = compileTask
        compileTask.dependsOn customJavacTask
    }
}

执行./gradlew assembleDebug ,再来一遍

BUILD SUCCESSFUL

Total time: 17.555 secs
Task spend time:
   1142ms  :app:fastdexCustomCompileDebugJavaWithJavac
     59ms  :app:generateDebugBuildConfig
    825ms  :app:processDebugManifest
    196ms  :app:mergeDebugResources
   3540ms  :app:processDebugResources
   3045ms  :app:transformClassesWithJarMergingForDebug
   1505ms  :app:transformClassesWithDexForDebug
    391ms  :app:transformNative_libsWithMergeJniLibsForDebug
    253ms  :app:transformResourcesWithMergeJavaResForDebug
   3413ms  :app:packageDebug

登时间快了10秒左右,good

=========
既然如此有缓存,就有缓存过期的题材,倘诺大家添加了某个第三方库的看重(器重关系暴发变化),并且在档次代码中援引了它,若是不拔除缓存打出去的包运行起来后自然会包类找不到,所以须要处理这些业务。
第一怎么得到依靠关系啊?通过以下代码可以取得一个依靠列表

project.afterEvaluate {
    project.configurations.all.findAll { !it.allDependencies.empty }.each { c ->
        if (c.name.toString().equals("compile")
                || c.name.toString().equals("apt")
                || c.name.toString().equals("_debugCompile".toString())) {
            c.allDependencies.each { dep ->
                String depStr =  "$dep.group:$dep.name:$dep.version"
                println("${depStr}")
            }
        }
    }
}

输入如下

com.dialonce:dialonce-android:2.3.1
com.facebook.fresco:fresco:1.1.0
com.google.guava:guava:18.0
......
com.android.support:design:23.4.0
com.bigkoo:alertview:1.0.2
com.bigkoo:pickerview:2.0.8

可以在首先次全量打包时,和变化项目源码目录快照的同一个时间点,获取一份当前的器重性列表并保存下去,当补丁打包时在赢得一份当前的依赖列表,与事先封存的作对照,假设发生变化就把缓存清除掉

除此以外最好提供一个再接再砺消除缓存的职分

public class FastdexCleanTask extends DefaultTask {
    String variantName

    @TaskAction
    void clean() {
        if (variantName == null) {
            FastdexUtils.cleanAllCache()
        }
        else {
            FastdexUtils.cleanCache(project,variantName)
        }
    }
}

先来一个清除所有缓存的职务

project.tasks.create("fastdexCleanAll", FastdexCleanTask)

接下来在依照buildType、flavor创立对应的铲除职分

android.applicationVariants.all { variant ->
    def variantName = variant.name.capitalize()
    //创建清理指定variantName缓存的任务(用户触发)
    FastdexCleanTask cleanTask = project.tasks.create("fastdexCleanFor${variantName}", FastdexCleanTask)
    cleanTask.variantName = variantName
}

==============================

(四)推流和传导

接轨的优化布署

  • 1、进步稳定性和容错性,那个是最要害的
  • 2、近期补丁打包的时候,是把尚未变动的类从app/build/intermediates/transforms/jarMerging/debug/jars/1/1f/combined.jar中移除,倘诺能hook掉transformClassesWithJarMergingForDebug那些任务,仅把爆发变化的class参预combined.jar的变迁,可以在IO上省出广大的岁月
  • 3、目前给项目源码目录做快照,使用的是文本copy的主意,若是能只是只把须求的音信写在文书文件里,可以在IO上省出有些时间
  • 4、近日还并未对libs目录中暴发变化做监控,后续必要补上这一块
  • 5、apk的设置速度相比慢(越发是ART下是因为在安装时对利用做AOT编译,所以导致安装速度更加慢,具体请参考张邵文大神的稿子Android
    N混合编译与对热补丁影响解析
    ),通过socket把代码补丁和资源补丁发送给app,做到免安装

==============================

(五)现代播放器原理

此处对包裹的流程做下统计

(六)延迟优化

打包流程

(七)SDK 性能测试模型

全量打包时的流程:
  • 1、合并所有的class文件生成一个jar包
  • 2、扫描所有的项目代码并且在构造方法里添加对fastdex.runtime.antilazyload.AntilazyLoad类的依靠
    诸如此类做的目标是为了缓解class verify的问题,
    详情请看
    安卓App热补丁动态修复技术介绍
  • 3、对品种代码做快照,为了未来补丁打包时相比这一个java文件发出了变更
  • 4、对脚下项目标之所以器重做快照,为了以后补丁打包时相比看重是否发生了变动,倘诺生成须求免去缓存
  • 5、调用真正的transform生成dex
  • 6、缓存生成的dex,并且把fastdex-runtime.dex插入到dex列表中,假设生成了七个dex,classes.dex
    classes2.dex 须要做一下操作
    fastdex-runtime.dex => classes.dex
    classes.dex => classes2.dex
    classes2.dex => classes3.dex
    然后运行期在入口Application(fastdex.runtime.法斯特dexApplication)使用MultiDex把拥有的dex加载进来
  • @see
    fastdex.build.transform.FastdexDexTransform
  • 7、保存资源映射表,为了维持id的值一致,详情看
  • @see
    fastdex.build.task.FastdexResourceIdTask

视频编码的含义

  • 本来视频数据存储空间大,一个 1080P 的 7 s 视频需求 817 MB
  • 原始摄像数据传输占用带宽大,10 Mbps 的带宽传输上述 7 s 录像须求 11
    分钟

而经过 H.264 编码压缩之后,录像大小只有 708 k ,10 Mbps 的带宽仅仅须要500 ms
,可以满意实时传输的须要,所以从视频采访传感器收集来的原始视频势需要通过录像编码。

补丁打包时的流水线
  • 1、检查缓存的卓有功效
  • @see
    fastdex.build.variant.FastdexVariant
    的prepareEnv方法求证
  • 2、扫描所有变更的java文件并编译成class
  • @see
    fastdex.build.task.FastdexCustomJavacTask
  • 3、合并所有变更的class并生成jar包
  • 4、生成补丁dex
  • 5、把具有的dex依据一定规律放在transformClassesWithMultidexlistFor${variantName}职务的输出目录
    fastdex-runtime.dex => classes.dex
    patch.dex => classes2.dex
    dex_cache.classes.dex => classes3.dex
    dex_cache.classes2.dex => classes4.dex
    dex_cache.classesN.dex => classes(N + 2).dex

=============

基本原理

这为何巨大的本来视频可以编码成很小的视频呢?那中间的技能是什么吧?
宗旨情想就是剔除冗余音讯:

  • 空间冗余:图像相邻像素之间有较强的相关性
  • 日子冗余:视频连串的隔壁图像之间内容类同
  • 编码冗余:不一样像素值出现的票房价值不一致
  • 视觉冗余:人的视觉系统对某些细节不灵活
  • 文化冗余:规律性的布局可由先验知识和背景知识得到

视频本质上讲是一文山会海图片三番五回快捷的播音,最简便的缩减情势就是对每一帧图片进行压缩,例如相比古老的
MJPEG
编码就是那种编码格局,那种编码方式只有帧内编码,利用空间上的抽样预测来编码。形象的比喻就是把每帧都当做一张图片,采用JPEG
的编码格式对图纸展开削减,那种编码只考虑了一张图片内的冗余音信压缩,如图
1,肉色的有些就是时下待编码的区域,红色就是不曾编码的区域,蓝色区域可以按照现已编码的局地开展前瞻(肉色的左边,下面,左下等)。

图1

只是帧和帧之间因为时间的相关性,后续开发出了部分相比较高档的编码器能够动用帧间编码,简单点说就是透过搜索算法选定了帧上的某些区域,然后经过测算当前帧和内外参考帧的向量差举办编码的一种格局,通过下面七个图
2
再三再四帧大家可以见到,滑雪的同班是上前位移的,但实际上是雪景在向后位移,P
帧通过参考帧(I 或任何 P
帧)就足以展开编码了,编码之后的轻重缓急非凡小,压缩比卓殊高。

图 2

也许有同学对那两张图片怎么来的感兴趣,那里用了 FFmpeg
的两行命令来贯彻,具体 FFmpeg 的越多内容请看后续章节:

  • 率先行生成带有移动矢量的视频
  • 其次行把每一帧都输出成图片

ffmpeg  -flags2 +export_mvs -i tutu.mp4 -vf codecview=mv=pf+bf+bb tutudebug2.mp4

ffmpeg -i tutudebug2.mp4 'tutunormal-%03d.bmp'

除此之外空间冗余和岁月冗余的削减,首要还有编码压缩和视觉收缩,下边是一个编码着首要的流程图:

图 3

图 4

图 3、图 4 七个流程,图 3 是帧内编码,图 4
是帧间编码,从图上看看的显要分化就是首先步分裂,其实那三个流程也是构成在协同的,大家一般说的
I 帧和 P 帧就是各自选择了帧内编码和帧间编码。

全副项目标代码近日曾经开源了 https://github.com/typ0520/fastdex

万一你喜欢本文就来给我们star吧

=============
增速apk的构建速度,怎样把编译时间从130秒降到17秒
加紧apk的构建速度,如何把编译时间从130秒降到17秒(二)

编码器的取舍

前面梳理了刹那间编码器的规律和宗旨流程,编码器经历了数十年的前进,已经从上马的只扶助帧内编码演进到现近来的
H.265 和 VP9
为表示的新一代编码器,就当前有些周边的编码器进行剖析,带我们研商一下编码器的社会风气。

参考的档次与文章

Instant
Run

Tinker

安卓App热补丁动态修复技术介绍

Android应用程序资源的编译和包装过程分析

关键字:
加速apk编译速度
加快app编译速度
增速android编译速度
加速android studio 编译速度
android 加速编译速度
android studio编译慢
android studio编译速度优化
android studio gradle 编译慢

H.264

简介

H.264/AVC
项目意向创造一种视频正式。与旧标准相相比较,它亦可在更低带宽下提供优质视频(换言之,唯有MPEG-2,H.263 或 MPEG-4 第 2
局地的一半带宽或更少),也不扩大太多设计复杂度使得无法落成或得以已毕资本过高。另一目标是提供丰裕的灵活性以在各样应用、网络及系统中应用,包罗高、低带宽,高、低录像分辨率,广播,DVD
存储,RTP/IP 网络,以及 ITU-T 多媒体公用电话系统。

H.264/AVC
包括了一层层新的风味,使得它比起此前的编解码器不但可以更实惠的拓展编码,还可以在各个网络环境下的行使中动用。那样的技艺基础让
H.264 成为包罗 YouTube
在内的在线视频公司使用它当做紧要的编解码器,可是利用它并不是一件很轻松的事情,理论上讲使用
H.264 要求上交不菲的专利用度。

专利许可

和 MPEG-2 第一片段、第二片段,MPEG-4第二部分同样,使用 H.264/AVC
的成品创立商和服务提供商须要向她们的制品所采取的专利的所有者支付专利许可费用。这几个专利许可的重大源于是一家名为
MPEG-LA LLC 的民用社团,该团体和 MPEG
标准化社团尚未别的关联,可是该社团也管理著 MPEG-2
第一有的系统、第二有的录像、MPEG-4
第二局地录像和此外一些技巧的专利许可。

此外的专利许可则要求向另一家名为 VIA Licensing
的个人社团申请,这家商店此外也管理偏向音频压缩的专业如 MPEG-2 AAC 及
MPEG-4 奥迪(Audi)o 的专利许可。

H.264 的开源已毕

  • openh264
  • x264

openh264
是思科完毕的开源 H.264 编码,纵然 H.264
必要缴纳不菲的专利成本,可是专利费有一个寒暑上限,思科把 OpenH264
落成的年度专利费交满后,OpenH264 事实上就可以免费自由的使用了。

x264
x264是一个运用GPL授权的摄像编码自由软件。x264 的紧要意义在于进行H.264/MPEG-4 AVC 的录像编码,而不是用作解码器(decoder)之用。

除开成本问题相比来看:

  • openh264 CPU 的占有相对 x264低很多
  • openh264 只援助 baseline profile,x264 接济越多 profile

HEVC/H.265

简介

高功效录像编码(High Efficiency Video
Coding,简称HEVC)是一种摄像压缩标准,被视为是 ITU-T H.264/MPEG-4 AVC
标准的子孙后代。2004 年起头由 ISO/IEC Moving Picture Experts
Group(MPEG)和 ITU-T Video Coding Experts Group(VCEG)作为 ISO/IEC
23008-2 MPEG-H Part 2 或称作 ITU-T H.265 先河制定。第一版的 HEVC/H.265
录像压缩正式在 2013 年 4 月 13
日被接受为国际电信联盟(ITU-T)的正经标准。HEVC
被认为不但提高视频质地,同时也能达标 H.264/MPEG-4 AVC
两倍之压缩率(等同于同样画面质地下比特率收缩了 50%),可支撑 4K
分辨率甚至到超高清电视机(UHD电视),最高分辨率可高达
8192×4320(8K分辨率)。

H.265 的开源落成

  • libde265
  • x265

libde265
HEVC 由 struktur 集团以开源许可证 GNU LesserGeneral Public License
(LGPL)
提供,观众得以较慢的网速下欣赏到最高质料的形象。跟此前基于H.264标准的解码器相比较,libde265
HEVC 解码器可以将您的全高清内容带给多达两倍的受众,或者,减弱 50%
流媒体播放所急需的带宽。高清或者 4K/8K
超高清流媒体播发,低顺延/低带宽录像会议,以及完整的移位设备覆盖。具有「拥塞感知」视频编码的安宁,非凡合乎拔取在
3/4G 和 LTE 网络。

专利许可

HEVC Advance 必要所有包含苹果、YouTube、Netflix、脸谱、亚马逊(Amazon)等利用
H.265 技术的情节创立商上缴内容收入的
0.5%看作技术使用费,而全套流媒体市场每年高达约 1000
亿欧元的范围,且不断增强中,征收
0.5%相对是一笔巨大的花费。而且他们还尚无放过设备创制商,其中电视机厂商需求开发每台
1.5 比索、移动设备厂商每台 0.8
法郎的专利费。他们照旧没有放过蓝光设备播放器、游戏机、视频机那样的厂商,那么些厂商必须开销每台
1.1 日元的花销。最不能令人承受的是,HEVC Advance
的专利使用权追溯到了厂商的「」”,意思是事先曾经出卖的成品仍旧要追缴花费。

x265 是由
MulticoreWare 开发,并开源。采取 GPL
协议,可是接济这一个类型的几个店家构成了联盟可以在非 GPL
协议下行使这么些软件。

VP8

简介

VP8 是一个怒放的录像压缩格式,最早由 On2 Technologies 开支,随后由
谷歌 发布。同时 谷歌 也发布了 VP8 编码的实做库:libvpx,以 BSD
授权条款的方法发行,随后也增大了专利使用权。而在经过一些争辨过后,最后VP8 的授权确认为一个怒放源代码授权。

眼前协助 VP8 的网页浏览器有 Opera、Firefox 和 Chrome。

专利许可

2013 年2月,谷歌 与 MPEG LA 及 11 个专利持有者已毕协议,让谷歌(Google) 获取
VP8 以及其之前的 VPx 等编码所可能侵袭的专利授权,同时 谷歌(Google)也得以无条件再度授权相关专利给 VP8 的用户,此协议同时适用于下一代 VPx
编码。至此 MPEG LA 扬弃创设 VP8 专利集中授权联盟,VP8
的用户将可确定义务使用此编码而无须担心可能的专利侵权授权金的题目。

VP8 的开源完结

  • libvpx

libvpx
是 VP8 的唯一开源完结,由 On2 Technologies 支出,谷歌(Google)收购后将其开放源码,License 格外宽松可以无限制使用。

VP9

简介

VP9 的支付从 2011 年第三季早先,目的是在同画质下,比 VP8 编码缩小50%的文件大小,另一个对象则是要在编码功能上跨越 HEVC 编码。

2012 年 12 月 13 日,Chromium 浏览器加入了 VP9 编码的支撑。Chrome
浏览器则是在 2013 年 2 月 21 日上马帮助 VP9 编码的录像播放。

谷歌(Google) 揭橥会在 2013 年 6 月 17 日落成 VP9 编码的制定工作,届时Chrome
浏览器将会把 VP9 编码默许率领。2014 年 3 月 18 日,Mozilla 在 Firefox
浏览器中插足了 VP9 的支撑。

2015 年 4 月 3 日,谷歌(谷歌(Google))发表了 libvpx1.4.0 增添了对 10 位和 12
位的比特深度辅助、4:2:2 和 4:4:4 色度抽样,并 VP9 多为重编/解码。

专利许可

VP9 是一个开花格式、无义务金的录像编码格式。

VP9 的开源落成

  • libvpx

libvpx
是 VP9 的绝无仅有开源完成,由 谷歌 开发珍爱,里面有一部分代码是 VP8 和 VP9
公用的,其他分别是 VP8 和 VP9 的编解码完结。

VP9 和 H.264 和 HEVC 比较

Codec HEVC x264 vp9
HEVC -42.2% 32.6%
x264 75.8% 18.5%
vp9 48.3% -14.6%
Codec HEVC vs. VP9(in %) VP9 vs. x264 (in %)
Total Average 612 39399

引用 Comparative Assessment of H.265/MPEG-HEVC, VP9, and
H.264/MPEG-AVC Encoders for Low-Delay Video Applications
那篇相比新的舆论对,低延迟录像进行编码的测试结果。

HEVC 和 H.264 在分裂分辨率下的可比

跟 H.264/MPEG-4 相相比较,HEVC 的平分比特率减低值为:

分辨率 480P 720P 1080P 4K UHD
HEVC 52% 56% 62% 64%

可见码率下降了 60% 以上。

  • HEVC (H.265) 对 VP9 和 H.264 在码率节省上有较大的优势,在同一 PSNR
    下独家节省了 48.3% 和 75.8%。
  • H.264 在编码时间上有巨大优势,相比较 VP9 和 HEVC(H.265) ,HEVC 是 VP9
    的6倍,VP9 是 H.264 的接近 40 倍

FFmpeg

谈到视频编码相关内容就不得不提一个伟大的软件包 — FFmpeg。

FFmpeg
是一个自由软件,可以运行音频和视频多种格式的录影、转换、流功用,包蕴了
libavcodec ——那是一个用于八个类型中音频和录像的解码器库,以及
libavformat ——一个节奏与视频格式转换库。

FFmpeg 这些单词中的 FF 指的是 法斯特 Forward。有些新手写信给 FFmpeg
的项目官员,询问 FF 是不是意味 法斯特 Free 或者 法斯特(Fast) Fourier
等意思,FFmpeg 的花色负责人回信说:「Just for the record, the original
meaning of FF in FFmpeg is 法斯特 Forward…」

这几个序列中期是由 Fabrice 贝尔ard 发起的,而近来是由 迈克尔(Michael) Niedermayer
在开展保证。许多FFmpeg的开发者同时也是 MPlayer 项目标积极分子,FFmpeg 在
MPlayer 项目中是被规划为服务器版本进行支付。

FFmpeg 下载地址是 : FFmpeg
Download

  • 可以浏览器输入下载,如今协理 Linux ,Mac OS,Windows
    八个主流的平台,也得以协调编译到 Android 或者 iOS 平台。
  • 倘假使 Mac OS ,能够因而 brew 安装
    brew install ffmpeg --with-libvpx --with-libvorbis --with-ffplay

大家得以用 FFmpeg
来做哪些有用有幽默的作业吗?通过一多元小试验来带大家明白 FFmpeg
的神奇和强硬。

FFmpeg 录屏

因此一个小例子看一下怎么在 Mac OS 上面选用 FFmpeg 举办录屏:

输入:

ffmpeg -f avfoundation -list_devices true -i ""

输出:

[AVFoundation input device @ 0x7fbec0c10940] AVFoundation video devices:
[AVFoundation input device @ 0x7fbec0c10940] [0] FaceTime HD Camera
[AVFoundation input device @ 0x7fbec0c10940] [1] Capture screen 0
[AVFoundation input device @ 0x7fbec0c10940] [2] Capture screen 1
[AVFoundation input device @ 0x7fbec0c10940] AVFoundation audio devices:
[AVFoundation input device @ 0x7fbec0c10940] [0] Built-in Microphone

交付了眼前装备协助的所有输入设备的列表和号码,我本地有两块展现器,所以 1
和 2 都是自己屏幕,可以选拔一块举办录屏。

翻看当前的 H.264 编解码器:

输入:

ffmpeg -codecs | grep 264

输出:

 DEV.LS h264                 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (decoders: h264 h264_vda ) (encoders: libx264 libx264rgb )

查看当前的 VP8 编解码器:

输入:

ffmpeg -codecs | grep vp8

输出:

  DEV.L. vp8                  On2 VP8 (decoders: vp8 libvpx ) (encoders: libvpx )

可以采纳用 vp8 或者 h264 做编码器

ffmpeg -r 30 -f avfoundation -i 1 -vcodec vp8 -quality realtime screen2.webm
# -quality realtime 用来优化编码器,如果不加在我的 Air 上帧率只能达到 2

or

ffmpeg -r 30 -f avfoundation -i 1 -vcodec h264 screen.mp4

接下来用 ffplay 播放就足以了

ffplay screen.mp4

or

ffplay screen2.webp

FFmpeg 视频转换成 gif

有一个特地实用的急需,在网上发现了一个尤其有意思的视频想把它转换成一个动态表情,作为一个
IT
从业者,我首先个想到的不是下载一个转码器,也不是去找一个在线转换网站,直接动用手边的工具
FFmpeg,须臾间就水到渠成了转码:

ffmpeg -ss 10 -t 10  -i tutu.mp4  -s 80x60  tutu.gif
## -ss 指从 10s 开始转码,-t 指转换 10s 的视频 -s

FFmpeg 录制屏幕并直播

可以再三再四壮大例子1,直播当前屏幕的内容,向我们介绍一下怎么通过几行命令搭建一个测试用的直播服务:

Step 1:首先安装 docker:
访问 Docker
Download

,按操作系统下载安装。

Step 2:下载 nginx-rtmp 镜像:

docker pull chakkritte/docker-nginx-rtmp

Step 3:创建 nginx html 路径,启动 docker-nginx-rtmp

mkdir ~/rtmp

docker run -d -p 80:80 -p 1935:1935 -v ~/rtmp:/usr/local/nginx/html chakkritte/docker-nginx-rtmp

Step 4:推送屏幕录制到 nignx-rtmp

ffmpeg -y -loglevel warning -f avfoundation -i 2 -r 30 -s 480x320 -threads 2 -vcodec libx264  -f flv rtmp://127.0.0.1/live/test

Step 5:用 ffplay 播放

ffplay rtmp://127.0.0.1/live/test

小结一下,FFmpeg
是个卓越的工具,可以透过它形成很多平凡的做事和实验,可是距离提供真正可用的流媒体服务、直播服务还有越发多的办事要做,那上边可以参照七牛云发表的
七牛直播云服务

封装

介绍完了录像编码后,再来介绍部分卷入。沿用前面的比喻,封装可以精晓为运用哪一类货车去运输,也就是媒体的器皿。

所谓容器,就是把编码器生成的多媒体内容(视频,音频,字幕,章节消息等)混合封装在协同的正式。容器使得分化多媒体内容同步播放变得很简短,而容器的另一个功能就是为多媒体内容提供索引,也就是说如若没有容器存在的话一部影片你不得不从一开端观望最终,无法拖动进程条(当然那种情状下有些播放器会话相比长的大运暂时创办索引),而且一旦你不团结去手动别的载入音频就一直不动静,下边介绍两种普遍的封装格式和优缺点:

  1. AVI 格式(后缀为 .AVI): 它的英文全称为 奥迪(Audi)o Video Interleaved
    ,即音频摄像交错格式。它于 1992 年被 Microsoft 集团推出。
    那种视频格式的优点是图像质量好。由于无损AVI可以保存 alpha
    通道,常常被大家运用。缺点太多,体积过于庞大,而且进一步不好的是削减正式不合并,最常见的情景就是高版本
    Windows 媒体播放器播放不了选用早期编码编辑的AVI格式视频,而低版本
    Windows
    媒体播放器又播放不了选用新式编码编辑的AVI格式视频,所以我们在展开一些AVI格式的视频播放时常会冒出由于视频编码问题而导致的视频不可以播放或就算可以播放,但存在无法调节播放进度和播发时唯有声响从未图像等一些无缘无故的题材。

  2. DV-AVI 格式(后缀为 .AVI): DV的英文全称是 Digital Video Format
    ,是由索尼(Sony)、松下、声擎 等多家厂商共同提出的一种家用数字视频格式。
    数字录像机就是运用那种格式记录视频数据的。它可以经过电脑的 IEEE 1394
    端口传输视频数据到计算机,也可以将统计机中编辑好的的视频数据回录到数码录像机中。那种录像格式的文件扩充名也是
    avi。电视机台利用视频带记录模拟信号,通过 EDIUS 由IEEE
    1394端口采集卡从视频带中收集出来的视频就是那种格式。

  3. Quick提姆e File Format 格式(后缀为 .MOV):
    美利哥Apple公司用度的一种录像格式,默许的播放器是苹果的Quick提姆e。
    具备较高的压缩比率和较周全的视频清晰度等风味,并能够保存alpha通道。

  4. MPEG 格式(文件后缀可以是 .MPG .MPEG .MPE .DAT .VOB .ASF .3GP
    .MP4等) : 它的英文全称为 Moving Picture Experts
    Group,即移动图像专家组格式,该专家组建于1988年,专门负责为 CD
    建立视频和旋律标准,而成员都是为摄像、音频及系统领域的技术专家。
    MPEG 文件格式是运动图像压缩算法的国际标准。MPEG
    格式近年来有几个压缩正式,分别是 MPEG-1、MPEG-2、和MPEG-4
    。MPEG-1、MPEG-2 近来早就运用较少,珍爱介绍
    MPEG-4,其制订于1998年,MPEG-4
    是为着播放流式媒体的高质地视频而特意安插的,以求使用最少的多寡得到最佳的图像质料。近来MPEG-4 最有吸引力的地点在于它亦可保留接近于DVD画质的小体积视频文件。

  5. WMV 格式(后缀为.WMV .ASF): 它的英文全称为Windows Media
    Video,也是微软生产的一种采用独立编码格局并且可以一贯在网上实时看到录像节目标文本压缩格式。
    WMV格式的根本优点包罗:本地或网络回看,丰盛的流间关系以及扩大性等。WMV
    格式需求在网站上播放,需求安装 Windows Media Player( 简称 WMP
    ),很不便利,现在早就大约从不网站采用了。

  6. Real Video 格式(后缀为 .RM .RMVB): Real Networks
    集团所制定的音频视频压缩正式称为Real Media。
    用户可以动用 RealPlayer
    按照不一样的网络传输速率制定出分化的压缩比率,从而完结在低速率的网络上开展形象数据实时传送和广播。RMVB
    格式:那是一种由RM视频格式升级延伸出的新视频格式,当然性能上有很大的升迁。RMVB
    视频也是享有较明朗的优势,一部大小为700MB左右的 DVD
    影片,固然将其转录成同样质量的 RMVB 格式,其个头最多也就 400MB
    左右。大家莫不注意到了,以前在网络上下载电影和视频的时候,平日接触到
    RMVB
    格式,可是随着时代的前进那种格式被越多的更不错的格式替代,盛名的芸芸众生影视字幕组在二零一三年一度发布不再限于
    RMVB 格式视频。

  7. Flash Video 格式(后缀为 .FLV):由 Adobe Flash
    延伸出来的的一种流行网络录像封装格式。随着视频网站的增加,这么些格式已经万分普及。

  8. Matroska 格式(后缀为
    .MKV):是一种新的多媒体封装格式,这些封装格式可把多种不一样编码的摄像及16条或以上分化格式的旋律和言语不一样的字幕封装到一个
    Matroska Media
    档内。它也是其中一种开放源代码的多媒体封装格式。Matroska
    同时还足以提供越发好的互动效用,而且比 MPEG 的福利、强大。

  9. MPEG2-TS 格式 (后缀为 .ts)(Transport
    Stream“传输流”;又称MTS、TS)是一种传输和存储包涵音效、视频与通信协议种种数码的正规化格式,用于数字电视广播系统,如DVB、ATSC、IP电视机等等。
    MPEG2-TS 定义于 MPEG-2
    第一有些,系统(即原来之ISO/IEC标准13818-1或ITU-T Rec. H.222.0)。
    Media Player Classic、VLC
    多媒体播放器等软件可以直接播放MPEG-TS文件。

现阶段,大家在流媒体传输,更加是直播中根本运用的就是 FLV 和 MPEG2-TS
格式,分别用于 RTMP/HTTP-FLV 和 HLS 协议。

下一期大家将系统讲授视频直播的推流和传导,尽请期待~