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.