Android系統更新防互刷功能實現與分析
寫在前面:
為了協助理解,這裡首先描述一個應用情境。
一個項目有兩個版本(一個項目兩個版本的原因或許是由於硬體不同導致的,如不同容量電池,不同解析度網路攝影機等),在升級的時候很容易將相同項目的兩個版本的升級包混淆,因此需要實現兩個版本的防互刷功能,那麼在該應用情境下需要如何?呢?
注意,這裡肯定會有疑問了,既然一個項目兩個版本容易混淆更新包,那麼把它作為兩個項目來實施不是有效避免這個問題了嗎?當然,把一個項目由於不同版本分拆為不同的項目來做當然可以有效避免更新包混淆的問題,這也是一個比較快捷的方案。因此該博文是在實施一個項目的情境下來開展的。
第一步:瞭解校正原理
熟悉ota_from_target_files、edify_generator.py或則瞭解install.c和updater-script指令碼執行過程的朋友應該很清楚在更新包安裝的一開始會對一些屬性資訊進行校正,這裡貼出部分指令碼產生器edify_generator.py的相關的函數:
#校正時間戳記
def AssertOlderBuild(self, timestamp, timestamp_text):
"""Assert that the build on the device is older (or the same as)
the given timestamp."""
self.script.append(
('(!less_than_int(%s, getprop("ro.build.date.utc"))) || '
'abort("Can\'t install this package (%s) over newer '
'build (" + getprop("ro.build.date") + ").");'
) % (timestamp, timestamp_text))
校正時間戳記,我們需要理解上面的方法,上面的函數只是產生一條edify語句,實際上在產生的updater-script指令碼中,也就是下面這條語句:
(!less_than_int(%s, getprop("ro.build.date.utc")))①||abort("Can\'t install this package (%s) over newer build (" + getprop("ro.build.date") + ").");②
arg1:timestamp, arg2:timestamp_text//參數1為世界時間,參數2為日期格式
上面的代碼告訴我們如果①的值為True則執行下一步,否則執行語句②,語句①對比時間戳記大小,語句②為中斷語句。也就是說如果當前更新包的打包時間如果比手機Build的時間older,就執行中止語句。
#校正裝置資訊
def AssertDevice(self, device):
"""Assert that the device identifier is the given string."""
cmd = ('getprop("ro.product.device") == "%s" || '
'abort("This package is for \\"%s\\" devices; '
'this is a \\"" + getprop("ro.product.device") + "\\".");'
) % (device, device)
self.script.append(cmd)
若更新包中打包的裝置資訊如果與手機Build中的裝置資訊不一致就會執行中止語句。
#校正系統指紋
def AssertSomeFingerprint(self, *fp):
"""Assert that the current system build fingerprint is one of *fp."""
if not fp:
raise ValueError("must specify some fingerprints")
cmd = (
' ||\n '.join([('file_getprop("/system/build.prop", '
'"ro.build.fingerprint") == "%s"')
% i for i in fp]) +
' ||\n abort("Package expects build fingerprint of %s; this '
'device has " + getprop("ro.build.fingerprint") + ".");'
) % (" or ".join(fp),)
self.script.append(cmd)
若更新包中新舊版本的系統指紋與當前手機中的系統指紋不一致就會執行中止語句。
無論是時間戳記還是裝置資訊還是系統指紋都是從build.prop中取值。但是,這裡呢,要對fingerprint資訊簡單介紹一下,fingerprint就是我們常說的系統指紋,我們可以在adb模式下能否通過getprop ro.build.fingerprint的方式進行查看或者參考alps\frameworks\base\core\java\android\os\Build.java下對應的FINGERPRINT的取值方式在代碼中進行取值。每個項目的每個版本Fingerprint都不同,按照Google的要求,前後不允許帶有空格、空行、斷行,字元不超過91個,
Fingerprint欄位定義格式如下:
BUILD_FINGERPRINT :=
$(PRODUCT_BRAND)/$(TARGET_PRODUCT)/$(TARGET_DEVICE):$(PLATFORM_VERSION)/$(BUILD_ID)/$(BUILD_NUMBER):$(TARGET_BUILD_VARIANT)/$(BUILD_VERSION_TAGS)
如:Google/Y100-T00/GoogleY100-T:4.2.2/GoogleY100-T00/CHSC01B001:user/release-keys
TCT/TCL_J900T/Camry2_TD:4.2.1/JOP40D/TCL_J900T_V2.0:user/release-keys
具體配置請按以下:
1.PRODUCT_BRAND為getprop檔案中的相關欄位:ro.product.brand的資訊,全字母大寫or首字母大寫均可。Eg:Google
2.TARGET_PRODUCT為getprop檔案中的相關欄位:ro.product.name的資訊,全字母大寫,Eg:Y516-T00。
3.TARGET_DEVICE為getprop檔案中的相關欄位:ro.product.device的資訊
4.ID為getprop檔案中的相關欄位:ro.build.id的資訊,
5.version.incremental為getprop檔案中的相關欄位:ro.build.version.incremental的資訊。要求version.incremental是組建號(真實版本號碼)
6.VERSION.RELEASE為getprop檔案中的相關欄位:ro.build.version.release的資訊,一般為Android系統版本號碼
7.TYPE必須是user版本,不能是eng版本。
8.TAGS必須是release-keys,不能是test-keys,(將宏MTK_SIGNATURE_CUSTOMIZATION = yes)。如果該版本帶有或者後續將開發OTA升級功能,須使用ota-rel-keys,release-keys,普通版本請使用release-keys。
那麼系統指紋在差分升級的時候才會校正,裝置資訊和簽名一般來說同一個項目後者同一個產品對於客戶來講都是不允許修改的,如果客戶允許這些也可以作為防互刷的手段。那麼可能到這裡你或許已經明白這篇博文的目的了。
那麼,對的,本篇博文主要是通過增加新的系統屬性來標識相同項目不同版本防互刷的功能。那麼該如何?呢?
第二步:添加校正標識
一、添加系統屬性
這裡需要在/vendor/{項目}/{項目分支}/config/system.prop檔案,當然不同平台不同公司對應的system.prop路徑不一致
這裡我在system.prop中最後一行添加如下屬性:
ro.update.version=a1
二、修改指令碼
首先在指令碼產生器edifty_generator.py檔案中定義如下方法:
def AssertUpdateVersion(self, update_version): """Assert that the update_version identifier is the given string.""" cmd = ('getprop("ro.update.version") == "%s" || ' 'abort("This package is for \\"%s\\" update_version; ' 'this is a \\"" + getprop("ro.update.version") + "\\".");' ) % (update_version, update_version) self.script.append(cmd)然後在ota_from_target_file檔案中的
AppendAssertions函數中添加如下:(紅色為新添加)
def
AppendAssertions(script, info_dict):
device = GetBuildProp("ro.product.device", info_dict)
script.AssertDevice(device)
update_version = GetBuildProp("ro.update.version", info_dict)//從字典中去除ro.update.version的值
script.AssertUpdateVersion(update_version)//在升級指令碼中添加驗證新的屬性函數
並修改WriteFullOTAPackage()函數,如下:
def WriteFullOTAPackage(input_zip, output_zip):
# TODO: how to determine this? We don't know what version it will
# be installed on top of. For now, we expect the API just won't
# change very often.
script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
metadata = {"post-build": GetBuildProp("ro.build.fingerprint",
OPTIONS.info_dict),
"pre-device": GetBuildProp("ro.product.device",
OPTIONS.info_dict),
"post-timestamp": GetBuildProp("ro.build.date.utc",
OPTIONS.info_dict),
"update-version": GetBuildProp("ro.update.version",
OPTIONS.info_dict),//定義新的屬性
}
三、驗證(注!不要偷懶,直接修改update包中的build.pro,否則會校正簽名不過的)
那麼接下來就需要進行驗證了,這裡呢首先使用ro.update.version值為a1的版本製作整包,然後在手機中download屬性ro.update.version值為a2的版本進行sd卡整包升級,在升級的過程中會對update-version進行校正,如果值不一樣則校正失敗。
下面貼出校正失敗的log(也就是防互刷功能成功實現的效果)
script aborted: This package is for "a1" update_version; this is a "a2".
This package is for "a1" update_version; this is a "a2".
E:Error in /sdcard/dload/update.zip
(Status 7)
Installation aborted.
I:no boot messages recovery
I:[1]check the otaupdate is done!
factory_poweroff_flag=0
demo_mode_flag=0
下面是對應的updater-script指令碼中防互刷的語句。
getprop("ro.update.version") == "a1" || abort("This package is for \"a1\" update_version; this is a \"" + getprop("ro.update.version") + "\".");