Зачем это нужно
Мы хотим общение с API сервером написать на C++, а дальше использовать написанную библиотеку во всех наших приложения под различными платформами. Конечно мы хотим, чтобы работало под android.
Libcurl — это библиотека интерфейса API для передачи, которую разработчики могут встроить в свои программы; cURL действует как автономная обёртка для библиотеки libcurl. libcurl используется, чтобы обеспечить возможность передачи файлов (адресуемых с помощью URL) многочисленным приложениям (как открытым, так и коммерческим). (wikipedia)
Для iOS можно скачать готовый пример подключения и использования cURL с сайта разработчика. И с iOS всё просто.
Под android мне на просторах google не удалось найти ни одного исходника, где бы производилось успешное обращение к этой кросс-платформенной библиотеке. (Может я плохо искал).
И вообще говоря под android заставить работать cURL оказалось немного сложнее чем хотелось бы.
Что нам понадобится:
- Установленный и настроенный для работы с android Eclipse;
- ndk и умение работы с ним;
- Скомпиленная под android библиотека cURL.
Получение библиотеки cURL для android
Если пойти на сайт cURL и зайти в загрузки, то там можно найти скомпиленный бинарник (Android 7.31.0 binary SSL) который видимо можно запускать как консольную утилиту из под девайса. Но он совершенно бесполезен, если мы хотим работать с библиотекой из своего приложения.
Хорошо погуглив можно найти туториал, как собрать нужную для ndk *.a библиотеку, с которой можно уже работать из приложения.
Есть про портирование cURL под android и на хабре. В результате мы получим желанный *.a файл библиотеки. Сам я его не собирал. Я его честно скачал.
Дальше
Дальше полученную библиотеку можно смело вставлять проект и используя всю мощь ndk обращаться к ней.
Java часть часть
Создадим MainActivity с одной кнопкой и полем для ввода адреса сайта, с которого будем получать информацию.
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<EditText
android:id="@+id/server_url"
android:text="@string/default_url"
android:hint="@string/server_url_hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"/>
<Button android:id="@+id/button_curl_call"
android:layout_below="@+id/server_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="@string/button_curl"
android:layout_gravity="right"
/>
<TextView
android:id="@+id/text"
android:layout_below="@+id/button_curl_call"
android:layout_weight="1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</RelativeLayout>
MainActivity.java
package com.ifree.ndkNative;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends Activity {
private static final String INTENT_HTML_DATA = ".html_data";
public static final int HANDLE_CALLBACK = 0;
final private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case HANDLE_CALLBACK:
String html = msg.getData().getString(INTENT_HTML_DATA);
SetHtmlText(html);
break;
}
}
};
private void SetHtmlText(String html){
TextView tv = (TextView) findViewById(R.id.text);
tv.setText(html);
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final EditText serverUrl = (EditText) findViewById(R.id.server_url);
Button btnCurl = (Button) findViewById(R.id.button_curl_call);
btnCurl.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Native curl = new Native();
curl.addCurlCallbackListener(new ICurlCallbackListener() {
@Override
public void curlCallBack(String callback) {
//для того, чтобы синхронизировать с потоком gui и отобразить полученный текст в TextView, используем handler
Bundle bundle = new Bundle();
bundle.putString(INTENT_HTML_DATA, callback);
Message message = handler.obtainMessage();
message.setData(bundle);
handler.sendMessage(message);
}
});
String response = curl.get_text_from_cpp(String.valueOf(serverUrl.getText()));//Собственно само обращение к С++ части
}
});
}
}
Напишем класс Native.java, в котором будет производиться обращение к С++ коду.
package com.ifree.ndkNative;
import java.util.HashSet;
public class Native {
private HashSet<ICurlCallbackListener> callBackListeners = new HashSet<ICurlCallbackListener>();
public void addCurlCallbackListener(ICurlCallbackListener listener){
callBackListeners.add(listener);
}
public void removeCurlCallbackListener(ICurlCallbackListener listener){
callBackListeners.remove(listener);
}
static {
System.loadLibrary("ndkNative");
}
//(ключевое слово native говорит, что реализация будет на C++):
public native String get_text_from_cpp(String data);
private void callback(String data) {
for(ICurlCallbackListener listener:callBackListeners){
listener.curlCallBack(data);
}
}
}
Нужно не забыть добавить в AndroidManifest разрешение на интернет
<uses-permission android:name="android.permission.INTERNET"/>
С++ часть
Созданный при помощи утилиты javah файл com_ifree_ndkNative_Native.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_ifree_ndkNative_Native */
#ifndef _Included_com_ifree_ndkNative_Native
#define _Included_com_ifree_ndkNative_Native
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_ifree_ndkNative_Native
* Method: get_text_from_cpp
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_ifree_ndkNative_Native_get_1text_1from_1cpp
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
Определим функцию получения данных с сервера (JNICALL Java_com_ifree_ndkNative_Native_get_1text_1from_1cpp) в ndkNative.cpp
#include <string.h>
#include <stdio.h>
#include "stddef.h"
#include <jni.h>
#include "com_ifree_ndkNative_Native.h"
#include "curl/curl.h"
#include "curl/easy.h"
JNIEnv * gEnv;
jobject gObj;
void function_callback(jstring str){
jclass cls = gEnv->GetObjectClass(gObj);
jmethodID mid = gEnv->GetMethodID(cls, "callback", "(Ljava/lang/String;)V");//вызов метода из java Native.callback(String data)
gEnv->CallVoidMethod(gObj, mid, str);
}
size_t function_pt(void *ptr, size_t size, size_t nmemb, void *stream){
function_callback(gEnv->NewStringUTF((char *) ptr));
size_t written = fwrite(ptr, size, nmemb, (FILE*)stream);
if(written <= 0)
return written * size;
}
JNIEXPORT jstring JNICALL Java_com_ifree_ndkNative_Native_get_1text_1from_1cpp
(JNIEnv * env, jobject obj, jstring str)
{
gEnv = env;
gObj = obj;
CURL *curl;
CURLcode res;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, env->GetStringUTFChars(str, 0));
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, function_pt);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
/* Check for errors */
if(res != CURLE_OK)
function_callback(gEnv->NewStringUTF(curl_easy_strerror(res)));
}else{
function_callback(gEnv->NewStringUTF("error"));
}
return env->NewStringUTF( "ok" );
}
Android.mf
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
include $(CLEAR_VARS)
LOCAL_MODULE:= libcurl
LOCAL_SRC_FILES := libcurl.a
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := ndkNative
LOCAL_SRC_FILES := ndkNative.cpp
LOCAL_STATIC_LIBRARIES := libcurl
include $(BUILD_SHARED_LIBRARY)
Исходник
P.s.: Необходимо сильно видоизменить проект для использования его в реальных приложения, в частности всю работу с cURL нужно вынести в отдельный С++ класс-обвертку.
P.s.s.: code convention в проекте немного хромает.
Автор: Alexey_Bespaly