Diewuxi

Belive tomorrow will be better, love science and technology, support communication and understanding, always ready for thought turn.

Blog / engineering_technology / computer / code_platform / android / 使用命令行工具开发 Android 应用

Blog


Article^ Parent

使用命令行工具开发 Android 应用


Date: 2023-12-29 17:29:01
Description: 不想用 IDE 和 Android 应用构建系统 Gradle,太庞大了,尝试用命令行工具通过简单直接的方式构建一个 Android 应用。
Keywords: Android, Command Line Tools, Android Studio, Gradle
Category: engineering_technology/computer/code_platform/android
Tag: android, build, android studio
Link: https://www.diewuxi.com/blog/article/51.html

1 前言

Android Studio 是开发 Android 应用的官方 IDE(集成开发环境),它可以完成项目创建、代码编写、应用构建、签名、测试等任务。然而,有人出于某些原因并不想使用 IDE 和 Android 应用构建系统--Gradle,只希望用基本的命令行工具来完成 Android 应用的开发。首先,如果不用 IDE 提供的界面设计工具,那么 Android 应用的所有文本类型的源文件都可以用任何一个文本编辑器完成编写,包括命令行文本编辑器。其次,其实 Android 官方也提供了命令行工具来实现应用的构建。需要指出,Android 开发者网站虽然介绍了在命令行下构建应用,但是其中代码的构建仍然用的是 gradlew 这个脚本,使用的是 Gradle 构建系统,这需要提供项目文件,实际上只是项目的构建过程在命令行下,项目创建和管理仍需要 IDE。

Android Studio 的下载页面,除了它身下载链接外,下方还有 Command Line Tool 的下载链接,并指出 Command Line Tool 包含在 Android Studio 中,但是用户可以只下载 Command Line Tools,然后用其中的 sdkmanager 工具下载其它 SDK 包,Android 开发者网站提供了这些工具的使用说明。sdkmanager 可以安装的工具包括:Android SDK Build Tools,Android SDK Platform Tools,Android SDK Platform,Android Emulator 等,其中 Android SDK Build Tools 提供的命令行工具用于构建 Android 应用。

另外对于使用 Java 语言开发的 Android 应用,Java Development Kit 是必须的。

把庞大复杂的 Android Studio 和 Android 构建系统先放一边,本文记录了用命令行工具通过简单直接的方式展示一个 Android 应用是如何一步一步生成的。

本人测试所用的开发环境和软件版本如下:

macOS Sonoma 14.1

OpenJDK v21.0.1 macos aarch64
    javac v21.0.1
    keytool v21.0.1

Android SDK Build Tools v30.0.3
    aapt v0.2-6966805
    dx v1.16
    zipalign unknown
    apksigner v0.9

Android SDK Platform Tools v34.0.5
    adb
                        

2 项目文件

项目名称为 hello_world,目录结构如下:

hello_world/
    src/
        com/
            diewuxi/
                hello_world/
                    MainActivity.java
    res/
        layout/
            activity_main.xml
        values/
            strings.xml
    gen/
    bin/
    AndroidManifest.xml
                        

以下给出各文件内容。

2.1 src/com/diewuxi/hello_world/MainActivity.java 文件:

package com.diewuxi.hello_world;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
                        

2.2 res/layout/activity_main.xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    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"
    >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="@string/hello_msg"
        tools:context=".MainActivity"
        />

</LinearLayout>
                        

2.3 res/values/strings.xml 文件:

<resources>
    <string name="app_name">Hello World APP</string>
    <string name="hello_msg">Hello World!</string>
    <string name="menu_settings">Settings</string>
    <string name="title_activity_main">MainActivity</string>
</resources>
                        

2.4 AndroidManifest 文件:

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.diewuxi.hello_world"
    android:versionCode="1"
    android:versionName="1.0"
    >

    <uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="21"
        />

    <application
        android:label="@string/app_name"
        >

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>
                        

3 构建 app 步骤

3.1 处理资源文件,生成 R.java 文件到 gen 目录

$ aapt package \
        -M AndroidManifest.xml \
        -S res \
        -I /Users/fjc/home/opt/android-sdk/android_sdk-macos/platforms/android-21/android.jar \
        --extra-packages "" \
        --auto-add-overlay \
        -m -J gen \
        -f \
        -v
                        

aapt 是 Android Asset Packaging Tool, aapt package 子命令打包 android 资源文件,选项说明如下:

Android Asset Packaging Tool

Usage:
aapt p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \
    [-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile] \
    [--debug-mode] [--min-sdk-version VAL] [--target-sdk-version VAL] \
    [--app-version VAL] [--app-version-name TEXT] [--custom-package VAL] \
    [--rename-manifest-package PACKAGE] \
    [--rename-instrumentation-target-package PACKAGE] \
    [--utf16] [--auto-add-overlay] \
    [--max-res-version VAL] \
    [-I base-package [-I base-package ...]] \
    [-A asset-source-dir]  [-G class-list-file] [-P public-definitions-file] \
    [-D main-dex-class-list-file] \
    [-S resource-sources [-S resource-sources ...]] \
    [-F apk-file] [-J R-file-dir] \
    [--product product1,product2,...] \
    [-c CONFIGS] [--preferred-density DENSITY] \
    [--split CONFIGS [--split CONFIGS]] \
    [--feature-of package [--feature-after package]] \
    [raw-files-dir [raw-files-dir] ...] \
    [--output-text-symbols DIR]

Package the android resources.  It will read assets and resources that are
supplied with the -M -A -S or raw-files-dir arguments.  The -J -P -F and -R
options control which files are output.

-M
    specify full path to AndroidManifest.xml to include in zip
-A
    additional directory in which to find raw asset files
-S
    directory in which to find resources.  Multiple directories will be scanned
    and the first match found (left to right) will take precedence.
-J
    specify where to output R.java resource constant definitions
-P
    specify where to output public resource definitions
-F
    specify the apk file to output
-I
    add an existing package to base include set
--extra-packages
    generate R.java for libraries. Separate libraries with ':'.
--auto-add-overlay
    Automatically add resources that are only in overlays.
-m
    make package directories under location specified by -J
-f
    force overwrite of existing files
                        

3.2 编译 src 和 gen 目录下的 java 文件,输出 class 文件到 bin 目录

$ javac \
        --boot-class-path /Users/fjc/home/opt/android-sdk/android_sdk-macos/platforms/android-21/android.jar \
        --class-path  "" \
        -d bin \
        --source 8 \
        --target 8 \
        -verbose \
        src/com/diewuxi/hello_world/*.java gen/com/diewuxi/hello_world/R.java
                        

选项解释如下:

Usage: javac <options> <source files>
where possible options include:

--boot-class-path <path>, -bootclasspath <path>
    Override location of bootstrap class files
--class-path <path>, -classpath <path>, -cp <path>
    Specify where to find user class files and annotation processors
-d <directory>
    Specify where to place generated class files
--source <release>, -source <release>
    Provide source compatibility with the specified Java SE release.
    Supported releases: 
        8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21
--target <release>, -target <release>
    Generate class files suitable for the specified Java SE release.
    Supported releases: 
        8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21
                        

3.3 转换 bin 目录下的 class 文件,输出 classes.dex 文件 到工程根目录

$ dx --dex --output=classes.dex --verbose bin
                        

dx --dex 子命令转换 java class 文件到 dex 文件,选项如下:

dx --dex [--debug] [--verbose] [--positions=<style>] [--no-locals]
    [--no-optimize] [--statistics] [--[no-]optimize-list=<file>] [--no-strict]
    [--keep-classes] [--output=<file>] [--dump-to=<file>] [--dump-width=<n>]
    [--dump-method=<name>[*]] [--verbose-dump] [--no-files] [--core-library]
    [--num-threads=<n>] [--incremental] [--force-jumbo] [--no-warning]
    [--multi-dex [--main-dex-list=<file> [--minimal-main-dex]]
    [--input-list=<file>] [--min-sdk-version=<n>]
    [--allow-all-interface-method-invokes]
    [<file>.class | <file>.{zip,jar,apk} | <directory>] ...

    Convert a set of classfiles into a dex file, optionally embedded in a
    jar/zip. Output name must end with one of: .dex .jar .zip .apk or be a
    directory.
    Positions options: none, important, lines.

    --multi-dex: allows to generate several dex files if needed. This option is
        exclusive with --incremental, causes --num-threads to be ignored and only
        supports folder or archive output.

    --main-dex-list=<file>: <file> is a list of class file names, classes
        defined by those class files are put in classes.dex.

    --minimal-main-dex: only classes selected by --main-dex-list are to be put
        in the main dex.

    --input-list: <file> is a list of inputs.
        Each line in <file> must end with one of: .class .jar .zip .apk or be a
        directory.

    --min-sdk-version=<n>: Enable dex file features that require at least sdk
        version <n>.
                        

3.4 打包资源文件到 apk 文件中

$ aapt package \
        -M AndroidManifest.xml \
        -S res \
        -I /Users/fjc/home/opt/android-sdk/android_sdk-macos/platforms/android-21/android.jar \
        --extra-packages "" \
        --auto-add-overlay \
        -F com.diewuxi.hello_world.apk \
        -f \
        -v
                        

该命令与之前 aapt 类似,不是 -m -J 输出 R.java 文件。而是 -F 输出 apk 文件,这是最终 apk 的前身。这个 apk 文件里面只有资源文件,使用 aapt 工具 aapt list 子命令列出 apk 文件内容:

$ aapt list -v com.diewuxi.hello_world.apk 
Archive:  com.diewuxi.hello_world.apk
 Length   Method    Size  Ratio   Offset      Date  Time  CRC-32    Name
--------  ------  ------- -----  -------      ----  ----  ------    ----
    1636  Deflate     620  62%         0  01-01-80 08:00  67731b84  AndroidManifest.xml
    8328  Deflate    1381  83%       669  01-01-80 08:00  ffffffff86d49223  res/layout/activity_main.xml
    2404  Stored     2404   0%      2108  01-01-80 08:00  302b1639  resources.arsc
--------          -------  ---                            -------
   12368             4405  64%                            3 files
                        

3.5 添加 dex 文件到 apk 文件中

$ aapt add -v com.diewuxi.hello_world.apk classes.dex
                        

该命令为 aapt add 子命令,选项解释如下:

aapt a[dd] [-v] file.{zip,jar,apk} file1 [file2 ...]
    Add specified files to Zip-compatible archive.
                        

此时 apk 文件内容如下:

$ aapt list -v com.diewuxi.hello_world.apk 
Archive:  com.diewuxi.hello_world.apk
 Length   Method    Size  Ratio   Offset      Date  Time  CRC-32    Name
--------  ------  ------- -----  -------      ----  ----  ------    ----
    1636  Deflate     620  62%         0  01-01-80 08:00  67731b84  AndroidManifest.xml
    8328  Deflate    1381  83%       669  01-01-80 08:00  ffffffff86d49223  res/layout/activity_main.xml
    2404  Stored     2404   0%      2108  01-01-80 08:00  302b1639  resources.arsc
    6004  Deflate    2733  54%      4556  01-01-80 08:00  ffffffffa3c673f9  classes.dex
--------          -------  ---                            -------
   18372             7138  61%                            4 files
                        

3.6 zip 文件对齐

$ zipalign -f -p -v 4 com.diewuxi.hello_world.apk com.diewuxi.hello_world-aligned.apk
                        

zipalign 是 zip 文件对齐工具,见详细说明,目的是为了加快 Android 应用的运行速度,减少内存占用,选项说明如下:

Zip alignment utility
Copyright (C) 2009 The Android Open Source Project

Usage: zipalign [-f] [-p] [-v] [-z] <align> infile.zip outfile.zip
       zipalign -c [-p] [-v] <align> infile.zip

  <align>: alignment in bytes, e.g. '4' provides 32-bit alignment
  -c: check alignment only (does not modify file)
  -f: overwrite existing outfile.zip
  -p: memory page alignment for stored shared object files
  -v: verbose output
  -z: recompress using Zopfli
                        

3.7 签名

3.7.1 生成 keystore

$ keytool -genkeypair -keyalg RSA -validity 36000 -keystore ~/.android/apk.keystore -alias apk_release
                        

根据提示输入密码和个人信息例如:

CN=CD DWX, OU=diewuxi.com, O=Diewuxi, L=Songjiang, ST=Shanghai, C=CN
                        

完成后会生成 ~/.android/apk.keystore 文件,只需生成一次,-keyalg RSA 指定加密算法为 RSA-validity 36000 指定有效期 36000 天,-alias apk_release 指定这个条目的别名为 apk_release(因为一个 keystore 文件里面能添加多个条目)。Android 开发者网站有相关介绍。keytool -genkeypair 子命令选项如下:

keytool -genkeypair [OPTION]...

Generates a key pair

Options:

 -alias <alias>          alias name of the entry to process
 -keyalg <alg>           key algorithm name
 -keysize <size>         key bit size
 -groupname <name>       Group name. For example, an Elliptic Curve name.
 -sigalg <alg>           signature algorithm name
 -dname <name>           distinguished name
 -startdate <date>       certificate validity start date/time
 -ext <value>            X.509 extension
 -validity <days>        validity number of days
 -keypass <arg>          key password
 -keystore <keystore>    keystore name
 -signer <alias>         signer alias
 -signerkeypass <arg>    signer key password
 -storepass <arg>        keystore password
 -storetype <type>       keystore type
 -providername <name>    provider name
 -addprovider <name>     add security provider by name (e.g. SunPKCS11)
   [-providerarg <arg>]    configure argument for -addprovider
 -providerclass <class>  add security provider by fully-qualified class name
   [-providerarg <arg>]    configure argument for -providerclass
 -providerpath <list>    provider classpath
 -v                      verbose output
 -protected              password through protected mechanism

Use "keytool -?, -h, or --help" for this help message
                        

3.7.2 签名

    $ apksigner sign \
            --ks /Users/fjc/.android/apk.keystore \
            --ks-pass pass:android \
            --ks-key-alias apk_release \
            --out com.diewuxi.hello_world-signed.apk \
            --verbose \
            com.diewuxi.hello_world-aligned.apk
                        

Android 应用需要被签名后才能安装到设备上,apksigner sign 子命令完成这个操作,其中用到了上一步生成的 keystore 文件和当时设置的密码。完成签名之后,该 apk 就可以安装到设备上了。选项解释如下:

USAGE: apksigner sign [options] apk

This signs the provided APK, stripping out any pre-existing signatures. Signing
is performed using one or more signers, each represented by an asymmetric key
pair and a corresponding certificate. Typically, an APK is signed by just one
signer. For each signer, you need to provide the signer's private key and
certificate.


        GENERAL OPTIONS

--in                  Input APK file to sign. This is an alternative to
                      specifying the APK as the very last parameter, after all
                      options. Unless --out is specified, this file will be
                      overwritten with the resulting signed APK.

--out                 File into which to output the signed APK. By default, the
                      APK is signed in-place, overwriting the input file.

-v, --verbose         Verbose output mode

--v1-signing-enabled  Whether to enable signing using JAR signing scheme (aka v1
                      signing scheme) used in Android since day one. By default,
                      signing using this scheme is enabled based on min and max
                      SDK version (see --min-sdk-version and --max-sdk-version).

--v2-signing-enabled  Whether to enable signing using APK Signature Scheme v2
                      (aka v2 signing scheme) introduced in Android Nougat,
                      API Level 24. By default, signing using this scheme is
                      enabled based on min and max SDK version (see
                      --min-sdk-version and --max-sdk-version).

--v3-signing-enabled  Whether to enable signing using APK Signature Scheme v3
                      (aka v3 signing scheme) introduced in Android P,
                      API Level 28. By default, signing using this scheme is
                      enabled based on min and max SDK version (see
                      --min-sdk-version and --max-sdk-version).  Multiple
                      signers are not supported when using v3 signing, but
                      multiple signers may be provided in conjunction with the
                      "lineage" option to make sure that the app is signed by
                      an appropriate signer on all supported platform versions.

--v4-signing-enabled  Whether to enable signing using APK Signature Scheme v4
                      (aka v4 signing scheme) introduced in Android 11,
                      API Level 30. By default, signing using this scheme is
                      enabled based on min and max SDK version (see
                      --min-sdk-version and --max-sdk-version).

--force-stamp-overwrite  Whether to overwrite existing source stamp in the
                      APK, if found. By default, it is set to false. It has no
                      effect if no source stamp signer config is provided.

--verity-enabled      Whether to enable the verity signature algorithm for the
                      v2 and v3 signature schemes.

--min-sdk-version     Lowest API Level on which this APK's signatures will be
                      verified. By default, the value from AndroidManifest.xml
                      is used. The higher the value, the stronger security
                      parameters are used when signing.

--max-sdk-version     Highest API Level on which this APK's signatures will be
                      verified. By default, the highest possible value is used.

--debuggable-apk-permitted  Whether to permit signing android:debuggable="true"
                      APKs. Android disables some of its security protections
                      for such apps. For example, anybody with ADB shell access
                      can execute arbitrary code in the context of a debuggable
                      app and can read/write persistently stored data of the
                      app. It is a good security practice to not sign
                      debuggable APKs with production signing keys, because
                      such APKs puts users at risk once leaked.
                      By default, signing debuggable APKs is permitted, for
                      backward compatibility with older apksigner versions.

--lineage             Signing certificate history to use in the event that
                      signing certificates changed for an APK using APK
                      Signature Scheme v3 supported signing certificate
                      rotation.  This object may be created by the apksigner
                      "rotate" command.  If used, all signers used to sign the
                      APK must be present in the signing lineage,
                      and if v1 or v2 signing is enabled, the first (oldest)
                      entry in the lineage must have a signer provided, so that
                      it can be used for those v1 and/or v2 signing. Multiple
                      signers are not supported when using APK Signature Scheme
                      v3, so multiple signers input will correspond to different
                      points in the lineage and will be used on older platform
                      versions when the newest signer in the lineage is
                      unsupported.
                      An APK previously signed with a SigningCertificateLineage
                      can also be specified; the lineage will then be read from
                      the signed data in the APK.

-h, --help            Show help about this command and exit


        PER-SIGNER OPTIONS
These options specify the configuration of a particular signer. To delimit
options of different signers, use --next-signer.

--next-signer         Delimits options of two different signers. There is no
                      need to use this option when only one signer is used.

--v1-signer-name      Basename for files comprising the JAR signature scheme
                      (aka v1 scheme) signature of this signer. By default,
                      KeyStore key alias or basename of key file is used.

--stamp-signer        The signing information for the signer of the source stamp
                      to be included in the APK.

        PER-SIGNER SIGNING KEY & CERTIFICATE OPTIONS
There are two ways to provide the signer's private key and certificate: (1) Java
KeyStore (see --ks), or (2) private key file in PKCS #8 format and certificate
file in X.509 format (see --key and --cert).

--ks                  Load private key and certificate chain from the Java
                      KeyStore initialized from the specified file. NONE means
                      no file is needed by KeyStore, which is the case for some
                      PKCS #11 KeyStores.

--ks-key-alias        Alias under which the private key and certificate are
                      stored in the KeyStore. This must be specified if the
                      KeyStore contains multiple keys.

--ks-pass             KeyStore password (see --ks). The following formats are
                      supported:
                          pass:<password> password provided inline
                          env:<name>      password provided in the named
                                          environment variable
                          file:<file>     password provided in the named
                                          file, as a single line
                          stdin           password provided on standard input,
                                          as a single line
                      A password is required to open a KeyStore.
                      By default, the tool will prompt for password via console
                      or standard input.
                      When the same file (including standard input) is used for
                      providing multiple passwords, the passwords are read from
                      the file one line at a time. Passwords are read in the
                      order in which signers are specified and, within each
                      signer, KeyStore password is read before the key password
                      is read.

--key-pass            Password with which the private key is protected.
                      The following formats are supported:
                          pass:<password> password provided inline
                          env:<name>      password provided in the named
                                          environment variable
                          file:<file>     password provided in the named
                                          file, as a single line
                          stdin           password provided on standard input,
                                          as a single line
                      If --key-pass is not specified for a KeyStore key, this
                      tool will attempt to load the key using the KeyStore
                      password and, if that fails, will prompt for key password
                      and attempt to load the key using that password.
                      If --key-pass is not specified for a private key file key,
                      this tool will prompt for key password only if a password
                      is required.
                      When the same file (including standard input) is used for
                      providing multiple passwords, the passwords are read from
                      the file one line at a time. Passwords are read in the
                      order in which signers are specified and, within each
                      signer, KeyStore password is read before the key password
                      is read.

--pass-encoding       Additional character encoding (e.g., ibm437 or utf-8) to
                      try for passwords containing non-ASCII characters.
                      KeyStores created by keytool are often encrypted not using
                      the Unicode form of the password but rather using the form
                      produced by encoding the password using the console's
                      character encoding. apksigner by default tries to decrypt
                      using several forms of the password: the Unicode form, the
                      form encoded using the JVM default charset, and, on Java 8
                      and older, the form encoded using the console's charset.
                      On Java 9, apksigner cannot detect the console's charset
                      and may need to be provided with --pass-encoding when a
                      non-ASCII password is used. --pass-encoding may also need
                      to be provided for a KeyStore created by keytool on a
                      different OS or in a different locale.

--ks-type             Type/algorithm of KeyStore to use. By default, the default
                      type is used.

--ks-provider-name    Name of the JCA Provider from which to request the
                      KeyStore implementation. By default, the highest priority
                      provider is used. See --ks-provider-class for the
                      alternative way to specify a provider.

--ks-provider-class   Fully-qualified class name of the JCA Provider from which
                      to request the KeyStore implementation. By default, the
                      provider is chosen based on --ks-provider-name.

--ks-provider-arg     Value to pass into the constructor of the JCA Provider
                      class specified by --ks-provider-class. The value is
                      passed into the constructor as java.lang.String. By
                      default, the no-arg provider's constructor is used.

--key                 Load private key from the specified file. If the key is
                      password-protected, the password will be prompted via
                      standard input unless specified otherwise using
                      --key-pass. The file must be in PKCS #8 DER format.

--cert                Load certificate chain from the specified file. The file
                      must be in X.509 PEM or DER format.


        JCA PROVIDER INSTALLATION OPTIONS
These options enable you to install additional Java Crypto Architecture (JCA)
Providers, such as PKCS #11 providers. Use --next-provider to delimit options of
different providers. Providers are installed in the order in which they appear
on the command-line.

--provider-class      Fully-qualified class name of the JCA Provider.

--provider-arg        Value to pass into the constructor of the JCA Provider
                      class specified by --provider-class. The value is passed
                      into the constructor as java.lang.String. By default, the
                      no-arg provider's constructor is used.

--provider-pos        Position / priority at which to install this provider in
                      the JCA provider list. By default, the provider is
                      installed as the lowest priority provider.
                      See java.security.Security.insertProviderAt.


        EXAMPLES

1. Sign an APK, in-place, using the one and only key in keystore release.jks:
$ apksigner sign --ks release.jks app.apk

1. Sign an APK, without overwriting, using the one and only key in keystore
   release.jks:
$ apksigner sign --ks release.jks --in app.apk --out app-signed.apk

3. Sign an APK using a private key and certificate stored as individual files:
$ apksigner sign --key release.pk8 --cert release.x509.pem app.apk

4. Sign an APK using two keys:
$ apksigner sign --ks release.jks --next-signer --ks magic.jks app.apk

5. Sign an APK using PKCS #11 JCA Provider:
$ apksigner sign --provider-class sun.security.pkcs11.SunPKCS11 \
    --provider-arg token.cfg --ks NONE --ks-type PKCS11 app.apk

6. Sign an APK using a non-ASCII password KeyStore created on English Windows.
   The --pass-encoding parameter is not needed if apksigner is being run on
   English Windows with Java 8 or older.
$ apksigner sign --ks release.jks --pass-encoding ibm437 app.apk

7. Sign an APK on Windows using a non-ASCII password KeyStore created on a
   modern OSX or Linux machine:
$ apksigner sign --ks release.jks --pass-encoding utf-8 app.apk

8. Sign an APK with rotated signing certificate:
$ apksigner sign --ks release.jks --next-signer --ks release2.jks \
    --lineage /path/to/signing/history/lineage app.apk
                        

4 安装测试

安装:

adb install -r com.diewuxi.hello_world-signed.apk
                        

启动:

adb shell am start -n com.diewuxi.hello_world/.MainActivity
                        

卸载:

adb uninstall com.diewuxi.hello_world
                        

也可以吧 apk 文件复制到手机上,进行安装,操作,卸载。

5 使用 Makefile 文件

利用 make 工具可以简化以上一系列命令行操作,只要建立 Makefile 文件:

PROJECT_NAME = hello_world
PACKAGE_NAME = com.diewuxi.hello_world
PACKAGE_PATH = com/diewuxi/hello_world

APK__BASE = $(PACKAGE_NAME).apk
APK__ALIGNED = $(PACKAGE_NAME)-aligned.apk
APK__SIGNED = $(PACKAGE_NAME)-signed.apk
START_STRING = $(PACKAGE_NAME)/.MainActivity

HOME = /Users/fjc
HOME_MAIN = /Users/fjc/home
OS_MARK = macos

SDK_HOME = $(HOME_MAIN)/opt/android-sdk/android_sdk-$(OS_MARK)
JAR__ANDROID = $(SDK_HOME)/platforms/android-21/android.jar

KEYSTORE = $(HOME)/.android/apk.keystore
KEYSTORE_PASS = android
KEYSTORE_ALIAS = apk_release

DIR__SRC = src
DIR__RES = res
DIR__GEN = gen
DIR__BIN = bin

APPT__RES               = -S $(DIR__RES)
APPT__EXTRA_PACKAGES    = ""
JAVA__CLASSPATH         = ""
JAVA__SRC               = $(DIR__SRC)/$(PACKAGE_PATH)/*.java $(DIR__GEN)/$(PACKAGE_PATH)/R.java
DX__INCLUDE             = $(DIR__BIN)


#DIR__CONSTRAINT_LAYOUT = $(SDK_HOME)/extras/m2repository/com/android/support/constraint/constraint-layout/1.0.2/constraint-layout-1.0.2
#JAR__CONSTRAINT_LAYOUT = $(DIR__CONSTRAINT_LAYOUT)/classes.jar
#JAR__CONSTRAINT_LAYOUT_SOLVER = $(SDK_HOME)/extras/m2repository/com/android/support/constraint/constraint-layout-solver/1.0.2/constraint-layout-solver-1.0.2.jar
#
#APPT__RES              += -S $(DIR__CONSTRAINT_LAYOUT)/res
#APPT__EXTRA_PACKAGES   += :android.support.constraint
#JAVA__CLASSPATH        += :$(JAR__CONSTRAINT_LAYOUT):$(JAR__CONSTRAINT_LAYOUT_SOLVER)
#JAVA__SRC              +=  $(DIR__GEN)/android/support/constraint/R.java
#DX__INCLUDE            +=  $(JAR__CONSTRAINT_LAYOUT) $(JAR__CONSTRAINT_LAYOUT_SOLVER)



.PHONY: all res class apk install run uninstall clean

all: run

res:
    aapt package \
        -M AndroidManifest.xml \
        $(APPT__RES) \
        -I $(JAR__ANDROID) \
        --extra-packages $(APPT__EXTRA_PACKAGES) \
        --auto-add-overlay \
        -m -J $(DIR__GEN) \
        -f \
        -v

class: res
    javac \
        --boot-class-path $(JAR__ANDROID) \
        --class-path  $(JAVA__CLASSPATH) \
        -d $(DIR__BIN) \
        --source 8 \
        --target 8 \
        -verbose \
        $(JAVA__SRC)

apk:  class
    dx --dex --output=classes.dex --verbose $(DX__INCLUDE)

    aapt package \
        -M AndroidManifest.xml \
        $(APPT__RES) \
        -I $(JAR__ANDROID) \
        --extra-packages $(APPT__EXTRA_PACKAGES) \
        --auto-add-overlay \
        -F $(APK__BASE) \
        -f \
        -v

    aapt add -v $(APK__BASE) classes.dex

    zipalign -f -p -v 4 $(APK__BASE) $(APK__ALIGNED)

    apksigner sign \
        --ks $(KEYSTORE) \
        --ks-pass pass:$(KEYSTORE_PASS) \
        --ks-key-alias $(KEYSTORE_ALIAS) \
        --out $(APK__SIGNED) \
        --verbose \
        $(APK__ALIGNED)

install: apk
    adb install -r $(APK__SIGNED)

run: install
    adb shell am start -n $(START_STRING)

uninstall:
    adb uninstall $(PACKAGE_NAME)

clean:
    - rm $(APK__BASE)
    - rm $(APK__ALIGNED)
    - rm $(APK__SIGNED)
    - rm $(APK__SIGNED).idsig
    - rm classes.dex
    - rm -r $(DIR__GEN)/*
    - rm -r $(DIR__BIN)/*
                        

例如用 make run 就可以自动完成从代码构建到 app 运行起来。

6 附录

  1. 命令行选项变化

新版本 Android SDK Build Tools 使用了 aapt2 和 d8,命令行选项有改动,本人对此不了解,不在此叙述。

  1. 本文所述命令不是通用的

命令行工具的选项很多,本文只使用了能完成本项目的选项,对于其它项目,所用命令行选项很可能与本文不同,需要根据实际需求找到适合的命令行选项。IDE 的优点之一就是自动帮你命令行选项。

7 参考资料

Last modified: 2024-01-21

Comments [0]

There is no comments now.

Write comment(* is necessary, and email is not shown to public)


Diewuxi 2017--2024