1. 程式人生 > >命令列構建 android apk

命令列構建 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.