Android原始碼環境下,用記事本制作一個使用JNI的APK
- 制作程序
- java方面代碼
- jni方面代碼
- 完成JNI部分的撰寫后,生成SO庫
- 最后附上一個清晰的檔案結構樹
制作程序
大家好! 這是博主第一次寫博客,想做一個保姆級攻略,用詞也許不準確、不規范,但是可以做到為自己的代碼負責,碼字不易,謝謝觀看,
下面的原始碼都是博主自己撰寫的,實作打開手機LED燈帶的小APK,
如果出現不一致,不成功的地方,需要自己對應你的Android原始碼因地制宜進行修改,主要思路是模仿,
學習Android路漫漫其修遠兮,也希望自己和大家一樣有足夠的耐心、細心和恒心走下去,
java方面代碼
一共六個內容:
- JNINative .java ,對連接的so庫和cpp中的方法進行申明;
package com.example.led;
public class JNINative {
static {
//加載so庫,"led_jni"為你編譯JNI后生成的so庫名去掉前綴lib和后綴.so
System.loadLibrary("led_jni");
}
//宣告cpp中定義的方法
public static native boolean device_test_led_r_on_jni();
public static native boolean device_test_led_r_off_jni();
public static native boolean device_test_led_path_open_jni();
}
- MainActivity.java ,該怎么實作你想要的功能就在這里啦;
這里提一嘴博主自己初學Android原始碼編譯的時候遇到的問題,因為之前使用Android Studio4.1等最新版本學習Android的,所以會extends AppCompatActivity并import androidx下的包,這樣的話,在Android.mk檔案撰寫的時候,不容易找到原始碼參考,對初學者不太友好,
所以建議養成用記事本撰寫Android原始碼的習慣,用保守的語法,如extends Activity和import android.app.Activity,
package com.example.led;
import android.Manifest;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.content.pm.PackageManager;
import java.util.ArrayList;
import java.util.List;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.app.Activity;
public class MainActivity extends Activity implements View.OnClickListener {
private int led_status = 0;
private static final String TAG = "DEVICE_TEST_LED: java: ";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
JNINative.device_test_led_path_open_jni();
init();
}
public void init() {
Button btnOpen = this.findViewById(R.id.open);
Button btnClose = this.findViewById(R.id.close);
btnOpen.setOnClickListener(this);
btnClose.setOnClickListener(this);
}
public void open() {
led_status = 1;
JNINative.device_test_led_r_on_jni();
Log.e(TAG,"open : h = "+led_status);
}
public void close() {
led_status = 0;
JNINative.device_test_led_r_off_jni();
Log.e(TAG,"close : h ="+led_status);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.open:
open();
break;
case R.id.close:
close();
break;
default:
break;
}
}
@Override
public void onStop() {
super.onStop();
if(led_status==1) {
led_status=0;
close();
}
}
}
- activity_main.xml, APK的界面樣式;
這里和第二點一樣,建議盡可能使用LinearLayout等簡單的格式,不要涉及到androidx,
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/open"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginLeft="14dp"
android:layout_marginTop="10dp"
android:text="open"
/>
<Button
android:id="@+id/close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginLeft="14dp"
android:layout_marginTop="10dp"
android:text="close"
/>
</LinearLayout>
- AndroidManifest.xml, 清單檔案,也是一個完整的APK必備的啦;
解釋一下android:sharedUserId=“android.uid.system”,是為了讓我的APK穿上system大佬的外衣,因為我的APK需要他的權限,
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.led"
android:sharedUserId="android.uid.system">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<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>
- values檔案夾,內含下面的三個組態檔

因為是用記事本撰寫的APK,這都是從同原始碼下其他APK檔案下復制過來的,這樣能夠確保可以使用,
這三個檔案,博主都建議你們模仿同原始碼下其他APK檔案中的修改,
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
strings.xml
<resources>
<string name="app_name">LED</string>
</resources>
styles.xml
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Base application theme. -->
<style name="AppTheme" parent="@android:style/Theme.Material.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:colorPrimaryDark">@color/purple_200</item>
<item name="android:colorPrimary">@color/purple_500</item>
<item name="android:actionModeStyle">@color/purple_700</item>
</style>
</resources>
- res檔案夾下的其他檔案,個性化的圖示選擇;
太多了,參考其他APK吧,主要就是設定APK的圖示,

- Android.mk,編譯就靠它啦;
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
#optional:指該模塊在所有版本下都編譯
#user:指該模塊只在user版本下才編譯
#eng:指該模塊只在eng版本下才編譯
#tests:指該模塊只在tests版本下才編譯
LOCAL_MODULE_TAGS := optional
#資源檔案路徑path,一般都是res
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
#java原始碼路徑,all-java-files-under宏的定義是在build/core/definitions.mk中,可以呼叫all-java-files-under得到,(這種形式來包含local_path目錄下的所有java檔案)
LOCAL_SRC_FILES := $(call all-java-files-under, java)
#應用(apk)的模塊名
LOCAL_PACKAGE_NAME := LEDControl
LOCAL_CERTIFICATE := platform
#設定后,會使用sdk的hide的api來編譯
LOCAL_PRIVATE_PLATFORM_APIS = true
#宏控,判斷使用32位還是64位的so庫,這個so庫是我從out目錄下,jni生成以后,手動拿過來,并建好檔案夾放好的(文章最后有檔案夾結構)
ifeq ($(strip $(TARGET_ARCH)),arm64)
LOCAL_MULTILIB := both
LOCAL_PREBUILT_LIBS := libled_jni:libs/arm64-v8a/libled_jni.so
else
ifeq ($(strip $(TARGET_ARCH)),x86_64)
$(warning "libled_jni x86_64")
LOCAL_MULTILIB := 64
LOCAL_PREBUILT_LIBS := libled_jni:libs/x86_64/libled_jni
else
LOCAL_MULTILIB := 32
LOCAL_PREBUILT_LIBS := libled_jni:libs/armeabi-v7a/libled_jni.so
endif
endif
#可以把so庫打包編譯進入apk
LOCAL_MODULE_INCLUDE_LIBRARY := true
#引java中用到包,不確定寫法的話,可以像博主一樣,多寫一點,只要不是不存在這個包是不會報錯的
LOCAL_STATIC_ANDROID_LIBRARIES := \
$(ANDROID_SUPPORT_DESIGN_TARGETS) \
android-support-percent \
android-support-transition \
android-support-compat \
android-support-core-ui \
android-support-media-compat \
android-support-v13 \
android-support-v14-preference \
android-support-v7-appcompat \
android-support-v7-gridlayout \
android-support-v7-preference \
android-support-v7-recyclerview
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
LOCAL_JAVA_LIBRARIES := android-support-v4
#使用目錄下的proguard.flags檔案混淆java代碼
LOCAL_PROGUARD_FLAG_FILES += proguard.flags
#編譯一個 Java 的靜態庫
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
- proguard.flags,java代碼規范混淆工具,
解決編譯時遇到打斷編譯的warnings
-ignorewarnings
jni方面代碼
- led_control_jni.cpp,內含用c++寫的(如果是.c檔案就是用c語言寫的)方法,及完整的功能代碼哦;
#pragma GCC diagnostic ignored "-Wunused"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#include <jni.h>
#include <utils/Log.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <pthread.h>
#include <linux/serial.h>
#include <poll.h>
#include <dlfcn.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/android_view_Surface.h>
#if defined(TRUSTKERNEL_TEE_SUPPORT)
#include <kphproxy.h>
#include <pl.h>
#endif
#include "JNIHelp.h"
#include <android/log.h>
#include <android/native_activity.h>
#define LED_TURN_TEST_PATH "手機內部路徑不重要"
#define LED_TURN_PATH "手機內部路徑不重要"
#define LED_EFFECT_PATH "手機內部路徑不重要"
#define BUF_LEN 16
#define LOG_TAG "DEVICE_TEST_LED:cpp"
#define LOG(LEVEL,...) __android_log_print(LEVEL, LOG_TAG, __VA_ARGS__)
#define LOGI(...) LOG(ANDROID_LOG_INFO,__VA_ARGS__)
#define LOGE(...) LOG(ANDROID_LOG_ERROR, __VA_ARGS__)
jboolean device_test_led_path_open(JNIEnv *env, jobject thiz)
{
//請開始你的表演,實作你的功能(用c++)
}
jboolean device_test_led_r_on(JNIEnv *env, jobject thiz)
{
//請開始你的表演,實作你的功能(用c++)
}
jboolean device_test_led_r_off(JNIEnv *env, jobject thiz)
{
//請開始你的表演,實作你的功能(用c++)
}
//JNI register 這個路徑一定不能錯!!!
static const char *classPathName = "com/example/led/JNINative";
//給你的函式起個c++的名字
static JNINativeMethod methods[] = {
{"device_test_led_r_on_jni", "()Z", (void*)device_test_led_r_on },
{"device_test_led_r_off_jni", "()Z", (void*)device_test_led_r_off },
{"device_test_led_path_open_jni", "()Z", (void*)device_test_led_path_open },
};
/*
* Register several native methods for one class.
*/
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL)
{
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0)
{
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
* Register native methods for all classes we know about.
* returns JNI_TRUE on success.
*/
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, classPathName,
methods, sizeof(methods) / sizeof(methods[0])))
{
return JNI_FALSE;
}
return JNI_TRUE;
}
// ----------------------------------------------------------------------------
/*
* This is called by the VM when the shared library is first loaded.
*/
typedef union
{
JNIEnv* env;
void* venv;
} UnionJNIEnvToVoid;
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
UnionJNIEnvToVoid uenv;
uenv.venv = NULL;
jint result = -1;
JNIEnv* env = NULL;
if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK)
{
goto bail;
}
env = uenv.env;
if (registerNatives(env) != JNI_TRUE)
{
goto bail;
}
result = JNI_VERSION_1_4;
bail:
return result;
}
- Android.mk,同java,根據此檔案進行編譯,
#每個Android.mk檔案必須以定義LOCAL_PATH為開始,它用于在開發tree中查找源檔案,
#宏my-dir 則由Build System提供,回傳包含Android.mk的目錄路徑,
LOCAL_PATH := $(call my-dir)
#CLEAR_VARS 變數由Build System提供,并指向一個指定的GNU Makefile,由它負責清理很多LOCAL_xxx.如:LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES等等,但不清理LOCAL_PATH.
#這個清理動作是必須的,因為所有的編譯控制檔案由同一個GNU Make決議和執行,其變數是全域的,所以清理后才能避免相互影響,
include $(CLEAR_VARS)
#簽名:該APK完成一些系統的核心功能,經過對系統中存在的檔案夾的訪問測驗,這種方式編譯出來的APK所在行程的UID為system,我的功能包含對手機系統內檔案的寫入和修改,所以需要這個權限,
LOCAL_CERTIFICATE := platform
#LOCAL_SRC_FILES變數必須包含將要打包如模塊的C/C++ 原始碼,
LOCAL_SRC_FILES := led_control_jni.cpp
#一個可選的path串列,相對于NDK ROOT 目錄,編譯時,將會把這些目錄附上,
LOCAL_C_INCLUDES := \
$(JNI_H_INCLUDE) \
frameworks/base/core/jni/include \
frameworks/native/libs/nativewindow/include \
frameworks/native/libs/arect/include \
frameworks/base/libs/androidfw/include \
libnativehelper/include/nativehelper \
#要鏈接到本模塊的動態庫
LOCAL_SHARED_LIBRARIES := \
liblog \
ifeq ($(strip $(TRUSTKERNEL_TEE_SUPPORT)), yes)
#相當于在所有源檔案中增加一個宏定義#define TRUSTKERNEL_TEE_SUPPORT,即能在所編譯的Cpp檔案中使用
LOCAL_CFLAGS += -DTRUSTKERNEL_TEE_SUPPORT
LOCAL_SHARED_LIBRARIES += \
libkphproxy_system \
libpl_system
endif
#可以用它來添加系統庫
LOCAL_LDLIBS += -llog
#Prelink利用事先鏈接代替運行時鏈接的方法來加速共享庫的加載
#好處:加快起動速度,減少部分記憶體開銷
#壞處:每次更新動態共享庫時,相關的可執行檔案都需要重新執行一遍Prelink才能保證有效,因為新的共享庫中的符號資訊、地址等很可能與原來的已經不同了,這就是為什么 android framework代碼一改動,這時候就會導致相關的應用程式重新被編譯,
LOCAL_PRELINK_MODULE := false
#LOCAL_MODULE模塊名,名字必須唯一且不包含空格,Build System會自動添加適當的前綴和后綴,要產生動態庫,則生成libled_jn.so,若按我下面的寫法就不會再添加前綴
LOCAL_MODULE := libled_jni
#BUILD_SHARED_LIBRARY:是Build System提供的一個變數,指向一個GNU Makefile Script,它負責收集自從上次呼叫 include $(CLEAR_VARS) 后的所有LOCAL_XXX資訊,并決定編譯成什么,
#BUILD_STATIC_LIBRARY:編譯為靜態庫,
#BUILD_SHARED_LIBRARY :編譯為動態庫
#BUILD_EXECUTABLE:編譯為Native C可執行程式
include $(BUILD_SHARED_LIBRARY)
完成JNI部分的撰寫后,生成SO庫
用單編命令
source build/envsetup.sh
lunch
mmm xxx/xxx/xxx/jni
然后在原始碼 out\target\product\專案名\system\lib 和 out\target\product\專案名\system\lib64 下找到生成的so庫,如下圖:

手動新建檔案夾并放入對應檔案夾:

arm64-v8a 和 armeabi-v7a 是放32位的
x86_64 是放64位的
不要放錯了呦
然后就可以編譯APK,用adb把APK放進手機了,大功告成!
最后附上一個清晰的檔案結構樹
檔案夾 PATH 串列
卷序列號為 4E94-09D9
D:/LED
│ Android.mk
│ AndroidManifest.xml
│ proguard.flags
│
├─java
│ └─com
│ └─example
│ └─led
│ JNINative.java
│ MainActivity.java
│
├─jni
│ Android.mk
│ led_control_jni.cpp
│
├─libs
│ ├─arm64-v8a
│ │ libled_jni.so
│ │
│ ├─armeabi-v7a
│ │ libled_jni.so
│ │
│ └─x86_64
│ libled_jni.so
│
└─res
├─drawable
│ ic_launcher_background.xml
│
├─drawable-v24
│ ic_launcher_foreground.xml
│
├─layout
│ activity_main.xml
│
├─mipmap-anydpi-v26
│ ic_launcher.xml
│ ic_launcher_round.xml
│
├─mipmap-hdpi
│ ic_launcher.png
│ ic_launcher_round.png
│
├─mipmap-mdpi
│ ic_launcher.png
│ ic_launcher_round.png
│
├─mipmap-xhdpi
│ ic_launcher.png
│ ic_launcher_round.png
│
├─mipmap-xxhdpi
│ ic_launcher.png
│ ic_launcher_round.png
│
├─mipmap-xxxhdpi
│ ic_launcher.png
│ ic_launcher_round.png
│
└─values
colors.xml
strings.xml
styles.xml
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/277370.html
標籤:其他
