android - 创建没有 gradle、ant 或 cmake 的仅 JNI/NDK apk 命令行
问题描述
我创建了一个仅使用 JNI 和 NDK 的 android 应用程序,没有使用超出将我的代码与 JNI 连接所必需的 Java 代码,因为是一个使用 opengl es 的应用程序,它使用一个代码用于 linux 和安卓。但是我不明白如何创建一个没有很多我现在不想让它咬我的东西的 apk 文件。所以,我尝试创建一个没有 gradle、ant、android-studio 的应用程序......也只有命令行。
我现在还有什么?
我可以使用 ndk-build 成功构建我的项目,它会创建一个包含文件 .o 的“obj/”目录,但是现在呢?
方法是什么?
使用aapt并在zipalign之后使用?
目标文件是我使用aapt所需要的吗?
解决方案
我什至给了 Gradle 一个机会,但我很不安不知道他在做什么,所以决定把他放在一边,自己动手学习,就是这样。
教程构建 java 和 JNI/NDK APK
1. 安装JDK8和android SDK和NDK、build-tools、platform-tools和android平台23。
2. 设置环境变量。
$ export SDK="${HOME}/Programs/Android" \
export BUILD_TOOLS="${SDK}/build-tools/29.0.2" \
export PLATFORM="${SDK}/platforms/android-23" \
export ANDROID_API=23 \
export APK_NAME="APKName" \
export PACKAGE_NAME="some.some.some" \
export ORG_DIRS="${PACKAGE_NAME//./\/}" \
export NDK="${SDK}/ndk-bundle"
export ANDROID_TOOLCHAIN="${NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi23-clang"
3. 创建项目目录和文件。
创建目录树:
$ mkdir -p src/"${ORG_DIRS}" res/layout build/gen build/obj build/apk jni
创建 ./AndroidManifest.xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="some.some.some"
versionCode="1"
versionName="0.1">
<uses-sdk android:minSdkVersion="23"/>
<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/main.xml 文件:
<?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>
创建 src/some/some/some/MainActivity.java 文件:
package some.some.some;
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.main);
TextView text = (TextView)findViewById(R.id.my_text);
text.setText("Hello, world!");
}
}
4. 使用 Android Asset Packaging Tool (aapt) 生成 R.java 文件。
$ "${BUILD_TOOLS}/aapt" package -f -m -J build/gen/ -S res \
-M AndroidManifest.xml -I "${PLATFORM}/android.jar"
该-f
标志用于覆盖任何现有的输出文件。
导致它在-m
输出目录下创建包目录。
这-J
使它生成 R.java 文件并设置输出目录。
-S
指出资源目录。-M 指定清单。
将-I
平台 .jar 添加为“包含文件”。
这一切都会创建:build/gen/"${ORG_DIRS}"/R.java。
5. 用 javac 编译 java 文件。
$ javac -bootclasspath "${JAVA_HOME}/jre/lib/rt.jar" \
-classpath "${PLATFORM}/android.jar" -d build/obj \
build/gen/"${ORG_DIRS}"/R.java src/"${ORG_DIRS}"/MainActivity.java
如果您看到有关 JDK 版本的编译错误,请尝试使用命令中的
-source 1.7 -target 1.7
标签javac
。
6. 使用dx工具将build/obj/中的.class文件翻译成Dalvik字节码。
$ "${BUILD_TOOLS}/dx" --dex --output=build/apk/classes.dex build/obj/
7. 再次使用 aapt 工具打包创建 APK。
$ "${BUILD_TOOLS}/aapt" package -f -M AndroidManifest.xml -S res/ \
-I "${PLATFORM}/android.jar" \
-F build/"${APK_NAME}".unsigned.apk build/apk/
8.在APK中使用zipalign工具。
它用于在 4 字节边界上对齐 APK 中的未压缩文件,以便于内存映射。
$ "${BUILD_TOOLS}/zipalign" -f -p 4 \
build/"${APK_NAME}".unsigned.apk build/"${APK_NAME}".aligned.apk
9. 创建密钥库和用于使用 Java 密钥工具进行签名的密钥。
$ keytool -genkeypair -keystore keystore.jks -alias androidkey \
-validity 10000 -keyalg RSA -keysize 2048
10. 使用 apksigner 工具对 APK 进行签名。
$ "${BUILD_TOOLS}/apksigner" sign --ks keystore.jks \
--ks-key-alias androidkey --out build/"${APK_NAME}".apk \
build/"${APK_NAME}".aligned.apk
11. 使用 adb 工具测试您的应用程序。
$ "${SDK}/platform-tools/adb" install -r build/"${APK_NAME}".apk
$ "${SDK}/platform-tools/adb" shell am start -n "${PACKAGE_NAME}"/.MainActivity
您可以使用adb logcat
beforeadb shell...
进行调试。
在第 11 步之前,您从 Java 代码构建了 APK。现在让我们看看如何制作 JNI/NDK 代码的 APK。
12、修改MainActivity.java文件,重新编译再翻译。
更改 src/some/some/some/MainActivity.java 文件:
package some.some.some;
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.main);
TextView text = (TextView)findViewById(R.id.my_text);
text.setText(getMessage());
}
}
重新编译:
$ javac -bootclasspath "${JAVA_HOME}/jre/lib/rt.jar" \
-classpath "${PLATFORM}/android.jar" -d build/obj \
build/gen/"${ORG_DIRS}"/R.java src/"${ORG_DIRS}"/MainActivity.java
并再次转换为 Dalvik 字节码:
$ "${BUILD_TOOLS}/dx" --dex --output=build/apk/classes.dex build/obj/
13. 用javah工具查找Java方法对应的C函数签名。
$ javah -classpath "${PLATFORM}/android.jar:build/obj" \
-o /tmp/jni.h "${PACKAGE_NAME}".MainActivity
$ grep -A1 _getMessage /tmp/jni.h
JNIEXPORT jstring JNICALL Java_some_some_some_MainActivity_getMessage
(JNIEnv *, jobject);
14. 创建 hello.c 文件。
#include <stdlib.h>
#include <jni.h>
#include <time.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;
srand(time(NULL));
i = rand() % (sizeof(messages) / sizeof(messages[0]));
return (*env)->NewStringUTF(env, messages[i]);
}
15. 使用 arm 工具链创建 libhello.so
首先,创建 build/apk/lib/armeabi-v7a 目录:
$ mkdir -p build/apk/lib/armeabi-v7a
构建 libhello.so:
$ ${ANDROID_TOOLCHAIN} -shared -o build/apk/lib/armeabi-v7a/libhello.so jni/hello.c
16. 再次打包APK。
$ "${BUILD_TOOLS}/aapt" package -f -M AndroidManifest.xml -S res/ \
-I "${PLATFORM}/android.jar" \
-F build/"${APK_NAME}".unsigned.apk build/apk/
17. 再次压缩对齐 APK。
$ "${BUILD_TOOLS}/zipalign" -f -p 4 \
build/"${APK_NAME}".unsigned.apk build/"${APK_NAME}".aligned.apk
18. 再次签署 APK。
$ "${BUILD_TOOLS}/apksigner" sign --ks keystore.jks \
--ks-key-alias androidkey --out build/"${APK_NAME}".apk \
build/"${APK_NAME}".aligned.apk
19、使用aapt工具或jar工具查看APK内容。
与 aapt:
$ "${BUILD_TOOLS}/aapt" list build/"${APK_NAME}".apk
或提供更多详细信息:
$ "${BUILD_TOOLS}/aapt" list -v build/"${APK_NAME}".apk
或用罐子:
$ jar tf build/"${APK_NAME}"
20. 再次使用 adb 工具测试您的应用程序。
$ "${SDK}/platform-tools/adb" install -r build/"${APK_NAME}".apk
$ "${SDK}/platform-tools/adb" shell am start --activity-clear-top -n "${PACKAGE_NAME}"/.MainActivity
这--activity-clear-top
是为了清理任务,避免警告
“警告:活动未启动,其当前任务已被带到前面”。
推荐阅读
- javascript - 当对象属性的属性发生更改时,如何重新渲染 litelement 组件?
- c++ - 向量构造函数的另一个违反直觉的行为
- azure - AzureIoTHub:如何以 JSON 格式发送遥测数据?
- node.js - 使用 Nginx 的 Docker Angular 应用程序:(111:连接被拒绝)同时连接到上游
- python - 如何在没有此错误的情况下将 python 文件转换为 exe 文件
- android - recyclerView - 使用 Kotlin 的 Android Studio 中未解决的参考错误
- http - 什么是 http 请求“HTTP/0.0”?
- node.js - 错误类型错误:无法读取 Angular 中未定义的属性“标题”
- python - python - 强制输入()从不同的线程读取换行符并向前执行
- python - 使用列表理解将字符串列表转换为列表列表