講比較重要的代碼放到native層是比較好的做法。如果你有需求需要把socket通信的部分封裝的jni中實現,那麼本文可做參考。
代碼思路
1.總共實現三個native方法:
public native void initSocket(String ip,int port);
public native void closeSocket();
public native void sendData(byte[] buffer);
2.initSocket中創建套接字,並連接指定ip和端口的服務器,連接成功後創建線程,由於總是客戶端發起請求,因此,當有數據發送給服務斷後,喚醒該線程,阻塞讀取來自服務端的消息。然後將讀到的消息發送到java層,這裏只需從native調用java的方法,並把讀到的數據傳入即可。
3.sendData送來發送數據,它不在本地另開闢線程,需要在java層中開闢線程後調用它向服務器發送請求。
需要注意的點
1.c++中,子線需要首先調用javavm->AttachCurrentThread(&SocketDataDealThread::env,NULL);來獲得JNIEnv *的變量,然後使用該變量做進一步操作。
2.c++中跨線程使用JNI創建的變量時,要將其創建爲全局引用:
gObj = env->NewGlobalRef(thiz);
mSTh = new SocketDataDealThread(socketFd,gObj);
創建工程
android studio2.2版本開始完善了對C/C++ ,請參考開發者文檔:向您的項目添加 C 和 C++ 代碼。文檔是中文的。
本文的工程如圖所示:
若感興趣,本文的源碼請從這裏下載:(免積分的)AndoridJniSocket.zip
本文運行結果展示:
操作方式
1.輸入Ip和端口號
2.點INIT CLIENT按鈕,連接服務器成功後,INIT CLIENT會變成CONNECTED字樣,並且按鈕無法點擊,背景編程深灰色。
3.輸入發送內容並點擊發送按鈕,會看到read:後面出現了你發送的內容。這是因爲服務器目前只會將它收到的數據原樣返回。
4.點擊CLOSE按鈕斷開連接。
服務器代碼
服務器端的代碼比較簡單,線貼出來:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class SockeTest {
public static void main(String[] args) {
boolean isgo=true;
int len;
// TODO Auto-generated method stub
/**
* 基於TCP協議的Socket通信,實現用戶登錄,服務端
*/
System.out.println("start ...");
//1、創建一個服務器端Socket,即ServerSocket,指定綁定的端口,並監聽此端口
ServerSocket serverSocket;
try {
serverSocket = new ServerSocket(10010);
//2、調用accept()方法開始監聽,等待客戶端的連接
Socket socket = null;
InputStream is = null;
InputStreamReader isr = null;
OutputStream os = null;
BufferedReader br = null;
while(isgo){
System.out.println("start accept");
socket = serverSocket.accept();
System.out.println("after accept");
//3、獲取輸入流,並讀取客戶端信息
is = socket.getInputStream();
os = socket.getOutputStream();
isr =new InputStreamReader(is);
br =new BufferedReader(isr);
String info =null;
System.out.println("before read");
byte[] bbbb = new byte[100];
while((len = is.read(bbbb))>0){
System.out.println(String.valueOf(bbbb));
byte[] bbbbb = new byte[len];
System.arraycopy(bbbb, 0,bbbbb, 0, len);
os.write(bbbbb);
os.flush();
}
System.out.println("someone connect me");
}
//5、關閉資源
br.close();
isr.close();
is.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
客戶端
MainActivity.java
import android.graphics.Color;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native_socket");
}
Button send;
Button close;
Button init;
EditText ip;
EditText port;
EditText sendEdit;
TextView readText;
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 11:
readText.setText(msg.getData().getString("data"));
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
send = (Button) findViewById(R.id.send_button);
close = (Button) findViewById(R.id.close_button);
init = (Button) findViewById(R.id.init_button);
ip = (EditText)findViewById(R.id.ip);
port = (EditText)findViewById(R.id.port);
sendEdit = (EditText) findViewById(R.id.send_edit);
readText = (TextView) findViewById(R.id.read_text);
init.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
initSocket(ip.getText().toString(),Integer.valueOf(port.getText().toString()));
init.setText("connected");
init.setClickable(false);
init.setBackgroundColor(Color.DKGRAY);
}
});
send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
sendData(sendEdit.getText().toString().getBytes());
}
}).start();
}
});
close.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
closeSocket();
}
}).start();
}
});
}
public void setRecevieData(byte[] bb){
Log.d("native_socket","setRecevieData");
char[] cc = new char[bb.length];
for(int i=0;i<bb.length;i++){
cc[i]=(char)bb[i];
}
Log.d("native_socket",String.valueOf(cc));
Message message = new Message();
message.what = 11;
Bundle bundle = new Bundle();
bundle.putString("data",String.valueOf(cc));
message.setData(bundle);
handler.sendMessage(message);
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native void initSocket(String ip,int port);
public native void closeSocket();
public native void sendData(byte[] buffer);
}
nativeSocket.cpp
–註冊本地方法
#include <stdlib.h>
#include <stdio.h>
#include <jni.h>
#include <assert.h>
#include <pthread.h>
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <jni.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <arpa/inet.h>
#include "my_log.h"
#include "StringTools.h"
#include "SocketDataDealThread.h"
extern "C"{
}
static int socketFd=0;
static jobject gObj;
static JNIEnv *gEnv;
static SocketDataDealThread *mSTh;
static void initSocket(JNIEnv* env, jobject thiz,jstring ip,jint port){
LOGI("%s:initSocket",TAG);
char *lip = StringTools::jstringTostring(env,ip);
socketFd = socket(AF_INET,SOCK_STREAM, 0);
struct timeval timeout = {1,0};
//設置發送超時
setsockopt(socketFd,SOL_SOCKET,SO_SNDTIMEO,(char *)&timeout,sizeof(struct timeval));
//設置接收超時
setsockopt(socketFd,SOL_SOCKET,SO_RCVTIMEO,(char *)&timeout,sizeof(struct timeval));
//接收緩衝區設置爲32K
int nRecvBuf = 32*1024;
setsockopt(socketFd,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
//發送緩衝區設置爲32K
int nSendBuf = 32*1024;
setsockopt(socketFd,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
int nZero=0;
setsockopt (socketFd,SOL_SOCKET,SO_SNDBUF,(const char *)&nZero,sizeof(nZero));
///定義sockaddr_in
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(lip); ///服務器ip
servaddr.sin_port = htons(port); ///服務器端口
///連接服務器,成功返回0,錯誤返回-1
assert(connect(socketFd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0);
gObj = env->NewGlobalRef(thiz);
mSTh = new SocketDataDealThread(socketFd,gObj);
LOGI("%s:initSocket,ip = %s,port = %d",TAG,lip,port);
}
static void closeSocket(JNIEnv* env, jobject thiz){
void *status;
LOGI("%s:closeSocket",TAG);
SocketDataDealThread::isShoudExit = true;
mSTh->wakeUpThread();
pthread_join(mSTh->getSocketThreadId(), &status);
delete mSTh;
env->DeleteGlobalRef(gObj);
close(socketFd);
}
static void sendData(JNIEnv* env, jobject thiz,jbyteArray buffer){
jbyte *lb = env->GetByteArrayElements(buffer, NULL);
int length = env->GetArrayLength(buffer);
LOGI("%s:sendData,%s,length : %d",TAG,lb,length);
mSTh->sendData((char *)lb,length);
env->ReleaseByteArrayElements(buffer,lb,0);
}
static JNINativeMethod gMethods[] = {
{ "initSocket", "(Ljava/lang/String;I)V",(void*) initSocket },
{ "closeSocket", "()V",(void*) closeSocket },
{ "sendData", "([B)V",(void*) sendData },
};
/*
* 爲某一個類註冊本地方法
*/
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods) {
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
* 爲所有類註冊本地方法
*/
static int registerNatives(JNIEnv* env) {
const char* kClassName = "com/sharenew/androidjnitsocket/MainActivity"; //指定要註冊的類
return registerNativeMethods(env, kClassName, gMethods,
sizeof(gMethods) / sizeof(gMethods[0]));
}
/*
* System.loadLibrary("lib")時調用
* 如果成功返回JNI版本, 失敗返回-1
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env = NULL;
jint result = -1;
SocketDataDealThread::javavm = vm;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
if (!registerNatives(env)) { //註冊
return -1;
}
//成功
result = JNI_VERSION_1_4;
return result;
}
SocketDataDealThread.cpp
–創建子線,接受服務器發來的數據,並調用java層的方法處理數據
//
// Created by Administrator on 2017/3/3 0003.
//
#include <sys/socket.h>
#include <jni.h>
#include "my_log.h"
#include "SocketDataDealThread.h"
pthread_cond_t SocketDataDealThread::cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t SocketDataDealThread::mutex = PTHREAD_MUTEX_INITIALIZER;
int SocketDataDealThread::socketFd = 0;
char * SocketDataDealThread::getBuffer = NULL;
bool SocketDataDealThread::isShoudExit = false;
JNIEnv* SocketDataDealThread::env=NULL;
jobject SocketDataDealThread::obj=NULL;
JavaVM* SocketDataDealThread::javavm = NULL;
void *SocketDataDealThread::clientThread(void *args) {
javavm->AttachCurrentThread(&SocketDataDealThread::env,NULL);
LOGI("%s:SocketDataDealThread is running",TAG);
while (!SocketDataDealThread::isShoudExit){
pthread_mutex_lock(&SocketDataDealThread::mutex);
pthread_cond_wait(&SocketDataDealThread::cond,&SocketDataDealThread::mutex);
pthread_mutex_unlock(&SocketDataDealThread::mutex);
LOGI("%s:clientThread wake ",TAG);
int len = recv(socketFd,getBuffer, sizeof(getBuffer),0);
LOGI("%s:get data %s,len = %d",TAG,getBuffer,len);
if(SocketDataDealThread::env!=NULL && SocketDataDealThread::obj){
jclass cls = SocketDataDealThread::env->GetObjectClass(SocketDataDealThread::obj);
// if(cls == NULL){
// LOGI("%s:find class error",TAG);
// pthread_exit(NULL);
// }
jmethodID mid = SocketDataDealThread::env->GetMethodID(cls,"setRecevieData","([B)V");
SocketDataDealThread::env->DeleteLocalRef(cls);
if(mid==NULL){
LOGI("%s:find method1 error",TAG);
pthread_exit(NULL);
}
jbyteArray jarray = SocketDataDealThread::env->NewByteArray(len);
SocketDataDealThread::env->SetByteArrayRegion(jarray,0,len,(jbyte*)getBuffer);
SocketDataDealThread::env->CallVoidMethod(obj,mid,jarray);
SocketDataDealThread::env->DeleteLocalRef(jarray);
}
}
LOGI("%s:SocketDataDealThread exit",TAG);
return NULL;
}
SocketDataDealThread::SocketDataDealThread(int fd,jobject obj1):
threadId(0), sendLength(0){
if(pthread_create(&threadId,NULL,SocketDataDealThread::clientThread,NULL) != 0 ){
LOGI("%s:pthread_create error",TAG);
}
LOGI("%s:mSTh->getSocketThreadId():%lu",TAG,(long)threadId);
SocketDataDealThread::obj = obj1;
getBuffer = new char[100];
sendBuffer = new char[100];
socketFd=fd;
}
SocketDataDealThread::~SocketDataDealThread() {
delete getBuffer;
delete sendBuffer;
}
void SocketDataDealThread::sendData(char *buff, int length) {
LOGI("%s:send data %s,len = %d",TAG,buff,length);
int len = send(socketFd,buff, length,0);
if(len<0){
LOGI("%s:send data error,len = %d",TAG,len);
}
wakeUpThread();
}
pthread_t SocketDataDealThread::getSocketThreadId() {
return threadId;
}
void SocketDataDealThread::wakeUpThread(){
pthread_mutex_lock(&SocketDataDealThread::mutex);
// 設置條件爲真
pthread_cond_signal(&SocketDataDealThread::cond);
pthread_mutex_unlock(&SocketDataDealThread::mutex);
}
SocketDataDealThread .h
//
// Created by Administrator on 2017/3/3 0003.
//
#ifndef ANDROIDJNITSOCKET_SOCKETDATADEALTHREAD_H
#define ANDROIDJNITSOCKET_SOCKETDATADEALTHREAD_H
#include <pthread.h>
class SocketDataDealThread {
public:
static void * clientThread(void *args);
static pthread_cond_t cond;
static JavaVM* javavm;
static pthread_mutex_t mutex;
static bool isShoudExit;
static JNIEnv* env;
static jobject obj;
SocketDataDealThread(int fd,jobject obj);
~SocketDataDealThread();
void sendData(char buff[],int length);
void setIsShoudExit(bool isShoud);
pthread_t getSocketThreadId();
void wakeUpThread();
private:
pthread_t threadId;
static int socketFd;
static char * getBuffer;
char * sendBuffer;
int sendLength=0;
};
#endif //ANDROIDJNITSOCKET_SOCKETDATADEALTHREAD_H
StringTools.cpp–工具類,提供字符串的轉換
//
// Created by Administrator on 2017/3/3 0003.
//
#include <cwchar>
#include <string.h>
#include "StringTools.h"
char* StringTools::jstringTostring(JNIEnv* env, jstring jstr){
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
StringTools .h
//
// Created by Administrator on 2017/3/3 0003.
//
#ifndef ANDROIDJNITSOCKET_STRINGTOOS_H
#define ANDROIDJNITSOCKET_STRINGTOOS_H
#include <jni.h>
class StringTools {
public:
static char* jstringTostring(JNIEnv* env, jstring jstr);
};
#endif //ANDROIDJNITSOCKET_STRINGTOOS_H