文档章节

libcurl在android下的移植、编译与测试以及java接口的封装

zhoulc
 zhoulc
发布于 2013/03/05 21:09
字数 6162
阅读 8608
收藏 10

curl是利用URL语法在命令行方式下工作的文件传输工具

它支持很多协议:FTP, FTPS, HTTP, HTTPS, GOPHER, TELNET, DICT, FILE 以及 LDAP。

curl同样支持HTTPS认证,HTTP POST方法, HTTP PUT方法, FTP上传, kerberos认证,HTTP上传, 代理服务器, cookies, 用户名/密码认证, 下载文件断点续传,上载文件断点续传,,http代理服务器管道( proxy tunneling), 甚至它还支持IPv6, socks5代理服务器,,通过http代理服务器上传文件到FTP服务器等等,功能十分强大。

移植之前需做的准备工作:

1、直接到网站上下载 curl 源码

2、利用tar在android编译环境下,一般放在 external 目录下

一.移植Curl工具到Android环境步骤

1.修改cURL源码下的mk文件。源码下面的Android.mk文件最后生成的是静态库libcurl.a,做如下修改(编译成动态库)

LOCAL_PRELINK_MODULE := false

LOCAL_MODULE:= libcurl

LOCAL_MODULE_TAGS := optional



# Copy the licence to a place where Android will find it.

# Actually, this doesn't quite work because the build system searches

# for NOTICE files before it gets to this point, so it will only be seen

# on subsequent builds.

ALL_PREBUILT += $(LOCAL_PATH)/NOTICE

$(LOCAL_PATH)/NOTICE: $(LOCAL_PATH)/COPYING | $(ACP)

$(copy-file-to-target)

#include $(BUILD_STATIC_LIBRARY)

include $(BUILD_SHARED_LIBRARY)

#########################

2.配置编译环境(cd Android.mk同一目录,直接在控制台输入下列代码或者把下面代码弄成sh脚本执行)红色部分根据自己源码情况

ANDROID_HOME=/home/zhoulc/android/ && \

NDK_HOME=/home/zhoulc/android/ndk && \

PATH="$ANDROID_HOME/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin:$PATH" \

./configure --host=arm-linux CC=arm-eabi-gcc --with-random=/dev/urandom \

CPPFLAGS="-I$NDK_HOME/platforms/android-8/arch-arm/usr/include \

-I $ANDROID_HOME/external/curl/include/  \

-I $ANDROID_HOME/external/curl/3rd/include   \

-I $ANDROID_HOME/external/curl   \

-I $ANDROID_HOME/out/target/product/generic/obj/STATIC_LIBRARIES/libcurl_intermediates   \

-I $ANDROID_HOME/dalvik/libnativehelper/include/nativehelper   \

-I $ANDROID_HOME/system/core/include   \

-I $ANDROID_HOME/hardware/libhardware/include   \

-I $ANDROID_HOME/hardware/libhardware_legacy/include   \

-I $ANDROID_HOME/hardware/ril/include   \

-I $ANDROID_HOME/dalvik/libnativehelper/include   \

-I $ANDROID_HOME/frameworks/base/include   \

-I $ANDROID_HOME/frameworks/base/opengl/include   \

-I $ANDROID_HOME/frameworks/base/native/include   \

-I $ANDROID_HOME/external/skia/include   \

-I $ANDROID_HOME/out/target/product/generic/obj/include   \

-I $ANDROID_HOME/bionic/libc/arch-arm/include   \

-I $ANDROID_HOME/bionic/libc/include   \

-I $ANDROID_HOME/bionic/libstdc++/include   \

-I $ANDROID_HOME/bionic/libc/kernel/common   \

-I $ANDROID_HOME/bionic/libc/kernel/arch-arm   \

-I $ANDROID_HOME/bionic/libm/include   \

-I $ANDROID_HOME/bionic/libm/include/arch/arm   \

-I $ANDROID_HOME/bionic/libthread_db/include \

-include $ANDROID_HOME/system/core/include/arch/linux-arm/AndroidConfig.h \

-I $ANDROID_HOME/system/core/include/arch/linux-arm/ \

-D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ -DANDROID -DNDEBUG -DNDEBUG -DHAVE_CONFIG_H" \

CFLAGS="-fno-exceptions -Wno-multichar -msoft-float -fpic -ffunction-sections \

-funwind-tables -fstack-protector -Wa,--noexecstack -Werror=format-security \

-fno-short-enums -march=armv5te -mtune=xscale  -Wno-psabi -mthumb-interwork  \

-fmessage-length=0 -W -Wall -Wno-unused -Winit-self -Wpointer-arith \

-Werror=return-type -Werror=non-virtual-dtor -Werror=address -Werror=sequence-point  \

-g -Wstrict-aliasing=2 -finline-functions -fno-inline-functions-called-once \

-fgcse-after-reload -frerun-cse-after-loop -frename-registers  -UDEBUG \

-mthumb -Os -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64   \

-Wpointer-arith -Wwrite-strings -Wunused -Winline -Wnested-externs \

-Wmissing-declarations -Wmissing-prototypes -Wno-long-long -Wfloat-equal \

-Wno-multichar -Wsign-compare -Wno-format-nonliteral -Wendif-labels \

-Wstrict-prototypes -Wdeclaration-after-statement -Wno-system-headers"  \

LIBS="-nostdlib -Bdynamic -Wl,-T,$ANDROID_HOME/build/core/armelf.x \

-Wl,-dynamic-linker,/system/bin/linker -Wl,--gc-sections -Wl,-z,nocopyreloc \

-L$ANDROID_HOME/out/target/product/generic/obj/lib -Wl,-z,noexecstack \

-Wl,-rpath-link=$ANDROID_HOME/out/target/product/generic/obj/lib \

-lc -llog -lcutils -lstdc++ \

-Wl,--no-undefined $ANDROID_HOME/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/lib/gcc/arm-eabi/4.4.0/libgcc.a  \

$ANDROID_HOME/out/target/product/generic/obj/lib/crtend_android.o \

-lm $ANDROID_HOME/out/target/product/generic/obj/lib/crtbegin_dynamic.o \

-L$ANDROID_HOME/external/curl/3rd/libs"

3.编译libcurl.so库

cd进入android/external/curl源码目录

mm-》编译生成libcurl.so库

4.编写测试case 以及Android.mk文件并生成可执行文件

新建一个测试案例curl_test.cpp

#include "curl/curl.h"

#include <stdio.h>;

int main() { 

    CURL *curl; 

    CURLcode res;

    curl_global_init(CURL_GLOBAL_ALL);

    curl = curl_easy_init();

    if (curl) {     

        curl_easy_setopt(curl, CURLOPT_URL, "http://www.baidu.com/"); 

        res = curl_easy_perform(curl); 

        if (0!=res) {      

            printf("curl error: %d\n", res);         

        }        

        curl_easy_cleanup(curl);     

    }   

    curl_global_cleanup();

    return 0;

}

在同一目录下写一个Android.mk文件生成curl_test可执行文件

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_C_INCLUDES += \

    $(TOP)/external/curl/include/ \

LOCAL_SRC_FILES:= curl_test.cpp

# No shared libraries.

# No static libraries.

LOCAL_SHARED_LIBRARIES := libcurl

LOCAL_MODULE_TAGS := optional

LOCAL_MODULE := curl_test

include $(BUILD_EXECUTABLE)


生成可执行文件:curl_test

4.运行查看测试结果

运行测试case:curl_test

5.(补充)移植libcurlandroid4.0,修改两个地方

1)把生成的路径改一下,一般默认为out/target/product/generic下面,我们根据系统不同(根据lunch选择不同,最终生成的路径不一样)改为系统的全局变量,

$ANDROID_HOME/out/target/product/generic替换成$ANDROID_PRODUCT_OUT

ANDROID_HOME_CURL=../../ && \

NDK_HOME_CURL=../../prebuilt/ndk && \

PATH="$ANDROID_HOME_CURL/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin:$PATH" \

./configure --host=arm-linux CC=gcc --with-random=/dev/urandom \

CPPFLAGS="-I$NDK_HOME_CURL/platforms/android-8/arch-arm/usr/include \

-I $ANDROID_HOME_CURL/external/curl/include/  \

-I $ANDROID_HOME_CURL/external/curl/3rd/include   \

-I $ANDROID_HOME_CURL/external/curl   \

-I $ANDROID_HOME_CURL/out/target/product/generic/obj/STATIC_LIBRARIES/libcurl_intermediates   \

-I $ANDROID_HOME_CURL/dalvik/libnativehelper/include/nativehelper   \

-I $ANDROID_HOME_CURL/system/core/include   \

-I $ANDROID_HOME_CURL/hardware/libhardware/include   \

-I $ANDROID_HOME_CURL/hardware/libhardware_legacy/include   \

-I $ANDROID_HOME_CURL/hardware/ril/include   \

-I $ANDROID_HOME_CURL/dalvik/libnativehelper/include   \

-I $ANDROID_HOME_CURL/frameworks/base/include   \

-I $ANDROID_HOME_CURL/frameworks/base/opengl/include   \

-I $ANDROID_HOME_CURL/frameworks/base/native/include   \

-I $ANDROID_HOME_CURL/external/skia/include   \

-I $ANDROID_HOME_CURL/out/target/product/generic/obj/include   \

-I $ANDROID_HOME_CURL/bionic/libc/arch-arm/include   \

-I $ANDROID_HOME_CURL/bionic/libc/include   \

-I $ANDROID_HOME_CURL/bionic/libstdc++/include   \

-I $ANDROID_HOME_CURL/bionic/libc/kernel/common   \

-I $ANDROID_HOME_CURL/bionic/libc/kernel/arch-arm   \

-I $ANDROID_HOME_CURL/bionic/libm/include   \

-I $ANDROID_HOME_CURL/bionic/libm/include/arch/arm   \

-I $ANDROID_HOME_CURL/bionic/libthread_db/include \

-include $ANDROID_HOME_CURL/system/core/include/arch/linux-arm/AndroidConfig.h \

-I $ANDROID_HOME_CURL/system/core/include/arch/linux-arm/ \

-D__ARM_ARCH_5__ -D__ARM_ARCH_5T__ -D__ARM_ARCH_5E__ -D__ARM_ARCH_5TE__ -DANDROID -DNDEBUG -DNDEBUG -DHAVE_CONFIG_H" \

CFLAGS="-fno-exceptions -Wno-multichar -msoft-float -fpic -ffunction-sections \

-funwind-tables -fstack-protector -Wa,--noexecstack -Werror=format-security \

-fno-short-enums -march=armv5te -mtune=xscale  -Wno-psabi -mthumb-interwork  \

-fmessage-length=0 -W -Wall -Wno-unused -Winit-self -Wpointer-arith \

-Werror=return-type -Werror=non-virtual-dtor -Werror=address -Werror=sequence-point  \

-g -Wstrict-aliasing=2 -finline-functions -fno-inline-functions-called-once \

-fgcse-after-reload -frerun-cse-after-loop -frename-registers  -UDEBUG \

-mthumb -Os -fomit-frame-pointer -fno-strict-aliasing -finline-limit=64   \

-Wpointer-arith -Wwrite-strings -Wunused -Winline -Wnested-externs \

-Wmissing-declarations -Wmissing-prototypes -Wno-long-long -Wfloat-equal \

-Wno-multichar -Wsign-compare -Wno-format-nonliteral -Wendif-labels \

-Wstrict-prototypes -Wdeclaration-after-statement -Wno-system-headers"  \

LIBS="-nostdlib -Bdynamic -Wl,-T,$ANDROID_HOME_CURL/build/core/armelf.x \

-Wl,-dynamic-linker,/system/bin/linker -Wl,--gc-sections -Wl,-z,nocopyreloc \

-L$ANDROID_PRODUCT_OUT/obj/lib -Wl,-z,noexecstack \

-Wl,-rpath-link=$ANDROID_PRODUCT_OUT/obj/lib \

-lc -llog -lcutils -lstdc++ \

-Wl,--no-undefined $ANDROID_HOME_CURL/prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/lib/gcc/arm-eabi/4.4.0/libgcc.a  \

$ANDROID_PRODUCT_OUT/obj/lib/crtend_android.o \

-lm $ANDROID_PRODUCT_OUT/obj/lib/crtbegin_dynamic.o \

-L$ANDROID_HOME_CURL/external/curl/3rd/libs"

2)修改Android.mk

#ALL_PREBUILT += $(LOCAL_PATH)/NOTICE

#$(LOCAL_PATH)/NOTICE: $(LOCAL_PATH)/COPYING | $(ACP)

# $(copy-file-to-target)

把关于ALL_PREBUILT模块全部注释调

二.编译过程中出现的错误以及修改方法

错误1:考入源码,修改android.mk改成生成.so动态库(默认curl.a静态库)

============================================

PLATFORM_VERSION_CODENAME=REL

PLATFORM_VERSION=2.3.1

TARGET_PRODUCT=generic

TARGET_BUILD_VARIANT=eng

TARGET_SIMULATOR=

TARGET_BUILD_TYPE=release

TARGET_BUILD_APPS=

TARGET_ARCH=arm

HOST_ARCH=x86

HOST_OS=linux

HOST_BUILD_TYPE=release

BUILD_ID=GRH78

============================================

make: Entering directory `/home/zhoulc/android'

build/core/base_rules.mk:151: *** external/curl: LOCAL_BUILT_MODULE and LOCAL_INSTALLED_MODULE must not be defined by component makefiles.  Stop.

解决方法:在android.mk里面把生成的静态库命令注释掉。

错误2:提示没有头文件

============================================

PLATFORM_VERSION_CODENAME=REL

PLATFORM_VERSION=2.3.1

TARGET_PRODUCT=generic

TARGET_BUILD_VARIANT=eng

TARGET_SIMULATOR=false

TARGET_BUILD_TYPE=release

TARGET_BUILD_APPS=

TARGET_ARCH=arm

HOST_ARCH=x86

HOST_OS=linux

HOST_BUILD_TYPE=release

BUILD_ID=GRH78

============================================

make: Entering directory `/home/zhoulc/android'

make: *** No rule to make target `external/curl/include/curl/curlbuild.h', needed by `out/target/product/generic/obj/include/libcurl/curl/curlbuild.h'.  Stop.

make: Leaving directory `/home/zhoulc/android'

解决方法:2cd 到 external/curl目录下,输入(红色字部分根据自己的环境做相应的更改):

参照执行步骤2

错误3:动态库生成,但出现stop问题,检测一下是不是静态库到动态库的改变,改写彻底没有。

错误4:出现超出 ALL_PREBUILT.定义

build/core/main.mk:537: *** Some files have been added to ALL_PREBUILT.

build/core/main.mk:538: *

build/core/main.mk:539: * ALL_PREBUILT is a deprecated mechanism that

build/core/main.mk:540: * should not be used for new files.

build/core/main.mk:541: * As an alternative, use PRODUCT_COPY_FILES in

build/core/main.mk:542: * the appropriate product definition.

build/core/main.mk:543: * build/target/product/core.mk is the product

build/core/main.mk:544: * definition used in all products.

build/core/main.mk:545: *

build/core/main.mk:546: * unexpected NOTICE in ALL_PREBUILT

build/core/main.mk:546: * unexpected NOTICE in ALL_PREBUILT

build/core/main.mk:547: *

build/core/main.mk:548: *** ALL_PREBUILT contains unexpected files.  Stop.

make: Leaving directory `/home/zhoulc/HAndroid_4.0'

解决方法:注释掉相关信息

#ALL_PREBUILT += $(LOCAL_PATH)/NOTICE

#$(LOCAL_PATH)/NOTICE: $(LOCAL_PATH)/COPYING | $(ACP)

# $(copy-file-to-target)

三.java层通过jni调用libcurl API学习摸索经验(CURL API对应java接口封装)

1.jni模块的实现与学习

 1jvm加载c的动态库

    应用层的Java类是在虚拟机(VM: Vitual Machine)上执行的,而C件不是在VM上执行,Java程式通过下面方法要求VM去载入(Load)所指定的C组件

System.loadLibrary(*.so的档案名);

(1)告诉VMC组件使用那一个JNI版本。如果你的*.so档没有提供JNI_OnLoad()函数,VM会默认该*.so档是使用最老的JNI 1.1版本。由于新版的JNI做了许多扩充,如果需要使用JNI的新版功能,例如JNI 1.4java.nio.ByteBuffer,就必须藉由JNI_OnLoad()函数来告知VM

(2)由于VM执行到System.loadLibrary()函数时,就会立即先呼叫JNI_OnLoad(),所以C组件的开发者可以藉由JNI_OnLoad()来进行C组件内的初期值之设定(Initialization) 

执行过程如下

在jni里面找到JNI_OnLoad方法-JNI_OnLoad里面对不同的分支进行注册,比如register_join_easy_natives-》在该方法中把native方法与c方法一一对应registerNativeMethods在该方法中找到java对应的类以及注册对应方法


static int registerNativeMethods(JNIEnv* env, const char* className,

JNINativeMethod* gMethods, int numMethods) {

jclass clazz;



clazz = env->FindClass(className);

if (clazz == NULL) {

return JNI_FALSE;

}

if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {

return JNI_FALSE;

}

return JNI_TRUE;

}

int register_join_multi_natives(JNIEnv *env) {



LOGI("register_join_easy_natives");

return registerNativeMethods(env, g_class_name, g_cls_methods,

sizeof(g_cls_methods) / sizeof(g_cls_methods[0]));



}


2)jni参数设置

其中比较难以理解的是第二个参数,例如

"()V"

"(II)V"

"(Ljava/lang/String;Ljava/lang/String;)V"

实际上这些字符是与函数的参数类型一一对应的。

"()" 中的字符表示参数,后面的则代表返回值。例如"()V" 就表示void Func();

"(II)V" 表示 void Func(int, int);

具体的每一个字符的对应关系如下

字符   Java类型     C类型

V      void         void

Z      jboolean     boolean

I       jint         int

J       jlong        long

D      jdouble       double

F      jfloat            float

B      jbyte            byte

C      jchar           char

S      jshort          short

数组则以"["开始,用两个字符表示

[I     jintArray       int[]

[F     jfloatArray     float[]

[B     jbyteArray     byte[]

[C    jcharArray      char[]

[S    jshortArray      short[]

[D    jdoubleArray    double[]

[J     jlongArray      long[]

[Z    jbooleanArray    boolean[]

上面的都是基本类型。如果Java函数的参数是class,则以"L"开头,以";"结尾,中间是用"/" 隔开的包及类名。而其对应的C函数名的参数则为jobject. 一个例外是String类,其对应的类为jstring

Ljava/lang/String; String jstring

Ljava/net/Socket; Socket jobject

如果JAVA函数位于一个嵌入类,则用$作为类名间的分隔符。

例如 "(Ljava/lang/String;Landroid/os/FileUtils$FileStatus;)Z"

3)在c中访问java的属性和方法

在此,针对andorid中c++与java中的方法互调,引用参考说明如下:

步骤1).andorid CPP调用java函数和访问其成员:原理 => CPP代码找到java的那个class里面的函数的入口地址,然后在CPP代码中调用java代码

步骤1) 用FindClass()函数找到该java类(如android.os.Binder)的实例对象的引用:

  jclass clazz = env->FindClass(kBinderPathName) = env->FindClass("android.os.Binder")

步骤2) 用GetFieldID()函数获取到要访问的域(field: 实际上就是该java class中的某个成员变量的名字)的ID:

  gBinderOffsets.mObject = env->GetFieldID(clazz, "mObject", "I") // mObject为java class "Binder"里的一个成员变量

  -> 注意,这里将要访问的那个java对象的成员mObject的ID保存到了全局变量gBinderOffsets.mObject中,这样做的前提和优点如下:

  前提: android里面,每个java进程中只允许有一个java虚拟机(sun公司原始的java架构中,一个进程中可以有多个java虚拟机)

  优点: 除了第一次,以后每次要访问该java对象的成员mObject就非常快了(不用再去FindClass()和GetFieldID())

步骤3) 用GetMethodID()函数获取到要访问的方法(Method: 实际上就是该java class中的某个成员函数的名字)的ID:

  gBinderOffsets.mExecTransact = env->GetMethodID(clazz, "execTransact", "(IIII)Z") // execTransact为java class "Binder"里的一个成员函数

步骤4) 用类似于GetIntField()的函数获取到该java对象的那个域(即成员)的值:

  IBinder* target = (IBinder*)env->GetIntField(obj,gBinderProxyOffsets.mObject)

     // 获取java android.os.Binder类型对象里面的成员mObject的值 
步骤5) 用类似于CallBooleanMethod()的函数调用到该java对象的那个成员函数:

  jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact, code, (int32_t)&data, (int32_t)reply, flags)

4)数组,字符串在jni中的定义与使用

要创建数组首先要知道类型及长度,JNI提供了一系列的数组类型及操作的函数如:

NewIntArray、NewLongArray、NewShortArray、NewFloatArray、NewDoubleArray、 NewBooleanArray、NewStringUTF、NewCharArray、NewByteArray、NewString,访问通过 GetBooleanArrayElements、GetIntArrayElements等函数。

5libcurl APIjava API转变过程中修改部分

     curl_easy_setopt中根据第二个参数不同,第三个参数传递格式不同,在C中该方法第三个参数有四种类型,intlong、回调方法、回调方法参数,想在java里面一一对应,只能使用方法的重载,通过不同的参数值传递,对应上c里面的方法,至于回调函数和回调函数参数,通过在java层传递一个Object对象过去,在JNI层实现对回调函数的注册和回调函数参数的设置。

2.java部分的设计

1.java模块划分

 1curl模块有三个部分

easy_handle:为libcurl的最基础部分,所有的操作都是在easy_handle上进行的,比如发送、请求数据都是在其上进行的。如果直接在easy_handle执行操作 curl_easy_perform 函数是阻塞的(即需要等到完成才返回)

multi_handlelibcurl为异步操作提供的接口,允许调用方在一个线程中处理多个操作(就是easy_handle上的操作,注意是单线程下的),内部multi_handle采用堆栈的方式保存多个easy_handle,然后在一个线程中可以同时对多个easy_handle进行处理,multi_handle的执行操作 curl_multi_perform 函数是立即返回的,不会阻塞

share_handle:有时候多个easy_handle需要分享一些信息,比如cookie,当一个连接获取一个新的cookie,就可以将这个cookie共享到所有的连接上

分别对应java的三个类,Easy类、Multi类、Share类(实体类)

除去个别API,其他接口基本和curl提供API一一对应

 2setopt所使用的第二个参数,对应C里面的模板类

OptValue.java里面初始化libcurl里面的常量

比如经常用到的CURLOPT_URL

 3)工具类,在进行文件上传下载、断点下载的时候,对文件经行相应操作

上传的时候,需判断需要上传的文件是否存在。

下载的时候,判断是否下载完成,如果没有完成,择可以选择断点下载,重新下载都行。如果选择重新下载,或者经过自动判断是第一次下载,则会删掉以前的文件,重新创建一个用来存放下载数据。

3.规划过程中出现的问题与解决方案

  1)网络不通

AndroidManifest.xml里面添加

<uses-permission android:name="android.permission.INTERNET" /> 

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

一个对应能否访问网络,另一个SDcard写权限

  2)文件无法下载

首先查看网络是否正常。

然后看下下载流程每一步是不是正常执行返回值一般为0

Perform是否调用并且调用成功。

在进行回调函数注册之后,注册回调函数参数,看设置是否正确。

看网络访问路径是否正确。

  3)文件无法读写

观察文件打开的方式,查看sdcard是否添加可操作权限。

  4) 对象从java传到C出现问题。

cls = env->GetObjectClass(object);

在java层经行向上类型转换,传递Object类型到C层,然后通过JNI直接获取自动封装和解析,在java层提供的是公共接口,既所有类的父类Object。对整个模块提供扩展。

  5)乱码问题(相当给力)

最开始提供了两个方法,一个从String类型到char*类型的转换,一个从char*jstring类型的转换,把原始数据进行了编码格式的转换。

//jstring to char*

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);

LOGI("barr = %d", alen);

jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);

if (alen > 0) {

rtn = (char*) malloc(alen + 1);

memcpy(rtn, ba, alen);

rtn[alen] = 0;

LOGI("sizeof(rtn) = %d", strlen(rtn));

}

env->ReleaseByteArrayElements(barr, ba, 0);

return rtn;

}

//char* to jstring

jstring stoJstring(JNIEnv* env, const char* pat) {

jclass strClass = env->FindClass("java/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("GB2312");

return (jstring) env->NewObject(strClass, ctorID, bytes, encoding);

}

这个两个功能比较实用。

在整个设计改革之前,是通过c层传递流到java层,然后再由java层进行文件的读写操作,这个过程中出现了一些乱码,通过以上提供的两种方法能够解决。Java默认的String类型是UTF-8编码格式,而下下来的文件好多是用GBK编码方式,所以在java层进行文件读写的时候容易出现问题。

后来把整个模块经行了改写,读写文件是由c层完成的,没有了编码问题,乱码问题迎刃而解。

6)断点续传

断点续传需要对服务器上的文件经行比较,在文件操作的工具中提供了两个构造函数,一个需要源文件大小,如果调用这个方法当size不为0的时候,可以进行判断是否下载完成,然后再下载过程中设置断点续传,就可以实现断点续传功能,如果不初始化size或直接赋值为0,则默认为重新下载。

注:源文件大小,就是需要通过httpftp下载的文件,需要获取服务器上文件的大小,暂时没有提供接口如何获取。

7)并发执行上传下载

由于网上multi模块资料较少,暂时只实现了multi一些简单功能,由于easy模块是单线程添加一个select接口,添加下载上传操作的handle,然后进行multi_perform

四.Libcurl使用案例与接口说明

1.http从头下载以及测试下载速度doubleValue

//http 下载以及测试下载速度

        String url = "http://www.sina.com.cn/";

        result = easy.easySetopt(handle, OptValue.CURLOPT_URL, url);

//      easy.easySetopt(handle, OptValue.CURLOPT_HEADER, 1);

        System.out.println("easySetopt state is "+result);

        easy.easySetopt(handle, OptValue.CURLOPT_WRITEFUNCTION, operate);

        result = easy.easyPerform(handle);

        double doubleValue = easy.easyGetinfo(handle, OptValue.CURLINFO_SPEED_DOWNLOAD, 0.0);

        System.out.println("dobleValue = "+doubleValue);

        easy.easyCleanUp(handle);

2.ftp上传

//ftp 上传

        String url = "ftp://192.168.18.37/sina.html";

        result = easy.easySetopt(handle, OptValue.CURLOPT_URL, url);



        easy.easySetopt(handle, OptValue.CURLOPT_USERPWD, "ipanel:ipanel123");

        easy.easySetopt(handle, OptValue.CURLOPT_UPLOAD, 1L);

        easy.easySetopt(handle, OptValue.CURLOPT_INFILESIZE, file.length());

        easy.easySetopt(handle, OptValue.CURLOPT_READFUNCTION, operate);

        result = easy.easyPerform(handle);

        easy.easySetopt(handle, OptValue.CURLOPT_INFILESIZE_LARGE, file.length());

        System.out.println("easyPerform state is "+result

3.tcp_ip 通信

//        //tcp_ip 通信

        String url = "192.168.21.13";

        result = easy.easySetopt(handle, OptValue.CURLOPT_URL, url);

        System.out.println("easySetopt state is "+result);

        easy.easySetopt(handle, OptValue.CURLOPT_PORT, 6666);

        easy.easySetopt(handle, OptValue.CURLOPT_CONNECT_ONLY, 1L);

        result = easy.easyPerform(handle);

        System.out.println("easyPerform state is "+result);

        String buf = "zhoulong潮";

        int len = easy.easySend(handle, buf, buf.length());

        for (int i = 1; i <=1; i++) {

        String value = null;

        value = easy.easyRecv(handle,len);

        System.out.println(value);

}

        easy.easyCleanUp(handle);

        Log.d("","清除成功");

4.并发执行下载任务

int http_handle = easy.easyInit();

       int  http_handle2 = easy.easyInit();

       easy.easySetopt(http_handle, OptValue.CURLOPT_URL, "http://www.sohu.com/"); 

       easy.easySetopt(http_handle2, OptValue.CURLOPT_URL, "http://www.sina.com.cn/");      

       int multi_handle = multi.multiInit();

       multi.multiAddHandle(multi_handle, http_handle);

       multi.multiAddHandle(multi_handle, http_handle2);

       easy.easySetopt(http_handle, OptValue.CURLOPT_WRITEFUNCTION, operate);

       easy.easySetopt(http_handle2, OptValue.CURLOPT_WRITEFUNCTION, operate2);  

       /* 

        * 调用curl_multi_perform函数执行curl请求 

        * url_multi_perform返回CURLM_CALL_MULTI_PERFORM(-1)时,表示需要继续调用该函数直到返回值不是CURLM_CALL_MULTI_PERFORM为止 

        * running_handles变量返回正在处理的easy curl数量,running_handles为0表示当前没有正在执行的curl请求 
          */  

       int still_running = 0;

       while(true){

         int perform[] = multi.multiPerform(multi_handle);

         System.out.println("perform step 1"+"         "+perform[0]+"      "+perform[1]);

         if(perform[0] != -1){

          still_running = perform[1];

          break;

         }

       }   

       while(still_running > 0) {

        if (-1 == multi.multiSelect(multi_handle))  

           {  

               break;  

           } else {  

               // select监听到事件,调用curl_multi_perform通知curl执行相应的操作 //  

            while(true){

                int perform[] = multi.multiPerform(multi_handle);

                System.out.println("perform step 2"+"         "+perform[0]+"      "+perform[1]);

                if(perform[0] != -1){

                 still_running = perform[1];

                 break;

                }

              }

           }  

        }

       multi.multiRemoveHandle(multi_handle, http_handle);

       multi.multiRemoveHandle(multi_handle, http_handle2);

       multi.multiCleanUp(multi_handle);

       easy.easyCleanUp(http_handle);

       easy.easyCleanUp(http_handle2);

5.断点续传 

File file = new File("sdcard/"+path);

//判断是否下载完全

          FileOperate operate = new FileOperate(file,size);

          int handle = -1;

          int result = -1;

          handle = easy.easyInit();

          if(handle != -1){

           Log.d("", "初始化成功");

           System.out.println(handle);

          }

          String url = "ftp://192.168.18.37/"+path;

          easy.easySetopt(handle, OptValue.CURLOPT_URL, url);

          easy.easySetopt(handle, OptValue.CURLOPT_USERPWD, "ipanel:ipanel123");

//断点续传声明

          easy.easySetopt(handle, OptValue.CURLOPT_RESUME_FROM_LARGE, file.length());

          easy.easySetopt(handle, OptValue.CURLOPT_WRITEFUNCTION, operate);

          result = easy.easyPerform(handle);

          easy.easySetopt(handle, OptValue.CURLOPT_INFILESIZE_LARGE, file.length());

          System.out.println("easyPerform state is "+result);

6.相关参数资料

下列选项的值将被作为长整形使用(option参数中指定):    
•    CURLOPT_INFILESIZE : 当你上传一个文件到远程站点,这个选项告诉PHP你上传文件的大小。
•    CURLOPT_VERBOSE : 如果你想CURL报告每一件意外的事情,设置这个选项为一个非零值。
•    CURLOPT_HEADER : 如果你想把一个头包含在输出中,设置这个选项为一个非零值。
•    CURLOPT_NOPROGRESS: 如果你不会PHPCURL传输显示一个进程条,设置这个选项为一个非零值。注意:PHP自动设置这个选项为非零值,你应该仅仅为了调试的目的来改变这个选项。
•    CURLOPT_NOBODY : 如果你不想在输出中包含body部分,设置这个选项为一个非零值。
•    CURLOPT_FAILONERROR : 如果你想让PHP在发生错误(HTTP代码返回大于等于300)时,不显示,设置这个选项为一人非零值。默认行为是返回一个正常页,忽略代码。
•    CURLOPT_UPLOAD: 如果你想让PHP为上传做准备,设置这个选项为一个非零值。
•    CURLOPT_POST : 如果你想PHP去做一个正规的HTTP POST,设置这个选项为一个非零值。这个POST是普通的 application/x-www-from-urlencoded 类型,多数被HTML表单使用。
•    CURLOPT_FTPLISTONLY : 设置这个选项为非零值,PHP将列出FTP的目录名列表。
•    CURLOPT_FTPAPPEND : 设置这个选项为一个非零值,PHP将应用远程文件代替覆盖它。
•    CURLOPT_NETRC : 设置这个选项为一个非零值,PHP将在你的 ~./netrc 文件中查找你要建立连接的远程站点的用户名及密码。
•    CURLOPT_FOLLOWLOCATION : 设置这个选项为一个非零值(象 Location: )的头,服务器会把它当做HTTP头的一部分发送(注意这是递归的,PHP将发送形如 Location: 的头)
•    CURLOPT_PUT : 设置这个选项为一个非零值去用HTTP上传一个文件。要上传这个文件必须设置CURLOPT_INFILECURLOPT_INFILESIZE选项.
•    CURLOPT_MUTE : 设置这个选项为一个非零值,PHP对于CURL函数将完全沉默。
•    CURLOPT_TIMEOUT : 设置一个长整形数,作为最大延续多少秒。
•    CURLOPT_LOW_SPEED_LIMIT: 设置一个长整形数,控制传送多少字节。
•    CURLOPT_LOW_SPEED_TIME : 设置一个长整形数,控制多少秒传送CURLOPT_LOW_SPEED_LIMIT规定的字节数。
•    CURLOPT_RESUME_FROM : 传递一个包含字节偏移地址的长整形参数,(你想转移到的开始表单)
•    CURLOPT_SSLVERSION: 传递一个包含SSL版本的长参数。默认PHP将被它自己努力的确定,在更多的安全中你必须手工设置。
•    CURLOPT_TIMECONDITION : 传递一个长参数,指定怎么处理CURLOPT_TIMEVALUE参数。你可以设置这个参数为TIMECOND_IFMODSINCE 或 TIMECOND_ISUNMODSINCE。这仅用于HTTP
•    CURLOPT_TIMEVALUE : 传递一个从1970-1-1开始到现在的秒数。这个时间将被CURLOPT_TIMEVALUE选项作为指定值使用,或被默认TIMECOND_IFMODSINCE使用。

下列选项的值将被作为字符串:
•    CURLOPT_URL: 这是你想用PHP取回的URL地址。你也可以在用curl_init()函数初始化时设置这个选项。
•    CURLOPT_USERPWD : 传递一个形如[username]:[password]风格的字符串,作用PHP去连接。
•    CURLOPT_PROXYUSERPWD : 传递一个形如[username]:[password] 格式的字符串去连接HTTP代理。
•    CURLOPT_RANGE : 传递一个你想指定的范围。它应该是X-Y格式,XY是被除外的。HTTP传送同样支持几个间隔,用逗句来分隔(X-Y,N-M)
•    CURLOPT_POSTFIELDS : 传递一个作为HTTP POST操作的所有数据的字符串。
•    CURLOPT_REFERER: HTTP请求中包含一个referer头的字符串。
•    CURLOPT_USERAGENT : HTTP请求中包含一个user-agent头的字符串。
•    CURLOPT_FTPPORT: 传递一个包含被ftp POST指令使用的IP地址。这个POST指令告诉远程服务器去连接我们指定的IP地址。这个字符串可以是一个IP地址,一个主机名,一个网络界面名(UNIX),或是-(使用系统默认IP地址)
•    CURLOPT_COOKIE : 传递一个包含HTTP cookie的头连接。
•    CURLOPT_SSLCERT : 传递一个包含PEM格式证书的字符串。
•    CURLOPT_SSLCERTPASSWD : 传递一个包含使用CURLOPT_SSLCERT证书必需的密码。
•    CURLOPT_COOKIEFILE : 传递一个包含cookie数据的文件的名字的字符串。这个cookie文件可以是Netscape格式,或是堆存在文件中的HTTP风格的头。
•    CURLOPT_CUSTOMREQUEST : 当进行HTTP请求时,传递一个字符被GETHEAD使用。为进行DELETE或其它操作是有益的,更Pass a string to be used instead of GET or HEAD when doing an HTTP request. This is useful for doing or another, more obscure, HTTP request. 注意在确认你的服务器支持命令先不要去这样做。下列的选项要求一个文件描述(通过使用fopen()函数获得)
•    CURLOPT_FILE: 这个文件将是你放置传送的输出文件,默认是STDOUT.
•    CURLOPT_INFILE : 这个文件是你传送过来的输入文件。
•    CURLOPT_WRITEHEADER : 这个文件写有你输出的头部分。
•    CURLOPT_STDERR : 这个文件写有错误而不是stderr。用来获取需要登录的页面的例子,当前做法是每次或许都登录一次,有需要的人再做改进了.

最后附带Easy模块的部分代码:

java接口:

public class Easy {
// create the handle 创建一个easy handle
public int easyInit() {
int res = -1;
res = native_join_curl_easy_init();
if (res == -1) {
Log.d("", "easyInit faild!");
}
return res;
}

……
native int native_join_curl_easy_init();

……

}

jni接口:

joineasy.cpp

……

jint join_curl_easy_init(JNIEnv *env, jobject thiz) {
int res = -1;
res = (int)curl_easy_init();
// LOGE("error is the cleanup res %d" ,res);
// curl_easy_cleanup((void*)res);
if (res != -1) {
LOGE("join_curl_easy_init convert success %d", res);
}
return res;
}
……

static JNINativeMethod g_cls_methods[] = { //
{ "native_join_curl_easy_init", "()I", (void*) join_curl_easy_init }
}

……

注意:在写测试case的时候记得加载动态库

static {
try {
Log.i("","load library");
System.loadLibrary("curl_runtime");//jni部分生成了libcurl_runtime.so动态库
} catch (java.lang.Error e) {
e.printStackTrace();
}
test.apk 依赖 libcurl_runtime.so同时 libcurl_runtime.so 依赖 libcurl.so


   





© 著作权归作者所有

共有 人打赏支持
zhoulc
粉丝 48
博文 32
码字总数 47964
作品 0
深圳
程序员
私信 提问
加载中

评论(2)

zhoulc
zhoulc

引用来自“lian_duan”的评论

您好,请问您在移植libcurl到android时,碰到的找不到头文件的问题,是怎么解决的,您的博文中没有详细写出来。我正在移植libcurl到4.4,有些问题不知道如何解决,希望能得到指点。谢谢!
2.配置编译环境(cd 到Android.mk同一目录,直接在控制台输入下列代码或者把下面代码弄成sh脚本执行)红色部分根据自己源码情况 1.把这下面的一段脚本直接在控制台下运行(粘贴复制,记住要改路径,ANDROID_HOME=/home/zhoulc/android/ && \ 找到自己的源码编译环境路径),然后再编译,2.或者在编译之前把这段脚本代码弄成一个shell脚本,然后运行。
l
lian_duan
您好,请问您在移植libcurl到android时,碰到的找不到头文件的问题,是怎么解决的,您的博文中没有详细写出来。我正在移植libcurl到4.4,有些问题不知道如何解决,希望能得到指点。谢谢!
Android NDK开发简介

最近由于项目的需要,使用到了Android的NDK技术,对项目核心算法跨平台的移植。简答而言,就是使用C对原来的算法进行了改进,并集成到原来的app项目里。 从前的项目一直没有使用NDK进行开发的...

zhiweiofli
2013/03/07
0
0
开发一个移动跨平台库 —— 第一部分:探索

本文中,我介绍了我在探索开发移动跨平台库(例如一个codebase,可成为不同移动平台上的app的一部分)时积累的经验:从移动跨平台开发工具(PhoneGap,Titanium之类的),到代码移植工具;从无法...

oschina
2014/03/01
4.2K
12
使用 Android NDK 重用现有的 C 代码

开始之前 首先,了解 Android 原生开发工具包(NDK)的动机之一是得以利用开源项目,大多数项目都是用 C 语言编写的。完成本教程后,您将了解到如何创建 Java 本地接口(JNI)库,它使用 C ...

IBMdW
2012/01/05
952
0
使用 Android NDK 重用现有的 C 代码

开始之前 首先,了解 Android 本机开发人员工具包(NDK)的动机之一是得以利用开源项目,大多数项目都是用 C 语言编写的。完成本教程后,您将了解到如何创建 Java 本地接口(JNI)库,它使用...

IBMdW
2011/05/12
2.1K
0
jvm虚拟机androidy移植-编译篇

有这个必要吗?都过时的东西了,android上的Dalvik效率不够高吗,不够逼格吗? 是的但有总东西是不是我们这些码农能决定的,领导和项目需求才是你要关心的,毕竟工作要向领导汇报,项目要去挣...

lonely1986
2015/04/22
0
1

没有更多内容

加载失败,请刷新页面

加载更多

node.js学习笔记之koa框架和简单爬虫练习

Koa -- 基于 Node.js 平台的下一代 web 开发框架 koa是由 Express 原班人马打造的,致力于成为一个更小、更富有表现力、更健壮的 Web 框架。 使用 koa 编写 web 应用,可以免除重复繁琐的回调...

前端小攻略
5分钟前
0
0
JavaScript中的继承及实现代码

JS虽然不像是JAVA那种强类型的语言,但也有着与JAVA类型的继承属性,那么JS中的继承是如何实现的呢? 一、构造函数继承 在构造函数中,同样属于两个新创建的函数,也是不相等的 function Fn...

peakedness丶
26分钟前
1
0
记一次面试最常见的10个Redis"刁难"问题

导读:在程序员面试过程中Redis相关的知识是常被问到的话题。作为一名在互联网技术行业打击过成百上千名的资深技术面试官,本文作者总结了面试过程中经常问到的问题。十分值得一读。 Redis在...

小刀爱编程
39分钟前
14
0
TiDB Lab 诞生记 | TiDB Hackathon 优秀项目分享

本文由红凤凰粉凤凰粉红凤凰队的成员主笔,他们的项目 TiDB Lab 在本届 TiDB Hackathon 2018 中获得了二等奖。TiDB Lab 为 TiDB 培训体系增加了一个可以动态观测 TiDB / TiKV / PD 细节的动画...

TiDB
52分钟前
4
0
当区块链遇到零知识证明

本文由云+社区发表 当区块链遇到零知识证明 什么是零知识证明 零知识证明的官方定义是能够在不向验证者任何有用的信息的情况下,使验证者相信某个论断是正确的。这个定义有点抽象,下面笔者举...

腾讯云加社区
今天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部