搭建工具环境
安装系统工具
sudo apt-get install autoconf ???point docbook2x tofrodos
下载NDK,android-ndk-r14b-linux-x86_64.zip,解压后,在NDK目录执行:
mkdir -p /mnt/work/toolchain/android-standalone-r14b
export ANDROID_HOME=/mnt/work/toolchain/android-standalone-r14b
build/tools/make_standalone_toolchain.py \
--arch arm --api 16 --stl=gnustl \
--install-dir $ANDROID_HOME/toolchain
export ANDROID_NDK_ROOT=/mnt/work/toolchain/ndk/android-ndk-r14b
export ANDROID_NDK_HOME=/mnt/work/toolchain/ndk/android-ndk-r14b
构建依赖库
c-ares
git clone https://github.com/c-ares/c-ares.git
cd c-ares
git br 1.15.0 e982924acee7f7313b4baa4ee5ec000c5e373c30
git co 1.15.0
./buildconf
./configure \
--prefix=${ANDROID_HOME}/usr/local \
--host=arm-linux-androideabi \
CC=arm-linux-androideabi-gcc \
--enable-shared=no \
--enable-static=yes
make install
cd ..
libexpat
git clone https://github.com/libexpat/libexpat.git
cd libexpat
git br 2.2.6 39e487da353b20bb3a724311d179ba0fddffc65b
git co 2.2.6
./buildconf
./configure \
--prefix=${ANDROID_HOME}/usr/local \
--host=arm-linux-androideabi \
CC=arm-linux-androideabi-gcc \
--enable-shared=no \
--enable-static=yes \
--with-docbook
make install
cd ..
openssl
git clone https://github.com/openssl/openssl.git
cd openssl
git br 1.1.1b 50eaac9f3337667259de725451f201e784599687
git co 1.1.1b
wget https://wiki.openssl.org/images/7/70/Setenv-android.sh
fromdos Setenv-android.sh
chmod u+x Setenv-android.sh
. Setenv-android.sh
./config shared no-ssl2 no-ssl3 no-comp no-hw no-engine \
--openssldir=${ANDROID_HOME}/usr/local \
--prefix=${ANDROID_HOME}/usr/local
make depend
make all
make install \
CC=$ANDROID_TOOLCHAIN/arm-linux-androideabi-gcc \
RANLIB=$ANDROID_TOOLCHAIN/arm-linux-androideabi-ranlib
export PATH=${PATH#*:}
cd ..
zlib
(tar.gz包手动下载)
tar -xzvf zlib-1.2.11.tar.gz
cd zlib-1.2.11
export CC=arm-linux-androideabi-gcc
./configure \
--prefix=${ANDROID_HOME}/usr/local \
-static
make install
libssh2
git clone https://github.com/libssh2/libssh2.git
cd libssh2
# 1.8.2 failed need patch d071e0e07b2, use master
# git br 1.8.2 02ecf17a6d5f9837699e8fb3aad0c804caa67eeb
# git co 1.8.2
./configure \
--prefix=${ANDROID_HOME}/usr/local \
--host=arm-linux-androideabi \
CC=arm-linux-androideabi-gcc \
--enable-shared=no \
--enable-static=yes
make install
构建aria2
git clone https://github.com/aria2/aria2.git
cd aria2
autoreconf -i
./android-config
./android-make
./android-release
ln -s src/.libs/libaria2.a
cd ..
如果要生成动态链接库
/bin/bash ../libtool --silent --tag=CXX --mode=link \
/home/cmguo/work/toolchain/android-standalone-r14b/toolchain/bin/arm-linux-androideabi-clang++ \
-export-dynamic -std=c++11 -pipe -Os -g -fPIE -pie -shared -o libaria2.so main.o libaria2.la \
/home/cmguo/work/toolchain/android-standalone-r14b/usr/local/lib/libz.a \
/home/cmguo/work/toolchain/android-standalone-r14b/usr/local/lib/libexpat.a \
/home/cmguo/work/toolchain/android-standalone-r14b/usr/local/lib/libssl.a \
/home/cmguo/work/toolchain/android-standalone-r14b/usr/local/lib/libcrypto.a \
/home/cmguo/work/toolchain/android-standalone-r14b/usr/local/lib/libssh2.a \
/home/cmguo/work/toolchain/android-standalone-r14b/usr/local/lib/libcares.a \
-llog ../deps/wslay/lib/libwslay.la
arm-linux-androideabi-strip -s libaria2.so -o ../libaria2.so
二次开发
动态库生成后,我们就可以在APP里面加载aria2库了。
JNI对接
我们只使用rpc(即http)接口操作aria2,所以只要完成启动其main入口就行了。为此,我们将main方法通过JNI形式提供给java层调用。
extern "C" JNIEXPORT jint JNICALL Java_com_xxx_aria2_Aria2_main(JNIEnv *env, jclass clazz, jobjectArray jargv)
{ //jargv is a Java array of Java strings
int argc = env->GetArrayLength(jargv);
typedef char *pchar;
pchar *argv = new pchar[argc];
int i;
for(i = 0; i < argc; i++)
{
jstring js = (jstring) env->GetObjectArrayElement(jargv, i); //A Java string
jboolean isCopy;
const char *pjc = env->GetStringUTFChars(js, &isCopy); //A pointer to a Java-managed char buffer
size_t jslen = strlen(pjc);
argv[i] = new char[jslen+1]; //Extra char for the terminating null
strcpy(argv[i], pjc); //Copy to *our* buffer. We could omit that, but IMHO this is cleaner. Also, const correctness.
env->ReleaseStringUTFChars(js, pjc);
}
//Call main
chdir(argv[0]);
int ret = main2(argc, argv);
//Now free the array
for(i=0;i<argc;i++)
delete [] argv[i];
delete [] argv;
return ret;
}
Log对接
但启动出现问题时,通过aria2自己的输出日志排查问题是很必要的。Aria2通过OutputFile基类输出日志,因此我们可以很方便的扩展一个使用Android Log的OutputFile——LogcatFile。
// LogcatFile.h
#include "OutputFile.h"
#include <android/log.h>
#include <stdarg.h>
namespace aria2 {
class LogcatFile : public OutputFile {
public:
LogcatFile(int l)
{
level_ = l;
}
virtual ~LogcatFile() = default;
virtual size_t write(const char* str)
{
__android_log_print(level_, "Aria2", "%s", str);
return 0;
}
virtual int flush() { return 0; }
virtual int vprintf(const char* format, va_list va)
{
__android_log_vprint(level_, "Aria2", format, va);
return 0;
}
inline int printf(const char* format, ...)
{
va_list va;
va_start(va, format);
int rv = vprintf(format, va);
va_end(va);
return rv;
}
virtual bool supportsColor() { return true; }
private:
int level_;
};
}
在Console初始化中,增加Android情况下的选择策略,使用LogcatFile。
#include "NullOutputFile.h"
#ifdef __MINGW32__
# include "WinConsoleFile.h"
#elif __ANDROID__
# include "LogcatFile.h"
#else // !__MINGW32__
# include "BufferedFile.h"
#endif // !__MINGW32__
void initConsole(bool suppress)
{
if (suppress) {
consoleCout = consoleCerr = std::make_shared<NullOutputFile>();
}
else {
#ifdef __MINGW32__
consoleCout = std::make_shared<WinConsoleFile>(STD_OUTPUT_HANDLE);
consoleCerr = std::make_shared<WinConsoleFile>(STD_ERROR_HANDLE);
#elif __ANDROID__
consoleCout = std::make_shared<LogcatFile>(3);
consoleCerr = std::make_shared<LogcatFile>(5);
#else // !__MINGW32__
consoleCout = std::make_shared<BufferedFile>(stdout);
consoleCerr = std::make_shared<BufferedFile>(stderr);
#endif // !__MINGW32__
}
}
Java封装
接下来,我们就可以在APP使用aria2了。
public class Aria2
{
protected static final String TAG = "Aria2";
static {
System.loadLibrary("aria2");
}
private static Thread sThread;
public static void start(Context context) {
File extDir = context.getExternalCacheDir();
File storageDir = new File(extDir, "aria2");
File sessionFile = new File(extDir, "aria2.session");
File cacheDir = context.getCacheDir();
File confFile = new File(cacheDir, "aria2.conf");
File logFile = new File(cacheDir, "aria2.log");
storageDir.mkdirs();
Assets.extract(context, "aria2.conf", confFile);
try {
new FileOutputStream(sessionFile).close();
} catch (Exception e) {
}
final List<String> argv = new ArrayList<String>();
argv.add(cacheDir.getAbsolutePath());
argv.add("--conf-path=" + confFile.getAbsolutePath());
argv.add("--log=" + logFile.getAbsolutePath());
argv.add("--input-file=" + sessionFile.getAbsolutePath());
argv.add("--save-session=" + sessionFile.getAbsolutePath());
argv.add("--dir=" + storageDir.getAbsolutePath());
sThread = new Thread(new Runnable() {
@Override
public void run() {
main(argv.toArray(new String[argv.size()]));
}
});
sThread.start();
}
}
这里我们做了下面几件事:
- 在assets里面集成了配置文件 aria2.conf,一开始将其解压出来
- 通过命令行参数传递一些与文件路径有关的传输,比如conf-path、log、dir等,有些目录和文件要提前准备好
- 配置文件 aria2.conf的deamon改成false,要不然aria2会fork一个新进程,其实是fork了一个应用进程,这会导致一些不可预见的问题。