Android源码环境下,用记事本制作一个使用JNI的APK

制作过程

大家好! 这是博主第一次写博客,想做一个保姆级攻略,用词也许不准确、不规范,但是可以做到为自己的代码负责,码字不易,谢谢观看。
下面的源码都是博主自己编写的,实现打开手机LED灯带的小APK。
如果出现不一致,不成功的地方,需要自己对应你的Android源码因地制宜进行修改,主要思路是模仿。
学习Android路漫漫其修远兮,也希望自己和大家一样有足够的耐心、细心和恒心走下去。

java方面代码

一共六个内容:

  1. 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();
}
  1. 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();
        }
        
    }
}
  1. 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>
  1. 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>
  1. 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>

  1. res文件夹下的其他文件,个性化的图标选择;

太多了,参考其他APK吧,主要就是设置APK的图标。
在这里插入图片描述

  1. 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))

  1. proguard.flags,java代码规范混淆工具。

解决编译时遇到打断编译的warnings

-ignorewarnings

jni方面代码

  1. 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;
}
  1. 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
            

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值