现在很多公司做产品,一款产品往往存在很多平台,比如有安卓、苹果、黑莓、塞班、wp等。这些平台都要实现的话,往往需要很多人力和财力,而且质量可能也不高,于是常见的方式就是做中间件来适配这些平台。我们只要一个中间件平台,将这些平台共同需要的功能抽到中间件去实现。上面这些平台开发的语言不尽相同,综合效率和通用性我们一般都是选择C/c++来实现这个中间件,所需要注意的就是适配的问题。对苹果、黑莓、塞班等还是比较容易适配,因为他们主要还是基于C相关的,但是对于安卓和wp,适配就相对麻烦多了,wp咋们暂且不管,我们重点来看下安卓,安卓上层开发是用java开发的,使java和c/c++联系起来这里就需要用到NDK开发了。
对于ndk开发,主要就是关注两件事情,如何实现java层传数据给C/c++以及如何实现C/C++传数据给java,比如:登陆注册的时候,你在上层java实现的一个activity界面输入用户名和密码(这里就是数据),这个时候自然就是你传数据给c/c++,然后通过C/C++(这里你可以看成是中间件了)向服务端发数据,服务端根据收到的数据与之前注册的数据进行匹配,匹配成功就返回数据给中间件,中间件再返回给上层。
接下来我们根据具体事例来给大家介绍一下这个过程。
我们以备份数据到云端和还原通讯录到本地来说明
1、上层的activity界面代码实现
package com.afmobi.palmchat.ui.activity.setting;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import com.afmobi.palmchat.BaseActivity;
import com.afmobi.palmchat.PalmchatApp;
import com.afmobi.palmchat.R;
import com.afmobi.palmchat.constant.JsonConstant;
import com.afmobi.palmchat.constant.RequestConstant.RequestType;
import com.afmobi.palmchat.listener.ReqCodeListener;
import com.afmobi.palmchat.network.Response;
import com.afmobi.palmchat.ui.activity.setting.Query.OnQueryComplete;
import com.afmobi.palmchat.util.DialogUtils;
import com.afmobi.palmchat.util.ToastManager;
import com.core.AfNearByGpsInfo;
import com.core.AfPalmchat;
import com.core.AfPbInfo;
import com.core.Consts;
import com.core.cache.CacheManager;
import com.core.listener.AfHttpResultListener;
public class BackupActivity extends BaseActivity implements OnClickListener,
AfHttpResultListener, ReqCodeListener, OnQueryComplete {
private View viewBackup;
private View viewRecovery;
public final static int startCode = 1;
ProgressDialog progress;
private Button mBtnBack;
private AfPalmchat mAfCorePalmchat;
private byte action;
private Query queryHandler;
private List<Contacts> list;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
}
private void setParams() {
findViewById(R.id.back_layout).setOnClickListener(this);
viewBackup = findViewById(R.id.backup);
viewRecovery = findViewById(R.id.recovery);
viewBackup.setOnClickListener(this);
viewRecovery.setOnClickListener(this);
}
@Override
public void findViews() {
getContacts();
// TODO Auto-generated method stub
mAfCorePalmchat = ((PalmchatApp) getApplication()).mAfCorePalmchat;
setContentView(R.layout.phone_asisstant);
((TextView) findViewById(R.id.title_text))
.setText(R.string.phonebook_backup);
setParams();
mBtnBack = (Button) this.findViewById(R.id.back_button);
mBtnBack.setOnClickListener(this);
progress = new ProgressDialog(this);
progress.setMax(100);
progress.setMessage(getString(R.string.restore_address_book));
progress.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
}
@Override
public void init() {
// TODO Auto-generated method stub
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (v == viewBackup) {//备份到云端
DialogUtils.confirmDialog(this,
getString(R.string.back_up_address_book),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
List<Contacts> locallist = CacheManager
.getInstance().getContacts();
if(locallist==null){
return;
}
if (locallist.size() == 0) {
locallist = BackupActivity.this.list;
CacheManager.getInstance().setContacts(
locallist);
}
if (locallist.size() == 0) {
ToastManager.getInstance().show(
BackupActivity.this,
getString(R.string.address_book_empty));
} else {
Contacts contacts = null;
String pb_name[] = new String[locallist.size()];
String pb_phone[] = new String[locallist.size()];
for (int i = 0; i < locallist.size(); i++) {
contacts = locallist.get(i);
pb_name[i] = contacts.name;
pb_phone[i] = contacts.number;
}
showProgressDialog(getString(R.string.loading));
mAfCorePalmchat.AfHttpPhonebookBackup(pb_name,
pb_phone, Consts.HTTP_ACTION_A, 0, 0, 0,
BackupActivity.this);
}
}
});
} else if (v == viewRecovery) {//恢复到本地
// 恢复 Are you sure to restore phonebook?
DialogUtils.confirmDialog(this,
getString(R.string.sure_to_restore),
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
showProgressDialog(getString(R.string.loading));
mAfCorePalmchat.AfHttpPhonebookBackup(null,
null, Consts.HTTP_ACTION_B, 0, 100,
1, BackupActivity.this);
}
});
} else if (v == mBtnBack) {
finish();
}
}
@Override
public void AfOnResult(int httpHandle, int flag, int code, Object result, Object user_data) {
int userdata=(Integer)user_data;
if( code != Consts.REQ_CODE_SUCCESS){
return;
}
switch (flag) {
case Consts.REQ_PHONEBOOK_BACKUP:
dismissAllDialog();
if(userdata==0){
// 备份
ToastManager.getInstance().show(this, R.string.bak_succeeded);
}
if(userdata==1){
AfPbInfo info = (AfPbInfo)result;
Contacts contact =new Contacts();
String names[] =info.name;
String phones[]=info.phone;
for(int i=0; i< names.length;i++){
String name=names[i];
contact.name=name;
}
List<Contacts> list = new ArrayList<Contacts>();
list.add(contact);
Query q = new Query(null, BackupActivity.this);
q.saveOrUpdate(list, handler);
ToastManager.getInstance().show(this, R.string.restore_succeeded);
}
}
}
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
System.out.println("arg1 " + msg.arg1);
// if (msg.arg1 != msg.what) {
// progress.setMax(msg.what);
// progress.setProgress(msg.arg1);
// } else {
// progress.cancel();
// }
}
};
@Override
public void reqCode(int code, int flag) {
// TODO Auto-generated method stub
}
private void getContacts() {
// TODO Auto-generated method stub
queryHandler = new Query(getContentResolver(), this);
queryHandler.setQueryComplete(this);
queryHandler.query();
}
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK){
finish();
}
return false;
}
@Override
public void onComplete(Cursor c) {
// TODO Auto-generated method stub
List<Contacts> list = queryHandler.getContacts(this, c);
this.list = list;
}
}
2、点击备份按钮,
mAfCorePalmchat.AfHttpPhonebookBackup(pb_name,pb_phone, Consts.HTTP_ACTION_A, 0, 0, 0,
BackupActivity.this);
被调用。AfHttpPhonebookBackup()
看下这个方法
public int AfHttpPhonebookBackup(String pb_name[], String pb_phone[], byte action, int start, int limit, Object user_data, AfHttpResultListener result){ int handle = HttpPhonebookBackUP(pb_name, pb_phone, action, start, limit, 0); AfAddHttpListener(handle, result, null, user_data); return handle; }
粗体部分是一个native本地方法 private native int HttpPhonebookBackUP(String pb_name[], String pb_phone[], byte action, int start, int limit, int user_data)
这里要实现的功能就是把本地通讯录备份到云端(服务端),通过传数据(通讯录姓名组,联系电话组等)到中间件,中间件再传给服务端。
3、本地方法的实现
注意上面标红部分,传姓名组和电话组过来,对应本地就是jobjectarray.其他类型道理类似JNIEXPORT jint JNICALL Java_com_core_AfPalmchat_HttpPhonebookBackUP(JNIEnv *env, jobject thiz, jobjectArray pb_name, jobjectArray pb_phone, jbyte action, jint start, jint limit, jint user_data) { AFMBOI_HTTP_HANDLE http_id = AFMBOI_HTTP_HANDLE_INVALID; AFMOBI_HTTP_PB_BACKUP_PARAM param = {0}; int len1 = 0, len2 = 0, len3 = 0; AFMOBI_ARRAY_PTR backup[2] = {NULL, NULL}; jobjectArray array[2] = {pb_name, pb_phone}; CHAR* tmp; jstring str; if(HTTP_ACTION_A == action || HTTP_ACTION_C == action) { if( NULL == pb_name || NULL == pb_phone) { return AFMBOI_HTTP_HANDLE_INVALID; } len1 = env->GetArrayLength(pb_name); len2 = env->GetArrayLength(pb_phone); if( len1 != len2 || len1 <= 0) { return AFMBOI_HTTP_HANDLE_INVALID; } for(len3 = 0; len3 < 2; len3++) { backup[len3] = afmobi_array_new(len1, AF_FALSE); for(len2 = 0; len2 < len1; len2++) { str = (jstring)env->GetObjectArrayElement(array[len3], len2); JNI_GET_UTF_CHAR(tmp, str); //AFMOBI_TRACE((const INT8S*)"HttpPhonebookBackUP: tmp = %s, len3 = %d, len2 = %d", tmp, len3, len2); afmobi_array_append(backup[len3],(void*)tmp); } } param.pb_name = backup[0]; param.pb_phone = backup[1]; } param.user_data = (INT32U)user_data; param.action = (HTTP_ACTION_TYPE)action; param.start = start; param.limit = limit; http_id = afmobi_http_pb_backup(¶m,(AFMOBI_ON_TASK_RESULT_CALLBACK)AfmobiOnResult); for(len3 = 0; len3 < 2; len3++) { for(len2 = 0; len2 < len1; len2++) { str = (jstring)env->GetObjectArrayElement(array[len3], len2); tmp = (CHAR*)afmobi_array_get_at(backup[len3], (INT32U)len2); JNI_RELEASE_STR_STR(tmp, str); } afmobi_array_delete(backup[len3], AF_NULL); } return http_id; }
这里重点工作就是
http_id = afmobi_http_pb_backup(¶m,(AFMOBI_ON_TASK_RESULT_CALLBACK)AfmobiOnResult);
1、这里的参数param就包含了前面传过来的姓名组合电话号码组等
2、这里有个函数指针,当其他地方使用到这里时,该函数会被调用
这样当该方法执行并成功后,就把通讯录传到服务端了,同时向java层返回一个代表执行成功的数据。那么这个过程是如何实现的呢?
在前面的activity之间有一个重载的
public void AfOnResult(int httpHandle, int flag, int code, Object result, Object user_data)方法
这个方法被
AfOnResultInner()调用,而
AfOnResultInner这个方法则被C/C++调用
private final static void AfOnResultInner(int httpHandle, int flag, int code, Object result, int progress, int user_data) { Handler mainHandler = getMainHandle(); if( null != mainHandler){ HttpResponseInner response = new HttpResponseInner(httpHandle, flag, code, result, progress, user_data); Message message= Message.obtain(mainHandler, (progress < 0 ? AF_MSG_RESULT : AF_MSG_PROGRESS), 0, 0, response); mainHandler.sendMessage(message); } }
@Override public void handleMessage(Message msg){ switch (msg.what){ case AF_MSG_RESULT: case AF_MSG_PROGRESS: { HttpResponseInner response = (HttpResponseInner)msg.obj; HttpListenerInner listener = (HttpListenerInner)mHttpListener.get(response.httpHandle); if( null != listener){ if( msg.what == AF_MSG_RESULT){ if( null != listener.mResultListener){ listener.mResultListener.AfOnResult(response.httpHandle, response.flag, response.code, response.result, listener.mUserData); AfRemoveHttpListener(response.httpHandle); } }else{ if(null != listener.mProgressListener){ listener.mProgressListener.AfOnProgress(response.httpHandle, response.flag, response.progress, listener.mUserData); } } } } break;
}
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env; jclass clazz; jint result = -1; jint i; AFMOBI_TRACE((const INT8S*)"JNI_OnLoad: entry\n"); if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { AFMOBI_TRACE((const INT8S*)"ERROR: GetEnv failed\n"); goto error; } if ((clazz = env->FindClass((const char*)JNI_GET_CLASS_NAME(JAVA_CLASS_AfCorePalmchat))) == NULL) { AFMOBI_TRACE((const INT8S*)"ERROR: FindClass com/core/AfPalmchat failed\n"); goto error; } if (env->RegisterNatives(clazz, g_method_table, sizeof(g_method_table) / sizeof(JNINativeMethod)) < 0) { AFMOBI_TRACE((const INT8S*)"RegisterNatives failed"); goto error; } //load global object for(i = 0; i < JAVA_CLASS_MAX; i++) { JNI_initClassHelper(env, (const char *)JNI_GET_CLASS_NAME(i), &JNI_GET_CLASS_OBJ(i), &JNI_GET_CLASS_CONSTRUCT_METHODE(i)); } //load static method JNI_SET_STATIC_METHOD(JAVA_STATIC_METHOD_RESULT, env->GetStaticMethodID(clazz, "AfOnResultInner","(IIILjava/lang/Object;II)V")); JNI_SET_STATIC_METHOD(JAVA_STATIC_METHOD_SYS, env->GetStaticMethodID(clazz, "AfSysMsgProcInner","(ILjava/lang/Object;I)V")); result = JNI_VERSION_1_4; g_JavaVM = vm; JNI_nativeHandleCrashes(env); AFMOBI_TRACE((const INT8S*)"JNI_OnLoad: success\n"); error: return result; }
注意:
由于VM执行到System.loadLibrary()函数时,就会立即先呼叫JNI_OnLoad(),这里通过
JNI_SET_STATIC_METHOD(JAVA_STATIC_METHOD_RESULT, env->GetStaticMethodID(clazz, "AfOnResultInner","(IIILjava/lang/Object;II)V"));加载静态方法,这个时候会将数据传给
AfOnResultInner(int httpHandle, int flag, int code, Object result, int progress, int user_data)
方法,传过来的数据是在子线程中,通过转换将数据传到主线程中,然后在主线程中调用处理AfOnResult()方法。
后面咱们再具体分析这些细节部分。。。