5.1 登陆功能
1.创建简单登陆界面
建立EditText和Button的关联。 相对java代码如下:
package com.cpp.itcast.black_mocar;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends AppCompatActivity {
public Button btn_login;//登陆按钮
public EditText et_username;//用户名文本框
public EditText et_passwd;//密码文本框
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_login = (Button)findViewById(R.id.button_login);
et_username =(EditText)findViewById(R.id.editText_username);
et_passwd=(EditText)findViewById(R.id.editText_passwd);
//绑定登陆按钮的点击事件
btn_login.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String username = et_username.getText().toString();
String passwd = et_passwd.getText().toString();
Log.e("MainActivity", username+", "+passwd);
}
});
}
}
2.创建JNI类
package com.cpp.itcast.black_mocar;
/**
* Created by itcast on 16-10-25.
*/
public class MoCarJni {
protected static MoCarJni obj = null;
public static MoCarJni getInstance() {
if (obj == null) {
obj = new MoCarJni();
}
return obj;
}
//声明登陆native接口
public native boolean Login(String username, String passwd);
//加载cpp给提供的 动态库
static {
System.loadLibrary("MoCar"); //libMoCar
}
}
3 生成对应的JNI头文件
/home/itcast/AndroidStudioProjects/Black-mocar/app/src/main/java
javah -jni com.cpp.itcast.black_mocar.MoCarJni
4. 创建jni路径和Android.mk
在jni路径下,创建MoCarJni.cpp文件和Android.mk
其中Android.mk如下:
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
#libMoCar.so
LOCAL_MODULE := MoCar
LOCAL_SRC_FILES := MoCarJni.cpp
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
MoCarJni.cpp如下:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <jni.h>
#include <android/log.h>
char* jstringTostring(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
//char* to jstring
jstring stoJstring(JNIEnv* env, const char* pat)
{
jclass strClass = env->FindClass("Ljava/lang/String;");
jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = env->NewByteArray(strlen(pat));
env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
jstring encoding = env->NewStringUTF("utf-8");
return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
}
#ifdef __cplusplus
extern "C" {
#endif
/*
MoCarJni 登陆功能
*/
JNIEXPORT jboolean JNICALL Java_com_cpp_itcast_black_1mocar_MoCarJni_Login
(JNIEnv *env, jobject obj, jstring username, jstring passwd)
{
char *pUsername = jstringTostring(env, username);
char *pPasswd = jstringTostring(env, passwd);
__android_log_print(ANDROID_LOG_ERROR, "MoCarJni", "username=%s, passwd=%s\n", pUsername, pPasswd);
if (pUsername != NULL) {
free(pUsername);
}
if (pPasswd != NULL) {
free(pPasswd);
}
return true;
}
#ifdef __cplusplus
}
#endif
5.编译及建立软连接
进入jni目录
cd jni/
ndk-build
会在项目路径下生成Libs文件,里面有cpp的so库。
建立软连接
cd /home/itcast/AndroidStudioProjects/Black-mocar/app/src/main/
ln -s /home/itcast/AndroidStudioProjects/Black-mocar/ jniLibs
然后运行程序,确保在输入数据之后,Login能够成功调用,并打出debug信息。
6 配置和部署libcurl
将生成的libcurl.a 和所对应的头文件拷贝到Android项目中的jni/路径下。
cd ./curl-android-ios-master/prebuilt-with-ssl/android/
cp armeabi/libcurl.a ~/AndroidStudioProjects/Black-mocar/jni/
cp include/curl/ ~/AndroidStudioProjects/Black-mocar/jni/ -R
7 修改Android.mk 关联libcurl
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
# libcurl.a
LOCAL_MODULE := libcurl
LOCAL_SRC_FILES := libcurl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
#libMoCar.so
LOCAL_MODULE := MoCar
LOCAL_SRC_FILES := MoCarJni.cpp
LOCAL_LDLIBS := -llog -lz
LOCAL_STATIC_LIBRARIES := libcurl
include $(BUILD_SHARED_LIBRARY)
8 开放Android联网权限
打开AndroidManifest.xml:
在 标签里面加入如下标签:
<uses-permission android:name="android.permission.INTERNET"/>
9 登陆协议
确保环境搭建完毕,并且没有问题,我们可以暂时规定一个登陆的协议。
这里我们采用 json 数据格式
url路径为
客户端登陆发送:
{
username: "aaa",
password: "bbb",
logintype: 1
}
服务端回复数据格式:
//成功
{
result: "ok",
sessionid: "xxxxxxxx"
}
//失败
{
result: "error",
reason: "why...."
}
10 实现登陆功能代码
我们可以在jni/目录下,新创建一个login.cpp
(当然,如果想全部代码都在一个cpp中写也没问题,但就是看上去很冗余)
#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
#include <curl/curl.h>
#include <cJSON.h>
#include <android/log.h>
typedef struct curl_response_data
{
char data[4096];
int datalen;
curl_response_data() {
datalen = 0;
memset(data, 0, 4096);
}
}curl_response_data_t;
size_t curl_login_callback(char* ptr, size_t n, size_t m, void* userdata)
{
curl_response_data_t *data = (curl_response_data_t*)userdata;
int response_data_len = n*m;
memcpy(data->data+data->datalen, ptr, response_data_len);
data->datalen+=response_data_len;
return response_data_len;
}
bool login(char *username, char *passwd)
{
// packet username and password to json
/*
{
username: "aaa",
password: "bbb",
logintype: 1
}
*/
cJSON* root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "username", username);
cJSON_AddStringToObject(root, "password", passwd);
cJSON_AddNumberToObject(root, "logintype", 1);
char *json_str = cJSON_Print(root);
cJSON_Delete(root);
//curl
CURLcode curl_ret;
CURL *curl = curl_easy_init();
curl_response_data_t res_data;
const char *uri = "http://192.168.2.113:8080/login";
//http://127.0.0.1:8080/login
curl_easy_setopt(curl, CURLOPT_URL, uri);
curl_easy_setopt(curl, CURLOPT_POST, 1);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_str);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_login_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &res_data);
curl_ret = curl_easy_perform(curl);
if (curl_ret != CURLE_OK) {
__android_log_print(ANDROID_LOG_ERROR, "login", "curl perform error\n");
return false;
}
curl_easy_cleanup(curl);
res_data.data[res_data.datalen] = '\0';
free(json_str);
//login end, server response json data in res_data
//parse json
/*
//成功
{
result: "ok",
sessionid: "xxxxxxxx"
}
//失败
{
result: "error",
reason: "why...."
}
*/
root = cJSON_Parse(res_data.data);
cJSON* result = cJSON_GetObjectItem(root, "result");
if (result && (strcmp(result->valuestring, "ok") == 0) ) {
//succ!
cJSON* sessionid = cJSON_GetObjectItem(root, "sessionid");
__android_log_print(ANDROID_LOG_ERROR, "login", "login succ, sid=%s\n", sessionid->valuestring);
return true;
}
else {
//fail
cJSON *reason = cJSON_GetObjectItem(root, "reason") ;
if (reason) {
__android_log_print(ANDROID_LOG_ERROR, "login", "login fail, reason=%s\n", reason->string);
}
else {
__android_log_print(ANDROID_LOG_ERROR, "login", "login fail, unknow reason\n");
}
}
cJSON_Delete(root);
return true;
}
这里面我们用到了cJSON库,所以在Android.mk需要将cJSON.c 一起编译
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
# libcurl.a
LOCAL_MODULE := libcurl
LOCAL_SRC_FILES := libcurl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
#libMoCar.so
LOCAL_MODULE := MoCar
LOCAL_SRC_FILES := MoCarJni.cpp login.cpp cJSON.c
LOCAL_LDLIBS := -llog -lz
LOCAL_STATIC_LIBRARIES := libcurl
include $(BUILD_SHARED_LIBRARY)
最后,我们在MoCarJni.cpp中调用我们封装好的login接口就可以了。
/*
MoCarJni 登陆功能
*/
JNIEXPORT jboolean JNICALL Java_com_cpp_itcast_black_1mocar_MoCarJni_Login
(JNIEnv *env, jobject obj, jstring username, jstring passwd)
{
char *pUsername = jstringTostring(env, username);
char *pPasswd = jstringTostring(env, passwd);
__android_log_print(ANDROID_LOG_ERROR, "MoCarJni", "username=%s, passwd=%s\n", pUsername, pPasswd);
login(pUsername, pPasswd);
if (pUsername != NULL) {
free(pUsername);
}
if (pPasswd != NULL) {
free(pPasswd);
}
return true;
}
11 对应服务端代码
web服务器就有很多选择了,比如可以nginx+fastCGI、libevent(http)、javaEE、python+Django、python+flask、python+turnado、Qt+tufao等等。
这里我们暂时选择libevent实现一个简单的http服务器。具体代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> //for getopt, fork
#include <string.h> //for strcat
//for struct evkeyvalq
#include <sys/queue.h>
#include <event.h>
//for http
#include <event2/http.h>
#include <event2/http_struct.h>
#include <event2/http_compat.h>
#include <event2/util.h>
#include <signal.h>
#include <cJSON.h>
#define MYHTTPD_SIGNATURE "MoCarHttpd v0.1"
//处理login登陆模块
void login_handler(struct evhttp_request *req, void *arg)
{
printf("get connection!\n");
//获取客户端请求的URI(使用evhttp_request_uri或直接req->uri)
const char *uri;
uri = evhttp_request_uri(req);
printf("[uri]=%s\n", uri);
//获取POST方法的数据
size_t post_size = EVBUFFER_LENGTH(req->input_buffer);
char *request_data = (char *) EVBUFFER_DATA(req->input_buffer);
char request_data_buf[4096] = {0};
memcpy(request_data_buf, request_data, post_size);
printf("[post_data]=\n%s\n", request_data_buf);
/*
具体的:可以根据GET/POST的参数执行相应操作,然后将结果输出
...
*/
//unpack json
cJSON* root = cJSON_Parse(request_data_buf);
cJSON* username = cJSON_GetObjectItem(root, "username");
cJSON* password = cJSON_GetObjectItem(root, "password");
printf("username = %s\n", username->valuestring);
printf("password = %s\n", password->valuestring);
cJSON_Delete(root);
//packet json
root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "result", "ok");
cJSON_AddStringToObject(root, "sessionid", "xxxxxxxx");
char *response_data = cJSON_Print(root);
cJSON_Delete(root);
/* 输出到客户端 */
//HTTP header
evhttp_add_header(req->output_headers, "Server", MYHTTPD_SIGNATURE);
evhttp_add_header(req->output_headers, "Content-Type", "text/plain; charset=UTF-8");
evhttp_add_header(req->output_headers, "Connection", "close");
//输出的内容
struct evbuffer *buf;
buf = evbuffer_new();
evbuffer_add_printf(buf, "%s", response_data);
//将封装好的evbuffer 发送给客户端
evhttp_send_reply(req, HTTP_OK, "OK", buf);
evbuffer_free(buf);
printf("[response]:\n");
printf("%s\n", response_data);
free(response_data);
}
void show_help() {
char *help = "http://localhost:8080\n"
"-l <ip_addr> interface to listen on, default is 0.0.0.0\n"
"-p <num> port number to listen on, default is 1984\n"
"-d run as a deamon\n"
"-t <second> timeout for a http request, default is 120 seconds\n"
"-h print this help and exit\n"
"\n";
fprintf(stderr,"%s",help);
}
//当向进程发出SIGTERM/SIGHUP/SIGINT/SIGQUIT的时候,终止event的事件侦听循环
void signal_handler(int sig) {
switch (sig) {
case SIGTERM:
case SIGHUP:
case SIGQUIT:
case SIGINT:
event_loopbreak(); //终止侦听event_dispatch()的事件侦听循环,执行之后的代码
break;
}
}
int main(int argc, char *argv[])
{
//自定义信号处理函数
signal(SIGHUP, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
signal(SIGQUIT, signal_handler);
//默认参数
char *httpd_option_listen = "0.0.0.0";
int httpd_option_port = 8080;
int httpd_option_daemon = 0;
int httpd_option_timeout = 120; //in seconds
//获取参数
int c;
while ((c = getopt(argc, argv, "l:p:dt:h")) != -1) {
switch (c) {
case 'l' :
httpd_option_listen = optarg;
break;
case 'p' :
httpd_option_port = atoi(optarg);
break;
case 'd' :
httpd_option_daemon = 1;
break;
case 't' :
httpd_option_timeout = atoi(optarg);
break;
case 'h' :
default :
show_help();
exit(EXIT_SUCCESS);
}
}
//判断是否设置了-d,以daemon运行
if (httpd_option_daemon) {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(EXIT_FAILURE);
}
if (pid > 0) {
//生成子进程成功,退出父进程
exit(EXIT_SUCCESS);
}
}
/* 使用libevent创建HTTP Server */
//初始化event API
event_init();
//创建一个http server
struct evhttp *httpd;
httpd = evhttp_start(httpd_option_listen, httpd_option_port);
evhttp_set_timeout(httpd, httpd_option_timeout);
//指定generic callback
// evhttp_set_gencb(httpd, httpd_handler, NULL);
//也可以为特定的URI指定callback
evhttp_set_cb(httpd, "/login", login_handler,NULL);
//循环处理events
event_dispatch();
evhttp_free(httpd);
return 0;
}
目前服务器并没有处理任何业务,只是单纯的解析json和封装json测试客户端接口。
12 编译及调试
编译好Android应用后,可以测试了。如果前端debug前世登陆成功,就表示测试成功。
这里需要注意:
Android手机必须能够ping通服务器