aapt2 适配之资源 id 固定_public.xml在aapt中的处理-程序员宅基地

技术标签: Android Hook  插件化  Android 开发  Android / aapt2  Android Gradle Plugin  aapt2  Android HotFix Solutions  热修复  Android  

前言

资源id的固定在热修复和插件化中极其重要。在热修复中,构建patch时,需要保持patch包的资源id和基线包的资源id一致;在插件化中,如果插件需要引用宿主的资源,则需要将宿主的资源id进行固定,因此,资源id的固定在这两种场景下是尤为重要的。而在Android Gradle Plugin 3.0.0中,默认开启了aapt2,原先aapt的资源固定方式public.xml也将失效,必须寻找一种新的资源固定的方式,而不是简单的禁用掉aapt2,因此本文来探讨一下开启aapt2的情况下如何进行资源id的固定。

aapt的资源固定方式

在探索aapt2资源固定方式前,先来温习一下aapt原先的资源固定方式。

  • 编译基线包时添加aapt参数-P导出public.xml文件
  • 编译插件或者patch包时,将public.xml文件拷贝至资源merge完成的目录,并根据values.xml中的定义和public.xml中的定义,选择性的生成ids.xml文件

    对应的代码如下


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90

apply plugin: PublicPlugin
class PublicPlugin implements Plugin< Project> {
void apply( Project project) {
project.afterEvaluate {
if ( project.plugins.hasPlugin( "com.android.application")) {
def android = project.extensions.getByName( "android")
android.applicationVariants.all { def variant ->
File publicXmlFile = project.rootProject. file( 'public.xml')
//public文件存在则应用,不存在则生成
if (publicXmlFile.exists()) {
//aapt的应用需要将文件拷贝到对应的目录
//aapt public.xml文件的应用并不是只是拷贝public.xml文件那么简单,还要根据生成的public.xml生成ids.xml文件,并将ids.xml中与values.xml中重复定义的id去除
String mergeResourcesTaskName = variant.variantData.getScope().getMergeResourcesTask().name
def mergeResourcesTask = project.tasks.getByName(mergeResourcesTaskName)
//资源merge的task存在则在其merge完资源后拷贝public.xml并生成ids.xml
if (mergeResourcesTask) {
mergeResourcesTask. doLast {
//拷贝public.xml文件
File toDir = new File(mergeResourcesTask.outputDir, "values")
project. copy {
project.logger.error "${variant.name}:copy from ${publicXmlFile.getAbsolutePath()} to ${toDir}/public.xml"
from(publicXmlFile.getParentFile()) {
include "public.xml"
rename "public.xml", "public.xml"
}
into( toDir)
}
//生成ids.xml文件
File valuesFile = new File( toDir, "values.xml")
File idsFile = new File( toDir, "ids.xml")
if (valuesFile.exists() && publicXmlFile.exists()) {
//记录在values.xml中存在的id定义
def valuesNodes = new XmlParser().parse(valuesFile)
Set<String> existIdItems = new HashSet<String>()
valuesNodes. each {
if ( "id".equalsIgnoreCase( "${it.@type}")) {
existIdItems.add( "${it.@name}")
}
}
GFileUtils.deleteQuietly(idsFile)
GFileUtils.touch(idsFile)
idsFile. append( "<?xml version=\"1.0\" encoding=\"utf-8\"?>")
idsFile. append( "\n")
idsFile. append( "<resources>")
idsFile. append( "\n")
def publicXMLNodes = new XmlParser().parse(publicXmlFile)
Pattern drawableGeneratePattern = Pattern. compile( '^(.*?_)([0-9]{0,})$')
publicXMLNodes. each {
//获取public.xml中定义的id类型item
if ( "id".equalsIgnoreCase( "${it.@type}")) {
//如果在values.xml中没有定义,则添加到ids.xml中
//如果已经在values.xml中定义,则忽略它
if (!existIdItems.contains( "${it.@name}")) {
idsFile. append( "\t<item type=\"id\" name=\"${it.@name}\" />\n")
} else {
project.logger.error "already exist id item ${it.@name}, ignore it"
}
} else if ( "drawable".equalsIgnoreCase( "${it.@type}")) {
//以'_数字'结尾的drawable资源,此类资源是aapt编译时生成的nested资源,如avd_hide_password_1, avd_hide_password_2
//但是可能会有其他资源掺杂,如abc_btn_check_to_on_mtrl_000, abc_btn_check_to_on_mtrl_015
//为了将此类资源过滤掉,将正则匹配到的数字转成int,对比原始数字部分匹配字符串,如果一致,则是aapt生成
//重要:为了避免此类nested资源生成顺序发生改变,应该禁止修改此类资源
Matcher matcher = drawableGeneratePattern.matcher(it.@name)
if (matcher.matches() && matcher.groupCount() == 2) {
String number = matcher. group( 2)
if (number.equalsIgnoreCase(Integer.parseInt(number).toString())) {
project.logger.info "[${PREFIX}] declared drawable resource ${it.@name} which is generated by aapt. like use '<aapt:attr name=\"android:drawable\">'"
idsFile. append( "\t<item type=\"drawable\" name=\"${it.@name}\" />\n")
}
}
}
}
idsFile. append( "</resources>")
}
}
}
} else {
//不存在则生成
project.logger.error "${publicXmlFile} not exists, generate it"
//aapt 添加-P参数生成
aaptOptions.additionalParameters( "-P", "${publicXmlFile}")
}
}
}
}
}
}

很简单,生成public.xml时使用aapt的参数-P,指定生成的文件路径即可;应用public.xml则将其拷贝到values目录下,唯一需要注意的是这个导出的public.xml文件会存在资源id未定义的情况,因此需要生成ids.xml文件,对未定义的id类型资源进行定义。而这个生成的ids.xml文件,可能与values/values.xml文件中的id存在重复定义的现象,因此生成的时候,则需要判断对应的id名在values.xml文件中是否存在,如果存在则直接忽略,因为它已经定义了;如果不存在,则添加到ids.xml中进行定义。

这种方式不支持删除现有的资源,如果删除了现有的资源,public.xml中的定义也得删除,否则会报资源未定义的错误。

aapt2的资源固定方式

那么在aapt2中上面这种方式还生效吗,答案是否定的,至于为什么,可以参考之前的一篇文章aapt2 资源 compile 过程,因为所有merge的资源都已经经过了预编译,产生了flat文件,这时候将public.xml文件拷贝至该目录就会产生编译错误。那么如何解决了。通过查看Android Gradle Plugin 3.0.0的代码发现了一些猫腻,关键代码如下


1
2
3
4
5
6
7
8
9
10
11
12
13

public static ImmutableList< String> makeLink(
@NonNull AaptPackageConfig config, @NonNull File intermediateDir) throws AaptException {
ImmutableList.Builder< String> builder = ImmutableList.builder();
if ( config.isVerbose()) {
builder.add( "-v");
}
File stableResourceIdsFile = new File(intermediateDir, "stable-resource-ids.txt");
// TODO: For now, we ignore this file, but as soon as aapt2 supports it, we'll use it.
//此处省略n行代码
}

大概可以猜测可以通过指定稳定的资源id映射文件达到固定资源id的作用,但是代码中这个文件并没有共同参与资源编译的过程,因此这部分代码暂时无效,接下来去aapt2命令中寻找一下。通过aapt2 link –help可以发现有两个参数。如下


1
2

--stable-ids arg File containing a list of name to ID mapping.
--emit-ids arg Emit a file at the given path with a list of name to ID mappings, suitable for use with --stable-ids.

大概意思就是说可以通过–emit-ids参数指定一个文件,该文件会输出资源名字到资源id的一个映射,这个文件可以被–stable-ids参数使用。

通过简单的测试,发现这两个参数可以完全满足我们的需求。而且这种方式支持删除现有的资源。编写代码验证之。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

apply plugin: PublicPlugin
class PublicPlugin implements Plugin< Project> {
void apply( Project project) {
project.afterEvaluate {
if ( project.plugins.hasPlugin( "com.android.application")) {
def android = project.extensions.getByName( "android")
android.applicationVariants.all { def variant ->
def processResourcesTask = project.tasks.getByName( "process${variant.name.capitalize()}Resources")
if (processResourcesTask) {
def aaptOptions = processResourcesTask.aaptOptions
File publicTxtFile = project.rootProject. file( 'public.txt')
//public文件存在,则应用,不存在则生成
if (publicTxtFile.exists()) {
project.logger.error "${publicTxtFile} exists, apply it."
//aapt2添加--stable-ids参数应用
aaptOptions.additionalParameters( "--stable-ids", "${publicTxtFile}")
} else {
project.logger.error "${publicTxtFile} not exists, generate it."
//aapt2添加--emit-ids参数生成
aaptOptions.additionalParameters( "--emit-ids", "${publicTxtFile}")
}
}
}
}
}
}
}

代码很简单,就是当public.txt文件不存在时,添加–emit-ids参数进行生产,如果存在时,则添加–stable-ids进行应用。

简单验证一下,定义3个颜色资源


1
2
3
4
5
6

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorAccent">#FF4081 </color>
<color name="colorPrimary">#3F51B5 </color>
<color name="colorPrimaryDark">#303F9F </color>
</resources>

编译,可以看到项目根目录下产生了public.txt文件,其中这三个资源对应的内容为


1
2
3

io .github .lizhangqu .aapt2: color/colorAccent = 0x7f040026
io .github .lizhangqu .aapt2: color/colorPrimary = 0x7f040027
io .github .lizhangqu .aapt2: color/colorPrimaryDark = 0x7f040028

在这三个资源中插入一个资源,打乱资源顺序,如下


1
2
3
4
5
6
7

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorAccent">#FF4081 </color>
<color name="colorAccentBBBBB">#FF4081 </color>
<color name="colorPrimary">#3F51B5 </color>
<color name="colorPrimaryDark">#303F9F </color>
</resources>

将刚才生成的public.txt文件备份至其他目录,删除根目录下的public.txt文件,保证重新生成该文件,重新编译资源,此时生成的public.txt文件中的这四个资源对应的内容为


1
2
3
4

io .github .lizhangqu .aapt2: color/colorAccent = 0x7f040026
io .github .lizhangqu .aapt2: color/colorAccentBBBBB = 0x7f040027
io .github .lizhangqu .aapt2: color/colorPrimary = 0x7f040028
io .github .lizhangqu .aapt2: color/colorPrimaryDark = 0x7f040029

app/build/intermediates/res/symbol-table-with-package/debug/package-aware-r.txt文件中对应的资源id为


1
2
3
4

int color colorAccent 0x7f040026
int color colorAccentBBBBB 0x7f040027
int color colorPrimary 0x7f040028
int color colorPrimaryDark 0x7f040029

两个文件中的内容完全可以对的上,只要看其中一个就可以了;可以看到由于colorAccentBBBBB的插入,colorPrimary和colorPrimaryDark都向后顺延了一位,也就是说,在没有资源固定的情况下,如果增删改等操作发生,是有可能导致现有资源id发生变化的。

因此我们开始验证–stable-ids参数的有效性,将根目录下的public.txt文件删除,将之前备份好的public.txt文件拷贝到根目录,重新编译资源。这时候编译产生的资源id映射为


1
2
3
4

int color colorAccent 0x7f040026
int color colorAccentBBBBB 0x7f040057
int color colorPrimary 0x7f040027
int color colorPrimaryDark 0x7f040028

可以看到colorAccent,colorPrimary和colorPrimaryDark的资源id并没有因为colorAccentBBBBB的插入而发生变化,而是保持了原有的资源id,而新增的资源colorAccentBBBBB则是重新分配了一个新的资源id。

至此,基本可以确定该方案是可行的(但不保证有没有坑)

适配aapt和aapt2

因此,需要进行aapt的版本判断,适配不同的情况,这里我已经把代码写好了,基本上就是对上面两段代码的组合,其中aapt版本的获取需要捕获一下异常,该函数在低版本中不存在,具体实现看代码,这里不再过多解释了


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120

apply plugin: PublicPlugin
class PublicPlugin implements Plugin< Project> {
void apply( Project project) {
project.afterEvaluate {
if ( project.plugins.hasPlugin( "com.android.application")) {
def android = project.extensions.getByName( "android")
android.applicationVariants.all { def variant ->
boolean aapt2Enable = false
def processResourcesTask = project.tasks.getByName( "process${variant.name.capitalize()}Resources")
if (processResourcesTask) {
try {
//判断aapt2是否开启,低版本不存在这个方法,因此需要捕获异常
aapt2Enable = processResourcesTask.isAapt2Enabled()
} catch (Exception e) {
project.logger.error "${e.getMessage()}"
}
def aaptOptions = processResourcesTask.aaptOptions
//aapt2开启走此流程
if (aapt2Enable) {
project.logger.error "aapt2 is enabled"
File publicTxtFile = project.rootProject. file( 'public.txt')
//public文件存在,则应用,不存在则生成
if (publicTxtFile.exists()) {
project.logger.error "${publicTxtFile} exists, apply it."
//aapt2添加--stable-ids参数应用
aaptOptions.additionalParameters( "--stable-ids", "${publicTxtFile}")
} else {
project.logger.error "${publicTxtFile} not exists, generate it."
//aapt2添加--emit-ids参数生成
aaptOptions.additionalParameters( "--emit-ids", "${publicTxtFile}")
}
} else {
//aapt2禁用走此流程
project.logger.error "aapt2 is disabled"
File publicXmlFile = project.rootProject. file( 'public.xml')
//public文件存在则应用,不存在则生成
if (publicXmlFile.exists()) {
//aapt的应用需要将文件拷贝到对应的目录
//aapt public.xml文件的应用并不是只是拷贝public.xml文件那么简单,还要根据生成的public.xml生成ids.xml文件,并将ids.xml中与values.xml中重复定义的id去除
String mergeResourcesTaskName = variant.variantData.getScope().getMergeResourcesTask().name
def mergeResourcesTask = project.tasks.getByName(mergeResourcesTaskName)
if (mergeResourcesTask) {
mergeResourcesTask. doLast {
//拷贝public.xml文件
File toDir = new File(mergeResourcesTask.outputDir, "values")
project. copy {
project.logger.error "${variant.name}:copy from ${publicXmlFile.getAbsolutePath()} to ${toDir}/public.xml"
from(publicXmlFile.getParentFile()) {
include "public.xml"
rename "public.xml", "public.xml"
}
into( toDir)
}
//生成ids.xml文件
File valuesFile = new File( toDir, "values.xml")
File idsFile = new File( toDir, "ids.xml")
if (valuesFile.exists() && publicXmlFile.exists()) {
//记录在values.xml中存在的id定义
def valuesNodes = new XmlParser().parse(valuesFile)
Set<String> existIdItems = new HashSet<String>()
valuesNodes. each {
if ( "id".equalsIgnoreCase( "${it.@type}")) {
existIdItems.add( "${it.@name}")
}
}
GFileUtils.deleteQuietly(idsFile)
GFileUtils.touch(idsFile)
idsFile. append( "<?xml version=\"1.0\" encoding=\"utf-8\"?>")
idsFile. append( "\n")
idsFile. append( "<resources>")
idsFile. append( "\n")
def publicXMLNodes = new XmlParser().parse(publicXmlFile)
Pattern drawableGeneratePattern = Pattern. compile( '^(.*?_)([0-9]{0,})$')
publicXMLNodes. each {
//获取public.xml中定义的id类型item
if ( "id".equalsIgnoreCase( "${it.@type}")) {
//如果在values.xml中没有定义,则添加到ids.xml中
//如果已经在values.xml中定义,则忽略它
if (!existIdItems.contains( "${it.@name}")) {
idsFile. append( "\t<item type=\"id\" name=\"${it.@name}\" />\n")
} else {
project.logger.error "already exist id item ${it.@name}, ignore it"
}
} else if ( "drawable".equalsIgnoreCase( "${it.@type}")) {
//以'_数字'结尾的drawable资源,此类资源是aapt编译时生成的nested资源,如avd_hide_password_1, avd_hide_password_2
//但是可能会有其他资源掺杂,如abc_btn_check_to_on_mtrl_000, abc_btn_check_to_on_mtrl_015
//为了将此类资源过滤掉,将正则匹配到的数字转成int,对比原始数字部分匹配字符串,如果一致,则是aapt生成
//重要:为了避免此类nested资源生成顺序发生改变,应该禁止修改此类资源
Matcher matcher = drawableGeneratePattern.matcher(it.@name)
if (matcher.matches() && matcher.groupCount() == 2) {
String number = matcher. group( 2)
if (number.equalsIgnoreCase(Integer.parseInt(number).toString())) {
project.logger.info "[${PREFIX}] declared drawable resource ${it.@name} which is generated by aapt. like use '<aapt:attr name=\"android:drawable\">'"
idsFile. append( "\t<item type=\"drawable\" name=\"${it.@name}\" />\n")
}
}
}
}
idsFile. append( "</resources>")
}
}
}
} else {
//不存在则生成
project.logger.error "${publicXmlFile} not exists, generate it"
//aapt 添加-P参数生成
aaptOptions.additionalParameters( "-P", "${publicXmlFile}")
}
}
}
}
}
}
}
}

另一种解决方式

如果项目确定使用的是aapt2,并且不想通过编写插件解决,这里提供一种更加简单的方式,就是直接利用aaptOptions参数进行指定,但是这种方式不好做aapt和aapt2之间的无缝适配,只适合aapt2,参考代码如下


1
2
3
4
5
6
7
8
9
10

android {
aaptOptions {
File publicTxtFile = project.rootProject.file( 'public.txt')
if (publicTxtFile.exists()) {
additionalParameters "--stable-ids", "${project.rootProject.file('public.txt').absolutePath}"
} else {
additionalParameters "--emit-ids", "${project.rootProject.file('public.txt').absolutePath}"
}
}
}

public.xml到public.txt的转换

如果之前是用aapt备份下来的public.xml,如果现在使用了aapt2,则需要将文件进行转换,转换方式也很简单,如下


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

task convertPublicXmlToPublicTxt() {
doLast {
//源public.xml
File publicXmlFile = project.rootProject.file( 'backup/public.xml')
//目标public.txt
File publicTxtFile = project.rootProject.file( 'backup/generate_public.txt')
//包名
String applicationId = "io.github.lizhangqu.aapt2"
GFileUtils.deleteQuietly(publicTxtFile)
GFileUtils.touch(publicTxtFile)
def nodes = new XmlParser().parse(publicXmlFile)
Pattern drawableGeneratePattern = Pattern.compile( '^(.*?_)([0-9]{0,})$')
nodes.each {
project.logger.error "${it}"
if ( "drawable".equalsIgnoreCase( "${it.@type}")) {
//以'_数字'结尾的drawable资源,此类资源是aapt编译时生成的nested资源,如avd_hide_password_1, avd_hide_password_2
//但是可能会有其他资源掺杂,如abc_btn_check_to_on_mtrl_000, abc_btn_check_to_on_mtrl_015
//为了将此类资源过滤掉,将正则匹配到的数字转成int,对比原始数字部分匹配字符串,如果一致,则是aapt生成
//重要:为了避免此类nested资源生成顺序发生改变,应该禁止修改此类资源
//aapt生成的是以下表1开始,aapt2是以下标0开始,因此转换的过程需要-1
Matcher matcher = drawableGeneratePattern.matcher(it. @name)
if (matcher.matches() && matcher.groupCount() == 2) {
String number = matcher.group( 2)
if (number.equalsIgnoreCase(Integer.parseInt(number).toString())) {
String prefixName = matcher.group( 1)
publicTxtFile.append( "${applicationId}:${it.@type}/\$${prefixName}_${Integer.parseInt(number) - 1} = ${it.@id}\n")
return
}
}
}
publicTxtFile.append( "${applicationId}:${it.@type}/${it.@name} = ${it.@id}\n")
}
}
}

执行 gradle convertPublicXmlToPublicTxt即可完成转换。

值得注意的是,这种转换方式,由于原先aapt导出的public.xml中没有styleable的定义,所以转换后的public.txt中也没有styleable,即转换后的数据是aapt2导出的数据的子集,而aapt2生成的public.txt是具有styleable类型的id的,但是实际应用过程中并没有发现什么大的问题,因此几乎可以忽略不计。如果你发现有问题,可以及时联系我。

意外的收获aapt2资源分区

在寻找解决aapt2资源id固定的过程中,意外发现aapt2自带了资源PP段分区功能。就是通过–package-id参数,指定PP段分区,但是值得注意的是这个值必须大于0x7f,经过测试,大于0x7f的PP段分区在Android7.0以下是无法识别的,可以安装但是启动会崩溃。因此通过这种方式分区生成的apk文件,只能安装在Android7.0以上的系统,比较鸡肋。示例代码如下


1
2
3
4
5

android {
aaptOptions {
additionalParameters "--package-id", "0x80"
}
}

此时生成的apk的资源PP段都是0x80,可以通过app/build/intermediates/res/symbol-table-with-package/debug/package-aware-r.txt文件进行验证。

总结

当上帝为你关上一扇门的时候,还会用门夹你的脑袋(开个玩笑),虽然public.xml在aapt2中无法用了,但是google在aapt2中提供给我们的–stable-ids和–emit-ids两个参数不见得那么不好用,甚至比aapt的public.xml还要好用,只需要生成和应用就好了,不需要进行中间的处理过程。简直完美!但是该方案还没有用足够多的case进行验证,不代表没有坑,出了坑,不负责!

http://fucknmb.com/2017/11/15/aapt2%E9%80%82%E9%85%8D%E4%B9%8B%E8%B5%84%E6%BA%90id%E5%9B%BA%E5%AE%9A/
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/omnispace/article/details/79788641

智能推荐

AVFrame&AVPacket_天天av-程序员宅基地

文章浏览阅读1.5w次。AVFrame:( This structure describes decoded (raw) audio or video data. AVFrame must be allocated using av_frame_alloc(). Note that this only allocates the AVFrame itself, the buffers for the data mus_天天av

Java经典例题07:用100元人民币兑换10元、5元、1元的纸币_编程把100元换成1元5元10元-程序员宅基地

文章浏览阅读3.5k次,点赞2次,收藏12次。解题思路分析:1.100元兑换10元纸币,可以兑换10张,但每种纸币都要有,所以最多只能兑换9张,最少兑换1张。则初始值为1;循环条件小于10或者小于等于9。2.100元兑换5元纸币,可以兑换20,但每种纸币都要有,所以最多只能兑换19张,最少兑换1张。初始值为1;循环条件小于20或者小于等于19。3.100元兑换1元纸币,可以兑换100张,但每种纸币都要有,所以最多只能兑换99张,最少兑换1张。则初始值为1;循环条件小于100或者小于等于99。_编程把100元换成1元5元10元

猜三次年龄_找人猜三次年龄-程序员宅基地

文章浏览阅读450次。1、允许用户最多尝试三次2、每尝试三次后,如果还没猜对,就问用户是否继续玩,如果回答Y,y,就继续猜三次,以此往复,如果回答N,n,就直接退出times=0count=3while times<=3:age=int(input(‘请输入年龄:’))if age == 18:print(‘猜对了’)breakelif age > 18:print(‘猜大了’)else:print(‘猜小了’)times+=1if times3:choose = input(‘继续猜Y_找人猜三次年龄

SDOI2017 Round2 详细题解-程序员宅基地

文章浏览阅读152次。这套题实在是太神仙了。。做了我好久。。。好多题都是去搜题解才会的 TAT。剩的那道题先咕着,如果省选没有退役就来填吧。「SDOI2017」龙与地下城题意丢 \(Y\) 次骰子,骰子有 \(X\) 面,每一面的概率均等,取值为 \([0, X)\) ,问最后取值在 \([a, b]\) 之间的概率。一个浮点数,绝对误差不超过 \(0.013579\) 为正确。数据范围每组数据有 \...

嵌入式数据库-Sqlite3-程序员宅基地

文章浏览阅读1.1k次,点赞36次,收藏25次。阅读引言: 本文将会从环境sqlite3的安装、数据库的基础知识、sqlite3命令、以及sqlite的sql语句最后还有一个完整的代码实例, 相信仔细学习完这篇内容之后大家一定能有所收获。

C++ Builder编写WinForm从Web服务器下载文件-程序员宅基地

文章浏览阅读51次。UnicodeString templateSavePath = ChangeFileExt(ExtractFilePath(Application->ExeName),"tmp.doc");IdAntiFreeze1->OnlyWhenIdle = false;//设置使程序有反应.TMemoryStream *templateStream ;templateStre..._c++webserver下载文件

随便推点

JAVA小项目潜艇大战_java潜艇大战-程序员宅基地

文章浏览阅读8.3k次,点赞10次,收藏41次。一、第一天1、创建战舰、侦察潜艇、鱼雷潜艇、水雷潜艇、水雷、深水炸弹类完整代码:package day01;//战舰public class Battleship { int width; int height; int x; int y; int speed; int life; void move(){ System.out.println("战舰移动"); }}package day01;//侦察潜艇_java潜艇大战

02表单校验的基本步骤-程序员宅基地

文章浏览阅读940次。表单校验的基本步骤_表单校验

libOpenBlas.dll缺失依赖解决办法-程序员宅基地

文章浏览阅读4.5k次。libOpenBlas.dll缺失依赖解决办法 intellij idea 1.dll文件缺失依赖,报错:“找不到指定模块”2.下载depends查看dll缺失文件3.下载缺失依赖libopenblas.dll出错起因由于java web项目需要调用openBlas库来进行运算,就下载了预编译的libopenblas文件进行调用,首先遇到路径出错问题、之后又是dll文件缺失依赖问题,以下是解决..._libopenblas.dll

Swoole 实践篇之结合 WebSocket 实现心跳检测机制-程序员宅基地

文章浏览阅读251次,点赞3次,收藏10次。这里实现的心跳检测机制是一个基础版的,心跳包的主要作用是用于检测用户端是否存活,有助于我们及时判断用户端是否存在断线的问题。在我之前开发过的项目中,有一个基于物联网在线直播抓娃娃的项目,其中就有需要实时监控设备在线状态的需求,该需求就是使用心跳包来实现的。实际上心跳检测技术,应用更广泛的是实时通信、或设备管理的场景偏多。

Maven dependency scope_maven dependent scope-程序员宅基地

文章浏览阅读714次。Dependency scope is used to limit the transitivity of a dependency, and also to affect the classpath used for various build tasks.There are 6 scopes available:compileThis is the default scop_maven dependent scope

TCP头部结构信息_tcp头部包含哪些信息-程序员宅基地

文章浏览阅读3.6k次。TCP 头部结构信息_tcp头部包含哪些信息