1. 程式人生 > >Python 實現Android打包並安裝後啟動

Python 實現Android打包並安裝後啟動

前言:

這個週末,學會了Python並寫了一個打包的指令碼,可以很方便的打包並安裝。因為剛開始使用Python,不知道Python怎麼面向物件,面向過程的寫,一個流程走下來,之後深入學習後可以寫的方便修改一點。

本文程式碼在: LearnTechDemo下00Something目錄下的auto.pyauto.config兩個檔案。

1. 主要功能介紹:

  1. 自動從Git拉取程式碼 git pullgit clone
    此過程中如果第一次執行指令碼,則需要向根目錄寫local.properties檔案,設定SDK路徑。
  2. 執行打包命令gradle cleangradle assembleDebug
  3. 自動安裝到機器adb installadb shell am start -n 自動開啟

該指令碼主要功能就是這三個,看完後就有個大概認識,只要一執行,就能自動將最新apk執行到手機上並開啟。

還有些其他很方便的功能:

  1. 在指定目錄下建立AndroidApp,並在該目錄下建立ApkSourceCode兩目錄。
    Apk目錄:打包後build目錄下的apk檔案拷貝一份該目錄,方便再次找到。
    SourceCode目錄:git clone的專案程式碼到該目錄下

  2. 新增auto.config配置檔案,用來配置一些基本的路徑。使用Python的ConfigParser包來讀取配置檔案。

    • 現在主要配置:
      Root_SDK_Dir: SDK的絕對路徑,用於local.properties
      git_clone_address:專案的Git地址
      git_branch_name:要拉取的遠端分支名
      assembleRelease:boolean值,是否打release包
      base_file_dir:是在哪個目錄下建立AndroidApp資料夾
      create_dir_name:就是這個AndroidApp資料夾的名字,可以設定這個更改
    • 這兩個可以不設定optional:
      create_code_dir_name:這個SourceCode資料夾的名字
      create_apk_dir_name
      :這個Apk資料夾的名字
  3. 解析manifest.xml檔案獲取PackageNameLauncherActivityName
    使用ElementTree來解析xml,使用遞迴獲取到LauncherActivity的名字用於開啟app。

  4. 相容了python2 和 python3。
    Mac上執行的是python2,在windows上執行時又適配到python3了。

2. 使用方法

  1. auto.pyauto.config 檔案放在同一級目錄下。

  2. 然後配置auto.config檔案:
    Root_SDK_Dir 是你的SDK的絕對路徑
    git_clone_address 是你的Git地址
    git_branch_name 要拉取的遠端分支名
    assembleRelease boolean值,是否打release包
    base_file_dir 是一個絕對路徑,可隨便填
    create_dir_name 將在base_file_dir生成一個目錄,用於存放專案程式碼和打出來的APK檔案

配置好後你就可以使用python命令列執行該指令碼,等待打包完成後執行到機器。

3. 主要程式碼介紹

1.git拉取程式碼

# 進入SourceCode目錄下
os.chdir(code_dir)
print('進入SourceCode下: ' + os.getcwd())

print('\n')
print('=============================================')
print('git clone or git pull')
print('=============================================')

if not os.listdir(code_dir):
    #空資料夾
    os.system('git clone ' + git_clone_address +' ' + code_dir)
else:
    #已經clone過
    os.system('git pull')

2.gradle打包

# 生成local.properties檔案
def createLocalPropertiesFile(sourceDir,fileName,root_sdk_dir):
    if not os.path.exists(sourceDir):
        return

    fileDir = sourceDir + '/' + fileName

    if os.path.exists(fileDir):
        return

    f = open(fileDir,'w');
    f.write('sdk.dir=' + root_sdk_dir)
    f.close()

createLocalPropertiesFile(code_dir,'local.properties',Root_SDK_Dir)

# 打包
print('\n')
print('=============================================')
print('gradle clean')
print('=============================================')
os.system('gradle clean');

print('\n')
print('=============================================')
print('gradle assembleDebug, generate apk')
print('=============================================')
os.system('gradle assembleDebug')

3.安裝apk

#安裝apk
print('\n')
print('=============================================')
print('install apk')
print('=============================================')

def getFileName(sourceDir):
    if not os.path.exists(sourceDir):
        return;

    for filename in os.listdir(sourceDir):
        if '.apk' in filename:
            return filename



apkName = getFileName(apk_dir)
apkPath = apk_dir + '/' + apkName
print(apkPath)

os.system('adb install -r ' + apkPath)

4.開啟app

#獲取PackageName,launcherActivity
def findLauncherActivityName(ele,targetString):
    for childElem in ele:
        if len(list(childElem)) == 0:
            if cmp(childElem.get('{http://schemas.android.com/apk/res/android}name'),"'" + targetString +"'"):
                return True
            else:
                return False
        else:
            # print('has child, ' + childElem.tag, childElem.attrib)
            return findLauncherActivityName(childElem,targetString)

def getLauncherActivity(xmlFileDir):
    tree = ET.ElementTree(file=xmlFileDir)
    root = tree.getroot()
    packageName = root.get('package')

    for elem in tree.iter(tag = 'activity'):
        if findLauncherActivityName(elem,'android.intent.action.MAIN'): #if has
            if(findLauncherActivityName(elem,'android.intent.category.LAUNCHER')):
                return packageName,elem.get('{http://schemas.android.com/apk/res/android}name')

PackageName,LauncherActivity = getLauncherActivity(code_dir + '/app/src/main/AndroidManifest.xml')

# 先關閉該程序
os.system('adb shell am force-stop ' + PackageName)
# 開啟該LauncherActivity
if PackageName in LauncherActivity:
    os.system('adb shell am start -n '+ PackageName +'/' + LauncherActivity)
else:
    os.system('adb shell am start -n '+ PackageName +'/' + PackageName + LauncherActivity)

4. 缺點

該指令碼需要大量的環境配置,尤其是Gradle的配置。在打包過程中,需要一系列的工具,BuildTools、gradle plungin等,並不適合開發以外的人使用,需要學習的東西太多。

但是這個指令碼是個學習python練手的好專案。

結語

發現Python很容易上手,而且很方便。沒有分號和大括號程式碼看起來真的很清爽,字串也可以用單引號,很方便。恩,在這個過程中,發現好像 jenkins 就能很方便的做這種事,可以研究研究。

恩,希望能對大家有幫助。在使用過程中有Bug或者有更好的實現請留issue或留言。