打包環(huán)境:
- 操作系統(tǒng):Centos7.0
- 編譯腳本:Ant
- 涉及到的工具:shell命令喷户、aapt隙轻、signapk(簽名)、axmlio(解析庫(kù))瞳购、zipalign、apksigner(簽名)
整理的背影以及說(shuō)初衷
- 解決當(dāng)前BUG:Android studio 從2.3升級(jí)到3.0后 現(xiàn)自動(dòng)化打包后臺(tái)無(wú)法對(duì)APK進(jìn)行再簽名
- 很久以前做過(guò)一次Ant的自動(dòng)化亏推,而目前自動(dòng)化打包是接手項(xiàng)目学赛,也恰是使用ant而非gradle年堆,解決BUG同時(shí),借此機(jī)會(huì)做一次總結(jié)
- 從年初開始盏浇,工作原因更多的接觸Linux变丧,對(duì)shell命令使用增多
- 開發(fā)了這么多年 但對(duì)打包簽名只停留在模糊的認(rèn)識(shí)
Ready 前置知識(shí)
-
Android 簽名剖析
-
Ant 語(yǔ)法規(guī)則
-
什么是aapt
-
linux shell簡(jiǎn)單命令
一、Android 簽名剖析
參考資料:http://zzqhost.com/2017/06/17/Android簽名原理剖析/
-
消息摘要 -Message Digest
- 簡(jiǎn)稱摘要绢掰,請(qǐng)看英文翻譯痒蓬,是摘要,不是簽名滴劲,網(wǎng)上幾乎所有APK簽名分析的文章都混淆了這兩個(gè)概念攻晒。
- 消息摘要的特性,很適合來(lái)驗(yàn)證數(shù)據(jù)的完整性班挖,比如在網(wǎng)絡(luò)傳輸過(guò)程中下載一個(gè)大文件BigFile鲁捏,我們會(huì)同時(shí)從網(wǎng)絡(luò)下載BigFile和BigFile.md5,BigFile.md5保存BigFile的摘要萧芙,我們?cè)诒镜厣葿igFile的消息摘要给梅,和BigFile.md5比較,如果內(nèi)容相同双揪,則表示下載過(guò)程正確动羽。
- 注意,消息摘要只能保證消息的完整性渔期,并不能保證消息的不可篡改性运吓。
-
MD5, SHA-0, SHA-1, SHA-256
- 這些都是摘要生成算法,和簽名沒有半毛錢關(guān)系擎场。如果非要說(shuō)他們和簽名有關(guān)系羽德,那就是簽名是要借助于摘要技術(shù)。
- SHA-256是SHA-1的升級(jí)版迅办,現(xiàn)在Android簽名使用的默認(rèn)算法都已經(jīng)升級(jí)到SHA-256了
-
數(shù)字簽名 - Signature
- 數(shù)字簽名就是信息的發(fā)送者用自己的私鑰對(duì)消息摘要加密產(chǎn)生一個(gè)字符串宅静,加密算法確保別人無(wú)法偽造生成這段字符串,這段數(shù)字串也是對(duì)信息的發(fā)送者發(fā)送信息真實(shí)性的一個(gè)有效證明站欺。
- 數(shù)字簽名是 非對(duì)稱密鑰加密技術(shù) + 數(shù)字摘要技術(shù) 的結(jié)合姨夹。
-
數(shù)字證書 - Certificate
- 數(shù)字證書是一個(gè)經(jīng) 證書授權(quán)中心 數(shù)字簽名 的包含公開密鑰擁有者信息以及公開密鑰的文件。CERT.RSA包含了一個(gè)數(shù)字簽名以及一個(gè)數(shù)字證書矾策。
-
簽名三兄弟
- 從我們解壓出來(lái)的APK目錄里面可以看到有一個(gè)META-INF目錄中
-
MANIFEST.MF:
所有文件的摘要信息磷账,但不包括它們仨自己 -
CERT.SF:
保存的是MANIFEST.MF的摘要值,以及MANIFEST.MF中每一個(gè)摘要項(xiàng)的摘要值 -
CERT.RSA:
保存的是利用密鑰對(duì)CERT.SF進(jìn)行加密生成的數(shù)字簽名和簽名時(shí)用到的數(shù)字證書贾虽,數(shù)字證書保存的就是公鑰和簽名算法
-
- 從我們解壓出來(lái)的APK目錄里面可以看到有一個(gè)META-INF目錄中
-
簽名工具
- jarsigner 在JAVA的bin目錄下
-verbose:簽名命令標(biāo)識(shí)符逃糟。
-keystore:后面跟著的是你簽名使用的密鑰文件(keystore)的絕對(duì)路徑。
-storepass:后面跟著的是你密鑰文件(keystore)的密碼
-signedjar:此后有三個(gè)參數(shù):
參數(shù)一:簽名后生成的apk文件所要存放的路徑。
參數(shù)二:未簽名的apk文件的存放路徑绰咽。
參數(shù)三:你的證書名稱菇肃,通俗點(diǎn)說(shuō)就是你keystore文件的別名,就是在你eclipse進(jìn)行簽名打包時(shí)的Alias的值取募。
例:jarsigner -verbose -keystore mystore.jks -storepass passwork -signedjar signed.apk unsigned.apk aliasname
- signapk
例:signapk 簽名文件 密碼 別名 別名密碼 signed.apk unsigned.apk
- IDE(這就不用說(shuō)了)
- jarsign和signapk區(qū)別:
很多文章都說(shuō) 他們的區(qū)別是在于簽名時(shí)使用的文件不一樣琐谤,前者使用的是keystore文件,后者是用pk8,x509.pem文件玩敏,但我在使用當(dāng)中發(fā)現(xiàn) 其實(shí) 都是可以使用keystore的斗忌,原打包ant腳本中就是使用的signapk 如下,很是不解旺聚,但也不重要织阳。
最后經(jīng)驗(yàn)證是因?yàn)閟ignapk對(duì)as3.0打出的包進(jìn)行再簽名時(shí) APK無(wú)法運(yùn)行,改用apksigner翻屈,接下來(lái) 就開始介紹 apksigner卓囚。
<exec dir="${apk.obj.dir}" executable="${utils.dir}/signapk" >
<arg value="${keystore.uri}" />
<arg value="${keystore.password}" />
<arg value="${keystore.alias}" />
<arg value="${keystore.alias.password}" />
<arg value="${apk.obj.dir}/${apkname}-unsigned.apk" />
<arg value="${apk.obj.dir}/${apkname}-unaligned.apk" />
</exec>
- apksigner
介紹apksigner之前 先說(shuō)一下 什么是V1與V2簽名 這很關(guān)鍵精算。
從Android 7.0開始, 谷歌增加新簽名方案 V2 Scheme (APK Signature);
但Android 7.0以下版本, 只能用舊簽名方案 V1 scheme (JAR signing)
V1與V2的區(qū)別:
V1簽名:
來(lái)自JDK(jarsigner), 對(duì)zip壓縮包的每個(gè)文件進(jìn)行驗(yàn)證, 簽名后還能對(duì)壓縮包修改(移動(dòng)/重新壓縮文件)
對(duì)V1簽名的apk/jar解壓,在META-INF存放簽名文件(MANIFEST.MF, CERT.SF, CERT.RSA),
其中MANIFEST.MF文件保存所有文件的SHA1指紋(除了META-INF文件), 由此可知: V1簽名是對(duì)壓縮包中單個(gè)文件簽名驗(yàn)證
V2簽名:
來(lái)自Google(apksigner), 對(duì)zip壓縮包的整個(gè)文件驗(yàn)證, 簽名后不能修改壓縮包(包括zipalign),
對(duì)V2簽名的apk解壓,沒有發(fā)現(xiàn)簽名文件,重新壓縮后V2簽名就失效, 由此可知: V2簽名是對(duì)整個(gè)APK簽名驗(yàn)證
V2簽名優(yōu)點(diǎn)很明顯:
簽名更安全(不能修改壓縮包)
簽名驗(yàn)證時(shí)間更短(不需要解壓驗(yàn)證),因而安裝速度加快
注意: apksigner工具默認(rèn)同時(shí)使用V1和V2簽名,以兼容Android 7.0以下版本
由此可見 我們需要使用打包后臺(tái)再打包 是一定不能使用V2簽名的,關(guān)于這個(gè)官方也必未強(qiáng)制要求使用V2 給出了一定的配置方法 :
-
在IDE中可以進(jìn)行選擇
在gradel中可以配置:
signingConfigs {
debug {
v1SigningEnabled true
v2SigningEnabled true
}
release {
v1SigningEnabled true
v2SigningEnabled true
}
}
- 在使用apksigner時(shí)也給出了參數(shù)設(shè)置
--v1-signing-enabled <true | false>
--v2-signing-enabled <true | false>
說(shuō)完了V1與V2 我們回頭來(lái)看apksigner的使用,
工具目錄在SDK\build-tools\版本號(hào)\lib下埋酬,執(zhí)行
java -jar apksigner.jar sign
參數(shù)說(shuō)明:
- –ks 你的jks路徑 //jks簽名證書路徑
- –ks-key-alias 你的alias //生成jks時(shí)指定的alias
- –ks-pass pass:你的密碼 //KeyStore密碼
- –key-pass pass:你的密碼 //簽署者的密碼扯键,即生成jks時(shí)指定alias對(duì)應(yīng)的密碼
- –out output.apk //輸出路徑
- input.apk //被簽名的apk
示例:
java -jar apksigner.jar sign --ks key.jks --ks-key-alias releasekey --ks-pass pass:pp123456 --key-pass pass:pp123456 --out output.apk input.apk
段落總結(jié):以上是關(guān)于簽名的資料整合撩满,也是自動(dòng)化打包的核心葵第,當(dāng)前自動(dòng)化打包工具無(wú)法對(duì)AS3.0再打包的關(guān)鍵也在于此,關(guān)鍵就在于signapk過(guò)時(shí)了嘴秸,而AS3.0的升級(jí)又改變很大毁欣,想要自動(dòng)化,就需要將signapk也升級(jí)岳掐,他的取代方案就是使用apksigner凭疮。當(dāng)中我也懷疑過(guò)是不是V2導(dǎo)致的問題,后經(jīng)驗(yàn)證串述,與它無(wú)關(guān)执解,使用AS3.0強(qiáng)制使用V1打包 也一樣不可用,或許打包通過(guò)纲酗,但在安裝時(shí)報(bào)如下錯(cuò)誤:
Failure [INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION: Failed reading AndroidManifest.xml in android.util.jar.StrictJarFile@6fa28f: META-INF/MANIFEST.MF has invalid digest for AndroidManifest.xml in AndroidManifest.xml]
說(shuō)到這里思路就清晰了衰腌,接下來(lái)的工作就是將signapk替換為apksigner,并結(jié)合使用在ANT當(dāng)中觅赊。
二右蕊、Ant 語(yǔ)法規(guī)則:
此項(xiàng)目從2015年以來(lái) 一直使用Ant,之前也有做過(guò),而這次問題的主因并不在ant是否過(guò)時(shí)吮螺,as3.0是否一定要用最新的gradle的問題饶囚,所以繼續(xù)在ANT基礎(chǔ)上修改帕翻,以前并未做過(guò)總結(jié),就又到處查了一遍資料坯约,太麻煩熊咽,所以就著這次機(jī)會(huì)做個(gè)最終了結(jié)莫鸭。
參考資料:
https://blog.csdn.net/leeandmins/article/details/50481450
https://blog.csdn.net/megatronkings/article/details/48012125
http://tianlihu.iteye.com/blog/741239
- 安裝和配置
1闹丐、下載ant,[http://mirror.esocc.com/apache//ant/binaries/apache-ant-1.9.1-bin.zip]
2被因、配置ANT_HOME環(huán)境變量 指向下載包中的bin目錄
3卿拴、運(yùn)行 ant 命令 (window下應(yīng)該是運(yùn)行ant.bat) 得到如下結(jié)果,代表安裝成功梨与。
從結(jié)果可以看出 他需要用到build.xml堕花,通俗的講 build.xml就ant的核心代碼文件,build.xml是默認(rèn)的ant執(zhí)行文件
ant
在當(dāng)前目錄下的build.xml運(yùn)行Ant粥鞋,執(zhí)行缺省的target缘挽。
ant -buildfile build-test.xml
在當(dāng)前目錄下的build-test.xml運(yùn)行Ant,執(zhí)行缺省的target呻粹。
ant -buildfile build-test.xml clean
在當(dāng)前目錄下的build-test.xml運(yùn)行Ant壕曼,執(zhí)行一個(gè)叫做clean的target。
ant -buildfile build-test.xml -Dbuild=build/classes clean
在當(dāng)前目錄下的build-test.xml運(yùn)行Ant等浊,執(zhí)行一個(gè)叫做clean的target腮郊,并設(shè)定build屬性的值為build/classes。
- build.xml文件的基本結(jié)構(gòu)
<?xml version="1.0" encoding="UTF-8"?>
<project name="test" default="build">
<property name="file.dir" value="D://"/>
<property file="local.properties" />
<loadproperties srcFile="project.properties" />
<import file="rules.xml" optional="true" />
<target name="build">
<echo>runing...</echo>
</target>
<target name="debug" depends="build">
<echo level="info">${file.dir} debugging...</echo>
</target>
</project>
project 根標(biāo)簽筹燕。
name屬性表示項(xiàng)目名稱轧飞,沒什么作用;default屬性表示默認(rèn)執(zhí)行命令撒踪,cmd命令行中使用ant和ant default屬性值(本例是ant build) 兩種方式等效过咬。
property 定義類標(biāo)簽。
可以定義一些常量值制妄,需要注意:定義后理論不能再修改(其實(shí)可以通過(guò)第三方庫(kù)修改)掸绞。比如第3行定義了一個(gè)file.dir的變量,值為”D://“忍捡,引用時(shí)使用 ${file.dir}調(diào)用集漾。第4行,是引入一個(gè)properties文件(里面定義了很多property)砸脊,相當(dāng)于導(dǎo)包具篇。
loadproperties 引用標(biāo)簽。
功能和第4行<property file=""/>等同凌埂,表示引入一個(gè)properties定義集群驱显。好處是便于封裝和管理。
import 引入標(biāo)簽。
和loadproperties不同的是埃疫,import是引入另一個(gè)構(gòu)建文件伏恐,包括變量和執(zhí)行命令。
target 執(zhí)行標(biāo)簽栓霜。
可以在cmd命令行中直接ant + target執(zhí)行翠桦,比如以上腳本可以執(zhí)行: ant build 和 ant debug。target標(biāo)簽中有個(gè)depends屬性胳蛮,表示執(zhí)行命令依賴销凑。如果要執(zhí)行debug命令,會(huì)自動(dòng)先執(zhí)行depends里面的命令仅炊。以上腳本執(zhí)行 ant debug斗幼,實(shí)際是執(zhí)行了 ant build 和 ant debug
echo 日志標(biāo)簽。
表示日志輸出抚垄,能在cmd命令中打印顯示蜕窿,level屬性表示:日志級(jí)別。 比較特殊的是echo中可以引用變量呆馁,用法同變量調(diào)用方式${name}桐经。
- ant的常用語(yǔ)法
1、文件語(yǔ)句
文件操作是ant中最常用的基本操作智哀,包括創(chuàng)建次询、復(fù)制、刪除瓷叫、遍歷等屯吊。由于ant涉及最多的就是文件操作,所以它的api相對(duì)來(lái)說(shuō)非常豐富摹菠,讓我們來(lái)逐一介紹和學(xué)習(xí)盒卸。
創(chuàng)建:mkdir標(biāo)簽。 傳入一個(gè)文件路徑次氨,直接創(chuàng)建出一個(gè)文件目錄蔽介。然而不知為何ant沒有提供創(chuàng)建文件的功能。
<mkdir dir="D:/test"/>
刪除:delete標(biāo)簽煮寡。刪除文件或文件夾
<delete file="D:/test/example.txt"/>
<delete dir="D:/test"/>
移動(dòng):move標(biāo)簽虹蓄。包括文件重命名、文件移動(dòng)幸撕、文件目錄移動(dòng)薇组。
<!-- 重命名 -->
<move file="D:/test/example1.txt" tofile="D:/test/example2.txt"/>
<!-- 移動(dòng)文件至新目錄,新目錄會(huì)自動(dòng)創(chuàng)建 -->
<move file="D:/test/example2.txt" todir="D:/test2"/>
<!-- 文件夾移動(dòng) -->
<move dir="D:/test/example2.txt" todir="D:/test2"/>
復(fù)制:copy標(biāo)簽坐儿。文件復(fù)制律胀。
<!-- 文件復(fù)制宋光,指定新文件名 -->
<copy file="D:/test/example.txt" tofile="D:/test/example2.txt"/>
<!-- 文件復(fù)制,指定新文件目錄 -->
<copy file="D:/test/example.txt" todir="D:/test/new/"/>
<!-- 文件夾復(fù)制炭菌,指定新文件夾 -->
<copy dir="D:/test/" todir="D:/test/new/"/>
2罪佳、條件語(yǔ)句
condition標(biāo)簽,配合istrue或者isfalse使用黑低。
<condition property="check">
<istrue value="false" />
</condition>
<target name="build" if="check">
<echo>build running...</echo>
</target>
在執(zhí)行名為build的target任務(wù)時(shí)赘艳,由于target中含有if的標(biāo)簽,所以需要判斷名為check的條件語(yǔ)句的值投储,但是istrue=false的語(yǔ)句表示條件不符合第练,echo并不會(huì)執(zhí)行。如果改成istrue=true玛荞,echo將執(zhí)行。當(dāng)然以上語(yǔ)句等價(jià)于:
<condition property="check">
<isfalse value="true" />
</condition>
<target name="build" if="check">
<echo>build running...</echo>
</target>
需要注意下呕寝,istrue和isfalse兩種標(biāo)簽不能同時(shí)存在勋眯。
除了直接使用istrue指定條件語(yǔ)句的值,還能動(dòng)態(tài)地使用equals比較變量下梢,比如:
<property name="id" value="99"></property>
<condition property="check">
<equals arg1="${id}" arg2="100"/>
</condition>
<target name="build" if="check">
<echo>build running...</echo>
</target>
3客蹋、循環(huán)語(yǔ)句
ant本身并沒有提供循環(huán)語(yǔ)句,但是我們可以借助于ant-contrib.jar使用循環(huán)語(yǔ)句孽江,舉個(gè)簡(jiǎn)單的例子:
<property name="ant-contrib" value="E:\\Android\\android-sdk\\tools\\lib\\ant-contrib-1.0b3.jar"></property>
<taskdef name="foreach" classname="net.sf.antcontrib.logic.ForEach" classpath="${ant-contrib}"/>
<target name="build">
<foreach list="1,2,3,4,5,6,7,8,9" param="number" delimiter="," target="log"/>
</target>
<target name="log">
<echo>foreach running: ${number}</echo>
</target>
4讶坯、自定義語(yǔ)句
ant的魅力所在之處就是強(qiáng)大的自定義語(yǔ)句,比如上面的foreach語(yǔ)句岗屏。ant官方庫(kù)只定義了一些簡(jiǎn)單的語(yǔ)句辆琅,但是在實(shí)際項(xiàng)目中遠(yuǎn)遠(yuǎn)不足以滿足我們的需要,比如新建一個(gè)文件这刷。這里我們就用自定義語(yǔ)句來(lái)實(shí)現(xiàn)下婉烟。
ant的原理是每個(gè)語(yǔ)句標(biāo)簽映射一個(gè)java類文件,每個(gè)標(biāo)簽里的屬性則映射java類的變量暇屋,有點(diǎn)類似spring中xml映射javabean似袁。每個(gè)ant標(biāo)簽映射的java類文件不是隨意編寫的,有一定的規(guī)范咐刨。
在ant安裝目錄下的lib文件目錄中有個(gè)名為ant.jar的包昙衅,這個(gè)就是ant的規(guī)范標(biāo)準(zhǔn)庫(kù),自定義語(yǔ)句Java類都需要依賴它來(lái)編譯定鸟,同時(shí)每個(gè)語(yǔ)句必須繼承其中名為Task.java的基類而涉,復(fù)寫execute方法執(zhí)行自定義操作。
package com.ant.test;
import java.io.File;
import java.io.IOException;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
public class FileCreater extends Task{
private String fileName;
public void setName(String fileName){
this.fileName = fileName;
}
@Override
public void execute() throws BuildException {
try {
new File(fileName).createNewFile();
} catch (IOException e) {
log("create file '" + fileName + "' failed!");
}
log("create file '" + fileName + "' successful!");
super.execute();
}
}
上面定義了創(chuàng)建文件的自定位標(biāo)簽仔粥,把這個(gè)java文件打成jar包婴谱,然后就可以在build.xml使用了蟹但,xml內(nèi)容如下:
<property name="fileJar" value="D:/file.jar"></property>
<taskdef name="filecreater" classname="com.ant.test.FileCreater" classpath="${fileJar}"/>
<target name="build">
<filecreater name="D:/test.txt"/>
</target>
以上執(zhí)行的操作是創(chuàng)建一個(gè)路徑為D:/test.txt的文件。filecreater是映射FileCreater.java的自定義標(biāo)簽谭羔,name屬性傳入文件路徑名华糖,會(huì)自動(dòng) 反射調(diào)用FileCreater.java中的setName方法注入?yún)?shù)值。在FileCreater.java中有個(gè)log打印輸出方法瘟裸,可以在cmd中輸出客叉,極大方便我們的調(diào)試。
最后 如下代碼是替換signapk為apksigner的主要代碼
<!-- <exec dir="${apk.obj.dir}" executable="${utils.dir}/signapk" >
<arg value="${keystore.uri}" />
<arg value="${keystore.password}" />
<arg value="${keystore.alias}" />
<arg value="${keystore.alias.password}" />
<arg value="${apk.obj.dir}/${apkname}-unsigned.apk" />
<arg value="${apk.obj.dir}/${apkname}-unaligned.apk" />
</exec> -->
<exec executable="java">
<arg line="-jar ${utils.dir}/lib/apksigner.jar sign --ks ${keystore.uri} --ks-key-alias ${keystore.alias} --ks-pass pass:${keystore.password} --key-pass pass:${keystore.password} --v2-signing-enabled false --out ${apk.obj.dir}/${apkname}-unaligned.apk ${apk.obj.dir}/${apkname}-unsigned.apk"/>
</exec>
總結(jié) 以上兩部分是這次任務(wù)的主要內(nèi)容话告,除了涉及這兩塊知識(shí)外 再就有些零散的appt知識(shí)點(diǎn)和linux shell命令兼搏,我做一下簡(jiǎn)單整理
三、什么是aapt
參考資料:
https://blog.csdn.net/electricity/article/details/6540247
項(xiàng)目用到的不多沙郭,而這塊的知識(shí)點(diǎn)內(nèi)容比較多 限于篇幅過(guò)長(zhǎng) 在此就簡(jiǎn)單列出 此次用到的一些點(diǎn)
打包好的apk中移除文件
aapt r[emove] [-v] file.{zip,jar,apk} file1 [file2 ...]
添加文件到打包好的apk中
aapt a[dd] [-v] file.{zip,jar,apk} file1 [file2 ...]
實(shí)例:
<target name="build-apk">
<sequential>
<copy file="${apk.obj.dir}/${apkfile}" tofile="${apk.obj.dir}/${apkname}-unsigned.apk" overwrite="true" />
<echo level="info">remove AndroidManifest</echo>
<exec dir="${apk.obj.dir}" executable="${utils.dir}/aapt">
<arg value="r" />
<arg value="${apk.obj.dir}/${apkname}-unsigned.apk" />
<arg value="AndroidManifest.xml" />
</exec>
<exec dir="${apk.obj.dir}" executable="${utils.dir}/aapt">
<arg value="a" />
<arg value="${apk.obj.dir}/${apkname}-unsigned.apk" />
<arg value="AndroidManifest.xml" />
</exec>
</sequential>
</target>
四佛呻、linux shell簡(jiǎn)單命令
參考資料:https://www.cnblogs.com/yinheyi/p/6648242.html
LOCAL_PATH=`dirname $0`
cd $LOCAL_PATH
if [ "$1" == "" ]; then
utils/ant/bin/ant help
else
echo $@
utils/ant/bin/ant $@
fi
總結(jié)完回頭一看 篇幅真的挺長(zhǎng),呵呵呵 沒辦法 項(xiàng)目總結(jié) 內(nèi)容是多一點(diǎn)病线,對(duì)于markdown的排版也不是很熟練吓著,整篇來(lái)看可能有些臃腫,結(jié)構(gòu)不清后繼再優(yōu)化吧送挑,熟能生巧绑莺,寫文章總結(jié)雖說(shuō)也是門技術(shù)活,但也是熟練工種惕耕,總結(jié)是越發(fā)的重要纺裁,會(huì)越來(lái)越好的。