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路径为

http://服务器IP:服务器端口/login

客户端登陆发送:

      {
            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通服务器

results matching ""

    No results matching ""