Android的网络请求
Android开发的初学者大部分都是基于Retrofit+Okhttp发送的网络请求。今天我带大家使用NDK底层代码来发送网络请求,提升应用的安全性。当然要做到真正的安全还要结合一些其他的手段,比如防调试、防静态分析(比如隐藏函数符号)、防动态注入、防接口劫持、防逆向破解(比如对接口请求进行加密)等。今天我们要用到的库是cURL库。
编译curl库
下载和安装
MacOS
我先使用MacBook安装测试一下。
sh
git clone https://github.com/curl/curl.git
cd curl
我们可以从Github下载curl的源代码。
然后使用Homebrew包管理器安装所有需要的库。
sh
brew install autoconf automake libtool openssl
安装过程一般不会有什么坑。 然后我们创建一个构建文件夹,命名为build,执行cmake命令试一下。报以下错误,说明我们少下载了依赖库。
sh
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DCURL_USE_OPENSSL=ON
那就给它一个libpsl库,安装,然后再次配置cmake编译脚本。
sh
brew install libpsl
cmake .. -DCMAKE_BUILD_TYPE=Release -DCURL_USE_OPENSSL=ON
这里提一嘴CMake的编译模式,用于指定编译器编译出来的是动态库还是静态库,包不包含调试信息等。
常见的CMake 编译模式
模式 | 描述 |
---|---|
Debug |
含有调试信息,适用于开发调试,默认不开启优化。 |
Release |
适用于正式发布,启用优化(-O3 )。 |
RelWithDebInfo |
结合 Release 和 Debug ,优化代码但保留调试信息。 |
MinSizeRel |
适用于嵌入式或小型二进制,优化大小(-Os )。 |
配置好后,我们执行make命令开始构建。
sh
make -j$(nproc)
可以看到构建成功了。接下来安装到电脑上。
sh
make install

最后我们测试安装成功没有。
sh
curl --version
查看如果正确显示版本号,就说明我们的电脑成功安装了curl。
Linux
但是我们Android开发编译Mac的dylib动态库可以吗?肯定是不行的啊。安卓底层是Linux内核,只能打.so
动态库或.a
的静态库。
Linux的话我要把我的云服务器用上了,我跑Java服务端程序一般用的是CentOS,就不单独再开一台Ubuntu来编译动态库了,反正也可以用。这次我打算使用官方的压缩包进行编译。
sh
wget https://curl.se/download/curl-8.2.1.tar.gz
tar -xvzf curl-8.2.1.tar.gz
cd curl-8.2.1
下载和解压curl包。

cURL官网的服务器带宽有点慢,稍安勿躁,等不及也可以从Github下载。
sh
yum install -y build-essential autoconf libtool pkg-config
这里注意下Linux也分版本的,我是用的CentOS版本的Linux,如果用的Ubuntu版本的Linux,则要用apt-get install
命令。

同样的,也要安装一些依赖库。
sh
yum install -y cmake
yum install -y openssl-devel
yum install -y epel-release
yum install -y cmake3
如果 cmake3
安装成功,你可以用它替换 cmake
,需要注意当前版本的curl需要用cmake3进行编译,否则会失败。
sh
sudo ln -sf /usr/bin/cmake3 /usr/bin/cmake

sh
./configure --with-openssl
使用configure命令配置cmake脚本,需要支持openssl,因为加解密需要用到openssl。后面openssl也是要打成so文件的。
那么我们就不将curl编译成动态库了,而是编译成静态库。这样我们业务相关的底层代码可以和静态库打成一个动态库。
那么怎么编译静态库呢?其实很简单,有两种方式配置。
CMake | cmake .. -DBUILD_SHARED_LIBS=OFF |
---|
configure | ./configure --disable-shared --enable-static |
---|
我们就使用cmake命令配置吧。
sh
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF
把构建共享库(动态库)关掉即可。然后执行make命令进行编译。
sh
make -j$(nproc)

可以看到我们已经成功生成了.a
静态库文件。
代码中引入静态库
在CMakeList.txt文件中加入以下代码将静态库参与编译,这两行代码是一组代码,不要单独使用,使用了IMPORTED,就要使用set_target_properties
。set_target_properties
用来指定库的路径。
sh
add_library(curl
STATIC
IMPORTED)
set_target_properties(curl
PROPERTIES IMPORTED_LOCATION
${CMAKE_CURRENT_SOURCE_DIR}/curl/libs/${ANDROID_ABI}/libcurl.a
)
最后链接库的时候像这样即可。
sh
# 链接库
target_link_libraries(
${CMAKE_PROJECT_NAME}
crypto
ssl
curl #curl依赖于zlib和openssl
z
android
log
)
使用curl发送网络请求
GET请求
cpp
#include <iostream>
#include <sstream>
#include <curl/curl.h>
#include <json/json.h>
size_t WriteCallback(char* ptr, size_t size, size_t nmemb, void* userdata) {
((std::string*)userdata)->append(ptr, size * nmemb);
return size * nmemb;
}
void SendGetRequest(const std::string& param1, const std::string& param2) {
CURL *curl;
CURLcode res;
std::string readBuffer;
curl = curl_easy_init();
if (curl) {
std::string url = "https://yourproject.com/getApi?param1="
+ param1 + "¶m2=" + param2;
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // 关闭 SSL 验证(仅用于测试)
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
if (res != CURLE_OK) {
std::cerr << "Request Error: " << curl_easy_strerror(res) << std::endl;
}
// 解析 JSON 响应
Json::Value root;
Json::CharReaderBuilder reader;
std::string errors;
std::istringstream s(readBuffer);
if (!Json::parseFromStream(reader, s, &root, &errors)) {
std::cerr << "JSON Parse Error: " << errors << std::endl;
}
// 开始解析json数据
if (root["field1"].asString() == "ok" && root["field2"].isObject()) {
Json::Value field2 = root["field2"];
int field3 = field2["field3"].asInt();
std::string field4 = field2["field4"].asString();
}
}
}
当然这之前还要引入jsoncpp的头文件。
sh
# 目录扫描
file(GLOB_RECURSE SOURCE_CPP_FILES_OF_JSONCPP "${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/jsoncpp/*.inl")
add_library(
${CMAKE_PROJECT_NAME} SHARED
# 这里省略你业务的cpp文件若干行
${SOURCE_CPP_FILES_OF_JSONCPP}
)
POST请求
cpp
#include <iostream>
#include <sstream>
#include <curl/curl.h>
#include <json/json.h>
// 回调函数
size_t WriteCallback(char *ptr, size_t size, size_t nmemb, void *userdata) {
((string *) userdata)->append(ptr, size * nmemb);
return size * nmemb;
}
void SendPostRequest() {
CURL *curl;
CURLcode res;
std::string readBuffer;
curl = curl_easy_init();
if (curl) {
std::string url = "https://yourproject.com/postApi";
Json::Value json;
json["key1"] = "value1";
json["key2"] = "value2";
Json::FastWriter fwriter;
std::string body = fwriter.write(json);
std::string response;
// 设置请求头
struct curl_slist *headers = nullptr;
headers = curl_slist_append(headers, "Content-Type:application/json");
// headers = curl_slist_append(headers, "Content-Type:application/x-www-form-urlencoded");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
// 设置请求为post请求
curl_easy_setopt(curl, CURLOPT_POST, 1);
// 设置post请求的参数
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
/////////////////通用请求////////////////
// 设置请求的URL地址
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
// 设置数据接收函数
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
// 进行网络操作
res = curl_easy_perform(curl);
/////////////////数据响应////////////////
// 查询HTTP状态码
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
if (res != CURLE_OK) {
std::cerr << "Request Error: " << curl_easy_strerror(res) << std::endl;
}
// 解析 JSON 响应
Json::Value root;
Json::CharReaderBuilder reader;
std::string errors;
std::istringstream s(readBuffer);
if (!Json::parseFromStream(reader, s, &root, &errors)) {
std::cerr << "JSON Parse Error: " << errors << std::endl;
}
// 开始解析json数据
if (root["field1"].asString() == "ok" && root["field2"].isObject()) {
Json::Value field2 = root["field2"];
int field3 = field2["field3"].asInt();
std::string field4 = field2["field4"].asString();
}
}
}
写在最后
那么使用NDK底层发送网络请求的大致流程就是这样了,如果有用到openssl,则自行编译成so文件引入即可。使用curl+jsoncpp+openssl可以开发高安全性的接口请求应用,具有很高的安全性和很强的隐蔽性,希望你不要拿来干坏事,干坏事也千万别说我教你的,哈哈。