日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

android 局域网聊天工具(可发送文字/语音)

發布時間:2023/12/18 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android 局域网聊天工具(可发送文字/语音) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最近比較有空,花了點時間寫了個android局域網聊天工具,使用java的異步tcp通信。基本功能實現(簡單的界面,聊天記錄,發送文字,發送語音),在此小結一下。

?

Java (非android)局域網聊天工具源碼,跟android的差別不大,參考:

http://download.csdn.net/detail/yarkey09/7052573

?

0,整個程序源碼結構

1,聊天功能 (ServerSocketChannel & SocketChannel)

實現這個功能的時候有一個非常大的感受,就是寫java程序真是方便!因為自己以前就寫過windows上的java異步socket通信程序,所以這次幾乎不需要修改很多代碼,就可以搬過來。頗有Write one, run everywhere的feel。

個人認為java.nio的核心就是Selector和Buffer吧。通過Selector輪詢各個已注冊的socket的事件。若沒有事件,則阻塞,若有事件則返回。因為在android,主線程不能做太多事情,

所以我起了一個新的線程,讓Selector自個兒跑去。

以下是TcpWorkerThread類的源碼,主要完成三件事

1,"開啟"一個Selector

2,提供registToSelector方法

3,處理客戶端的連接事件,處理socket接收消息事件

Class : TcpWorkerThread

package com.yarkey.tcp;import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set;import android.os.Handler; import android.util.Log;public class TcpWorkerThread extends Thread {private static final String TAG = "TcpWorkerThread";/** 出錯!返回String,描述出錯原因 */public static final int EVENT_ERROR = 0;/** 線程結束, 停止運行 */public static final int EVENT_STOPPED = 1;/** 收到來自客戶端的tcp連接, 報告一個socketchannel */public static final int EVENT_ACCEPTED = 2;/** 收到來自客戶端的tcp消息, 報告一個TcpArgs, content為FileSerial對象 */public static final int EVENT_RECEIVED = 3;/** SocketChannel,ServerSocketChannel關閉 */public static final int EVENT_CLOSED = 4;/** TCP線程往主線程通信 */private Handler mHandler;private Selector mSelector;private boolean mIsRun = true;protected static class TcpArgs {SocketChannel sc;Object content;// 接收消息}/*** 如果拋出異常,不能進行異步通信了* * @throws Exception*/public TcpWorkerThread(Handler handler) throws Exception {Log.d(TAG, "TcpWorkerThread contructor");if (handler == null) {throw new Exception("Handler is null!");} else {mHandler = handler;}mSelector = Selector.open();}/*** 將一個服務端的ServerSocketChannel設置為非阻塞模式,并將其注冊到selector中(OP_ACCEPT)* * @param ssc* @throws IOException*/public void registToSelector(ServerSocketChannel ssc) throws IOException {Log.d(TAG, "registToSelector, ServerSocketChannel");ssc.configureBlocking(false);mSelector.wakeup();ssc.register(mSelector, SelectionKey.OP_ACCEPT);}/*** 將一個客戶端的SocketChannel設置為非阻塞模式,并將其注冊到selector中(OP_READ)* * @param ss* @throws IOException*/public void registToSelector(SocketChannel ss) throws IOException {Log.d(TAG, "registToSelector, SocketChannel");ss.configureBlocking(false);mSelector.wakeup();ss.register(mSelector, SelectionKey.OP_READ);}/*** 停止線程運行*/public void stopWorkerThread() {Log.d(TAG, "stopWorkerThread");mIsRun = false;mSelector.wakeup();}@Overridepublic void run() {// TODO Auto-generated method stubLog.d(TAG, "線程開始運行,run()");// 用于裝入接收到的數據ByteBuffer buffer = ByteBuffer.allocate(1024);while (mIsRun) {int events = 0;try {events = mSelector.select();} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();mHandler.obtainMessage(EVENT_ERROR, "Selector IOException").sendToTarget();// 出錯break;}if (events <= 0) {// 走到這里,只能說明被wakeup了,應該是別的地方需要,因此這里暫停100msLog.d(TAG, "sleep 100 ms >>>");try {sleep(100);} catch (InterruptedException e) {// interrupt! ignore thise.printStackTrace();}Log.d(TAG, "sleep 100 ms <<< wake up.");continue;}Log.d(TAG, "mSelector.select(), events ===========================> " + events);Set<SelectionKey> selectionKeys = mSelector.selectedKeys();Iterator<SelectionKey> iter = selectionKeys.iterator();// 代表連接成功后的socketSocketChannel socketChannel;while (iter.hasNext()) {SelectionKey key = iter.next();socketChannel = null;// 服務端收到連接if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {ServerSocketChannel ssc = (ServerSocketChannel) key.channel();try {socketChannel = ssc.accept();Log.d(TAG, "ssc.accept()");} catch (IOException e) {e.printStackTrace();try {ssc.close();} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}mHandler.obtainMessage(EVENT_CLOSED, ssc).sendToTarget();}if (socketChannel != null) {try {socketChannel.configureBlocking(false);socketChannel.register(mSelector, SelectionKey.OP_READ);Log.d(TAG, "來自客戶端的新連接");mHandler.obtainMessage(EVENT_ACCEPTED, socketChannel).sendToTarget();} catch (IOException e) {e.printStackTrace();try {socketChannel.close();} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}mHandler.obtainMessage(EVENT_CLOSED, socketChannel).sendToTarget();}} else {Log.e(TAG, "socketChannel is null !");}iter.remove();}// 接收到客戶的消息else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {socketChannel = (SocketChannel) key.channel();Log.d(TAG, "接收到新消息");boolean hasException = false;ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();while (true) {// 把position設為0,把limit設為capacitybuffer.clear();int a = 0;try {a = socketChannel.read(buffer);} catch (Exception e) {e.printStackTrace();try {socketChannel.close();} catch (IOException e1) {// TODO Auto-generated catch blocke1.printStackTrace();}hasException = true;mHandler.obtainMessage(EVENT_CLOSED, socketChannel).sendToTarget();break;}Log.d(TAG, "a=" + a);if (a == 0) {break;}if (a == -1) {try {socketChannel.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}hasException = true;mHandler.obtainMessage(EVENT_CLOSED, socketChannel).sendToTarget();Log.w(TAG, "讀取到EOS,我們關閉了一個連接!");break;}if (a > 0) {buffer.flip();try {byteOutput.write(buffer.array());} catch (IOException e) {// TODO Auto-generated catch blockhasException = true;e.printStackTrace();}}}if (!hasException) {byte[] b = byteOutput.toByteArray();TcpArgs args = new TcpArgs();args.sc = socketChannel;args.content = SerialUtil.toObject(b);mHandler.obtainMessage(EVENT_RECEIVED, args).sendToTarget();}iter.remove();}}}// 關閉資源try {mSelector.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}Log.d(TAG, "線程結束運行");mHandler.sendEmptyMessage(EVENT_STOPPED);} }

一般我們需要對socket處理的事件應該有三個:來自客戶端的連接(accept),接收消息(read),發送消息(write)。TcpWorkerThread完成了前兩件事,至于發送消息,我在另外一個類里面完成,也是新起一個線程,不過消息發送完后,發送線程也就停止了。

以下TcpManager類有幾個特點:

1,靜態單例

2,擁有一個TcpWorkerThread對象

3,擁有當前所有連接成功的socket

4,具有“新建服務端”“新建客戶端”“發送消息”方法

5,采用register/notify機制,提供注冊監聽的方法

6,處理兩個特殊的TCP事件( socket連接后,還需要雙方互發昵稱,才算聊天建立成功;如果接收到音頻文件,需要保存到SD卡中;)

Class : TcpManager

package com.yarkey.tcp;import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.ArrayList;import android.os.Handler; import android.os.Message; import android.util.Log;import com.yarkey.groupchat.Global; import com.yarkey.tcp.TcpWorkerThread.TcpArgs; import com.yarkey.utils.RegistrantList;/*** 擁有TcpWorkerThread* * @author yeqi.zhang* */ public class TcpManager {private static final String TAG = "TcpManager";private static TcpManager mInstance;// 單例private static final String mRecFolder = TcpConfig.TCP_REC_FOLDER;// 保存文件的路徑private TcpWorkerThread mThread;// 異步線程private ServerSocketChannel mServerSocketChannel;private ArrayList<SocketChannel> mSocketChannelList;// 保存連接的socketChannelprivate ArrayList<String> mNameList;// 保存對方名字public static class MessageArgs {public String name;// 對方的昵稱public String content;// 文件路徑,或者消息內容}protected RegistrantList mRegistrantListConnect = new RegistrantList();protected RegistrantList mRegistrantListReceiveAudio = new RegistrantList();protected RegistrantList mRegistrantListReceiveText = new RegistrantList();protected RegistrantList mRegistrantListError = new RegistrantList();public ServerSocketChannel getServerSocketChannel() {return mServerSocketChannel;}public ArrayList<SocketChannel> getSocketChannelList() {return mSocketChannelList;}public ArrayList<String> getNameList() {return mNameList;}/** 連接建立成功, obj=name */public void registerForConnect(Handler h, int what, Object obj) {mRegistrantListConnect.addUnique(h, what, obj);}public void unRegisterForConnect(Handler h) {mRegistrantListConnect.remove(h);}/** 收到音頻消息, obj=MessageArgs */public void registerForReceiveAudio(Handler h, int what, Object obj) {mRegistrantListReceiveAudio.addUnique(h, what, obj);}public void unRegisterForReceiveAudio(Handler h) {mRegistrantListReceiveAudio.remove(h);}/** 收到文字消息, obj=MessageArgs */public void registerForReceiveText(Handler h, int what, Object obj) {mRegistrantListReceiveText.addUnique(h, what, obj);}public void unRegisterForReceiveText(Handler h) {mRegistrantListReceiveText.remove(h);}/** 有錯誤發生, obj=errorReason */public void registerForError(Handler h, int what, Object obj) {mRegistrantListError.addUnique(h, what, obj);}public void unRegisterForError(Handler h) {mRegistrantListError.remove(h);}/*** 如果TcpWorkerThread沒有初始化成功,返回null !* * @return*/public static TcpManager getInstance() {Log.d(TAG, "getInstance");if (mInstance == null) {mInstance = new TcpManager();}if (mInstance.mThread == null) {Log.e(TAG, "TcpWorkerThread 初始化不成功,這是致命的錯誤!");return null;}return mInstance;}public static void release() {Log.d(TAG, "release");mInstance.mThread.stopWorkerThread();// 釋放監聽的socketif (mInstance.mServerSocketChannel != null) {try {mInstance.mServerSocketChannel.close();Log.d(TAG, "release ServerSocketChannel!");} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}// 釋放連接的socketint i = 0;for (; i < mInstance.mSocketChannelList.size(); i++) {try {mInstance.mSocketChannelList.get(i).close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}Log.i(TAG, "release " + (i - 1) + " SocketChannels!");}private TcpManager() {Log.d(TAG, "TcpManager private contructor");mSocketChannelList = new ArrayList<SocketChannel>();mNameList = new ArrayList<String>();try {mThread = new TcpWorkerThread(mThreadEventHandler);mThread.start();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();mThread = null;// 這個會導致getInstance 返回null}}// public void setHandler(Handler h) {// Log.d(TAG, "setHandler,h=" + h);// mHandler = h;// }/*** 處理TcpWorkerThread*/private Handler mThreadEventHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {Log.d(TAG, "mThreadEventHandler, handleMessage");// TODO Auto-generated method stubswitch (msg.what) {case TcpWorkerThread.EVENT_ACCEPTED:Log.d(TAG, "TcpWorkerThread.EVENT_ACCEPTED");sendName((SocketChannel) msg.obj);break;case TcpWorkerThread.EVENT_RECEIVED:Log.d(TAG, "TcpWorkerThread.EVENT_RECEIVED");TcpWorkerThread.TcpArgs argsRec = (TcpArgs) msg.obj;SocketChannel socketch = argsRec.sc;FileSerial objSerial = (FileSerial) argsRec.content;// 準備發送一個MessageArgs對象MessageArgs msgargs = null;if (objSerial.getType() != FileSerial.TYPE_NAME) {msgargs = new MessageArgs();int index = 0;for (; index < mSocketChannelList.size(); index++) {if (socketch.equals(mSocketChannelList.get(index))) {break;}}msgargs.name = mNameList.get(index);}switch (objSerial.getType()) {case FileSerial.TYPE_AUDIO:Log.d(TAG, "收到音頻信息");FileOutputStream fileOut;try {fileOut = new FileOutputStream(mRecFolder + objSerial.getFileName());fileOut.write(objSerial.getFileContent(), 0, (int) objSerial.getFileLength());fileOut.close();Log.d(TAG, "音頻文件已保存到本地!");} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}// if (mHandler != null) {// mHandler.obtainMessage(EVENT_REC_AUDIO, mRecFolder +// objSerial.getFileName()).sendToTarget();// }msgargs.content = mRecFolder + objSerial.getFileName();mRegistrantListReceiveAudio.notifyResult(msgargs);break;case FileSerial.TYPE_TEXT:Log.d(TAG, "收到文字信息");// if (mHandler != null) {// mHandler.obtainMessage(EVENT_REC_TEXT,// objSerial.getFileName()).sendToTarget();// }msgargs.content = objSerial.getFileName();Log.d(TAG, "message=" + objSerial.getFileName());mRegistrantListReceiveText.notifyResult(msgargs);break;case FileSerial.TYPE_NAME:Log.d(TAG, "收到對方名稱,連接正式成功!");mSocketChannelList.add(argsRec.sc);mNameList.add(objSerial.getFileName());// if (mHandler != null) {// mHandler.obtainMessage(EVENT_CONNECT,// objSerial.getFileName()).sendToTarget();// }mRegistrantListConnect.notifyResult(objSerial.getFileName());break;}break;case TcpWorkerThread.EVENT_ERROR:Log.w(TAG, "error:" + (String) msg.obj);break;case TcpWorkerThread.EVENT_STOPPED:Log.w(TAG, "EVENT_STOPPED");break;// ---------------------------------------------case TcpAsyncClient.EVENT_CONNECTED:TcpAsyncClient.TcpArgs args = (TcpAsyncClient.TcpArgs) msg.obj;try {mThread.registToSelector(args.result);Log.d(TAG, "客戶端連接服務端成功,發送昵稱...");sendName(args.result);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();this.sendEmptyMessage(TcpAsyncClient.EVENT_ERROR);}break;case TcpAsyncClient.EVENT_ERROR:Log.e(TAG, "客戶端發起連接,發生錯誤!");// mHandler.obtainMessage(EVENT_ERROR,// "客戶端發起連接失敗").sendToTarget();mRegistrantListError.notifyResult("客戶端發起連接失敗");break;}}};/*** 作為客戶端,連接到指定的地址。如果返回false,表示連接沒成功,如果返回true,那么需要等待* * @deprecated android較高的版本,不允許在主線程訪問network* @param ip* @param port* @return*/public boolean newConnection(String ip, int port) {TcpClient client = new TcpClient(ip, port);SocketChannel sc = client.connect();try {mThread.registToSelector(sc);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();return false;}return true;}/*** 作為客戶端,異步創建連接* * @param ip* @param port*/public void newAsyncConnection(String ip, int port) {Log.d(TAG, "newAsyncConnection, ip=" + ip + ",port=" + port);TcpAsyncClient client = new TcpAsyncClient();TcpAsyncClient.TcpArgs args = new TcpAsyncClient.TcpArgs();args.handler = mThreadEventHandler;args.ip = ip;args.port = port;client.connect(args);}/*** 作為服務端,啟動服務監聽* * @param port* @return*/public boolean newServer(int port) {Log.d(TAG, "newServer, port=" + port);if (mServerSocketChannel != null) {// serversocketchannel 不一定注冊到selector成功,但是這里暫時只能允許存在一個Log.w(TAG, "can only have one server for accepting !");} else {TcpServer server = new TcpServer(port);mServerSocketChannel = server.accepting();}try {mThread.registToSelector(mServerSocketChannel);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();return false;}return true;}/*** 發送給對方,昵稱/文字消息/文件* * @param sc* SocketChannel* @param content* name/message/filepath* @param fileName* if type==AUDIO, you should set fileName* @param type* {@link FileSerial#TYPE_TEXT}, {@link FileSerial#TYPE_NAME},* {@link FileSerial#TYPE_AUDIO}*/private void send(final SocketChannel sc, String content, String fileName, int type) {Log.d(TAG, "content=" + content + ",fileName=" + fileName + ",type=" + type);FileSerial fpo = new FileSerial();// typefpo.setType(type);switch (type) {case FileSerial.TYPE_AUDIO:// namefpo.setFileName(fileName);// lengthFile f = new File(content);long fileLength = f.length();fpo.setFileLength(fileLength);// contentFileInputStream fis = null;byte[] fileContent = new byte[(int) fileLength];try {fis = new FileInputStream(content);fis.read(fileContent, 0, (int) fileLength);fis.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}fpo.setFileContent(fileContent);break;case FileSerial.TYPE_TEXT:case FileSerial.TYPE_NAME:// namefpo.setFileName(content);// lengthfpo.setFileLength(content.length());// content// nullbreak;}// toByte -> sendbyte[] bytes = SerialUtil.toByte(fpo);final ByteBuffer buffer = ByteBuffer.wrap(bytes);// E/AndroidRuntime(18606): android.os.NetworkOnMainThreadExceptionnew Thread() {@Overridepublic void run() {// TODO Auto-generated method stubLog.d(TAG, "send, async ! 異步發送 ... ");try {// 此處在主線程調用, 有可能會阻塞!sc.write(buffer);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}.start();}/*** 發送音頻文件* * @param index* @param content* @param fileName*/public void sendAudioFile(int index, String content, String fileName) {Log.d(TAG, "sendAudioFile, index=" + index + ",content=" + content + ",fileName=" + fileName);SocketChannel sc = mSocketChannelList.get(index);send(sc, content, fileName, FileSerial.TYPE_AUDIO);}/*** 發送音頻文件* * @param name* @param content* @param fileName*/public void sendAudioFile(String name, String content, String fileName) {Log.d(TAG, "sendAudioFile, name=" + name + ",content=" + content + ",fileName=" + fileName);int index = -1;for (int i = 0; i < mNameList.size(); i++) {if (mNameList.get(i).equals(name)) {index = i;break;}}if (index != -1) {sendAudioFile(index, content, fileName);} else {Log.w(TAG, "sendAudioFile, name=" + name + " not found !");}}/*** 發送文字消息* * @param index* @param msg*/public void sendMessage(int index, String msg) {Log.d(TAG, "sendMessage, index=" + index + ",msg=" + msg);SocketChannel sc = mSocketChannelList.get(index);send(sc, msg, null, FileSerial.TYPE_TEXT);}/*** 發送文字消息* * @param name* @param msg*/public void sendMessage(String name, String msg) {Log.d(TAG, "sendMessage, name=" + name + ",msg=" + msg);int index = -1;for (int i = 0; i < mNameList.size(); i++) {if (mNameList.get(i).equals(name)) {index = i;break;}}if (index != -1) {sendMessage(index, msg);} else {Log.w(TAG, "sendMessage, name=" + name + " not found !");}}/*** TCP連接后,需要雙方相互發送昵稱,才能算連接正式成立!* * @param sc* @param name*/private void sendName(SocketChannel sc) {Log.d(TAG, "sendName, name=" + Global.NAME_LOCAL);send(sc, Global.NAME_LOCAL, null, FileSerial.TYPE_NAME);} }

請注意類里面每個方法的權限(private,protected,public),我把這個類作為整個聊天工具TCP的核心類,具有所有需要的TCP操作方法。

TcpManager中還用到TcpAsyncClient,TcpServer,這兩個類完成ServerSocketChannel, SocketChannel的“打開”,打開完成后便可以將他們注冊到Selector了。

Class : TcpAsyncClient

主要方法:由于較新版本的android,不允許在主線程做網絡操作,所以以下代碼在一個新的線程中執行!

SocketChannel sc = SocketChannel.open(); sc.connect(new InetSocketAddress(args.ip, args.port));

Class : TcpServer

主要方法:

/*** 如果初始化不成功,返回null* * @param port* @return*/protected ServerSocketChannel accepting(int port) {Log.d(TAG, "accepting,port=" + port);ServerSocketChannel ssc = null;try {ssc = ServerSocketChannel.open();ssc.configureBlocking(false);ServerSocket ss = ssc.socket();ss.bind(new InetSocketAddress(port));} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();return null;}return ssc;}

TCP通信功能大概就這么多吧,另外有一個關于序列化的類FileSerial,在“語音發送與接收”再說一下。

2,聊天記錄 (AsyncQueryHandler)

聊天記錄使用android自帶的Sqlite數據庫做持久化。由于數據庫訪問可能會花很多時間,不宜在主線程操作,所以在這里主要關鍵考慮如果完成“異步”訪問數據庫。

android的AsyncQueryHandler類寫得很好,我只是在它的基礎上做了一點小修改就可以用了。拿來主義^_^

Class : ChatAsyncQueryHandler

package com.yarkey.database;import android.content.ContentValues; import android.database.Cursor; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.util.Log;public abstract class ChatAsyncQueryHandler extends Handler {private static final String TAG = "ChatAsyncQueryHandler";private ChatDatabase mDatabase;private static Looper mLooper = null;private Handler mWorkerHandler = null;private static final int EVENT_QUERY = 0;private static final int EVENT_INSERT = 1;private static final int EVENT_UPDATE = 2;private static final int EVENT_DELECT = 3;public ChatAsyncQueryHandler(ChatDatabase database) {mDatabase = database;synchronized (ChatAsyncQueryHandler.class) {if (mLooper == null) {HandlerThread thread = new HandlerThread("ChatAsyncQueryHandler");thread.start();mLooper = thread.getLooper();}}mWorkerHandler = new WorkerHandler(mLooper);}protected static final class WorkerArgs {public Handler handler;public String[] projection;public String selection;public ContentValues values;public Object result;}/*** 查詢。通過兩個字段,查詢ID,DATA,TIME,TYPE,SELF,CONTENT* * @param token* @param localName* @param remoteName*/public void startQuery(int token, String localName, String remoteName) {Log.d(TAG, "startQuery, token=" + token + ",localName=" + localName + ",remoteName=" + remoteName);Message msg = mWorkerHandler.obtainMessage(token);msg.arg1 = EVENT_QUERY;WorkerArgs args = new WorkerArgs();args.handler = this;args.projection = new String[] { ChatLog.C_ID, ChatLog.C_DATE, ChatLog.C_TIME, ChatLog.C_TYPE, ChatLog.C_SELF,ChatLog.C_CONTENT };args.selection = ChatLog.C_LOCAL + "=\"" + localName + "\" AND " + ChatLog.C_REMOTE + "=\"" + remoteName + "\"";msg.obj = args;mWorkerHandler.sendMessage(msg);}public void startInsert(int token, ContentValues values) {Log.d(TAG, "startInsert, values=" + values);Message msg = mWorkerHandler.obtainMessage(token);msg.arg1 = EVENT_INSERT;WorkerArgs args = new WorkerArgs();args.handler = this;args.values = values;msg.obj = args;mWorkerHandler.sendMessage(msg);}protected class WorkerHandler extends Handler {public WorkerHandler(Looper looper) {super(looper);// TODO Auto-generated constructor stub}@Overridepublic void handleMessage(Message msg) {// TODO Auto-generated method stubWorkerArgs args = (WorkerArgs) msg.obj;int token = msg.what;int event = msg.arg1;switch (event) {case EVENT_QUERY:args.result = mDatabase.query(args.projection, args.selection);break;case EVENT_INSERT:mDatabase.insert(args.values);break;case EVENT_UPDATE:break;case EVENT_DELECT:break;}Message reply = args.handler.obtainMessage(token);reply.obj = args;reply.arg1 = event;reply.sendToTarget();}}protected void onQueryCompleted(int token, Cursor cursor) {// empty}protected void onInsertCompleted(int token) {// empty}@Overridepublic void handleMessage(Message msg) {// TODO Auto-generated method stubWorkerArgs args = (WorkerArgs) msg.obj;int token = msg.what;int event = msg.arg1;switch (event) {case EVENT_QUERY:onQueryCompleted(token, (Cursor) args.result);break;case EVENT_INSERT:onInsertCompleted(token);break;case EVENT_UPDATE:break;case EVENT_DELECT:break;}} }


至于ChatDatabase類,繼承于SQLiteOpenHelper,就不多說了。

3,語音發送與接收 (ObjectOutputStream)

本聊天工具通過發送音頻文件,來實現語音聊天的。其他真正的聊天工具怎么實現的就不得而知了,有知道的網友也請分享一下^^。在這里主要需要解決幾個問題:

1,錄音與錄音播放 (MediaRecorder, MediaPlayer)

MediaRecorder 與 MediaPlayer 的例子網上有很多了,本人也是一知半解,不敢在這里說太多。主要是通過MediaRecorder調用錄音方法,結束后,我們得到一個存放在SD卡中的.3gp文件,有了這個文件,便可以調用MediaPlayer來播放它了!

可能需要注意的地方有幾個:MediaRecorder 錄音超時,系統是有一個上限的,設計程序的時候需要注意一下;另外,是關于MediaRecorder什么時候釋放,在本程序里面,每次用的時候就new一個,每次用完都調用release方法釋放它。不過,這里似乎需要考慮一些問題,就沒有做過多了解了。

2,錄音文件的發送與接收

TCP發送文件,一開始用java.io的時候,我用的是ObjectOutputStream, ObjectInputStream來序列化一個類然后發出去( 即writeObject方法 )。

writeObject 方法的輸入參數是一個FileSerial對象,實現Serializable接口

Class : FileSerial

package com.yarkey.tcp;import java.io.Serializable;public class FileSerial implements Serializable {private static final long serialVersionUID = 1L;private String fileName; // 文件名稱private long fileLength; // 文件長度private byte[] fileContent; // 文件內容private int type;/** name保存在filename字段里面! */protected static final int TYPE_NAME = 0;// 連接成功后,向對方發出自己的名字/** text保存在filename字段里面! */protected static final int TYPE_TEXT = 1;// 文字protected static final int TYPE_AUDIO = 2;// 音頻// public static final int TYPE_PICTURE = 3;// 圖片public int getType() {return type;}public void setType(int t) {type = t;}public String getFileName() {return fileName;}public void setFileName(String fileName) {this.fileName = fileName;}public long getFileLength() {return fileLength;}public void setFileLength(long fileLength) {this.fileLength = fileLength;}public byte[] getFileContent() {return fileContent;}public void setFileContent(byte[] fileContent) {this.fileContent = fileContent;} }

java.io發送FileSerial對象源碼:其中:

ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());

/*** * @param filePath* C:/haha.java* @param fileName* haha.java*/public void sendFile(String filePath, String fileName) {Log.d(TAG, fileName);if (out == null) {return;}try {FileSerial fpo = new FileSerial();// typefpo.setType(FileSerial.TYPE_AUDIO);// namefpo.setFileName(fileName);// lengthFile f = new File(filePath);long fileLength = f.length();fpo.setFileLength(fileLength);// contentFileInputStream fis = new FileInputStream(filePath);byte[] fileContent = new byte[(int) fileLength];fis.read(fileContent, 0, (int) fileLength);fis.close();fpo.setFileContent(fileContent);// sendlong start = System.currentTimeMillis();out.writeObject(fpo);long end = System.currentTimeMillis();System.out.println("It takes " + (end - start) + "ms");out.flush();out.reset();} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}


可是,用java.nio的時候,就會出錯了!我們需要將需要發送的內容裝在一個Buffer( ByteBuffer )中,然后通過SocketChannel的 write 方法發送出去。

// toByte -> sendbyte[] bytes = SerialUtil.toByte(fpo);final ByteBuffer buffer = ByteBuffer.wrap(bytes);// E/AndroidRuntime(18606): android.os.NetworkOnMainThreadExceptionnew Thread() {@Overridepublic void run() {// TODO Auto-generated method stubLog.d(TAG, "send, async ! 異步發送 ... ");try {// 此處不能在主線程調用!sc.write(buffer);} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}.start();


用到一個工具類SerialUtil,轉換對象 FileSerial -> byte[] , byte[] -> FileSerial

class : SerialUtil

package com.yarkey.tcp;import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream;import android.util.Log;public class SerialUtil {private static final String TAG = "SerialUtil";public static byte[] toByte(Object obj) {Log.d(TAG, "toByte");ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = null;try {oos = new ObjectOutputStream(baos);oos.writeObject(obj);byte[] bytes = baos.toByteArray();return bytes;} catch (IOException ex) {throw new RuntimeException(ex.getMessage(), ex);} finally {try {oos.close();} catch (Exception e) {}}}/** 此方法byte[]數組長度確定 */public static Object toObject(byte[] bytes) {Log.d(TAG, "toObject");ByteArrayInputStream bais = new ByteArrayInputStream(bytes);ObjectInputStream ois = null;try {ois = new ObjectInputStream(bais);Object object = ois.readObject();return object;} catch (IOException ex) {throw new RuntimeException(ex.getMessage(), ex);} catch (ClassNotFoundException ex) {throw new RuntimeException(ex.getMessage(), ex);} finally {try {ois.close();} catch (Exception e) {}}} }

音頻文件的發送大概情況就是上面說的了,但是接收有另外一個問題。我們不知道音頻文件的大小,所以不知道需要多大的ByteBuffer來接收它。

所以,在TcpWorkerThread中,處理接收到的消息時,還用到一個

ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();

for( 循環 ){

??? byteOutput.write(buffer.array()); // 分次保存byteBuffer中的數據

}

byte[] b = byteOutput.toByteArray(); //最后一口氣弄出來


我們申請的bytebuffer就1024個字節的空間,超過1024個字節,所以我們分次“存”到這個ByteArrayOutputStream中,最后一口氣全部弄出來。然后調用SerialUtil將byte[]裝換成FileSerial類對象。

總結

以上是生活随笔為你收集整理的android 局域网聊天工具(可发送文字/语音)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。