最近项目中遇到需要在C++层进行加密,然后编译成so。我们知道,MAC地址能够辨别设备的唯一性。所以有个需求就是需要在C++层获取MAC地址,这里我们就需要用到JNI编程了,话不多说,开始看看如何获取吧。
在这之前,不要忘记添加权限
android:name="android.permission.INTERNET" />
android:name="android.permission.READ_PHONE_STATE" />
android:name="android.permission.ACCESS_WIFI_STATE" />
首先,我们来看看java中是怎么获取的。Android6.0之前的方法在6.0之后已经无法获取成功。所以我们直接使用最新的获取方法。代码很简单,通过静态方法getByName获取NetworkInterface实例,然后通过getHardwareAddress()获取byte[]数组,注意这里获取到的是16进制的数组,需要转换。
/** * 获取设备MAC地址 * */ public static String getMacAddress() { /*获取mac地址有一点需要注意的就是android 6.0版本后,以下注释方法不再适用,不管任何手机都会返回"02:00:00:00:00:00"这个默认的mac地址,这是googel官方为了加强权限管理而禁用了getSYstemService(Context.WIFI_SERVICE)方法来获得mac地址。*/ // String macAddress= ""; // WifiManager wifiManager = (WifiManager) MyApp.getContext().getSystemService(Context.WIFI_SERVICE); // WifiInfo wifiInfo = wifiManager.getConnectionInfo(); // macAddress = wifiInfo.getMacAddress(); // return macAddress; String macAddress = null; StringBuffer buf = new StringBuffer(); NetworkInterface networkInterface = null; try { networkInterface = NetworkInterface.getByName("eth1"); if (networkInterface == null) { networkInterface = NetworkInterface.getByName("wlan0"); } if (networkInterface == null) { return "02:00:00:00:00:02"; } byte[] addr = networkInterface.getHardwareAddress(); for (byte b : addr) { buf.append(String.format("%02X:", b)); } if (buf.length() > 0) { buf.deleteCharAt(buf.length() - 1); } macAddress = buf.toString(); } catch (SocketException e) { e.printStackTrace(); return "02:00:00:00:00:02"; } return macAddress; }然后我们根据此方法用JNI来实现
//通过JNI找到java中的NetworkInterface类 jclass cls_networkInterface = env->FindClass("java/net/NetworkInterface"); if (cls_networkInterface == 0) { return env->NewStringUTF(""); } //找到getByName方法 jmethodID jmethodID1 = env->GetStaticMethodID(cls_networkInterface, "getByName", "(Ljava/lang/String;)Ljava/net/NetworkInterface;"); if (jmethodID1 == 0) return env->NewStringUTF(""); std::string ss = "wlan0"; jstring jss2 = env->NewStringUTF(ss.c_str()); //调用getByname方法返回NetworkInterface的实例 jobject jobject1 = env->CallStaticObjectMethod(cls_networkInterface, jmethodID1, jss2); //找到getHardAddress方法 jmethodID getHardwareAddress = env->GetMethodID(cls_networkInterface, "getHardwareAddress", "()[B"); if (getHardwareAddress == 0) return env->NewStringUTF(""); //调用getHardAddress方法获取MAC地址的byte[]数组 jbyteArray jbyte1 = (jbyteArray)env->CallObjectMethod(jobject1, getHardwareAddress); //下面一些列流程就是讲byte[]数组转换成char类型字符在转换成字符串 jbyte * olddata = (jbyte*)env->GetByteArrayElements(jbyte1, 0); jsize oldsize = env->GetArrayLength(jbyte1); // BYTE定义为 #define BYTE unsigned char BYTE* bytearr = (BYTE*)olddata; int len = (int)oldsize; char* data = (char*)env->GetByteArrayElements(jbyte1, 0); char *temp = new char[len*2 + 1]; memset(temp,0,len*2 +1); for (int i = 0; i < len; i++) { char * buffer = new char[2]; memset(buffer,2,0); sprintf(buffer, "%02X", data[i]); memcpy(temp+i*2, buffer, 2); delete[] (buffer); } jstring jMac = charTojstring(env, temp); delete[] temp; return jMac;
其中BYTE为宏定义 在cpp文件中加上
#define BYTE unsigned charcharTojstring方法如下:
jstring charTojstring(JNIEnv* env, const char* pat) { //定义java String类 strClass jclass strClass = (env)->FindClass("java/lang/String"); //获取String(byte[],String)的构造器,用于将本地byte[]数组转换为一个新String jmethodID ctorID = (env)->GetMethodID(strClass, "" , "([BLjava/lang/String;)V"); //建立byte数组 jbyteArray bytes = (env)->NewByteArray(strlen(pat)); //将char* 转换为byte数组 (env)->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*) pat); // 设置String, 保存语言类型,用于byte数组转换至String时的参数 jstring encoding = (env)->NewStringUTF("GB2312"); //将byte数组转换为java String,并输出 return (jstring) (env)->NewObject(strClass, ctorID, bytes, encoding); }
主要的难点就是如何进行数据的转换。JNI通过一系列类似java反射方法来完成java所做的事情,来进行对java的调用。具体参数转换网上很多,这里就不列出。这样我们就获取到了设备的MAC地址,当然一般情况下我们不需要再返回java层(java能获取,为什么还这么麻烦在c层获取,再返回呢,除非我有病。)二十直接在JNI层对MAC地址进行操作,比如加密。
文章就到这了,大家有兴趣还可以对java的很多方法进行JNI调用,除了系统的类,我们也可以自定义类,如果需要对代码加密,不想让别人轻易的反编译,也可以使用这招哦,毕竟so还是很安全的。