为什么使用ndk开发了,就是 稍微将代码隐藏一下。。Android 虽然打包能混淆,但是有些东西是不能混淆的,如下图,压缩密码把直勾勾的下载代码中。下面手把手稍微优化下。
將操作密码的部分我们通过 jni开发,在C++里面操作。
打开APP build.gradle进行配置NDK信息,配置CMake.
defaultConfig {
externalNativeBuild {
cmake {
cppFlags '-std=c++11'
abiFilters "arm64-v8a", "armeabi-v7a"
}
}
}
android{
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt') //这里可以使任意路径,我就放在跟Java代码同级了
version '3.10.2'
}
}
ndkVersion '21.0.6113669' //自己选一个你本机安装过的 ndk版本
}
接下来创建CMake文件,如果包含多个的话,就多赋值几个lib就好了。
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.10.2)
# Declares and names the project.
project("RiverwayApplication")
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
xm-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
xm-lib.cpp)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log)
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
xm-lib
# Links the target library to the log library
# included in the NDK.
${log-lib})
基本配置好了我们接下来就开始写cpp了。
首先创建好我们的Java类,并设置好native方法,
下面是C++代码!!通过反射调用,这样我们就把密码隐藏到C中!本人只是在学习c++中,可能这里还有优化的地方!!!
extern "C"
JNIEXPORT void JNICALL
Java_com_xm_j_XmJni_inputPassword(JNIEnv *env, jclass clazz, jobject zip_file) {
//拿到调用的对象实例,根据实例 获取class,在根据class获取 isEncrypted方法,()Z 返回布尔型不懂的话就多百度看看方法签名
jmethodID isEncryptedMethod = env->GetMethodID(env->GetObjectClass(zip_file), "isEncrypted",
"()Z");
//执行方法验证
jboolean isEncrypted = env->CallBooleanMethod(zip_file, isEncryptedMethod);
if (isEncrypted) {
std::string str = "258258258";//密码写在这里。
cin >> str;
int len = str.size() + 1;
char *arr = new char[len];
strcpy(arr, str.c_str());
//1.反射Java类中的setPassword方法,(根据对象拿到class,根据class拿方法)
jmethodID setPassword = env->GetMethodID(env->GetObjectClass(zip_file), "setPassword",
"([C)V");
//2.因为调用Java方法,所以需要将密码转正 jchar。
jcharArray array = env->NewCharArray(len);
jchar *pArray;
pArray = (jchar *) calloc(len, sizeof(jchar));
for (int i = 0; i < len; i++) {
*(pArray + i) = *(arr + i);
}
env->SetCharArrayRegion(array, 0, len, pArray);
//3.反射调用方法。
env->CallVoidMethod(zip_file, setPassword, array);
free(pArray);
delete[] arr;
}
}
下面是我个人学习的Java类和完整的C++代码。后续有补充在说吧。
Java类。包名:com.xm.j
public class XmJni {
static {
System.loadLibrary("xm");
}
public static native long callTime();
//这里的application 子类都行。你可以自定义更换这里的application
public static native Application getApp();
public static native void numberFunctionAddStr();
public static native void cppPointer();//指针和应用相关。
public static native void dataType();//c++数据结构
public static native void jstring2char(String j);
public static native void fstream();
}
c++类有点杂乱无章自己凑合看吧:
#include <jni.h>
#include <string>
#include <sstream>
#include <android/log.h>
using namespace std;
#include <iostream>
#include "cmath"
static const char *kTAG = "xiaoma_JNI";
#ifndef FINENGINE_LOG_H
#define FINENGINE_LOG_H
//#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,kTAG,__VA_ARGS__)
//#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,kTAG,__VA_ARGS__)
#define MLog(...) __android_log_print(ANDROID_LOG_ERROR,kTAG,__VA_ARGS__)
#endif
extern "C"
JNIEXPORT void JNICALL
Java_com_xm_j_XmJni_inputPassword(JNIEnv *env, jclass clazz, jobject zip_file) {
//拿到调用的对象实例,根据实例 获取class,在根据class获取 isEncrypted方法,()Z 返回布尔型不懂的话就多百度看看方法签名
jmethodID isEncryptedMethod = env->GetMethodID(env->GetObjectClass(zip_file), "isEncrypted",
"()Z");
//执行方法验证
jboolean isEncrypted = env->CallBooleanMethod(zip_file, isEncryptedMethod);
if (isEncrypted) {
std::string str = "258258258";//密码写在这里。
cin >> str;
int len = str.size() + 1;
char *arr = new char[len];
strcpy(arr, str.c_str());
//1.反射Java类中的setPassword方法,(根据对象拿到class,根据class拿方法)
jmethodID setPassword = env->GetMethodID(env->GetObjectClass(zip_file), "setPassword",
"([C)V");
//2.因为调用Java方法,所以需要将密码转正 jchar。
jcharArray array = env->NewCharArray(len);
jchar *pArray;
pArray = (jchar *) calloc(len, sizeof(jchar));
for (int i = 0; i < len; i++) {
*(pArray + i) = *(arr + i);
}
env->SetCharArrayRegion(array, 0, len, pArray);
//3.反射调用方法。
env->CallVoidMethod(zip_file, setPassword, array);
free(pArray);
delete[] arr;
}
}
//使用int64_t 接收。这样才是java层的long!!
//unsigned 无符号正数,, signed有符号,+ - 是正数的一半
string longToString(int64_t t) {
std::string result;
stringstream ss;
ss << t;
ss >> result;
return result;
}
extern "C"
JNIEXPORT jlong JNICALL
Java_com_xm_j_XmJni_callTime(JNIEnv *env, jclass clazz) {
jclass j = env->FindClass("java/util/Date");
jmethodID jm = env->GetMethodID(j, "<init>", "()V");
jobject jNewObject = env->NewObject(j, jm);
jmethodID jmetthodIdTime = env->GetMethodID(j, "getTime", "()J");
jlong jresultTime = env->CallLongMethod(jNewObject, jmetthodIdTime);
//如果类型对不上的话。那么看到的值肯定就是错误的。。一定要先确认类型对的上。通过Java反射创建的对象获取的long。。对应c++的 int64_t
MLog("打印的%s", longToString(jresultTime).c_str());
unsigned int d = 0;
unsigned long f = 0;
MLog("d定义,自定初始化%d,%lu", d, f);
std::string s1 = "xiao ming";
std::string s2 = "wo cao";
MLog("%s", (s1 +
s2).c_str()); //如果要传递给某个函数时候。则调用 some_c_api(s.c_str(), s.size()); some_c_api(char const *input, size_t length);
// for (unsigned int i = 1, countI = 9; i <= countI; i++) {
// for (unsigned int j = 1, countJ = i; j <= countJ; j++) {
// MLog("%d * %d = %d", i, j, i * j);
// }
// }
return jresultTime;
}
extern "C"
JNIEXPORT jobject JNICALL
Java_com_xm_j_XmJni_getApp(JNIEnv *env, jclass clazz) {
jclass activityThread = env->FindClass("android/app/ActivityThread");
jmethodID currentActivityThread = env->GetStaticMethodID(activityThread,
"currentActivityThread",
"()Landroid/app/ActivityThread;");
jobject at = env->CallStaticObjectMethod(activityThread, currentActivityThread);
//获取Application,也就是全局的Context
jmethodID getApplication = env->GetMethodID(activityThread, "getApplication",
"()Landroid/app/Application;");
jobject context = env->CallObjectMethod(at, getApplication);
return context;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_xm_j_XmJni_numberFunctionAddStr(JNIEnv *env, jclass clazz) {
//数学函数,Java也提供了 https://www.runoob.com/cplusplus/cpp-numbers.html
int i, j;
// 设置用时间做随机种子
srand((unsigned) time(NULL));
/* 生成 10 个随机数 */
for (i = 0; i < 3; i++) {
// 生成实际的随机数
j = rand();
MLog("当前随机数:%d", j);
}
//c++ 连接字符串
std::string hello = "hello word";
hello.append("111");
MLog("c++的String类更简单%s", hello.c_str());
//c 链接字符串
char str1[] = "hello ";
char str2[] = "word";
//方法一
char str3[strlen(str1) + strlen(str2) + 1];
str3[0] = '\0';//没有初始化,第一个元素设置为空!!这样的话就不需要像下面那样 设置足够大的值了。
strcat(str3, str1);
strcat(str3, str2);
//str2 追加到 str1里面。str1需要足够大!!!方法二
// char *s = strcat(str1, str2);
MLog("链接后的字符串:%s", str3);
char *cat = strstr(str3, "wo"); //如果没有找到返回null,找到了就是返回这个字符串的指针
if (cat == NULL) {
MLog("查找字符串:%s,没有找到这个字符串", cat);
} else {
MLog("查找到字符串:%s", cat);
}
}
extern "C"
JNIEXPORT void JNICALL
Java_com_xm_j_XmJni_jstring2char(JNIEnv *env, jclass clazz, jstring s) {
//直接jni层就提供了。直接返回jstring 转到 c++的指针
const char *rtn = env->GetStringUTFChars(s, NULL);
MLog("jstring to char %s", rtn);
}
// 函数声明,求数组的平局值!!先定义在使用,在实现
double getAverage(const int *arr, int count);
//跟Java一模一样方法重载。根据形参不同
double getAverage(const int *arr);
extern "C"
JNIEXPORT void JNICALL
Java_com_xm_j_XmJni_cppPointer(JNIEnv *env, jclass clazz) {
//学习c++指针
//###################认识什么什么是指针地址,16进制的内存地址
int var1;
char var2[10];
MLog("var1 变量的地址%p", &var1);
MLog("var2 变量的地址%p", &var2);
//######################### 操作指针!操作变量 var 或者操作 *ip 指针都是操作的一个东西
//总结,在= 右边 &:取出内存地址。。。。。 * :取值。
int var = 20; // 实际变量的声明
int *ip; // 指针变量的声明
ip = &var; //将var的指针交给ip。此时 指针变量ip和 变量var指向的是同一块内存
MLog("var 变量的值%d", var);
MLog("var 变量的指针地址%p", &var);
MLog("ip 指针变量的值%d", *ip);
var = 15;//改变变量
MLog("var 直接改变:变量的值%d", var);
MLog("ip 直接改变:指针变量的值%d", *ip);
*ip = 10;
MLog("var 通过指针改变:变量的值%d", var);
MLog("ip 通过指针改变:指针变量的值%d", *ip);
//结尾用-1表示,判断个数的时候也是用 -1 来判断,,你不允许出现-1的值
int varArr[] = {100, 200, 30, 50, 60, 0, 50, -1};
// double average = getAverage(varArr, 5); //明确知道个数的这样处理。
double average = getAverage(varArr);
MLog("varArr 的平局值%f", average);
//#####################################引用。 通过使用引用来替代指针,会使 C++ 程序更容易阅读和维护 类型后面跟& 是引用
// 在 = 左边的 &:是引用。 后面操作这个变量都是直接操作实际的值
int &update = varArr[0];
update = 50;
average = getAverage(varArr);
MLog("varArr 修改第一个引用的的平局值%f", average);
int *j = &varArr[5];//从第六个元素取出指针!
average = getAverage(j);
MLog("varArr 从第6个元素开始算平局值%f", average);
time_t now = time(0);
tm *time = localtime(&now);
MLog("1970到现在的秒数%ld", now);
MLog("当前时间:%d-%d-%d %d:%d:%d", time->tm_year + 1900, time->tm_mon + 1, time->tm_mday,
time->tm_hour,
time->tm_min,
time->tm_sec);
}
double getAverage(const int *arr, int count) {
int sum = 0;
for (int i = 0; i < count; ++i) {
sum += arr[i];
}
return double(sum) / count;
}
double getAverage(const int *arr) {
int sum = 0;
int count = 0;
while (arr[count] != -1) {
count++;
}
for (int i = 0; i < count; ++i) {
sum += arr[i];
}
return double(sum) / count;
}
//定义了数据结构
struct Books {
char title[50];
char author[50];
char subject[100];
int book_id;
};
//定义了数据结构!! class全部都是private,但是你可以通过添加public 为公开的!
class BookClass {
public:
//公开的字写在最上面就是全公开,将不需要公开的写在上面
char title[50];
char author[50];
char subject[100];
protected:
int book_id = 10;
//private: //访问修饰符,默认是private!
//protected:
//public:
// virtual char *toString(){ //virtual 必须有继承关系,默认调用子类的函数!
//
// }
//virtual 多态,=0 纯虚函数,子类必须实现不然编译器报错!!也可以放方法体虚函数,这个写法有点像Java的方法(abstract)
virtual char *toString() = 0;
};
class NumberBookClass : public BookClass {
public:
int passNumber;
NumberBookClass(const char *t = "未知书籍", const char *a = "未知作者") {
//构造方法。。如果没有值就使用默认值。循环添加到 父类的变量中
int count = 0;
while (t[count] != '\0') {
title[count] = t[count];
count++;
}
title[count] = '\0';
count = 0;
while (a[count] != '\0') {
author[count] = a[count];
count++;
}
author[count] = '\0';
}
~NumberBookClass() {
//析构函数。当被调用delet的时候。
}
//将父类被保护的 book_id指针返给调用者
int *getSuperBookId() {
return &book_id;
}
char *toString() {
std::string str = std::string("passNumber:") + std::to_string(passNumber) +
std::string(",书名:") + std::string(title) +
std::string(",作者:") + std::string(author) +
std::string(",book_id:") + std::to_string(book_id);
return const_cast<char *>(str.c_str());
}
};
#include <stdbool.h>
extern "C"
JNIEXPORT void JNICALL
Java_com_xm_j_XmJni_dataType(JNIEnv *env, jclass clazz) {
//初步看有点像 Java的 JavaBean
Books book1;
strcpy(book1.title, "童话故事");
strcpy(book1.author, "小马");
strcpy(book1.subject, "这是可爱的童话故事哟");
book1.book_id = 12345;
//定义的数据类型直接可以 . 来进行操作他和赋值。。
MLog("这本:%s的,作者是:%s,书的基本描述:%s", book1.title, book1.author, book1.subject);
//如果交给指针后。则必须用 -> 来使用
struct Books *p_book1 = &book1;
MLog("这本:%s的,作者是:%s,书的基本描述:%s", p_book1->title, p_book1->author, p_book1->subject);
// typedef 给 数据类型 1、重新定义一个新名字,2、简化声明(隐藏指针什么的)
// typedef long int *pint32;
// pint32 x, y, z; // x, y 和 z 都是指向长整型 long int 的指针。
typedef Books B;
B b;
NumberBookClass numberBookClass = NumberBookClass("c++", "小马");
numberBookClass.passNumber = 60;//60分及格
//因为父类是被保护的。只有子类才能获取。。我本来没办法修改,但是我通过子类获取到父类的成员变量指针,我就可以修改了
int *protectedBookId = numberBookClass.getSuperBookId();
MLog("通过子类派生类 的方法获取了指针:%d", *protectedBookId);
*protectedBookId = 1314520;
MLog("获取指针,非子类赋值:%d", *protectedBookId);
MLog("这是子类拼接的\n%s", numberBookClass.toString());
}
extern "C"
JNIEXPORT void JNICALL
Java_com_xm_j_XmJni_fstream(JNIEnv *env, jclass clazz) {
}