热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Android资源编译和打包过程分析

这一篇是我们Android热修复学习深入分析的第一篇。学习总纲计划可以看上一篇文章总纲首先我们先来分析资源修复相关知识。资源修复的过程基本可以分析为这么一个过程:老包

这一篇是我们Android热修复学习深入分析的第一篇。 学习总纲计划可以看上一篇文章总纲 首先我们先来分析资源修复相关知识。资源修复的过程基本可以分析为这么一个过程: 老包(线上出现bug需要修复的那个apk包)打包成apk的时候把它需要的资源都打包进去了,然后新的补丁包加入了增加的资源或者需要替换的资源,apk在运行时读取相关资源的时候进行了增加或者替换相关的操作。所以今天我们先来分析资源编译和打包的整个过程。

简介

我们都知道apk其实是一个压缩包,我将一个平时开发的apk解压得到如下目录:

这里我们可以看到,经过编译和打包以后,apk里有:

  1. 二进制的AndroidManifest.xml
  2. assets资源,原封不动的打包到了apk里
  3. classes.dex,java代码编译为dex文件,这里不详述
  4. kotlin代码
  5. lib包
  6. res文件夹,打开可以看到里面都是些二进制文件
  7. resources.arsc,资源索引表。因为Android设备种类繁多,资源索引表的作用就是知道设备的配置信息的情况以后,快速的根据资源ID去匹配到最合适的那个资源。

这里我们重点分析资源文件,Android是通过aapt(Android Asset Package Tool)把资源文件打包到apk里的,也就是上面的2和6,在打包到apk里之前,会先把除了assets资源,res/raw文件资源以外的资源都编译成二进制格式,之所以要编译成二进制文件,原因无非两点:

  1. 空间占用小
  2. 解析速度快

这之后,除了assets资源以外,会给其他所有的资源都生成一个ID,也就是代码里的R.id.xxxxxxxxxx。根据这些ID,打包工具会生成上面我们看到的resources.arsc资源索引表以及一个R.java。资源索引表负责记录所有资源信息,根据资源ID和设备信息,快速的匹配最合适的那个资源。R.java文件则负责记录各个资源ID常量。

那么资源索引表resources.arsc跟R.java文件打开都是什么样的呢?接下来我们就先来看看这两个文件里最终形态到底是怎么样的,根据最后展示给我们的样子再去反推过程会更加容易理解。

解析resources.arsc文件内容

注意:本篇文章Android 源码都出自Android 9.0, 这里,我新建了一个项目,加了各种资源:

然后打包出了一个apk,拿到他的resources.arsc文件对其进行解析:

先来看网上这张神图:

这张图基本已经把resources.arsc的结构画的很清楚了。最终resources.arsc文件是由一系列的chunk组成的,每一个chunk都有一个头部,用来描述chunk的元信息。从图上可以看到,其实整个资源索引表也可以看成是一个总的chunk,头部描述了头大小,文件大小等参数。可以理解成设计模式中的组合模式。解析完一个chunk后,从这个chunk+size的位置开始,就可以得到下一个chunk的起始位置,这样就可以一次读取玩整个文件的数据内容了。

我们来看chunk_header的源码,ResChunk_header源码位于在线源码地址链接:

/*** Header that appears at the front of every data chunk in a resource.*/
struct ResChunk_header
{// Type identifier for this chunk. The meaning of this value depends// on the containing chunk.uint16_t type;// Size of the chunk header (in bytes). Adding this value to// the address of the chunk allows you to find its associated data// (if any).uint16_t headerSize;// Total size of this chunk (in bytes). This is the chunkSize plus// the size of any data associated with the chunk. Adding this value// to the chunk allows you to completely skip its contents (including// any child chunks). If this value is the same as chunkSize, there is// no data associated with the chunk.uint32_t size;
};

type对应的是chunk的类型 headerSize对应的是chunk头部的大小 size对应的是chunk的大小

接着我们再来看下整个资源索引表的头部信息,也就是ResourceTableHeader源码,源码地址

/*** Header for a resource table. Its data contains a series of* additional chunks:* * A ResStringPool_header containing all table values. This string pool* contains all of the string values in the entire resource table (not* the names of entries or type identifiers however).* * One or more ResTable_package chunks.** Specific entries within a resource table can be uniquely identified* with a single integer as defined by the ResTable_ref structure.*/
struct ResTable_header
{struct ResChunk_header header;// The number of ResTable_package structures.uint32_t packageCount;
};

header对应的是整个table的header, packageCount对应的是被编译的资源包的个数

这里我们运行解析resources.arsc代码,解析Resource Table的头部得到如下信息:

整个chunk大小位1417151232byte,headerSize = 12, 所以下图中的高亮部分就是我一开始建立的项目apk资源索引表的header部分。

接下来来看Global String Pool部分,即为资源项的值字符串资源池。写入字符串资源池的chunk同样也是有一个header的,结构如下,代码地址位于:添加链接描述

struct ResStringPool_header
{struct ResChunk_header header;// Number of strings in this pool (number of uint32_t indices that follow// in the data).uint32_t stringCount;// Number of style span arrays in the pool (number of uint32_t indices// follow the string indices).uint32_t styleCount;// Flags.enum {// If set, the string index is sorted by the string values (based// on strcmp16()).SORTED_FLAG &#61; 1<<0,// String pool is encoded in UTF-8UTF8_FLAG &#61; 1<<8};uint32_t flags;// Index from header of the string data.uint32_t stringsStart;// Index from header of the style data.uint32_t stylesStart;
};

header即为一个chunk的header&#xff0c; stringCount即为字符串的个数&#xff0c; styleCount即为字符串样式的个数&#xff0c; stringsStart和stylesStart分别指的是字符串内容与字符串样式的内容相对于其头部的距离。

解析之前我们的项目apk发现内容如下&#xff1a;

接下来就是package数据块部分了&#xff0c;按照上面那张神图&#xff0c;我们先来看其头部部分&#xff1a;

/*** A collection of resource data types within a package. Followed by* one or more ResTable_type and ResTable_typeSpec structures containing the* entry values for each resource type.*/
struct ResTable_package
{struct ResChunk_header header;// If this is a base package, its ID. Package IDs start// at 1 (corresponding to the value of the package bits in a// resource identifier). 0 means this is not a base package.uint32_t id;// Actual name of this package, \0-terminated.uint16_t name[128];// Offset to a ResStringPool_header defining the resource// type symbol table. If zero, this package is inheriting from// another base package (overriding specific values in it).uint32_t typeStrings;// Last index into typeStrings that is for public use by others.uint32_t lastPublicType;// Offset to a ResStringPool_header defining the resource// key symbol table. If zero, this package is inheriting from// another base package (overriding specific values in it).uint32_t keyStrings;// Last index into keyStrings that is for public use by others.uint32_t lastPublicKey;uint32_t typeIdOffset;
};

header是这个chunk的头部信息 id也就是资源的package id&#xff0c;一般apk都有两个id&#xff0c;一个是系统资源包&#xff0c;id为0x01&#xff0c;还有一个是用户包&#xff0c;也就是0x7F&#xff0c;Android规定id在0x01-0x7F之间都是合理的&#xff0c;所以阿里Sophix热修复框架在资源修复上就采用了新增一个package id为0x66的资源包来达到热修复的效果&#xff0c;这是后话&#xff0c;之后我们会详细深入&#xff0c;这里先提一下。 name也就是包名。 typeStrings就是类型字符串资源池相对头部的偏移位置。 lastPublicType指的是最后一个导出的Public类型字符串在类型字符串资源池中的索引&#xff0c;目前这个值设置为类型字符串资源池的大小。 keyStrings指的是资源项名称字符串相对头部的偏移量。 lastPublicKey指的是最后一个导出的Public资源项名称字符串在资源项名称字符串资源池中的索引&#xff0c;目前这个值设置为资源项名称字符串资源池的大小。

根据上面的内容我们再来看我们的项目apk的实例&#xff1a;

得到type &#61; RES_TABLE_PACKAGE_TYPE, typeHexValue &#61; 0x0200, headerSize &#61; 288, headerHexValue &#61; 0x0120, size &#61; 167104, sizeHexValue &#61; 0x00028cc0, id &#61; 127, idHexValue &#61; 0x0000007f name &#61; com.jjq.resourcesarscdemo typeStrings &#61; 288, typeStringsHexValue &#61; 0x00000120 lastPublicType &#61; 0, lastPublicTypeHexValue &#61; 0x00000000 keyStrings &#61; 536, keyStringsHexValue &#61; 0x00000218 lastPublicKey &#61; 0, lastPublicKeyHexValue &#61; 0x00000000。

从上面那张神图上我们可以看到&#xff0c;package数据块其实包括了&#xff1a; 1、header 2、资源类型字符串池&#xff0c;也就是type string pool 3、资源项名称字符串池&#xff0c;也就是key string pool 4、类型规范数据块&#xff0c;也就是type specification 5、资源类型项数据块&#xff0c;也即是type info

先来看2和3&#xff0c;实际项目apk解析得到如下&#xff1a;

header : type &#61; RES_TABLE_TYPE_SPEC_TYPE, typeHexValue &#61; 0x0202, headerSize &#61; 16, headerHexValue &#61; 0x0010, size &#61; 1060, sizeHexValue &#61; 0x00000424 , id &#61; 2, idHexValue &#61; 0x02, res0 &#61;0 ,res1 &#61; 0 , entryCount &#61; 261, entryCountHexValue &#61; 0x00000105, idValue &#61; imattrboolcolordimendrawableidintegerla realSize &#61; 110 size &#61; 12 c &#61; 2

我们发现已经把一些基本的名称&#xff0c;类型都已经打印了出来。 接下来来看type specification部分&#xff1a;

/*** A specification of the resources defined by a particular type.** There should be one of these chunks for each resource type.** This structure is followed by an array of integers providing the set of* configuration change flags (ResTable_config::CONFIG_*) that have multiple* resources for that configuration. In addition, the high bit is set if that* resource has been made public.*/
struct ResTable_typeSpec
{struct ResChunk_header header;// The type identifier this chunk is holding. Type IDs start// at 1 (corresponding to the value of the type bits in a// resource identifier). 0 is invalid.uint8_t id;// Must be 0.uint8_t res0;// Must be 0.uint16_t res1;// Number of uint32_t entry configuration masks that follow.uint32_t entryCount;enum : uint32_t {// Additional flag indicating an entry is public.SPEC_PUBLIC &#61; 0x40000000u,// Additional flag indicating an entry is overlayable at runtime.// Added in Android-P.SPEC_OVERLAYABLE &#61; 0x80000000u,};
};

header是这个chunk的头部信息 id就是资源的type id&#xff0c;每个type都会被赋予一个id。 res0一直是0&#xff0c;保留以便以后使用 res1一直是0&#xff0c;保留以便以后使用 entryCount指的是本类型也就是名称相同的资源个数

转到我们的项目apk里&#xff0c;解析得到如下&#xff1a; header: type &#61; RES_TABLE_TYPE_TYPE, typeHexValue &#61; 0x0201, headerSize &#61; 76, headerHexValue &#61; 0x004c, size &#61; 9424, sizeHexValue &#61; 0x000024d0 , id &#61; 2, idHexValue &#61; 0x02, res0 &#61; 0,res1 &#61; 0, entryCount &#61; 261, entryCountHexValue &#61; 0x00000105,

我们看到一个id为2&#xff0c;type为RES_TABLE_TYPE_TYPE&#xff0c;资源数量为261的chunk。ResTable_typeSpec后面紧跟着的是一个大小为entryCount的uint32_t数组&#xff0c;每一个数组元素都用来描述一个资源项的配置差异性的。

接下来&#xff0c;我们再来看资源类型项数据块&#xff1a;

struct ResTable_type
{struct ResChunk_header header;enum {NO_ENTRY &#61; 0xFFFFFFFF};// The type identifier this chunk is holding. Type IDs start// at 1 (corresponding to the value of the type bits in a// resource identifier). 0 is invalid.uint8_t id;enum {// If set, the entry is sparse, and encodes both the entry ID and offset into each entry,// and a binary search is used to find the key. Only available on platforms >&#61; O.// Mark any types that use this with a v26 qualifier to prevent runtime issues on older// platforms.FLAG_SPARSE &#61; 0x01,};uint8_t flags;// Must be 0.uint16_t reserved;// Number of uint32_t entry indices that follow.uint32_t entryCount;// Offset from header where ResTable_entry data starts.uint32_t entriesStart;// Configuration this collection of entries is designed for. This must always be last.ResTable_config config;
};

haeder指的是这个chunk的头部信息 id指的是标识资源的type id res0&#xff0c;res1&#xff0c;entryCount同type spec entriesStart指的是资源项数据块相对头部的偏移值。 config指的是一个配置信息&#xff0c;里面包括了地区&#xff0c;语言&#xff0c;分辨率等信息

看我们项目的apk&#xff0c;解析得到如下信息&#xff1a; header: type &#61; RES_TABLE_TYPE_TYPE, typeHexValue &#61; 0x0201, headerSize &#61; 76, headerHexValue &#61; 0x004c, size &#61; 9424, sizeHexValue &#61; 0x000024d0 , id &#61; 2, idHexValue &#61; 0x02, res0 &#61; 0,res1 &#61; 0, entryCount &#61; 261, entryCountHexValue &#61; 0x00000105, entriesStart &#61; 1120, entriesStartHexValue &#61; 0x00000460 resConfig &#61; size &#61; 0x00000038, imsi &#61; 0x00000000, locale &#61; 0x00000000, screenType &#61; 0x00000000, input &#61; 0x00000000, screenSize &#61; 0x00000000, version &#61; 0x00000000, screenConfig &#61; 0x00000000, screenSizeDp &#61; 0x00000000, localeScript &#61; 0x00000000, localeVariant &#61; 0x00000000

restable_type后面跟的是一个大小为entryCount的uint32_t数组&#xff0c;每一个数组元素都用来描述一个资源项数据块的偏移位置&#xff0c;紧跟在这个uint32_t数组后面的是一个大小为entryCount的ResTable_entry数组&#xff0c;每一个数组元素&#xff0c;即每一个ResTable_entry&#xff0c;都是用来描述一个资源项的具体信息。这又是什么东西呢&#xff1f;

首先我们先来看我们自建项目的资源情况&#xff1a;

这里我们drawable类型的资源有2个不同的资源和2中不同的配置&#xff0c;其他的比如string/colors/integers这种都是有几个item选项就几个资源&#xff0c;只有1种配置。所以我们其实是有类型为drawable&#xff0c;配置为xhdpi&#xff1b;类型为drawable&#xff0c;配置为xxhdpi&#xff1b;类型为string&#xff0c;配置为default&#xff1b;类型为id&#xff0c;配置为default……n&#43;1个资源项数据块。这里我们说的资源项数据&#xff0c;其实就是刚才说的ResTable_entry。ResTable_entry结构如下&#xff1a;

struct ResTable_entry
{// Number of bytes in this structure.uint16_t size;enum {// If set, this is a complex entry, holding a set of name/value// mappings. It is followed by an array of ResTable_map structures.FLAG_COMPLEX &#61; 0x0001,// If set, this resource has been declared public, so libraries// are allowed to reference it.FLAG_PUBLIC &#61; 0x0002};uint16_t flags;// Reference into ResTable_package::keyStrings identifying this entry.struct ResStringPool_ref key;
};

sizeof指的是资源头部大小 flag我们可以看到&#xff0c;如果是bag资源为1&#xff0c;如果不是在public.xml里定义的&#xff0c;也就是非bag资源&#xff0c;则为2 key也就是资源项名称在资源项名称字符串资源池的索引。

ok&#xff0c;这里我们基本上对资源索引表的文件格式有了一定了解&#xff0c;接下来我们就来看这个资源索引表是如何生成的以其其他的一些文件就比如R.java。

打包流程详解

接下来我们就来着重看看这个resources.arsc跟R.java文件是如何生成的。过程比较复杂&#xff0c;这里我画了一个流程图&#xff0c;下面以流程图为准一步一步的看&#xff1a;

1、解析AndroidManifest.xml

主要做一些检查&#xff0c;获取package ID&#xff0c;minSdkVersion&#xff0c;uses-sdk等属性。

2、添加被引用资源包

上面我们也讲到了&#xff0c;通常在编译一个apk的时候至少会牵扯到两个资源包&#xff0c;一个是被引用的系统资源包&#xff0c;里面包含了很多系统级的&#xff0c;就比如一个LinearLayout&#xff0c;有layout_width&#xff0c;layout_height&#xff0c;layout_oritation等属性。 这里有一点要注意&#xff0c;这里有一个处理重叠包的过程&#xff0c;其实也就是上面我们讲到的entryCount&#xff08;本类型也就是名称相同的资源个数&#xff09;&#xff0c;如果名称相同&#xff0c;则使用重叠包。

3、收集资源文件

这里aapt会创建一个AaptAssets对象&#xff0c;将当前需要编译的资源文件根据类别保存下来。注意&#xff0c;这里的资源文件指的是除了values资源外的资源&#xff0c;因为values资源是在编译的时候进行收集的。

4、把收集到的资源文件保存到ResourceTable对象

这里我们就要新建一个ResourceTable对象了&#xff0c;没错&#xff0c;就是最上面那张神图&#xff0c;也就是上面我们叽里呱啦讲了一大堆格式的部分。第3部中&#xff0c;我们只是把资源文件保存到了AaptAssets对象中而已&#xff0c;这里我们要保存到ResourceTable对象中&#xff0c;在aapt源码里对应的是makeFileResources函数&#xff1a;

static status_t makeFileResources(Bundle* bundle, const sp& assets,ResourceTable* table,const sp& set,const char* resType);

另外注意这一步资源保存指的是除了values资源以外的资源&#xff0c;values资源比较特别&#xff0c;需要进行编译以后才会保存。

5、编译values资源

values下的资源都是诸如strings/colors/ids这种轻量级的资源&#xff0c;这些资源都是在编译的时候进行收集的。

6、给bag资源分配id

bag资源是什么&#xff0c;bag资源就是这类资源在赋值的时候&#xff0c;不能随便赋值&#xff0c;只能从事先定义好的值中选取一个赋值。很像枚举&#xff0c;比如layout_oritation这种&#xff0c;如attr资源。这一步我们会给bag资源分配资源id。可以理解成给枚举的两个值分配资源id&#xff0c;当然这个不是枚举。

7、编译xml文件

ok&#xff0c;前面的步骤主要是为了给我们编译xml文件做准备&#xff0c;现在开始&#xff0c;我们就可以编译xml文件了。这里&#xff0c;程序会对layouts&#xff0c;anims&#xff0c;animators等文件逐一调用ResourceTable.cpp的如下方法进行编译&#xff1a;

status_t compileXmlFile(const sp& assets,const sp& target,ResourceTable* table,int options);

内部流程可以分为&#xff1a; 1、解析xml文件&#xff1a; 这一步主要是为了将xml文件转化为一系列树形结构XmlNode来表示。

2、赋予属性名称id&#xff1a; 给每一个资源的属性名称赋予id。就比如一个最基本的button&#xff0c;他有layout_width和layout_height两个属性&#xff0c;这两个属性都属于bag资源&#xff0c;在上一步中我们已经把他们编译了&#xff0c;这一步就是把编译后的id赋值给这个button。 每一个xml都是从根节点开始赋予属性名称id&#xff0c;直到该文件下所有节点都有属性id了为止。

3、解析属性值 这一步是第二部的深化&#xff0c;第二部我们对layout_width和layout_height这两个属性名称赋予了id&#xff0c;这一步我们将对其值进行解析。仍然是这个button&#xff0c;我们将对match_parent或者wrap_content进行解析。

4、扁平化为二进制文件 将xml改为二进制格式。步骤分为以下几步&#xff1a; &#xff08;1&#xff09;、首先aapt会将那些有资源id的属性名称收集起来并将他们放在一个数组里。 &#xff08;2&#xff09;、收集xml文件中其他的所有的字符串。 &#xff08;3&#xff09;、写入文件头&#xff0c;也就是一个chunk的chunk_header文件。 &#xff08;4&#xff09;、将第一步第二步获取到的内容写入Global String pool里&#xff0c;也就是上面解析resources.arsc里的字符串资源池中。具体结构上面解析的时候已经详述。 &#xff08;5&#xff09;、把所有的资源id都收集起来&#xff0c;生成package的时候要用&#xff0c;也就是上面解析package的时候讲到的资源项名称字符串池&#xff0c;也就是key string pool。 &#xff08;6&#xff09;、压平xml文件&#xff0c;也就是把里面的元素都替换掉&#xff0c;完全变成二进制文件。

8、给资源生成资源ID

这里就是给资源生成资源id&#xff0c;id是一个32位数字&#xff0c;用十六进制来表示就是0XPPTTEEEE。 PP为package id&#xff0c;也就是上面我们提到的ResTable_package数据结构中的id&#xff1b; TT位type id&#xff0c;也就是我们上面提到的ResTable_typeSpec数据结构中的id&#xff1b; EEEE为entry id&#xff0c;每个entry表示一个资源项&#xff0c;按照先后顺序自动排列&#xff0c;这里需要注意&#xff0c;是根据顺序自动排列&#xff0c;因为这个entry id牵扯到热修复更新资源下面的内容&#xff0c;所以这里需要特别注意&#xff0c;之后会提到&#xff0c;这里就不展开了。

9、根据资源ID生成资源索引表

这里我们将生成resources.arsc步骤拆解如下&#xff1a;

  1. 以package为单位&#xff0c;收集类别字符串&#xff0c;例如“drawable”&#xff0c;“string”等。
  2. 以package为单位&#xff0c;收集资源项名称字符串&#xff0c;就比如图2我们建的那个项目&#xff0c;以strings.xml为例&#xff0c;这里我们就收集了"app_name","jjq","hahahaha"三个字符串。
  3. 所有资源项值字符串&#xff0c;再以图2项目为例&#xff0c;就是"ResourceDemo","好帅"和“哈哈哈哈哈哈”&#xff1b;
  4. 生成package数据块&#xff0c;package的数据结构上面解析的时候已经讲过了&#xff0c;这里其实就是把步骤1、2、3获取到的资源一个一个填进去。
  5. 写入资源索引表头部&#xff0c;也就是ResTable_header。
  6. 写入字符串资源池&#xff0c;因为数据都准备好了&#xff0c;所以这里直接写就好了
  7. 写入package&#xff0c;第4步中已经生成好了

10、编译AndroidManifest.xml

现在我们可以编译AndroidManifest.xml文件&#xff0c;将其编译成二进制文件。

11、生成R.java

这里我们已经知道了所有的资源项以及其id&#xff0c;这里我们就可以把他们都写到R.java文件里了。。。这里需要注意的是&#xff0c;R.java里每一个资源类别对应一个内部类&#xff0c;就像这样&#xff1a;

图片上举例了就是两个anim&#xff0c;attr两个类别对应的内部类。

12、打包到apk里

接下来就是打包到apk里了&#xff0c;这里我们会将assets文件目录&#xff0c;res目录下但不包括res/values目录下的资源文件&#xff0c;resources.arsc资源索引文件打包进apk里。

至此&#xff0c;整个Android 资源编译和打包过程就分析完了。。。。。

有了这个基础&#xff0c;接下来我们就可以研究apk运行的时候是如何读取最适合的&#xff0c;相对应的资源文件的。知道了这个过程以后&#xff0c;我们就可以深入探索Android热修复如何才能做到运行的时候去替换资源文件。

本系列目录&#xff1a; Android热修复原理简要介绍和学习计划

参考文章&#xff1a; 1、《深入探索Android热修复技术原理》 2、老罗&#xff1a;Android应用程序资源的编译和打包过程分析 3、blog.csdn.net/jiangwei091… 4、blog.zhaiyifan.cn/2016/02/13/…

个人微信公共号已上线&#xff0c;欢迎关注&#xff1a;


转:https://juejin.im/post/5cbfc97bf265da037c7ce6a5



推荐阅读
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 本文介绍了Android中的assets目录和raw目录的共同点和区别,包括获取资源的方法、目录结构的限制以及列出资源的能力。同时,还解释了raw目录中资源文件生成的ID,并说明了这些目录的使用方法。 ... [详细]
  • 如何压缩网站页面以减少页面加载时间
    本文介绍了影响网站打开时间的两个因素,即网页加载速度和网站页面大小。重点讲解了如何通过压缩网站页面来减少页面加载时间。具体包括图片压缩、Javascript压缩、CSS压缩和HTML压缩等方法,并推荐了相应的压缩工具。此外,还提到了一款Google Chrome插件——网页加载速度分析工具Speed Tracer。 ... [详细]
  • 近来有一个需求,是需要在androidjava基础库中插入一些log信息,完成这个工作需要的前置条件有编译好的android源码具体android源码如何编译,这 ... [详细]
  • HTML学习02 图像标签的使用和属性
    本文介绍了HTML中图像标签的使用和属性,包括定义图像、定义图像地图、使用源属性和替换文本属性。同时提供了相关实例和注意事项,帮助读者更好地理解和应用图像标签。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 第四章高阶函数(参数传递、高阶函数、lambda表达式)(python进阶)的讲解和应用
    本文主要讲解了第四章高阶函数(参数传递、高阶函数、lambda表达式)的相关知识,包括函数参数传递机制和赋值机制、引用传递的概念和应用、默认参数的定义和使用等内容。同时介绍了高阶函数和lambda表达式的概念,并给出了一些实例代码进行演示。对于想要进一步提升python编程能力的读者来说,本文将是一个不错的学习资料。 ... [详细]
  • 本文介绍了如何使用Express App提供静态文件,同时提到了一些不需要使用的文件,如package.json和/.ssh/known_hosts,并解释了为什么app.get('*')无法捕获所有请求以及为什么app.use(express.static(__dirname))可能会提供不需要的文件。 ... [详细]
author-avatar
教坏的黑天使_203
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有