命令行构建 android apk

原文翻译自:
https://www.hanshq.net/command-line-android.html

C语言程序

vim hello.c

#include <stdio.h>

int main()
{
        printf("Hello, world!\n");
        return 0;
}
gcc hello.c
./a.out 

然而,对于Android,编写Hello World的官方方法是启动Android Studio ,使用其向导创建新项目,然后在几分钟内自动生成和构建应用程序。

这当然是为了方便开发人员,但对于想知道发生了什么的人来说,这会让事情变得困难。 究竟发生了什么? 以下哪些文件对于Hello World来说真的是必需的?

也许这是我说话的控制狂(程序员对他们的程序的一个很好的特质),但我不觉得不理解如何构建我自己的程序。

以下是关于如何从命令行手动构建Android应用程序的说明。 这些说明适用于Linux,但它们应该很容易适应Mac或Windows。 完整的源代码和构建脚本可在command_line_android.tar.gz中找到 。

安装开发工具

Android应用程序通常用Java编写,因此构建它们需要安装Java Development Kit(JDK)。 当前版本的Android工具需要Java 8,我从这里下载,提取并放在我的PATH上,如下所示:

tar zxf jdk-8u112-linux-x64.tar.gz
export JAVA_HOME=${HOME}/jdk1.8.0_112
export PATH=${JAVA_HOME}/bin:$PATH

Android软件开发工具包(SDK)中提供了Android特定工具。 通常,这是在安装Android Studio时安装的,但我们也可以自己安装(请参阅Android Studio下载页面上的 “获取命令行工具”部分):

curl -O https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz
tar zxf android-sdk_r24.4.1-linux.tgz

不幸的是,该文件不包含我们需要的所有内容,如SDK Readme.txt中所述 :

Android SDK存档最初仅包含基本SDK工具。 不包含Android平台或任何第三方库。
事实上,它甚至没有开发应用程序所需的所有工具。
要开始开发应用程序,必须安装Platform-tools以及至少一个版本的Android平台,使用SDK Manager。

我们需要的是构建工具 (用于aapt dx和apksigner),用于定位的Android平台(我将使用版本16)和平台工具 (用于adb )。

我们可以直接安装它们(文件名在repository-11.xml中找到),而不是像上面建议的那样使用SDK Manager:

curl -O https://dl.google.com/android/repository/build-tools_r25-linux.zip
unzip build-tools_r25-linux.zip
mkdir android-sdk-linux/build-tools
mv android-7.1.1 android-sdk-linux/build-tools/25.0.0

curl -O https://dl.google.com/android/repository/android-16_r05.zip
unzip android-16_r05.zip
mv android-4.1.2 android-sdk-linux/platforms/android-16

curl -O https://dl.google.com/android/repository/platform-tools_r25-linux.zip
unzip platform-tools_r25-linux.zip -d android-sdk-linux/

The Hello World Program

我们的Hello World程序包含三个文件:应用程序清单,布局和活动。

应用清单如下所示( AndroidManifest.xml )。 它指定应用程序的名称,它所针对的Android API等.apect -filter元素将MainActivity设置为程序的主要入口点。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="net.hanshq.hello"
          versionCode="1"
          versionName="0.1">
    <uses-sdk android:minSdkVersion="16"/>
    <application android:label="Hello">
        <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>

下面是布局文件( res/layout/activity_main.xml )。 它定义了在我们的程序中使用的UI元素。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/my_text"/>
</LinearLayout>

最后, Activity实现如下:( java/net/hanshq/hello/MainActivity.java )

package net.hanshq.hello;

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

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

        TextView text = (TextView)findViewById(R.id.my_text);
        text.setText("Hello, world!");
    }
}

Building

我们将使用shell变量更方便地引用我们之前安装的SDK。所有构建工件都将放在我们在此创建的构建目录的子目录中。

SDK="${HOME}/android-sdk-linux"
BUILD_TOOLS="${SDK}/build-tools/25.0.0"
PLATFORM="${SDK}/platforms/android-16"
mkdir -p build/gen build/obj build/apk

第一个构建步骤是生成R.java文件,该文件用于引用资源(例如上面的R.id.my_text )。 这是使用Android资产包装工具完成的, aapt :

"${BUILD_TOOLS}/aapt" package -f -m -J build/gen/ -S res \
      -M AndroidManifest.xml -I "${PLATFORM}/android.jar"

他将创建 build/gen/net/hanshq/hello/R.java.

-f标志使aapt覆盖任何现有的输出文件, -m使它在输出目录下创建包目录, -J使它生成R.java文件并设置输出目录。 -S指出资源目录, -M指定清单, -I将平台.jar添加为“包含文件”。

现在所有Java代码都准备好了,我们可以使用javac编译它:

javac -source 1.7 -target 1.7 -bootclasspath "${JAVA_HOME}/jre/lib/rt.jar" \
      -classpath "${PLATFORM}/android.jar" -d build/obj \
      build/gen/net/hanshq/hello/R.java java/net/hanshq/hello/MainActivity.java

( 1.7和-bootclasspath用于发出Java 7字节代码,这是Android工具所期望的,尽管使用的是JDK版本8.)

Java编译器创建了包含Java虚拟机字节代码的.class文件。 然后必须使用dx工具将其转换为Dalvik字节代码:

"${BUILD_TOOLS}/dx" --dex --output=build/apk/classes.dex build/obj/

有一套新的Android工具, jack ,可以直接将Java代码编译成Dalvik字节码。也许这将成为未来的工作方式。)

然后,我们再次使用aapt工具将 build/apk/目录的内容以及清单和资源打包到Android应用程序包(APK)文件中:

"${BUILD_TOOLS}/aapt" package -f -M AndroidManifest.xml -S res/ \
      -I "${PLATFORM}/android.jar" \
      -F build/Hello.unsigned.apk build/apk/

该应用程序现已构建,但APK文件需要在任何设备允许运行之前进行签名 ,即使在调试模式下也是如此,如果我们想要在Play商店中发布它,则需要zipaligned。

首先,我们运行zipalign工具,它将APK中的未压缩文件与4字节边界对齐,以便更容易地进行内存映射:

"${BUILD_TOOLS}/zipalign" -f -p 4 \
      build/Hello.unsigned.apk build/Hello.aligned.apk

然后我们使用Java keytool创建一个密钥库和密钥进行签名:

keytool -genkeypair -keystore keystore.jks -alias androidkey \
      -validity 10000 -keyalg RSA -keysize 2048 \
      -storepass android -keypass android
What is your first and last name?
  [Unknown]:
What is the name of your organizational unit?
  [Unknown]:
What is the name of your organization?
  [Unknown]:
What is the name of your City or Locality?
  [Unknown]:
What is the name of your State or Province?
  [Unknown]:
What is the two-letter country code for this unit?
  [Unknown]:
Is CN=Unknown, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=Unknown correct?
  [no]:  yes

并使用该密钥与apksigner签署我们的应用程序:

"${BUILD_TOOLS}/apksigner" sign --ks keystore.jks \
      --ks-key-alias androidkey --ks-pass pass:android \
      --key-pass pass:android --out build/Hello.apk \
      build/Hello.aligned.apk

在这里插入图片描述

使用native代码

虽然Android应用程序通常用Java编写,但它们也可以包含本机代码,即机器代码直接由设备的处理器运行。 这对于性能很有用,因为它可以消除运行Java代码的开销,并且可移植性,因为它打开了用其他语言编写的代码的平台。

在我们的程序中添加本机代码使得构建起来有点困难,但事实并非如此。

Android Native Development Kit (NDK)提供了用于为Android构建C和C ++代码的编译器和库。 它可以像这样安装:

curl -O https://dl.google.com/android/repository/android-ndk-r13b-linux-x86_64.zip
unzip android-ndk-r13b-linux-x86_64.zip
NDK="${HOME}/android-ndk-r13b"

我们设置了更多shell变量来指向我们将使用的特定工具链:(如果你不使用Linux,你会想要其他一个预建目录)

ARM_TOOLCHAIN="${NDK}/toolchains/arm-linux-androideabi-4.9/prebuilt/"
ARM_TOOLCHAIN+="linux-x86_64/bin/arm-linux-androideabi-gcc"

X86_TOOLCHAIN="${NDK}/toolchains/x86-4.9/prebuilt/"
X86_TOOLCHAIN+="linux-x86_64/bin/i686-linux-android-gcc"

我们将更新我们的Activity以使用Java Native Interface (另请参阅Android JNI Tips )获取新方法getMessage() ,并使用该方法设置TextView的文本。 本机方法将由名为hello的库实现,我们在静态初始化程序块中加载System.loadLibrary :

package net.hanshq.hello;

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

public class MainActivity extends Activity {
    static {
        System.loadLibrary("hello");
    }

    public native String getMessage();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView text = (TextView)findViewById(R.id.my_text);
        text.setText(getMessage());
    }
}

hello库必须提供getMessage方法的本机实现。 要弄清楚C函数签名对应于Java方法,我们使用javah工具:

javah -classpath "${PLATFORM}/android.jar:build/obj" \
      -o /tmp/jni.h net.hanshq.hello.MainActivity
grep -A1 _getMessage /tmp/jni.h
JNIEXPORT jstring JNICALL Java_net_hanshq_hello_MainActivity_getMessage
  (JNIEnv *, jobject);

我们在hello.c中实现它:

#include <stdlib.h>
#include <jni.h>

static const char *const messages[] = {
        "Hello, world!",
        "Hej världen!",
        "Bonjour, monde!",
        "Hallo Welt!"
};

JNIEXPORT jstring JNICALL
Java_net_hanshq_hello_MainActivity_getMessage(JNIEnv *env, jobject obj)
{
        int i;

        i = rand() % (sizeof(messages) / sizeof(messages[0]));

        return (*env)->NewStringUTF(env, messages[i]);
}

C文件被编译到共享库libhello.so中 (注意额外的lib前缀)。 我们为ARMv7构建一个,为X86构建一个,以支持大多数设备和模拟器,并将它们放在APK的lib/目录下:

mkdir -p build/apk/lib/armeabi-v7a build/apk/lib/x86

"${ARM_TOOLCHAIN}" --sysroot="${NDK}/platforms/android-16/arch-arm" \
      -march=armv7-a -mfpu=vfpv3-d16 -mfloat-abi=softfp -Wl,--fix-cortex-a8 \
      -fPIC -shared -o build/apk/lib/armeabi-v7a/libhello.so jni/hello.c

"${X86_TOOLCHAIN}" --sysroot="${NDK}/platforms/android-16/arch-x86" \
      -fPIC -shared -o build/apk/lib/x86/libhello.so jni/hello.c

(请参阅ABI管理文档,其中ABI可以用NDK作为目标,并在哪个lib/目录下放置.so文件.ARM编译器标志的灵感来自$ {NDK}/build/core/toolchains/arm-linux -androideabi-4.9/setup.mk 。)

为了构建APK,我们重复上面的构建步骤( aapt一直到apksigner )。 可以使用jar工具检查APK的内容:

$ jar tf build/Hello.apk
AndroidManifest.xml
classes.dex
lib/armeabi-v7a/libhello.so
lib/x86/libhello.so
res/layout/activity_main.xml
resources.arsc
META-INF/ANDROIDK.SF
META-INF/ANDROIDK.RSA
META-INF/MANIFEST.MF

该文件应包含清单,转换为DEX格式的Java类,我们的两个本机.so文件,raw( activity_main.xml )和二进制( resources.arsc )形式的应用程序资源。 META-INF目录包含JAR文件清单和加密签名。

运行时,应用程序如下所示:

在这里插入图片描述

For a larger example, see the Othello project.

猜你喜欢

转载自blog.csdn.net/yeshennet/article/details/84929041
今日推荐