LoginSignup
6
6

More than 5 years have passed since last update.

コマンドラインからビルド・インストール・apk起動するためのGradleタスク

Posted at

コマンドラインからのビルドは assenbleDebug とか installDebug で行うことができる。これらはAndroid Pluginの標準タスクである。ただ、インストールまではできるがapk起動はしないし、接続端末の選択などはできないなど実用とまではいかない。

コマンドラインからビルド・対象端末選択・インストール・アプリ起動ができるタスクを追加してみた。

準備

install peco

端末を選択するために peco を使うのでインストール。peco は標準出力をインクリメンタルサーチでき、選択することができる便利なツールだ。

$ brew install peco

※ pecoを使った非常に便利なadbツールがあります。 (adb-pect)

実装

対象モジュールの build.gradle にタスクを追加する。 (android studioで作ったプロジェクトなら app/build.gradle)

SDKパスを取得する

adbaapt など使うのでAndroid SDKのパスを取得するメソッドを用意する

app/build.gradle
def getSdkPath = {
    def sdkDir
    def localProperties = new File(rootDir, "local.properties")
    if (localProperties.exists()) {
        Properties properties = new Properties()
        localProperties.withInputStream { instr ->
            properties.load(instr)
        }
        sdkDir = properties.getProperty('sdk.dir')
    } else {
        sdkDir = System.env.ANDROID_HOME;
    }
    return sdkDir;
}

接続デバイス名を取得

接続デバイスを取得するメソッド。peco がなくてもデバイスは自動で選択されるようにする。

app/build.gradle
def getConnectedDevice = {
    def sdkDir = getSdkPath();
    def adb = sdkDir + "/platform-tools/adb"
    Process peco = 'peco'.execute()
    Process devices = "$adb devices".execute()
    Process sed = ["sed", "-e", "1,1d"].execute()

    def device = null;
    // pecoがある場合は選択できるようにする
    if ("which peco".execute().text.isEmpty()) {
        def list = devices.pipeTo(sed).text;
        list.eachLine { line, no ->
            if (!line.isEmpty()) {
                device = line.split()[0]     // <device name> device
                return true;
            }
        }
    } else {
        def select = devices.pipeTo(sed).pipeTo(peco).text
        if (!select.isEmpty()) {
            device = select.split()[0]     // <device name> device
        }
    }
    return device;
}

デバイスにapkをインストールするタスク

デバッグビルドの assembleDebug を依存タスクとしておけば、このタスクの実行前にビルドが行われる。 選択された device と 今回のビルドで生成されたapk場所 apkPath は次のタスクでも使えるように拡張プロパティに設定しておく。

app/build.gradle
task installToDevice(dependsOn: 'assembleDebug') << {
    android.applicationVariants.all { variant ->
        if (!variant.install) { return }

        // set command
        def sdkDir = getSdkPath();
        def adb = sdkDir + "/platform-tools/adb"
        def apkPath;

        variant.outputs.each { output ->
            if (output.zipAlign.outputFile.path.endsWith('.apk')) {
                apkPath = output.zipAlign.outputFile.path;
                return true;
            }
        }
        if (apkPath == null || apkPath.isEmpty()) { return; }

        // set property
        ext.device = getConnectedDevice();
        ext.apkPath = apkPath;

        if (device != null && !device.isEmpty()) {
            println "$adb -s $device install -r $apkPath".execute().text
        }
    }
}

インストールしたapkを起動するタスク

apkを起動するにはパッケージ名と起動アクティビティが必要で、そのためにビルドapkをダンプして各情報を取得する。なお、LAUNCHER<activity-alias> に設定しているなどapkによっては launchable-activity: が取得できない場合がある。そういった場合を考慮して、下記コードの activityName に初期値を設定しておく。

app/build.gradle
task startDebug(dependsOn: installToDevice) << {
    android.applicationVariants.all { variant ->
        if (!variant.install) { return }
        if (ext.device == null || ext.device.isEmpty()) { return; }
        if (ext.apkPath == null || ext.apkPath.isEmpty()) { return; }

        def device = ext.device;
        def apkPath = ext.apkPath;

        // set command
        def sdkDir = getSdkPath();
        def aapt = sdkDir + "/build-tools/$project.android.buildToolsRevision/aapt"
        def adb = sdkDir + "/platform-tools/adb"

        // get apk info
        def result = "$aapt dump badging $apkPath".execute().text
        // package name
        def packageLine = result.readLines().find { it.startsWith "package:" }
        def packageName
        packageLine.eachMatch(".*name='(.*?)'.*") { all, match ->
            packageName = match
        }
        // launcher activity
        def launchableActivityLine = result.readLines().find {
            it.startsWith "launchable-activity:"
        }
        def activityName = "com.hoge.example.MainActivity" // TODO: 取れなかった時のために初期化
        if (launchableActivityLine != null) {
            launchableActivityLine.eachMatch(".*name='(.*?)'.*") { all, match ->
                activityName = match
            }
        }
        def intentAction = "android.intent.action.MAIN"
        def intentCategory = "android.intent.category.LAUNCHER"
        def activity = packageName + "/" + activityName

        println "$adb -s $device shell am start -a $intentAction -c $intentCategory -n $activity".execute().text
    }
}

使ってみる

./gradlew startDebug で今回追加したタスクを実行。複数端末接続でも peco により選択することが可能。

$ ./gradlew startDebug
        :
    :main:assembleDebug
    :main:installToDevice
    [  0%] /data/local/tmp/main-debug.apk
    [100%] /data/local/tmp/main-debug.apk
        pkg: /data/local/tmp/main-debug.apk
    Success

    :main:startDebug
    Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] ....


    BUILD SUCCESSFUL

おしまい

CIで自動スクリプトで全部やりたいとか、コマンドラインから操作したいといった時に使えると思います。個人的にはコマンドラインから色々出来たほうが好きなので、こういった対応は非常に好きです。
コードはgistにあります。コピペでも多分動くはずです。

Gradleを使って業務を楽にする の記事を参考させてもらいました。

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6