2023年2月3号,负责发版的同事通知说魅族市场审核不通过,原因是安装包包含了32位库,请处理32位库后再重新提交。 然后我们通过市场手机abi分布分析,还有一部分只支持32位的手机。所以除了魅族市场其他市场还是需要包含32位的apk。
但是我们的包是通过多渠道打包脚本一次打包出来的,其中包含华为、小米、oppo、vivo、魅族、应用宝。
所以现在面临的问题是需要单独对魅族打一次包。我想这种方式不符合我门的风格,是否可以一次打包打所有市场的包,其中魅族市场的包只包含64位so库,其他市场包含32位和64位。
我分析apk包发现魅族和其他市场的唯一区别就包里面少了一个lib/armeabi-v7a/文件夹。是否可以通过脚本在apk打包之后将 lib/armeabi-v7a/ 目录删除,然后再重新签名呢?
其中主要的修改点是增加了下面的代码:
# 如果只保留64位so库,则删除32位so库
if only64abiChannels and str(channel["id"]) in only64abiChannels:
# 删除lib目录下的armeabi-v7a文件夹
deleteArmeabiV7a = f"zip {dest_apk_path} -d lib/armeabi-v7a/\*"
delete_result = subprocess.getoutput(deleteArmeabiV7a)
print(delete_result)
if "name not matched: lib/armeabi-v7a/*" not in delete_result:
print(f"----delete lib/armeabi-v7a/* success in {dest_apk_name}")
zipalign_out = path.join(base_dir, f"output_{dest_apk_name}")
# 对删除后的apk资源文件进行首位4个字节的对齐
delete_result = subprocess.getoutput(f"{apk_zipalign} -p -f -v 4 {dest_apk_path} {zipalign_out}")
delete_array = delete_result.split('\n')
if len(delete_array) > 1:
print(delete_array[-1])
else:
delete_result = subprocess.getoutput(f"{apk_zipalign} -c -v 4 {zipalign_out}")
delete_array = delete_result.split('\n')
if len(delete_array) > 1:
print(delete_array[-1])
# 将对齐后的apk覆盖目标apk
os.remove(dest_apk_path)
os.rename(zipalign_out, dest_apk_path)
print(f"---zipaligned apk: {dest_apk_path}")
下面是多渠道打包脚本的完整代码,当然,这里面还包含了加固脚本的代码没有贴出来,加固的流程在多渠道打包之前,加固脚本是支持部分市场加固的,加固之后也是可以删除32位的so库,已经亲自测试过。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import zipfile
import json
import os, sys
from os import path
import argparse
import shutil
if sys.version_info.major <= 2:
import urllib2
import commands as subprocess
reload(sys)
sys.setdefaultencoding("utf-8")
else:
import urllib.request as urllib2
import subprocess
def getJsonData(str):
try:
fjson = open(str, "r")
jsonobj = json.load(fjson)
fjson.close
except IOError:
jsonobj = json.load(urllib2.urlopen(str))
return jsonobj
try:
H = dict(line.strip().split('=') for line in open('local.properties') if not line.startswith('#') and not line.startswith('\n'))
except IOError:
H = dict(line.strip().split('=') for line in open('../local.properties') if not line.startswith('#') and not line.startswith('\n'))
sdk = H['sdk.dir']
apk_signer_str = subprocess.getoutput("find {sdk}/build-tools/ -name 'apksigner'".format(sdk=sdk))
apk_signer_arr = apk_signer_str.split('\n')
apk_signer = apk_signer_arr[-1]
apk_zipalign = ''
if apk_signer and len(apk_signer) > 0:
apk_zipalign = path.join(path.dirname(apk_signer), "zipalign")
print(f"---apk_signer: {apk_signer}, apk_zipalign: {apk_zipalign}")
key_password = '123456'
base_dir = "app/build/outputs/apk"
base_apk = "app-debug.apk"
jiagu_base_apk = ''
json_path = ''
key_store = ''
channel_ids = []
default_apk_name = 'app-{code}.apk'
parser = argparse.ArgumentParser()
parser.add_argument('-apk_path', action='store', dest='apk_path', help='the apk path')
parser.add_argument('-json_path', action='store', dest='json_path', help='the channel data config path')
parser.add_argument('-key_password', action='store', dest='key_password', help='the password of keystore')
parser.add_argument('-key_store', action='store', dest='key_store', help='the keystore')
parser.add_argument('-default_apk_name', action='store', dest='default_apk_name', help='the default apk name')
parser.add_argument('-channel_ids', action='store', dest='channel_ids', help='the channel packaged')
parser.add_argument('-ex_channel_ids', action='store', dest='ex_channel_ids', help='the channel to exclude')
parser.add_argument('-jiagu_channel_ids', action='store', dest='jiagu_channel_ids', help='the channel packaged')
parser.add_argument('-jiagu_ex_channel_ids', action='store', dest='jiagu_ex_channel_ids', help='the channel to exclude')
parser.add_argument('-is_enable_jiagu', action='store', dest='is_enable_jiagu', help='the is_enable_jiagu to exclude')
parser.add_argument('-only64abiChannels', action='store', dest="only64abiChannels", help='the 32 bit abi to exclude')
argResult = parser.parse_args()
apk_path = argResult.apk_path
json_path = argResult.json_path
key_password = argResult.key_password
key_store = argResult.key_store
default_apk_name = argResult.default_apk_name
jiagu_default_apk_name = "jiagu_" + default_apk_name
channel_ids = argResult.channel_ids
ex_channel_ids = argResult.ex_channel_ids
is_enable_jiagu = argResult.is_enable_jiagu
jiagu_channel_ids = argResult.jiagu_channel_ids
jiagu_ex_channel_ids = argResult.jiagu_ex_channel_ids
only64abiChannels = argResult.only64abiChannels
if apk_path and len(apk_path) > 0:
base_dir = path.dirname(apk_path)
base_apk = path.basename(apk_path)
print(f"base_dir: {base_dir}, base_apk: {base_apk}")
jiagu_apk_path = ''
if is_enable_jiagu == 'true':
jiagu_apk_path = subprocess.getoutput(f"find {base_dir} -name '*jiagu_sign.apk'")
if jiagu_apk_path and len(jiagu_apk_path) > 0:
jiagu_base_apk = path.basename(jiagu_apk_path)
print(f"jiagu_base_apk: {jiagu_base_apk}")
if channel_ids and len(channel_ids) > 0:
channel_ids = channel_ids.split(',')
if ex_channel_ids and len(ex_channel_ids) > 0:
ex_channel_ids = ex_channel_ids.split(',')
if jiagu_channel_ids and len(jiagu_channel_ids) > 0:
jiagu_channel_ids = jiagu_channel_ids.split(',')
if jiagu_ex_channel_ids and len(jiagu_ex_channel_ids) > 0:
jiagu_ex_channel_ids = jiagu_ex_channel_ids.split(',')
print(f"----only64abiChannels: {only64abiChannels}")
if only64abiChannels and len(only64abiChannels) > 0:
only64abiChannels = only64abiChannels.split(',')
if not key_store:
key_store_str = subprocess.getoutput("find . -maxdepth 1 -name '*.keystore' ")
key_store_arr = key_store_str.split('\n')
print(key_store_arr)
if key_store_arr[0] == '':
key_store_str = subprocess.getoutput("find . -maxdepth 1 -name '*.jks' ")
key_store_arr = key_store_str.split('\n')
key_store = key_store_arr[0]
data = getJsonData(json_path)
shutil.rmtree(path.join(base_dir, "META-INF"), ignore_errors=True)
os.makedirs(path.join(base_dir, "META-INF"))
def packagingApk(p_channels, p_ex_channel_ids, p_base_apk, p_default_apk_name):
for channel in data:
if p_channels and not (str(channel["id"]) in p_channels):
continue
if p_ex_channel_ids and (str(channel["id"]) in p_ex_channel_ids):
continue
empty_channel_file_name = f'fc-multi-channel-{channel["code"]}-{channel["id"]}'
empty_channel_file = path.join(base_dir, "META-INF", empty_channel_file_name)
print(f"---empty_channel_file: {empty_channel_file}, empty_channel_file_name: {empty_channel_file_name}")
channel_file_writer = open(empty_channel_file, 'w')
if "extInfo" in channel:
channel_file_writer.write(json.dumps(channel["extInfo"], ensure_ascii=False))
channel_file_writer.close()
dest_apk_name = p_default_apk_name.format(code = channel['code'], id = channel['id'], name = channel['name'])
dest_apk_path = path.join(base_dir, dest_apk_name)
src_apk_path = path.join(base_dir, p_base_apk)
print(f"---src_apk_path: {src_apk_path}, dest_apk_path: {dest_apk_path}")
shutil.copyfile(src_apk_path, dest_apk_path)
zipped = zipfile.ZipFile(dest_apk_path, 'a', zipfile.ZIP_DEFLATED)
zipped.write(empty_channel_file, f"META-INF/{empty_channel_file_name}")
zipped.close()
# 如果只保留64位so库,则删除32位so库
if only64abiChannels and str(channel["id"]) in only64abiChannels:
# 删除lib目录下的armeabi-v7a文件夹
deleteArmeabiV7a = f"zip {dest_apk_path} -d lib/armeabi-v7a/\*"
delete_result = subprocess.getoutput(deleteArmeabiV7a)
print(delete_result)
if "name not matched: lib/armeabi-v7a/*" not in delete_result:
print(f"----delete lib/armeabi-v7a/* success in {dest_apk_name}")
zipalign_out = path.join(base_dir, f"output_{dest_apk_name}")
delete_result = subprocess.getoutput(f"{apk_zipalign} -p -f -v 4 {dest_apk_path} {zipalign_out}")
delete_array = delete_result.split('\n')
if len(delete_array) > 1:
print(delete_array[-1])
else:
delete_result = subprocess.getoutput(f"{apk_zipalign} -c -v 4 {zipalign_out}")
delete_array = delete_result.split('\n')
if len(delete_array) > 1:
print(delete_array[-1])
os.remove(dest_apk_path)
os.rename(zipalign_out, dest_apk_path)
print(f"---zipaligned apk: {dest_apk_path}")
if apk_signer == '':
print(f'apksigner {dest_apk_path}: warning no found apksigner command')
elif key_store == '':
print(f'apksigner {dest_apk_path}: warning no found keystore')
else:
signapk = f"echo '{key_password}' | {apk_signer} sign --ks {key_store} {dest_apk_path}"
result = subprocess.getoutput(signapk)
result_array = result.split('\n')
if len(result_array) > 1:
print(result)
else:
print(f'apksigner {dest_apk_path} success')
if jiagu_base_apk and len(jiagu_base_apk) > 0:
subprocess.getoutput(f"find {base_dir} -type f -not -name '{base_apk}' -not -name '{jiagu_base_apk}' -name '*.apk' -delete")
packagingApk(channel_ids, ex_channel_ids, base_apk, default_apk_name)
packagingApk(jiagu_channel_ids, jiagu_ex_channel_ids, jiagu_base_apk, jiagu_default_apk_name)
else:
subprocess.getoutput(f"find {base_dir} -type f -not -name '{base_apk}' -name '*.apk' -delete")
packagingApk(channel_ids, ex_channel_ids, base_apk, default_apk_name)