现在很多公司做产品,一款产品往往存在很多平台,比如有安卓、苹果、黑莓、塞班、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、本地方法的实现
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()方法。
后面咱们再具体分析这些细节部分。。。