Android源码环境下,用记事本制作一个使用JNI的APK
制作过程
大家好! 这是博主第一次写博客,想做一个保姆级攻略,用词也许不准确、不规范,但是可以做到为自己的代码负责,码字不易,谢谢观看。
下面的源码都是博主自己编写的,实现打开手机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