【腾讯Bugly干货分享】Android 插件技术实战总结

2024-03-23 18:48

本文主要是介绍【腾讯Bugly干货分享】Android 插件技术实战总结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/1p5Y0f5XdVXN2EZYT0AM_A

前言

安卓应用开发的大量难题,其实最后都需要插件技术去解决。

现今插件技术的使用非常普遍,比如微信、QQ、淘宝、天猫、空间、携程、大众点评、手机管家等等这些大家在熟悉不过的应用都在使用。

插件技术可以给项目开发带来巨大的好处,比如:并行高效开发、模块解耦、解除单个dex函数不能超过65535的限制、动态更新升级、按需加载等等。

本文的目的是从一个典型的复杂项目中总结出较为全面与完整的安卓插件技术。

掌握好插件技术,需要如下的安卓基础和相关知识,例如:
1. Android应用程序安装,加载过程
2. Android应用运行机制,生命周期调用原理
3. Android应用资源编译打包原理
4. Android应用读取资源原理
5. Android系统AMS、PMS、NMS等系统服务的运作原理
6. 增量更新
7. HOOK等技术

插件技术知识领域如图:

这些技术中每一个点都需要大篇幅内容才能完全讲清楚。不过,好在Android是开源的,每一个插件技术涉及到的技术点都可以翻阅源码进行进一步的研究。下面我从当前所负责的一个插件化项目(PACEWEAR手表助手)经历,来梳理一下插件技术的应用及核心内容。

项目的困惑

PACEWEAR手表助手原自腾讯TOS的智能穿戴项目。

因为目前大部分智能手表和手环还不能独立联网通讯,须通过蓝牙连接手机,借助手机的网络来完成一系列业务功能。PACEWEAR手表助手就是这么一个手机软件,帮助智能穿戴设备使用手机网络,并通过蓝牙连接的方式完成对智能穿戴设备的各种配置和管理。

PACEWEAR手表助手项目开始初期,业务并没有大面积铺开,三四个工程师还算跑的比较顺利,随着项目的进展,主工程框架、登录、配对、设置、ota、市场、天气、地图、运动、音乐、健康管理、支付、应用管理、表盘管理等功能不断加入,参与的人也慢慢变多,问题也就多了起来,维护越来越困难,总结有如下几点:

  1. 工程频繁报方法数超65535
  2. 多个模块在同一个app中开发代码耦合,架构冗余,牵一发而动全身
  3. 人员效率低下,时间往往花费在沟通,构建问题处理上
  4. 分工不明确,灰色地带重复逻辑比较多
  5. 业务与业务之间互相调用,不够独立
  6. 问题跟进原来越繁琐,牵扯人数众多
  7. 功能越来越多,目前这种开发方式不可持续
  8. 连接的手表和手环设备种类越来越多

针对以上问题虽然我们考虑过动态加载jar、Html5等措施来缓解,但最终还是没能彻底从根本上解决这些问题,一直在苦恼着整个项目团队...

寻找适合项目的插件框架

这种情况下我们很快意识到需要引入插件化的开发模式,才能一劳永逸地这解决这一系列问题。

引入Dynamic-load-apk插件框架

团队在2015年中开始引入了Dynamic-load-apk(后面简称DyLA)框架,这套框架是从App应用层解决加载插件的问题:创建一个继承自Activity的ProxyActivity类,然后让插件中的所有Activity都继承自ProxyActivity,并重写Activity所有的方法。然而在功能上,仅支持Activity组件,这个是这套框架最大的短板;另外基于这套框架进行的插件应用开发,依赖条件复杂[需要内置jar包,组件必须实现ProxyActivity的所有接口]、调试困难等各种问题。重重约束是的项目插件化业务进展及其缓慢,比如支付模块两个同事开发了两个月最后发现很多需求没法实现,最终不得不放弃插件化;健康模块开发不到两周的同事开始抓狂,被各种问题不断折腾着(为啥不能联调、为什么这个要特殊处理、为什么这里资源找不到等等)。最后仅有健康、Yiya语音极少数几个模块勉强插件化。随着项目的进展,业务模块的不断增多,当初的问题不但没有得到解决,反而增加了对DyLA模块的维护,这个状态一直持续到了2016下半年9月。

预研适合项目的插件框架

PACEWEAR手表助手项目团队在9月份初对比了一些开源插件框架的能力:

同时评估了他们的优缺点,最后确定基于APF进行开发一套适合PACEWEAR手表助手的插件框架。

然而,仅是支持application和四大组件还远远不能满足PACEWEAR手表助手项目的要求,PACEWEAR手表助手有二十多个业务模块,第一批需要进行插件化的就有十五个,由不同的同事进行开发负责,而且有些业务还需要和第三方进行交互对接...因此,团队要能高效的将PACEWEAR手表助手项目完成插件化并且让所有插件业务都符合产品需求稳定的运行,对插件框架要求首先就需要做到基于框架开发的插件应用功能对齐原生,这样框架就需要:

  1. 支持application、四大组件(activity4个LaunchMode)、so、fragment、notification、toast等基础能力
  2. 支持联调插件应用
  3. 支持加载本地网页等
  4. 支持插件自定义控件和样式
  5. 组件进程配置等原生应用程序的能力;

同时需要这套框架支持将宿主的基础能力:设备账号信息、和手表通讯、统计上报、文件传输、网络、ota、控件库及宿主的资源共享给插件应用;

另外需要将插件运行时间及在宿主中的显示与宿主完全解耦。不然插件的调整必然要影响到宿主的代码调整,这可不是一个明智的落地方案。

综合上面的要求及项目进行过程中的调整,经过进一个月的努力,这套框架终于预研成功,正式应用到PACEWEAR手表助手项目上。

这套框架就叫TwsPluginFramework框架(后面简称TPF框架,已经开源:https://github.com/rickdynasty/TwsPluginFramework )。

这套框架相比业界其他插件框架能力对比如下:

另外Hook系统服务的安全隐患是不可预知的,因此TwsPluginFramework框架尽可能少的对系统服务等进行hook处理。

TPF框架原理

插件技术的实现原理是源于Android系统(Android系统本身就是一套插件框架,运行在这个系统之上的应用就是一个个的”插件应用”)对应用的管理机制:安装(Install)、运行(Running)、卸载(Uninstall)。

运行在TPF框架之上的插件应用和android应用程序又有所不同,不同点主要有下面几点:

  1. 应用程序的安装有android系统负责完成,而插件应用的安装流程由插件框架负责完成;
  2. 插件应用没有走系统的安装流程,组件等信息没有被注册到系统里面,要使插件应用能正常的运行,插件框架需要将这些插件应用内部的组件全部“合法化”;
  3. 插件应用的卸载也不走系统的卸载流程,而是由宿主负责完成的。

上面三个流程中安装、卸载基本和系统的处理方式是一样的。而运行就一样,插件应用程序的运行需要经过“插件框架”这个中间层进行合法化后才能运行在系统里面,这个合法化过程就需要做很多事,下面会重点讲解,先来看一下插件控件的这几个流程和系统的差别:


系统应用管理机制示例图


TPF框架插件应用管理机制示意图

插件框架是插件化项目的核心,它运行在宿主应用里面。宿主程序在启动过程中的第一件事就是将插件框架加载好,以便接下来可以运行插件应用里面的业务。

插件框架是插件应用的承载体,负责了插件应用的安装、运行、卸载管理。因插件应用并不是直接安装在系统里面,因此插件框架就必须承载android系统的这一系列能力:

  1. 必须自己去识别插件应用并完成拷贝解析工作
  2. 必须给插件应用组件赋予android系统正常的生命才能让插件应用正常运行。
  3. 必须自己去清理将要卸载的应用数据和正在运行的功能及组件。

剖析TPF框架

下面我就从加载TPF插件框架、安装插件应用程序、运行插件应用程序、卸载插件应用程序四个环节详细讲述一下TPF框架内幕。

加载TPF插件框架

宿主程序在启动过程中的首要事情就是将插件框架加载好,以便接下来可以将插件应用正常的运作起来。插件框架在整个项目工程中扮演的是一个极其核心的角色:除了负责所有插件应用的安装卸载,还需要赋予插件应用组件一个合法的身份。

在android系统中,应用程序运行的背后有很多服务在维持这些组件的运作,比如ActivityManagerService、PackageManagerService、WindowManagerService、NotificationManagerService等以及应用程序背后的ActivityThread等等,这些都是TPF框架需要Hook的范围内容。

具体的流程如下:

为了让插件应用内部的组件合法化,插件框架需要对应用程序做一些HOOK处理,以便让插件的组件能正常运行。

安装插件应用程序

插件应用程序要能够运行在宿主里面,首先得经过安装这个过程让宿主知道当前这个插件应用的信息,然后插件框架就会将当前插件解压拷贝到指定目录以便后面的运行需要。

在TwsPluginFramework框架中,插件包就是一个应用程序apk。对插件信息的收集方式和系统一样,通过解析AndroidManifest.xml来收集应用信息,包括版本、sdk、application、四大组件等等。

具体的流程如下:

这个过程基本和应用程序的安装过程无异,只是插件应用程序的显示图标等内容直接由插件框架在解析的过程中获取并拷贝到私有目录下面。

运行插件应用程序

运行插件内部的任何组件之前,首先得加载好插件的代码和资源,然后就在构建插件的上下文以及Application等信息,TwsPluginFramework框架启动插件的流程图如下:

类加载

在TwsPluginFramework框架中,通过DexClassLoader来加载插件应用的代码, DexClassLoade的使用示意图如下:

TwsPluginFramework框架在构建插件应用的ClassLoader的时候会指定其父ClassLoader为宿主的。这样插件内部就可以直接访问宿主的代码内容。

资源加载

在TwsPluginFramework框架中资源的加载和系统一样,也是通过AssetManager的addAssetPath/addAssetPaths方法进行处理的,只是这两个方法是隐藏的,得用通过反射来调用。

在TwsPluginFramework框架里,在构建插件应用上下文Resource的时候,将宿主的资源与插件的资源合并在一起了。这样做的好处就是插件应用可以共享宿主的资源数据。

对于插件框架来说,如何处理插件资源和宿主资源是一个非常纠结的选择:

然而,资源合并方案就得处理资源ID冲突问题,在TwsPluginFramework框架里面是通过修改AAPT来指定插件应用资源的package id,从而达到区分宿主和插件的资源id的目的。

生命周期

插件应用程序是运行在插件框架这个中间层上面的,而非直接运行在android系统里的。也正因为如此,插件框架就需得自己去完成应用程序包的内容加载以及组件的生命赋予工作。

在Android的世界里面,应用的组件是有“生命”的,比如:activity、service、BroadcastReceive、application等,这种“生命”是由Android系统所赋予的。

对于应用程序来说,只要在AndroidManifest.xml里面注册便可以轻易获得这种生命,因为应用的I(安装)R(运行)U(卸载)是由安装系统来承载的。而对于插件应用的I(安装)R(运行)U(卸载)是由运行在宿主里面的插件框架来承载的。仅因这一点的差别,使得插件应用内部的组件如果不做一些特殊处理,系统是不会给予它们“生命”的。

在TwsPluginFramework框架里面,插件的组件是拥有真正生命周期,完全交由系统管理、非反射代理。插件应用并没有经过系统安装,内部的组件并没有注册到系统里面。那TPF是怎么做到让插件里面的组件也能让系统给没被注册的插件应用组件拥有完整生命周期的?

答案就在TPF框架里面的两个计策: 偷梁换柱、瞒天过海。

瞒天过海:在宿主中提前申明好多个组件,在向系统请求启动的过程中用这些预先申明号的组件去做请求,等系统的校验流程结束后换回成目标的插件组件,从而达到瞒过系统。

瞒天过海环节需要在宿主中申明好用来做替身的receiver、service(多个)[独立进程的单独配置多个]、activity(多个) [不同single模式的单独配置多个]。

偷梁换柱:为了让系统能够按着我们的意愿在组件启时将目标插件组件替换成宿主中预先申明号的对应组件,等系统校验环节过了在换回成目标插件组件,我们就需要替换掉应用程序空间一些重要的处理对象,比如:ActivityThread里面负责应用程序与系统交互的Instrumentation对象以及组件处理流程的回调Handler.Callback等。

下面就以基本组件的启动流程来描述一下这两个计策:

Activity

Activity生命周期大家在熟悉不过了,可是在onCreate之前系统做很多你所不知道的事。

从点击桌面图标(或者出发启动一个activity)到这个应用activity组件进入onCreate()

这个环节是解决插件组件activity完整生命周期的关键。这个环节在TwsPluginFramework框架内部的处理流程:

从开始执行execStartActivity到最终将Activity对象new出来这个过程,系统层会去校验需要启动的activity的合法性[是否有在应用的AndroidManifest.xml里面注册]以及按启动要求创建activity对象。了解了这点就可以很好的绕过系统的约束,达到需要的目的。

Service

stopService、bindService以及sendBroadcast的流程和startService是一样的,这里就不赘述了。

卸载插件应用程序

当前插件应用要下架或者需要更新到新版本的时候,就需要将当前的插件应用给卸载掉。这个过程和Android系统卸载应用程序是一样的。

和插件应用安装过程相反,这个过程就是清理记录在宿主插件框架里面的信息、删除代码和资源同时停止所有该插件正在运行的组件及服务。

流程如下:

显示协议框架

TPF框架将插件在宿主中的调用时机及显示入口完全与宿主解耦,也就是说插件应用的调整不需调整宿主程序的任何代码。这些都归功于TPF提供了一套显示协议框架,插件应用只需要知道显示协议的使用就可以,显示协议(可以根据项目需求自定义,下面是输出给PACEWEAR手表助手插件应用项目的规范) 的概要如下:

显示位置pos: 1 Hotseat; 2 MyWatchFragment; 3 ActionBarMenu; 4 其他
分隔符: # 分割DisplayConfig; @ 分割DisplayConfig的属性; = 属性赋值; / 分割属性值
图标资源icon:统一使用 模块名_[hotseat or watch_fragment or menu]_描述信息.png 配置在AndroidManifest.xml不需要带后缀。 【normal/focus/press/...】
标题title:中文/英文 也可以只配置一个
显示内容content:如果是fragment 直接配置name,其他的配置类名信息内容类型ctype:1 fragment; 2 activity; 3 service; 4 application; 5 view
插件启动时机: 1 手动触发 2 随DM启动 3 配对成功后插件依赖: 1 已安装的app 2 已安装的插件ActionBar 配置只在显示位置是Hotseat的前提下可用
ActionBar标题ab-title:actionbar标题 中文/英文 也可以只配置一个 暂不支持subTitle
ActionBar右侧按钮显示内容ab-rbtncontent:actionbar右侧按钮点击触发显示内容
ActionBar右侧按钮显示内容类型ab-rbtnctype:  触发显示内容 的类型 1 fragment; 2 activity; 3 service; 4 application; 5 view(当前只支持activity,如果是activity可以不配置)
ActionBar右侧按钮内容ab-rbtnres: 显示在按钮上的内容根据类型不同而不一样(类型1 文本;类型2 图标
ActionBar右侧按钮内容ab-rbtnrestype:1、文本按钮(res配置中英文String) 2、ImageButton(res配置图标) 

更多详细的内容请移步到https://github.com/rickdynasty/TwsPluginFramework。

TPF框架给项目团队带来的好处

当前PACEWEAR手表助手项目除宿主应用外还有15个(业务)插件应用,PACEWEAR手表助手仅仅是一个包含基础功能和插件框架的调度平台。后续所有新增加的业务都会议插件应用的方式集成进来,宿主基本不用care到底有哪些业务会集成进来。而且当前PACEWEAR手表助手项目计划将其他两个产品项目合并进来成一个平台产品。这一切的改善很大部分是TPF带来的,下面总结了一下TPF框架的好处:

  1. 业务模块完全解耦,不再有调整一个模块而影响到另一个甚至多个模块的情况。
  2. 各个业务的插件应用开发、编译各自进行,开发效率大幅度提升,从而缩短开发周期。
  3. 业务插件可单独动态更新升级,不需要重启PACEWEAR手表助手便可生效。
  4. 对于宿主 — PACEWEAR手表助手来说,可以按需求加载需要的插件应用,这样本来多个相似的产品线就可以合成一个,大幅度降低人力成本。
  5. 不再被65535困扰。
  6. 团队协作更和谐。
  7. ...

TPF框架一路走过的经典Bug

Theme/Style异常

Log截图:

这类问题主要出现在第一套区分资源ID方案(通过public.xml的public-padding特性来处理)上,这类问题的根本原因是:android系统处理应用资源,在底层处理ResourceTable的bag资源的出现了异常。

Android资源管理机制是一个非常复杂的课题(包括:资源打包、资源加载、资源寻找,每一块又分java层和C层),有兴趣兴趣的可以去翻一下源码,在线地址:http://androidxref.com 。简单来说这个问题:“就是style不同于其他资源,style本身是不创建资源的,它仅仅是一个资源的应用集合,而系统访问资源是通过偏移量的方式去获取资源。这种方式在同一个packageID的段来说,只要style是连续的就ok。但是如果不符合这个要求,那上面的问题就会出现。”

在TPF的第一套区分资源ID方案中,通过public.xml的public-padding特性来区分资源id,不难做到让style连续,但要做到多个插件工程并发的情况下做到连续却是基本不可能。这也是为什么TPF放弃了这套方案的原因。

明白了其中的原因,要解决这类问题也就简单了。

解决方案:尽可能的符合系统规则,在同一个packageID段内让相同type的资源ID连续就行。当前通过修改aapt来指定资源的packageID是一个很好的方式。

ClassNotFound

严格来说这个不是TPF框架的问题,TPF框架在处理加载代码上完全是按着系统的规格要求。把这类问题拿出来放这里,只是因为在项目开发过程中插件工程反馈之类问题不较多。

出现ClassNotFound,无非两种情况:1、类被混淆了 2、类不在当前ClassLoader的可视范围内。

解决方案:

  1. 混淆的很容有处理,找出来不做混淆就行。
  2. 不在ClassLoader可视范围内这个就需要注意一下,插件的ClassLoader父类是宿主的ClassLoader,这个自然就不存在插件内部范文不了的情况。在TPF里面多次出现这个问题的主要原因在共享库的更新上:TPF提供了一套共享库,这套库里面包括了一套控件、宿主基础能力、和手表通讯、网络、文件传输等等一系列共性的内容,在开发阶段难免会对内容进行变更处理,而有些插件工程如果长时间没有更新,那就有可能出现ClassNotFound的问题。这样就需要在调整的时候做兼容,同事插件开发同事及时更新sdk。
Resources$NotFoundException

在TPF里面,插件是可以直接访问宿主提供的共享资源,然而这仅仅只能满足插件内部的逻辑流程。

  1. 但在极少特定机型(比如:vivo)里面会比较奇葩的存在这类问题。
    解决方案:插件的上下文以及Resources对象(PluginResourceWrapper)都是由TPF构造的。在插件的PluginResourceWrapper内部进行重定向到宿主就可以了。

  2. 但对于Notification等这些系统的通用服务也是会出这类问题。这些服务内部通过id获取资源,最终是会落到宿主的上下文上面。而对于宿主来说,插件的资源是不可见,自然就没法通过插件的resID来获取插件的资源。
    解决方案:像Notification这类的系统服务,如果需要传递资源id到系统里面进行处理获取资源,一律使用宿主的资源id。

备注:情况②没法用情况①的方式进行处理的原因这里简单描述一下:应用程序在启动的过程中,在application被关联之前Resources就创建好了,而且这个Resources对象在ContextImpl里面还是final类型,这样再java层就没法实施偷梁换柱的方式进行替换处理。

项目进展过程中更多的bug记录请移步:https://github.com/rickdynasty/TwsPluginFramework_Doc。

TwsPluginFramework(TPF)框架现已经开源:
https://github.com/rickdynasty/TwsPluginFramework


更多精彩内容欢迎关注腾讯 Bugly的微信公众账号:

腾讯 Bugly是一款专为移动开发者打造的质量监控工具,帮助开发者快速,便捷的定位线上应用崩溃的情况以及解决方案。智能合并功能帮助开发同学把每天上报的数千条 Crash 根据根因合并分类,每日日报会列出影响用户数最多的崩溃,精准定位功能帮助开发同学定位到出问题的代码行,实时上报可以在发布后快速的了解应用的质量情况,适配最新的 iOS, Android 官方操作系统,鹅厂的工程师都在使用,快来加入我们吧!

这篇关于【腾讯Bugly干货分享】Android 插件技术实战总结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/839205

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

HarmonyOS学习(七)——UI(五)常用布局总结

自适应布局 1.1、线性布局(LinearLayout) 通过线性容器Row和Column实现线性布局。Column容器内的子组件按照垂直方向排列,Row组件中的子组件按照水平方向排列。 属性说明space通过space参数设置主轴上子组件的间距,达到各子组件在排列上的等间距效果alignItems设置子组件在交叉轴上的对齐方式,且在各类尺寸屏幕上表现一致,其中交叉轴为垂直时,取值为Vert

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

学习hash总结

2014/1/29/   最近刚开始学hash,名字很陌生,但是hash的思想却很熟悉,以前早就做过此类的题,但是不知道这就是hash思想而已,说白了hash就是一个映射,往往灵活利用数组的下标来实现算法,hash的作用:1、判重;2、统计次数;

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

【专题】2024飞行汽车技术全景报告合集PDF分享(附原数据表)

原文链接: https://tecdat.cn/?p=37628 6月16日,小鹏汇天旅航者X2在北京大兴国际机场临空经济区完成首飞,这也是小鹏汇天的产品在京津冀地区进行的首次飞行。小鹏汇天方面还表示,公司准备量产,并计划今年四季度开启预售小鹏汇天分体式飞行汽车,探索分体式飞行汽车城际通勤。阅读原文,获取专题报告合集全文,解锁文末271份飞行汽车相关行业研究报告。 据悉,业内人士对飞行汽车行业

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

金融业开源技术 术语

金融业开源技术  术语 1  范围 本文件界定了金融业开源技术的常用术语。 本文件适用于金融业中涉及开源技术的相关标准及规范性文件制定和信息沟通等活动。