Browse Source

增加录屏sdk

yhz 6 years ago
parent
commit
a41d4b6780
34 changed files with 1600 additions and 1 deletions
  1. 6 0
      .gitignore
  2. 102 0
      config_utils_record.py
  3. 3 0
      package_record.py
  4. 1018 0
      package_utils_record.py
  5. 5 1
      package_web.py
  6. 214 0
      package_web_record.py
  7. BIN
      sdk/record/jniLibs/armeabi-v7a/libffmpeg-core.so
  8. BIN
      sdk/record/jniLibs/armeabi-v7a/libffmpeg-invoke.so
  9. BIN
      sdk/record/jniLibs/armeabi/libffmpeg-core.so
  10. BIN
      sdk/record/jniLibs/armeabi/libffmpeg-invoke.so
  11. BIN
      sdk/record/libs/android-support-v4.jar
  12. BIN
      sdk/record/libs/record_sdk.jar
  13. BIN
      sdk/record/libs/rxffmpeg-1.2.0.jar
  14. BIN
      sdk/record/libs/screenrecorder-2.4.jar
  15. 14 0
      sdk/record/manifest.xml
  16. BIN
      sdk/record/res/drawable-hdpi/jm_record_back.png
  17. BIN
      sdk/record/res/drawable-hdpi/jm_record_float.png
  18. BIN
      sdk/record/res/drawable-xxhdpi/jm_record_hidden.png
  19. BIN
      sdk/record/res/drawable-xxhdpi/jm_record_record_delete.png
  20. BIN
      sdk/record/res/drawable-xxhdpi/jm_record_record_pause.png
  21. BIN
      sdk/record/res/drawable-xxhdpi/jm_record_record_share.png
  22. BIN
      sdk/record/res/drawable-xxhdpi/jm_record_record_start.png
  23. BIN
      sdk/record/res/drawable-xxhdpi/jm_record_record_stop.png
  24. BIN
      sdk/record/res/drawable-xxhdpi/jm_record_video_record.png
  25. BIN
      sdk/record/res/drawable-xxhdpi/jm_record_video_record_start.png
  26. 6 0
      sdk/record/res/drawable/jm_record_float_bg.xml
  27. 6 0
      sdk/record/res/drawable/jm_record_video_record_bg.xml
  28. 6 0
      sdk/record/res/drawable/jm_record_video_record_bg2.xml
  29. 53 0
      sdk/record/res/layout/jm_record_float_view.xml
  30. 127 0
      sdk/record/res/layout/jm_record_fragment_record.xml
  31. 8 0
      sdk/record/res/values/jm_record_colors.xml
  32. 13 0
      sdk/record/res/values/jm_record_dimens.xml
  33. 17 0
      sdk/record/res/values/jm_record_strings.xml
  34. 2 0
      sdk/record/res/values/jm_record_styles.xml

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+/__pycache__
+/game
+/gen
+/target
+/test
+/test.py

+ 102 - 0
config_utils_record.py

@@ -0,0 +1,102 @@
+import time
+
+def checkConfig(config):
+    '''
+    检查配置
+    '''
+    print('check config ...')
+    if type(config) == dict:
+        return checkChannelConfig(config)
+    elif type(config) == list:
+        for itemConfig in config:
+            if not checkChannelConfig(itemConfig):
+                return False
+    return True
+
+def checkChannelConfig(config):
+    if 'name' not in config or 'packageName' not in config:
+        print('name or packageName not exists in config')
+        return False
+
+    if 'subChannel' not in config:
+        print('subChannel not exists in config')
+        return False
+
+    # 默认值
+    if 'changeIcon' not in config:
+        config['changeIcon'] = False
+    if 'switchIcon' not in config:
+        config['switchIcon'] = False
+    if 'addLauncher' not in config:
+        config['addLauncher'] = False
+    if 'splitDex' not in config:
+        config['splitDex'] = True
+    if 'clearCache' not in config:
+        config['clearCache'] = True
+    if 'screenOrientation' not in config:
+        config['screenOrientation'] = 'landscape'
+    if 'outName' not in config:
+        config['outName'] = config['name']
+
+    if 'recordConfig' in config:
+        setRecordConfig(config, config['recordConfig'])
+        
+    return True
+
+def setRecordConfig(config, recordConfig):
+    configData = None
+    if 'configData' in config:
+        configData = config['configData']
+        configData['bgMusic'] = 'bg_music.mp3'
+        configData['gameId'] = recordConfig['gameId']
+        configData['gameName'] = recordConfig['gameName']
+        configData['gameIcon'] = recordConfig['gameIcon']
+        configData['gameUrl'] = recordConfig['gameUrl']
+        configData['skinId'] = recordConfig['skinId']
+    else:
+        config['configData'] = {
+            'bgMusic':'bg_music.mp3',
+            'gameId':recordConfig['gameId'],
+            'gameName':recordConfig['gameName'],
+            'gameIcon':recordConfig['gameIcon'],
+            'gameUrl':recordConfig['gameUrl'],
+            'skinId':recordConfig['skinId']
+        }
+
+def replaceArgs(config):
+    '''
+    替换占位符
+    '''
+    if type(config) == dict:
+        replaceItemArgs(config)
+    elif type(config) == list:
+        # 遍历数组
+        for arg in config:
+            replaceItemArgs(arg)
+
+def replaceItemArgs(config):
+    '''
+    替换占位符
+    '''
+    # 遍历字典
+    for arg in config:
+        if type(config[arg]) == dict:
+            replaceArgs(config[arg])
+        elif type(config[arg]) == str:
+            replaceString(config, arg)
+
+def replaceString(config, arg):
+    '''
+    替换占位符
+    '''
+    content = config[arg]
+    if '${DATE}' in content:
+        content = content.replace('${DATE}', getDate())
+
+    config[arg] = content
+
+def getDate():
+    '''
+    获取日期
+    '''
+    return time.strftime("%Y%m%d", time.localtime()) 

+ 3 - 0
package_record.py

@@ -0,0 +1,3 @@
+import package_utils_record
+
+package_utils_record.packConsoleInput()

+ 1018 - 0
package_utils_record.py

@@ -0,0 +1,1018 @@
+# 安卓游戏打包脚本
+# 1.用apktool解包
+# 2.复制res资源、assets资源、jniLib资源
+# 3.修改包名、app名、渠道文件
+# 4.添加app icon、合并AndroidManifest文件
+# 5.添加app启动图,修改AndroidManifest文件
+# 6.生成新的R文件
+# 7.将新的R文件编译,生成dex,再用baksmali生成smali,替换旧的R文件
+# 8.将jar资源打包成dex,再用baksmali生成smali,拷贝到smali目录下
+# 9.统计方法量,并分割dex(将smali文件拷贝到目录smali_classes2)
+# 10.用apktool回包
+# 11.重新签名
+import file_utils
+import xml_utils
+import smali_utils
+import config_utils_record
+import game_utils
+import os
+import os.path
+import json
+import sys
+import importlib
+import uuid
+
+def pack(game, sdk, config):
+    config['cache'] = uuid.uuid1()
+    subChannel = config['subChannel']
+    # 解包
+    ret = decomplie(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 删除旧代码
+    '''ret = removeOldCode(game, sdk, subChannel, config)
+    if ret:
+        return ret'''
+    # 删除一些不支持的属性
+    ret = removeNoSupportAttr(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 删除一些不支持的配置
+    ret = fixUnSupportConfig(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 合并Drawable-v4目录
+    ret = mergeDrawableRes(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 移除相同的资源
+    ret = removeSameRes(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 复制res资源
+    ret = copyRes(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 合并主文件
+    ret = mergeManifestRes(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 替换占位符
+    ret = changePlaceholders(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 添加meta-data
+    ret = addMetaData(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 复制app res资源
+    ret = copyAppRes(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 更改包名
+    if 'packageName' in config and config['packageName'] != '':
+        ret = changePackageName(game, sdk, subChannel, config)
+        if ret:
+            return ret
+    else:
+        config['packageName'] = getPackageName(game, sdk, subChannel, config)
+    # 更改app名
+    if 'name' in config and config['name'] != '':
+        ret = changeAppName(game, sdk, subChannel, config)
+        if ret:
+            return ret
+    # 更改app icon
+    if config['changeIcon']:
+        ret = changeAppIcon(game, sdk, subChannel, config)
+        if ret:
+            return ret
+    # 添加启动图操作
+    if config['addLauncher']:
+        ret = addLauncher(game, sdk, subChannel, config)
+        if ret:
+            return ret
+    # 打包lib依赖
+    ret = packJar(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # sdk脚本处理
+    ret = doSDKPostScript(game, sdk, config)
+    if ret:
+        return ret
+    # 乐变sdk的特殊处理
+    ret = game_utils.sdkLebianChange(game, sdk, config)
+    if ret:
+        return ret
+    # 游戏脚本处理
+    ret = doGamePostScript(game, sdk, config)
+    if ret:
+        return ret
+    # 生成R文件
+    ret = generateNewRFile(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 添加MultiDex支持
+    if config['splitDex']:
+        ret = splitDex(game, sdk, subChannel, config)
+        if ret:
+            return ret
+    # 更改版本号
+    ret = changeVersion(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 回编译
+    ret = recomplie(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 对齐apk
+    ret = alignApk(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 签名
+    ret = apksignerApk(game, sdk, subChannel, config)
+    if ret:
+        return ret
+    # 添加渠道信息
+    '''ret = addChannel(game, sdk, subChannel, config)
+    if ret:
+        return ret'''
+    # 清理产生的中间文件
+    if config['clearCache']:
+        clearTemp(game, sdk, subChannel, config)
+
+    return 0
+
+def decomplie(game, sdk, subChannel, config):
+    '''
+    解包
+    '''
+    apktoolPath = file_utils.getApkToolPath()
+    gamePath = file_utils.getFullGameApk(game)
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+
+    if os.path.exists(decompliePath):
+        print('delete decomplie folder...')
+        file_utils.deleteFolder(decompliePath)
+
+    print('decomplie apk...')
+
+    return file_utils.execJarCmd(apktoolPath, 'd -f "%s" -o "%s"' % (gamePath, decompliePath))
+
+def removeOldCode(game, sdk, subChannel, config):
+    '''
+    删除旧代码
+    '''
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    codePath = os.path.join(decompliePath, 'smali', 'com', 'jmhy', 'lib', 'record')
+    file_utils.deleteFolder(codePath)
+    return 0
+
+def copyRes(game, sdk, subChannel, config):
+    '''
+    复制res资源
+    '''
+    # 拷贝sdk资源
+    print('copy res...')
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    sdkPath = file_utils.getFullSDKPath(sdk)
+    resPath = os.path.join(sdkPath, 'res')
+    decomplieResPath = file_utils.getFullPath(decompliePath, 'res')
+
+    for d in os.listdir(resPath):
+        copyResWithType(resPath, decomplieResPath, d)
+
+    # 拷贝assets
+    print('copy assets...')
+    assetsPath = file_utils.getFullPath(sdkPath, 'assets')
+    decomplieAssetsPath = file_utils.getFullPath(decompliePath, 'assets')
+    if os.path.exists(assetsPath):
+        ret = file_utils.copyFileAllDir(assetsPath, decomplieAssetsPath)
+        if ret:
+            return ret
+
+    # 拷贝jniLib
+    print('copy jniLibs...')
+    jniPath = file_utils.getFullPath(sdkPath, 'jniLibs')
+    decomplieJniPath = file_utils.getFullPath(decompliePath, 'lib')
+    abiFilters = []
+    if os.path.exists(decomplieJniPath):
+        for abi in os.listdir(decomplieJniPath):
+            if abi == 'armeabi-v7a' or abi == 'armeabi':
+                abiFilters.append(abi)
+    else:
+        abiFilters = ['armeabi-v7a']
+
+    if os.path.exists(jniPath):
+        ret = file_utils.copyFileAllDir(jniPath, decomplieJniPath, False, abiFilters)
+        if ret:
+            return ret
+    return 0
+
+def mergeDrawableRes(game, sdk, subChannel, config):
+    '''
+    合并Drawable-v4目录
+    '''
+    print('merge drawable path...')
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    resPath = os.path.join(decompliePath, 'res')
+
+    for path in os.listdir(resPath):
+        if path.startswith('drawable') and path.endswith('-v4'):
+            v4DrawablePath = os.path.join(resPath, path)
+            drawablePath = os.path.join(resPath, path[:-3])
+
+            if os.path.exists(drawablePath):
+                ret = file_utils.copyFileAllDir(v4DrawablePath, drawablePath, True)
+                if ret:
+                    return ret
+            else:
+                os.rename(v4DrawablePath, drawablePath)
+    return 0
+
+def removeSameRes(game, sdk, subChannel, config):
+    '''
+    移除相同的资源
+    '''
+    # 读取sdk的资源
+    print('remove same res...')
+    sdkPath = file_utils.getFullSDKPath(sdk)
+    sdkResPath = os.path.join(sdkPath, 'res')
+    resList = []
+    for path in os.listdir(sdkResPath):
+        if not path.startswith('values'):
+            continue
+
+        absPath = os.path.join(sdkResPath, path)
+        for resFile in os.listdir(absPath):
+            '''if not resFile.startswith('jm_'):
+                continue'''
+
+            resList = xml_utils.readAllRes(os.path.join(absPath, resFile), resList)
+
+    if len(resList) == 0:
+        print('no same res found')
+        return 0
+
+    # 移除相同的资源
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    resPath = os.path.join(decompliePath, 'res')
+    for path in os.listdir(resPath):
+        if not path.startswith('values'):
+            continue
+
+        absPath = os.path.join(resPath, path)
+        for resFile in os.listdir(absPath):
+            xml_utils.removeSameRes(os.path.join(absPath, resFile), resList)
+
+    return 0
+
+def mergeManifestRes(game, sdk, subChannel, config):
+    '''
+    合并主文件
+    '''
+    print('merge AndroidManifest...')
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
+    sdkPath = file_utils.getFullSDKPath(sdk)
+    libManifest = file_utils.getFullPath(sdkPath, 'manifest.xml')
+    return xml_utils.mergeManifestRes(manifest, libManifest)
+
+def copyAppRes(game, sdk, subChannel, config):
+    '''
+    拷贝app的资源,比如app icon、启动图等
+    '''
+    channelPath = file_utils.getSubChannelPath(game, sdk, subChannel)
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+
+    # assets
+    print('copy assets...')
+    assetsPath = file_utils.getFullPath(channelPath, 'assets')
+    decomplieAssetsPath = file_utils.getFullPath(decompliePath, 'assets')
+    if os.path.exists(assetsPath):
+        ret = file_utils.copyFileAllDir(assetsPath, decomplieAssetsPath)
+        if ret:
+            return ret
+
+    # icon
+    print('copy icon...')
+    ret = copyAppResWithType(decompliePath, channelPath, 'icon')
+    if ret:
+        return ret
+    # 启动图
+    print('copy splash...')
+    ret = copyAppResWithType(decompliePath, channelPath, 'splash')
+    if ret:
+        return ret
+    # 其他图片
+    print('copy image...')
+    ret = copyAppResWithType(decompliePath, channelPath, 'image')
+    if ret:
+        return ret
+    return 0
+    
+def copyAppResWithType(decompliePath, channelPath, typeName):
+    decomplieResPath = os.path.join(decompliePath, 'res')
+    iconPath = os.path.join(channelPath, typeName)
+    if not os.path.exists(iconPath):
+        print('dir "%s" not exists' % iconPath)
+        return 0
+    for d in os.listdir(iconPath):
+        ret = copyResWithType(iconPath, decomplieResPath, d)
+        if ret:
+            return ret
+
+    return 0
+
+def copyResWithType(resPath, decomplieResPath, typeName):
+    # appt的打包目录会带-v4后缀
+    resDir = os.path.join(resPath, typeName)
+    target = os.path.join(decomplieResPath, typeName)
+    targetV4 = os.path.join(decomplieResPath, typeName + '-v4')
+    if not os.path.exists(target) and not os.path.exists(targetV4):
+        os.makedirs(target)
+        return file_utils.copyFileAllDir(resDir, target, False)
+    elif not os.path.exists(target):
+        return file_utils.copyFileAllDir(resDir, targetV4, False)
+    else:
+        return file_utils.copyFileAllDir(resDir, target, False)
+
+def removeNoSupportAttr(game, sdk, subChannel, config):
+    '''
+    删除一些不支持的属性
+    '''
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
+    
+    xml_utils.removeRootAttr(manifest, 'compileSdkVersion')
+    xml_utils.removeRootAttr(manifest, 'compileSdkVersionCodename')
+    
+    return 0
+
+def fixUnSupportConfig(game, sdk, subChannel, config):
+    '''
+    删除一些不支持的配置
+    '''
+    # 检查minSdkVersion
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    yml = os.path.join(decompliePath, 'apktool.yml')
+
+    minSdkVersion = 15
+    file_utils.changeMinSdkVersion(yml, minSdkVersion)
+
+    resPath = os.path.join(decompliePath, 'res')
+
+    tag = '-v'
+    for res in os.listdir(resPath):
+        print('res = ' + res)
+        if res.startswith('values') and tag in res:
+            start = res.index(tag)
+            version = res[start+len(tag):]
+            if not version.isdigit():
+                continue
+            
+            version = int(version)
+            print('version = %d' % version)
+            if version < minSdkVersion:
+                unSopportPath = os.path.join(resPath, res)
+                print('unSopportPath = ' + unSopportPath)
+                file_utils.deleteFolder(unSopportPath)
+                print('deleteFolder = ' + unSopportPath)
+    
+    return 0
+
+def changePackageName(game, sdk, subChannel, config):
+    '''
+    更改包名
+    '''
+    # 全局替换AndroidManifest里面的包名
+    newPackageName = config['packageName']
+
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
+    packageName = xml_utils.getPackageName(manifest)
+    
+    xml_utils.changePackageName(manifest, newPackageName)
+    print('change package name %s --> %s' % (packageName, newPackageName))
+    return 0
+
+def getPackageName(game, sdk, subChannel, config):
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
+    packageName = xml_utils.getPackageName(manifest)
+    return packageName
+
+def changeAppName(game, sdk, subChannel, config):
+    '''
+    更改app名
+    '''
+    # 生成string.xml文件
+    name = config['name']
+
+    resName = 'shanshen_sdk_name'
+    if 'outName' in config:
+        resName = resName + '_' + config['outName']
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    stringFile = os.path.join(decompliePath, 'res', 'values', 'sdk_strings.xml')
+    content = '<?xml version="1.0" encoding="utf-8"?><resources><string name="%s">%s</string></resources>' % (resName, name)
+    file_utils.createFile(stringFile, content)
+
+    # 修改主文件的app名的值
+    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
+    xml_utils.changeAppName(manifest, '@string/%s' % resName)
+    print('change app name %s' % name)
+    return 0
+
+def changeAppIcon(game, sdk, subChannel, config):
+    '''
+    更改app icon
+    '''
+    print('change app icon...')
+    resName = 'shanshen_sdk_icon'
+
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
+    xml_utils.changeAppIcon(manifest, '@mipmap/%s' % resName)
+    return 0
+
+def addMetaData(game, sdk, subChannel, config):
+    '''
+    添加meta-data
+    '''
+    if 'metaData' not in config:
+        return 0
+
+    print('add meta-data...')
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
+    xml_utils.addMetaData(manifest, config['metaData'])
+    return 0
+
+def changePlaceholders(game, sdk, subChannel, config):
+    '''
+    处理掉占位符
+    '''
+    if 'placeholders' not in config:
+        return 0
+
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
+
+    placeholders = config['placeholders']
+    for placeholder in placeholders:
+        oldText = '${%s}' % placeholder
+        newText = placeholders[placeholder]
+        print('change placeholder %s -> %s' % (oldText, newText))
+        file_utils.replaceContent(manifest, oldText, newText)
+
+    return 0
+
+def addLauncher(game, sdk, subChannel, config):
+    '''
+    添加启动图
+    '''
+    channelPath = file_utils.getSubChannelPath(game, sdk, subChannel)
+    splashPath = os.path.join(channelPath, 'splash')
+    if len(os.listdir(splashPath)) == 0:
+        print('dir splash is empty')
+        return 0
+
+    print('add launcher...')
+    # 添加关联资源
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    internalPath = os.path.join(file_utils.getCurrentPath(), 'internal_shanshen')
+    ret = copyAppResWithType(decompliePath, internalPath, 'launcher_res')
+    if ret:
+        return ret
+
+    # 拷贝代码
+    print('copy launcher code...')
+    codePath = os.path.join(internalPath, 'launcher_code', 'smali')
+    smaliPath = file_utils.getFullPath(decompliePath, 'smali')
+    ret = file_utils.copyFileAllDir(codePath, smaliPath)
+    if ret:
+        return ret
+
+    # 修改主文件信息
+    print('change launcher config...')
+    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
+    orientation = xml_utils.getScreenOrientation(manifest)
+    activity = xml_utils.removeLauncherActivity(manifest)
+    xml_utils.addLauncherActivity(manifest, orientation, 'com.shanshen.sdk.template.LauncherActivity')
+
+    # 修改跳转的
+    launcherActivity = os.path.join(decompliePath, 'smali', 'com', 'shanshen', 'sdk', 'template', 'LauncherActivity.smali')
+    file_utils.replaceContent(launcherActivity, '{class}', activity)
+
+    print('change launcher %s to %s' % (activity, 'com.shanshen.sdk.template.LauncherActivity'))
+
+    # config['oldLauncher'] = activity
+
+    if 'launcherTime' in config:
+        timeHex = formatHex(config['launcherTime'])
+        file_utils.replaceContent(launcherActivity, '0x0BB8', timeHex)
+
+    return 0
+
+def addMoreIcon(game, sdk, subChannel, config):
+    '''
+    添加多个图标
+    '''
+    print('add more icon support...')
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    icon = '@mipmap/common_sdk_icon'
+    if not config['changeIcon']:
+        manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
+        icon = xml_utils.getApplicationAttr(manifest, 'icon')
+
+    switchIcon = icon
+    if config['switchIcon']:
+        switchIcon = '@mipmap/common_sdk_icon2'
+
+    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
+    return xml_utils.addMoreIcon(manifest, icon, switchIcon)
+
+def formatHex(millisecond):
+    '''
+    将毫秒转为16进制,4位格式
+    '''
+    timeHex = str(hex(millisecond)).upper()
+    timeHex = timeHex[2:]
+    formatHex = ''
+    if len(timeHex) == 3:
+        formatHex = '0x0' + timeHex
+    elif len(timeHex) == 4:
+        formatHex = '0x' + timeHex
+    else:
+        formatHex = '0x0BB8'
+    return formatHex
+
+def doSDKPostScript(game, sdk, config):
+    '''
+    执行sdk相关特殊处理脚本
+    '''
+    sdkPath = file_utils.getFullSDKPath(sdk)
+    scriptPath = os.path.join(sdkPath, 'script')
+    targetScript = os.path.join(scriptPath, 'sdk_script.py')
+    if not os.path.exists(targetScript):
+        print('sdk_script no exists')
+        return 0
+
+    print('doSDKPostScript...')
+    sys.path.append(scriptPath)
+
+    module = importlib.import_module('sdk_script')
+    ret = module.execute(game, sdk, config)
+
+    sys.path.remove(scriptPath)
+
+    return ret
+
+def doGamePostScript(game, sdk, config):
+    '''
+    执行游戏相关特殊处理脚本
+    '''
+    channelPath = file_utils.getFullGamePath(game)
+    scriptPath = os.path.join(channelPath, 'script')
+    targetScript = os.path.join(scriptPath, 'game_script.py')
+    if not os.path.exists(targetScript):
+        print('game_script no exists')
+        return 0
+
+    print('doGamePostScript...')
+    sys.path.append(scriptPath)
+
+    module = importlib.import_module('game_script')
+    ret = module.execute(game, sdk, config)
+
+    sys.path.remove(scriptPath)
+
+    return ret
+
+def generateNewRFile(game, sdk, subChannel, config):
+    '''
+    生成新的R文件
+    '''
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    aapt = file_utils.getAAPTPath()
+    androidPlatforms = file_utils.getAndroidCompileToolPath()
+    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
+    decomplieResPath = file_utils.getFullPath(decompliePath, 'res')
+    compliePath = file_utils.getFullPath(decompliePath, 'gen')
+
+    if not os.path.exists(compliePath):
+        os.makedirs(compliePath)
+
+    ret = file_utils.getExecPermission(aapt)
+    if ret:
+        return ret
+
+    # 生成R文件
+    print('create R.java ...')
+    createRCmd = '"%s" p -f -m -J "%s" -S "%s" -I "%s" -M "%s"' % (aapt, compliePath, decomplieResPath, androidPlatforms, manifest)
+    ret = file_utils.execFormatCmd(createRCmd)
+    if ret:
+        return ret
+
+    # 编译R文件
+    print('complie R.java ...')
+    packageName = xml_utils.getPackageName(manifest)
+    packagePath = file_utils.getPackagePath(compliePath, packageName)
+    RSourceFile = os.path.join(packagePath, 'R.java')
+    complieRCmd = 'javac -source 1.7 -target 1.7 -encoding UTF-8 "%s"' % RSourceFile
+    ret = file_utils.execFormatCmd(complieRCmd)
+    if ret:
+        return ret
+
+    # 生成dex
+    print('dex R.class ...')
+    dx = file_utils.getDxPath()
+    outDex = os.path.join(compliePath, 'classes.dex')
+    ret = file_utils.execJarCmd(dx, '--dex --output="%s" "%s"' % (outDex, compliePath))
+    if ret:
+        return ret
+
+    # 反向dex生成smali
+    # 存放在out目录
+    print('baksmali classes.dex ...')
+    baksmaliPath = file_utils.getBaksmaliPath()
+    outPath = file_utils.getFullPath(decompliePath, 'out')
+    ret = file_utils.execJarCmd(baksmaliPath, '-o "%s" "%s"' % (outPath, outDex))
+    if ret:
+        return ret
+
+    # 将生成的文件拷贝到目标目录
+    print('copy R.smali ...')
+    smaliPath = file_utils.getFullPath(decompliePath, 'smali')
+    file_utils.copyFileAllDir(outPath, smaliPath)
+    return 0
+
+def packJar(game, sdk, subChannel, config):
+    '''
+    打包所有的jar
+    '''
+    splitDex = config['splitDex']
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    outPath = file_utils.getFullPath(decompliePath, 'gen')
+    dx = file_utils.getDxPath()
+
+    if not os.path.exists(outPath):
+        os.makedirs(outPath)
+
+    # --no-warning
+    dexCmd = '--dex --multi-dex --no-warning --output="%s"' % outPath
+
+    # 找到所有lib依赖
+    sdkPath = file_utils.getFullSDKPath(sdk)
+    libs = os.path.join(sdkPath, 'libs')
+    libConfig = os.path.join(libs, 'config.json')
+
+    # 存在配置文件
+    if os.path.exists(libConfig):
+        jsonText = file_utils.readFile(libConfig)
+        libConf = json.loads(jsonText)
+        if 'libConfig' in config and config['libConfig'] in libConf:
+            conf = config['libConfig']
+            libList = libConf[conf]
+            for jar in libList:
+                dexCmd += ' ' + os.path.join(libs, jar)
+        else:
+            for jar in os.listdir(libs):
+                if not jar.endswith('.jar'):
+                    continue
+                dexCmd += ' ' + os.path.join(libs, jar)
+    else:
+        for jar in os.listdir(libs):
+            if not jar.endswith('.jar'):
+                continue
+            dexCmd += ' ' + os.path.join(libs, jar)
+
+    # multidex.jar
+    if splitDex:
+        dexCmd += ' ' + file_utils.getMultiDexPath()
+
+    # sdk实现类
+    print('packageing all jar ...')
+    ret = file_utils.execJarCmd(dx, dexCmd)
+    if ret:
+        return ret
+
+    # 反向dex生成smali
+    # 存放在out目录
+    print('baksmali classes.dex ...')
+    outDex = os.path.join(outPath, 'classes.dex')
+    baksmaliPath = file_utils.getBaksmaliPath()
+    outPath = file_utils.getFullPath(decompliePath, 'out')
+    
+    ret = file_utils.execJarCmd(baksmaliPath, '-o "%s" "%s"' % (outPath, outDex))
+    if ret:
+        return ret
+
+    # 将生成的文件拷贝到目标目录
+    print('copy all smali ...')
+    smaliPath = file_utils.getFullPath(decompliePath, 'smali')
+    ret = file_utils.copyFileAllDir(outPath, smaliPath, True)
+    if ret:
+        return ret
+
+    return 0
+
+def splitDex(game, sdk, subChannel, config):
+    '''
+    分割dex
+    '''
+    # 判断是否已经存在application
+    # 存在,则往原application添加内容
+    # 不存在,则拷贝一个默认的android.support.multidex.MultiDexApplication
+    print('add MultiDex support...')
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    manifest = os.path.join(decompliePath, 'AndroidManifest.xml')
+    application = xml_utils.getApplicationAttr(manifest, 'name')
+    if application is None:
+        ret = xml_utils.changeApplicationAttr(manifest, 'name', 'android.support.multidex.MultiDexApplication')
+        if ret:
+            return ret
+    else:
+        smaliPath = os.path.join(decompliePath, 'smali')
+        applicationFile = file_utils.getPackagePath(smaliPath, application)
+        applicationFile += '.smali'
+        ret = changeApplicationDex(applicationFile)
+        if ret:
+            return ret
+
+    return splitSmali(game, sdk, subChannel, config, application)
+
+def changeApplicationDex(file):
+    '''
+    修改application的smali文件,增加MultiDex操作
+    '''
+    index = file_utils.getApplicationSmaliIndex(file)
+    file_utils.insertApplicationSmali(file, index)
+    return 0
+
+def splitSmali(game, sdk, subChannel, config, application):
+    '''
+    如果函数上限超过限制,自动拆分smali,以便生成多个dex文件
+    '''
+    print('splitSmali...')
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    smaliPath = os.path.join(decompliePath, 'smali')
+
+    appPackage = None
+    if application:
+        appPackage = application[:application.rfind('.')]
+        appPackage = appPackage.replace('.', '/')
+
+    allFiles = []
+    allFiles = file_utils.list_files(smaliPath, allFiles)
+    
+    #print('file count is %d' % len(allFiles))
+
+    #maxFuncNum = 65535
+    # 留一点空间,防止计算误差
+    maxFuncNum = 64000
+    currFucNum = 0
+    totalFucNum = 0
+
+    currDexIndex = 1
+
+    allRefs = []
+
+    #保证Application等类在第一个classex.dex文件中
+    for f in allFiles:
+        f = f.replace('\\', '/')
+        if (appPackage and appPackage in f) or '/android/support/multidex' in f:
+            currFucNum += smali_utils.get_smali_method_count(f, allRefs)
+
+    totalFucNum = currFucNum
+    for f in allFiles:
+        f = f.replace('\\', '/')
+        if not f.endswith('.smali'):
+            continue
+
+        if (appPackage and appPackage in f) or '/android/support/multidex' in f:
+            continue
+
+        thisFucNum = smali_utils.get_smali_method_count(f, allRefs)
+        totalFucNum += thisFucNum
+
+        #print('%d # %d ==> %s' % (thisFucNum, currDexIndex, f))
+        #print('totalFucNum is %d' % totalFucNum)
+        if currFucNum + thisFucNum >= maxFuncNum:
+            currFucNum = thisFucNum
+            currDexIndex += 1
+            newDexPath = os.path.join(decompliePath, 'smali_classes%d' % currDexIndex)
+            os.makedirs(newDexPath)
+        else:
+            currFucNum += thisFucNum
+
+        if currDexIndex > 1:
+            newDexPath = os.path.join(decompliePath, 'smali_classes%d' % currDexIndex)
+            targetFile = newDexPath + f[len(smaliPath):]
+            file_utils.copyFile(f, targetFile, True)
+    return 0
+
+def changeVersion(game, sdk, subChannel, config):
+    '''
+    更改版本号
+    '''
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    yml = os.path.join(decompliePath, 'apktool.yml')
+
+    versionCode = None
+    versionName = None
+    targetSdkVersion = None
+    if 'versionCode' in config:
+        versionCode = config['versionCode']
+    if 'versionName' in config:
+        versionName = config['versionName']
+    if 'targetSdkVersion' in config:
+        targetSdkVersion = config['targetSdkVersion']
+
+    return file_utils.changeVersion(yml, versionCode, versionName, targetSdkVersion)
+
+def recomplie(game, sdk, subChannel, config):
+    '''
+    回编译
+    '''
+    print('recomplie apk...')
+    apktoolPath = file_utils.getApkToolPath()
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    outApk = file_utils.getOutApkPath(game, sdk, subChannel, config['cache'])
+
+    return file_utils.execJarCmd(apktoolPath, 'b -f "%s" -o "%s"' % (decompliePath, outApk))
+
+def alignApk(game, sdk, subChannel, config):
+    '''
+    对齐apk
+    '''
+    print('align apk...')
+    outApk = file_utils.getOutApkPath(game, sdk, subChannel, config['cache'])
+    alignApk = file_utils.getAlignApkPath(game, sdk, subChannel, config['cache'])
+    alignapkTool = file_utils.getAlignPath()
+    if os.path.exists(alignApk):
+        os.remove(alignApk)
+
+    ret = file_utils.getExecPermission(alignapkTool)
+    if ret:
+        return ret
+
+    # zipalign.exe -v -p 4 input.apk output.apk
+    return file_utils.execFormatCmd('"%s" -f -p 4 "%s" "%s"' % (alignapkTool, outApk, alignApk))
+
+def apksignerApk(game, sdk, subChannel, config):
+    '''
+    签名apk
+    '''
+    print('sign apk...')
+    path = os.path.join(file_utils.getCurrentPath(), 'keystore', 'key.json')
+    jsonText = file_utils.readFile(path)
+    signConfig = json.loads(jsonText)
+    keystore = {}
+    if game in signConfig:
+        if sdk in signConfig[game] and subChannel in signConfig[game][sdk]:
+            keystore = signConfig[game][sdk][subChannel]
+        else:
+            keystore = signConfig['default']
+    else:
+        keystore = signConfig['default']
+
+    print('storeFile is "%s"' % keystore['storeFile'])
+    
+    apksigner = file_utils.getApksignerPath()
+    alignApk = file_utils.getAlignApkPath(game, sdk, subChannel, config['cache'])
+    signedApk = file_utils.getSignApkPath(game, sdk, subChannel, config['cache'])
+    storeFile = os.path.join(file_utils.getCurrentPath(), 'keystore', keystore['storeFile'])
+
+    if 'outName' in config and 'outPath' in config:
+        if not os.path.exists(config['outPath']):
+            os.makedirs(config['outPath'])
+
+        signedApk = os.path.join(config['outPath'], config['outName'] + '.apk')
+    elif 'outName' in config:
+        signedApk = file_utils.getRenameApkPath(game, sdk, config['cache'], config['outName'])
+    
+    # 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
+    v2disable = ''
+    if 'v2disable' in config and config['v2disable']:
+        v2disable = ' --v2-signing-enabled=false'
+        
+    return file_utils.execJarCmd(apksigner, 'sign%s --ks "%s" --ks-key-alias %s --ks-pass pass:%s --key-pass pass:%s --out "%s" "%s"' % (v2disable, storeFile, keystore['keyAlias'], keystore['storePassword'], keystore['keyPassword'], signedApk, alignApk))
+
+def addChannel(game, sdk, subChannel, config):
+    '''
+    添加渠道信息
+    '''
+    if 'v2disable' in config and config['v2disable']:
+        return 0
+    
+    walle = file_utils.getWallePath()
+    signedApk = file_utils.getSignApkPath(game, sdk, subChannel, config['cache'])
+    if 'outName' in config and 'outPath' in config:
+        signedApk = os.path.join(config['outPath'], config['outName'] + '.apk')
+    elif 'outName' in config:
+        signedApk = file_utils.getRenameApkPath(game, sdk, config['cache'], config['outName'])
+
+    properties = config['properties']
+
+    appid = ''
+    appkey = ''
+    if 'appid' in properties:
+        appid = properties['appid']
+    if 'appkey' in properties:
+        appkey = properties['appkey']
+
+    return file_utils.execJarCmd(walle, 'put -e version=%s,agent=%s,appid=%s,appkey=%s "%s" "%s"' % (config_utils_record.getDate(), properties['agent'], appid, appkey, signedApk, signedApk))
+
+def clearTemp(game, sdk, subChannel, config):
+    '''
+    清空中间产生的文件
+    '''
+    print('clear temp...')
+    alignApk = file_utils.getAlignApkPath(game, sdk, subChannel, config['cache'])
+    outApk = file_utils.getOutApkPath(game, sdk, subChannel, config['cache'])
+    if os.path.exists(alignApk):
+        os.remove(alignApk)
+    if os.path.exists(outApk):
+        os.remove(outApk)
+
+    decompliePath = file_utils.getDecompliePath(game, sdk, subChannel, config['cache'])
+    file_utils.deleteFolder(decompliePath)
+
+    print('clear temp end')
+
+def packConsoleInput():
+    '''
+    控制台打包
+    '''
+    if len(sys.argv) < 3:
+        print('argument is missing')
+        return 1
+
+    # 校验参数
+    game = sys.argv[1]
+    sdk = sys.argv[2]
+    # 可选参数,没有则默认打全部渠道
+    subChannel = None
+    if len(sys.argv) > 3:
+        subChannel = sys.argv[3]
+        
+    return packConsole(game, sdk, subChannel)
+
+def packConsole(game, sdk, subChannel):
+    '''
+    控制台打包
+    '''
+    if not os.path.exists(file_utils.getFullGameApk(game)):
+        print('game "%s" not exists' % game)
+        return 1
+    if not os.path.exists(file_utils.getFullSDKPath(sdk)):
+        print('sdk "%s" not exists' % sdk)
+        return 1
+
+    # 读取配置
+    channelPath = file_utils.getChannelPath(game, sdk)
+    configPath = os.path.join(channelPath, 'config.json')
+    if not os.path.exists(configPath):
+        print('%s not exists' % configPath)
+        return 1
+    
+    jsonText = file_utils.readFile(configPath)
+    config = json.loads(jsonText)
+
+    # 检查参数
+    if not config_utils_record.checkConfig(config):
+        return 1
+
+    # 处理参数
+    config_utils_record.replaceArgs(config)
+
+    successCount = 0
+    failureCount = 0
+    if type(config) == dict:
+        if subChannel is None or config['subChannel'] == subChannel:
+            ret = pack(game, sdk, config)
+            if ret:
+                failureCount += 1
+            else:
+                successCount += 1
+        else:
+            print('subChannel "%s" no found' % subChannel)
+            return 1
+    elif type(config) == list:
+        found = False
+        for itemConfig in config:
+            if subChannel is None or itemConfig['subChannel'] == subChannel:
+                found = True
+                ret = pack(game, sdk, itemConfig)
+                if ret:
+                    failureCount += 1
+                else:
+                    successCount += 1
+
+        if not found:
+            print('subChannel "%s" no found' % subChannel)
+            return 1
+
+    print('success %d, failure %d' % (successCount, failureCount))
+        
+    return 0

+ 5 - 1
package_web.py

@@ -1,5 +1,6 @@
 import file_utils
 import package_utils
+import package_web_record
 import package_web_shanshen
 import os.path
 import sys
@@ -33,6 +34,7 @@ scriptMapping = {
 }
 
 newSdk = ["shanshen", "gzjysdk"]
+recordSdk = ["record"]
 
 def packageWeb():
     if len(sys.argv) < 2:
@@ -50,7 +52,9 @@ def packageWeb():
     print('************************************')
 
     sdk = getMappingSdk(config)
-    if sdk in newSdk:
+    if sdk in recordSdk:
+        package_web_record.package(config, sdk)
+    elif sdk in newSdk:
         package_web_shanshen.package(config, sdk)
     else:
         package(config, sdk)

+ 214 - 0
package_web_record.py

@@ -0,0 +1,214 @@
+import file_utils
+import package_utils_record
+import os.path
+import sys
+import json
+import importlib
+
+def packageWeb():
+    if len(sys.argv) < 2:
+        print('argument is missing')
+        return 1
+
+    # 打包配置的路径
+    packageConfig = sys.argv[1]
+
+    jsonText = file_utils.readFile(packageConfig)
+    config = json.loads(jsonText)
+
+    print('*************config*****************')
+    print(jsonText)
+    print('************************************')
+
+    package(config, config['sdk'])
+
+def package(config, sdk):
+    print('use script 3rd')
+    jsonConfig = {}
+    game = config['app']
+    subChannel = None
+
+    if 'subChannel' in config:
+        subChannel = config['subChannel']
+        jsonConfig['subChannel'] = config['subChannel']
+
+    # 必须参数
+    jsonConfig['packageName'] = config['packageName']
+    jsonConfig['name'] = config['name']
+
+    # 可选参数
+    if 'outName' in config:
+        jsonConfig['outName'] = config['outName']
+
+    if 'outPath' in config:
+        jsonConfig['outPath'] = config['outPath']
+
+    if 'changeIcon' in config:
+        jsonConfig['changeIcon'] = toBoolean(config['changeIcon'])
+
+    if 'addLauncher' in config:
+        jsonConfig['addLauncher'] = toBoolean(config['addLauncher'])
+
+    if 'versionCode' in config:
+        jsonConfig['versionCode'] = config['versionCode']
+
+    if 'versionName' in config:
+        jsonConfig['versionName'] = config['versionName']
+
+    if 'targetSdkVersion' in config:
+        jsonConfig['targetSdkVersion'] = config['targetSdkVersion']
+        
+    if 'v2disable' in config:
+        jsonConfig['v2disable'] = toBoolean(config['v2disable'])
+
+    # sdk相关参数
+    if 'properties' in config:
+        jsonConfig['properties'] = config['properties']
+
+    # 获取sdk相关配置
+    getSdkConfig(sdk, jsonConfig, config)
+
+    # 生成配置文件
+    createOrUpdateConfigFile(game, sdk, jsonConfig)
+
+    # 拷贝资源
+    copyRes(game, sdk, subChannel, config)
+
+    # 打包
+    package_utils_record.packConsole(game, sdk, subChannel)
+
+def toBoolean(booleanStr):
+    if type(booleanStr) == bool:
+        return booleanStr
+
+    if booleanStr == 'true':
+        return True
+    return False
+
+def getSdkConfig(sdk, jsonConfig, config):
+    scriptPath = os.path.join(file_utils.getCurrentPath(), 'sdk_script')
+    sdkScript = getScriptMapping(sdk)
+    targetScript = os.path.join(scriptPath, '%s.py' % sdkScript)
+    if not os.path.exists(targetScript):
+        print('%s no exists' % targetScript)
+        return 0
+
+    sys.path.append(scriptPath)
+    module = importlib.import_module(sdkScript)# 动态导入相应模块
+    module.getSdkConfig(jsonConfig, config)# 执行脚本功能
+    sys.path.remove(scriptPath)
+
+def createOrUpdateConfigFile(game, sdk, jsonConfig):
+    '''
+    更新配置文件
+    '''
+    if 'subChannel' not in jsonConfig:
+        return 0
+
+    print('createOrUpdateConfigFile ...')
+
+    channelPath = file_utils.getChannelPath(game, sdk)
+    configPath = os.path.join(channelPath, 'config.json')
+    #configPath = os.path.join(file_utils.getCurrentPath(), 'test', 'test.json')
+
+    if os.path.exists(configPath):
+        # 更新数据
+        jsonText = file_utils.readFile(configPath)
+        config = json.loads(jsonText)
+
+        count = 0
+        if type(config) == list:
+            for item in config:
+                if item['subChannel'] == jsonConfig['subChannel']:
+                    print('find same config ...')
+                    del config[count]
+                    break
+
+                count += 1
+
+            config.append(jsonConfig)
+
+            createConfigFile(config, configPath)
+        elif type(config) == dict:
+            if config['subChannel'] == jsonConfig['subChannel']:
+                print('find same config ...')
+                createConfigFile(jsonConfig, configPath)
+            else:
+                print('add a new config ...')
+                config = [config, jsonConfig]
+
+                createConfigFile(config, configPath)
+    else:
+        print('create a new config ...')
+        createConfigFile([jsonConfig], configPath)
+
+def createConfigFile(jsonConfig, configPath):
+    '''
+    创建配置文件
+    '''
+    jsonStr = json.dumps(jsonConfig, ensure_ascii=False)
+    print('*************out config*************')
+    print(jsonStr)
+    print('************************************')
+    file_utils.createFile(configPath, jsonStr)
+
+def copyRes(game, sdk, subChannel, config):
+    '''
+    拷贝资源
+    '''
+    if subChannel is None:
+        return 0
+
+    channelPath = file_utils.getChannelPath(game, sdk)
+    subChannelPath = os.path.join(channelPath, subChannel)
+    if 'icon' in config and os.path.exists(config['icon']):
+        mipmapSupport = ['mipmap-xhdpi', 'mipmap-xxhdpi', 'mipmap-xxxhdpi']
+        for mipmap in mipmapSupport:
+            iconPath = os.path.join(subChannelPath, 'icon', mipmap, 'record_sdk_icon.png')
+            file_utils.copyFile(config['icon'], iconPath)
+
+    if 'splash' in config and os.path.exists(config['splash']):
+        splashPath = os.path.join(subChannelPath, 'splash', 'drawable-hdpi', 'record_sdk_launcher_bg.jpg')
+        file_utils.copyFile(config['splash'], splashPath)
+
+    if 'bgMusic' in config and os.path.exists(config['bgMusic']):
+        musicPath = os.path.join(subChannelPath, 'assets', 'bg_music.mp3')
+        file_utils.copyFile(config['bgMusic'], musicPath)
+
+    if 'copyList' in config:
+        for item in config['copyList']:
+            if item['toFile'] == '':
+                continue
+
+            if not os.path.exists(item['fromFile']):
+                continue
+
+            toFile = item['toFile']
+            if toFile[:3] == 'res':
+                toFile = 'image' + toFile[3:]
+                
+            resPath = getPackagePath(subChannelPath, toFile)
+            file_utils.copyFile(item['fromFile'], resPath)
+
+    if 'package' in config and os.path.exists(config['package']):
+        newGameApk = config['package']
+        gameApk = file_utils.getFullGameApk(game)
+        file_utils.copyFile(newGameApk, gameApk)
+
+def getPackagePath(basePath, packageName):
+    '''
+    包名对应的目录
+    '''
+    packageNameSplit = packageName.replace('\\', '/').split('/')
+    newPath = basePath
+    for item in packageNameSplit:
+        newPath = os.path.join(newPath, item)
+    return newPath
+
+def getMappingSdk(config):
+    return config['sdk']
+
+def getScriptMapping(sdk):
+    return sdk
+
+#packageWeb()

BIN
sdk/record/jniLibs/armeabi-v7a/libffmpeg-core.so


BIN
sdk/record/jniLibs/armeabi-v7a/libffmpeg-invoke.so


BIN
sdk/record/jniLibs/armeabi/libffmpeg-core.so


BIN
sdk/record/jniLibs/armeabi/libffmpeg-invoke.so


BIN
sdk/record/libs/android-support-v4.jar


BIN
sdk/record/libs/record_sdk.jar


BIN
sdk/record/libs/rxffmpeg-1.2.0.jar


BIN
sdk/record/libs/screenrecorder-2.4.jar


+ 14 - 0
sdk/record/manifest.xml

@@ -0,0 +1,14 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+	<permissions>
+		<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+		<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+	</permissions>
+
+    <application>
+        <activity
+            android:name="com.jmhy.lib.record.ui.PermissionActivity"
+            android:configChanges="orientation|keyboardHidden|screenSize"
+            android:screenOrientation="behind"
+            android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen" />
+    </application>
+</manifest>

BIN
sdk/record/res/drawable-hdpi/jm_record_back.png


BIN
sdk/record/res/drawable-hdpi/jm_record_float.png


BIN
sdk/record/res/drawable-xxhdpi/jm_record_hidden.png


BIN
sdk/record/res/drawable-xxhdpi/jm_record_record_delete.png


BIN
sdk/record/res/drawable-xxhdpi/jm_record_record_pause.png


BIN
sdk/record/res/drawable-xxhdpi/jm_record_record_share.png


BIN
sdk/record/res/drawable-xxhdpi/jm_record_record_start.png


BIN
sdk/record/res/drawable-xxhdpi/jm_record_record_stop.png


BIN
sdk/record/res/drawable-xxhdpi/jm_record_video_record.png


BIN
sdk/record/res/drawable-xxhdpi/jm_record_video_record_start.png


+ 6 - 0
sdk/record/res/drawable/jm_record_float_bg.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <stroke android:width="1dp" android:color="@color/jm_record_blue" />
+    <solid android:color="@android:color/white" />
+    <corners android:radius="25dp"/>
+</shape>

+ 6 - 0
sdk/record/res/drawable/jm_record_video_record_bg.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <solid android:color="@color/jm_record_loading_bg"/>
+    <corners android:bottomRightRadius="@dimen/jm_record_video_record_radius"
+        android:topRightRadius="@dimen/jm_record_video_record_radius"/>
+</shape>

+ 6 - 0
sdk/record/res/drawable/jm_record_video_record_bg2.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
+    <solid android:color="@color/jm_record_record_state_bg"/>
+    <corners android:bottomLeftRadius="@dimen/jm_record_video_record_radius"
+        android:topLeftRadius="@dimen/jm_record_video_record_radius"/>
+</shape>

+ 53 - 0
sdk/record/res/layout/jm_record_float_view.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="56dp">
+
+    <LinearLayout
+        android:id="@+id/ll_menu"
+        android:layout_width="wrap_content"
+        android:layout_height="56dp"
+        android:paddingLeft="61dp"
+        android:background="@drawable/jm_record_float_bg"
+        android:gravity="center"
+        android:orientation="horizontal"
+        android:paddingRight="12dp"
+        android:visibility="gone"
+        tools:visibility="visible">
+
+        <LinearLayout
+            android:id="@+id/tv_record"
+            android:layout_width="wrap_content"
+            android:layout_height="45dp"
+            android:gravity="center"
+            android:orientation="vertical">
+
+            <ImageView
+                android:layout_width="@dimen/jm_record_float_icon_size"
+                android:layout_height="@dimen/jm_record_float_icon_size"
+                android:src="@drawable/jm_record_video_record_start" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/jm_record_record"
+                android:textColor="@color/jm_record_blue"
+                android:textSize="12sp" />
+        </LinearLayout>
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/float_view"
+        android:layout_width="56dp"
+        android:layout_height="56dp">
+        <ImageView
+            android:id="@+id/float_view_icon_imageView"
+            android:layout_width="56dp"
+            android:layout_height="56dp"
+            android:layout_gravity="start|center_vertical"
+            android:scaleType="fitStart"
+            android:src="@drawable/jm_record_float" />
+    </FrameLayout>
+
+</FrameLayout>

+ 127 - 0
sdk/record/res/layout/jm_record_fragment_record.xml

@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <LinearLayout
+        android:id="@+id/record_layout"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:gravity="center_vertical"
+        android:orientation="horizontal">
+        <LinearLayout
+            android:id="@+id/record"
+            android:layout_width="93dp"
+            android:layout_height="60dp"
+            android:orientation="vertical"
+            android:gravity="center"
+            android:background="@drawable/jm_record_video_record_bg">
+            <ImageView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:src="@drawable/jm_record_video_record"/>
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="5dp"
+                android:textColor="@color/jm_record_white"
+                android:textSize="@dimen/jm_record_text_desc"
+                android:text="@string/jm_record_start_record"/>
+        </LinearLayout>
+
+        <ImageView
+            android:id="@+id/hidden"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="16dp"
+            android:src="@drawable/jm_record_hidden"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/record_state"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="80dp"
+        android:layout_gravity="end"
+        android:gravity="center_vertical"
+        android:orientation="horizontal"
+        android:visibility="gone"
+        tools:visibility="visible">
+        <ImageView
+            android:id="@+id/video_record_delete"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginRight="8dp"
+            android:src="@drawable/jm_record_record_delete"/>
+
+        <LinearLayout
+            android:id="@+id/video_record_layout"
+            android:layout_width="96dp"
+            android:layout_height="33dp"
+            android:orientation="horizontal"
+            android:gravity="center_vertical"
+            android:background="@drawable/jm_record_video_record_bg2">
+
+            <TextView
+                android:id="@+id/video_record_state"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="4dp"
+                tools:drawableLeft="@drawable/jm_record_record_pause"
+                android:drawablePadding="8dp"
+                android:layout_marginLeft="8dp"
+                android:gravity="center_vertical"
+                android:textColor="@color/jm_record_record_state"
+                android:textSize="@dimen/jm_record_text_desc"
+                tools:text="@string/jm_record_record_state_start"/>
+
+            <TextView
+                android:id="@+id/video_record_time"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textColor="@color/jm_record_record_state"
+                android:textSize="@dimen/jm_record_text_desc"
+                tools:text="10s"/>
+        </LinearLayout>
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/next"
+        android:layout_width="96dp"
+        android:layout_height="33dp"
+        android:layout_marginTop="40dp"
+        android:layout_gravity="end"
+        android:orientation="horizontal"
+        android:gravity="center_vertical"
+        android:visibility="gone"
+        android:background="@drawable/jm_record_video_record_bg2">
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginRight="4dp"
+            android:drawableLeft="@drawable/jm_record_record_share"
+            android:drawablePadding="8dp"
+            android:layout_marginLeft="8dp"
+            android:gravity="center_vertical"
+            android:textColor="@color/jm_record_record_state"
+            android:textSize="@dimen/jm_record_text_desc"
+            android:text="@string/jm_record_record_state_share"/>
+    </LinearLayout>
+
+    <FrameLayout
+        android:id="@+id/loading_progress"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone"
+        tools:visibility="visible">
+        <ProgressBar
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            style="?android:attr/progressBarStyleInverse"
+            android:layout_gravity="center"/>
+    </FrameLayout>
+</FrameLayout>

+ 8 - 0
sdk/record/res/values/jm_record_colors.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="jm_record_blue">#2abfff</color>
+    <color name="jm_record_loading_bg">#8A000000</color>
+    <color name="jm_record_white">#FFFFFF</color>
+    <color name="jm_record_record_state">#80FFFFFF</color>
+    <color name="jm_record_record_state_bg">#7F000000</color>
+</resources>

+ 13 - 0
sdk/record/res/values/jm_record_dimens.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <dimen name="jm_record_float_icon_size">24dp</dimen>
+    <dimen name="jm_record_view_icon">30dp</dimen>
+    <dimen name="jm_record_default_padding_small">10dp</dimen>
+    <dimen name="jm_record_back_icon">30dp</dimen>
+    <dimen name="jm_record_record_icon">40dp</dimen>
+    <dimen name="jm_record_record_icon_offset">40dp</dimen>
+    <dimen name="jm_record_record_ok_offset">100dp</dimen>
+    <dimen name="jm_record_record_delete_offset">60dp</dimen>
+    <dimen name="jm_record_text_desc">12sp</dimen>
+    <dimen name="jm_record_video_record_radius">30dp</dimen>
+</resources>

+ 17 - 0
sdk/record/res/values/jm_record_strings.xml

@@ -0,0 +1,17 @@
+<resources>
+    <string name="jm_record_permission_tip_float">需要授权显示在其他应用上层才能正常使用</string>
+    <string name="jm_record_permission_tip_record">请开启读写手机存储权限,以保证程序能够获取正确的数据</string>
+    <string name="jm_record_android_version">您的手机版本太低,暂不支持该功能</string>
+    <string name="jm_record_hint_delete_video_record">是否删除录制的视频</string>
+    <string name="jm_record_hint_video_record_success">视频录制完成</string>
+
+    <string name="jm_record_share">分享到抖游</string>
+    <string name="jm_record_record">录制视频</string>
+    <string name="jm_record_confirm">确定</string>
+    <string name="jm_record_cancel">取消</string>
+    <string name="jm_record_start_record">开启录屏</string>
+    <string name="jm_record_record_state_start">续录</string>
+    <string name="jm_record_record_state_pause">暂停</string>
+    <string name="jm_record_record_state_stop">完成</string>
+    <string name="jm_record_record_state_share">分享</string>
+</resources>

+ 2 - 0
sdk/record/res/values/jm_record_styles.xml

@@ -0,0 +1,2 @@
+<resources>
+</resources>