现在很多公司做产品,一款产品往往存在很多平台,比如有安卓、苹果、黑莓、塞班、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; } }
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、本地方法的实现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; }注意上面标红部分,传姓名组和电话组过来,对应本地就是jobjectarray.其他类型道理类似这里重点工作就是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()方法。
后面咱们再具体分析这些细节部分。。。