Pull to refresh

Работа с cURL на android

Reading time 6 min
Views 22K

Зачем это нужно


Мы хотим общение с 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 в проекте немного хромает.
Tags:
Hubs:
+15
Comments 18
Comments Comments 18

Articles