編輯:關於Android編程
先對上一遍的工具類,補充兩點:
1、Client關閉異常
如果沒有連接host就調用close()的話,會導致NullPointException,因為mInputStream為null。雖然socket關閉後,輸入輸出流也會隨之關閉,但為了加快回收速度,建議把流也關閉。
public void close() {
if (mSocket != null) {
try {
mInputStream.close();
mOutputStream.close();
mSocket.close();
mInputStream = null;
mOutputStream = null;
mSocket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
修改為:
public void close() {
if (mInputStream != null) {
try {
mInputStream.close();
// mInputStream輸入流不置為null,因為子線程中要用,防止空指針異常
} catch (IOException e) {
e.printStackTrace();
}
}
if (mOutputStream != null) {
try {
mOutputStream.close();
mOutputStream = null;
} catch (IOException e) {
e.printStackTrace();
}
}
if (mSocket != null) {
try {
mSocket.close();
mSocket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
2、使用available()來監測輸入流
用設置讀取流超時,然後處理異常的方法,會在日志一直打印信息:

強迫症,沒有辦法,總想要解決它。
就想到用available()取代之:
// 讀取流
byte[] data = new byte[0];
try {
while (mInputStream.available() > 0) {
byte[] buf = new byte[1024];
int len = mInputStream.read(buf);
byte[] temp = new byte[data.length + len];
System.arraycopy(data, 0, temp, 0, data.length);
System.arraycopy(buf, 0, temp, data.length, len);
data = temp;
}
} catch (IOException e) {
}
這樣日志也會一直打印信息,這裡沒定時,所以頻率更高:

想到前一篇說的,在查看前,先等待一會。拿來先試試再說:<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwcmUgY2xhc3M9"brush:java;">
// 讀取流
byte[] data = new byte[0];
try {
Thread.sleep(100);
while (mInputStream.available() > 0) {
byte[] buf = new byte[1024];
int len = mInputStream.read(buf);
byte[] temp = new byte[data.length + len];
System.arraycopy(data, 0, temp, 0, data.length);
System.arraycopy(buf, 0, temp, data.length, len);
data = temp;
}
} catch (IOException | InterruptedException e) {
}
OK,日志很干淨了。這才爽。。。(雖然沒理解why)
接著上篇繼續來,目錄也連續著。
UDP發送數據,不管對方有沒收到,也就不需要把兩主機先連接好再通信。所以,UDP一般不用做自由通信用。下面是最簡單的demo,服務器只負責接收數據,而客戶端只負責發送數據。
關於UDP網絡編程,主要區分TCP,注意以下幾點:
連接網絡屬於耗時,必須在子線程中執行。網絡的連接主要在socket的send()與receive(); 服務器與客戶端的套接字都是DatagramSocket; 接收時監聽的端口與DatagramSocket直接綁定,此綁定的端口也可直接用於發送數據; 目標主機及端口信息都是封裝在數據報DatagramPacket中。本機的發送端口若未綁定,則是由系統分配; 是數據報模式(TCP是流模式),數據發送與接收都是使用數據報。一次性發送完畢,接收也是一次性必須接收完畢,所以數據緩沖區要足夠大,否則會導致數據丟失; 能在局域網內組播與廣播。主要API:
DatagramSocket:
private boolean mIsServerOn;
private void turnOnUdpServer() {
final int port = 8000;
new Thread(){
@Override
public void run() {
super.run();
DatagramSocket socket = null;
try {
// 1、創建套接字
socket = new DatagramSocket(port);
// 2、創建數據報
byte[] data = new byte[1024];
DatagramPacket packet = new DatagramPacket(data, data.length);
// 3、一直監聽端口,接收數據包
mIsServerOn = true;
while (mIsServerOn) {
socket.receive(packet);
String rece = new String(data, 0, packet.getLength(), Charset.forName("UTF-8"));
pushMsgToMain(rece); // 推送信息到主線程
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != socket) {
socket.close();
socket = null;
}
}
}
}.start();
}
主要API(與服務器一樣的,就不介紹了):
DatagramSocket:
private void turnOnUdpClient() {
final String hostIP = "192.168.1.145";
final int port = 8000;
new Thread(new Runnable() {
@Override
public void run() {
DatagramSocket socket = null;
try {
// 1、創建套接字
socket = new DatagramSocket(8888);
// 2、創建host的地址包裝實例
SocketAddress socketAddr = new InetSocketAddress(hostIP, port);
// 3、創建數據報。包含要發送的數據、與目標主機地址
byte[] data = "Hello, I am Client".getBytes(Charset.forName("UTF-8"));
DatagramPacket packet = new DatagramPacket(data, data.length, socketAddr);
// 4、發送數據
socket.send(packet);
// 再次發送數據
packet.setData("Second information from client".getBytes(Charset.forName("UTF-8")));
socket.send(packet);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != socket) {
socket.close();
}
}
}
}).start();
}
廣播就是發送信息給網絡中內所有的計算機設備。
廣播的實現方法:在發送消息時,把目標主機IP地址修改為廣播地址即可。
廣播地址,一般有兩種:
UDP有固定的廣播地址:255.255.255.255 另外,使用TCP/IP協議的網絡,主機標識段host ID全為1的IP地址也為廣播地址。如:我的局域網網段為192.168.1.0(255.255.255.0),廣播地址為:192.168.1.255。組播,是讓同一組的計算機設備都接收到信息。讓具有相同需求功能的計算機設備,加入到同一組中,然後任一計算機發送組播信息,其他成員都能接收到。
發送和接收信息,都必須使用組播地址(224.0.0.0~239.255.255.255)。計算機要加入該組,就必須加入該多播組地址。
具有以下特點:
它與廣播都是UDP獨有的; 只有相同組的計算機設備才能接收到信息; 發送和接收的套接字都是MulticastSocket。主要API(基本使用方法與DatagramSocket是一樣的,就多了幾個方法):
MulticastSocket:下面是發送和接收的demo代碼。
發送:
private void sendUdpMulticast() {
final String groupIP = "224.1.1.1";
final int port = 8000;
new Thread(new Runnable() {
@Override
public void run() {
MulticastSocket mcSocket = null;
try {
// 1、創建組播套接字
mcSocket = new MulticastSocket();
// 設置TTL為1,套接字發送的范圍為本地網絡。默認也為1
mcSocket.setTimeToLive(1);
// 2、創建組播網絡地址,並判斷
InetAddress groupAddr = InetAddress.getByName(groupIP);
if (!groupAddr.isMulticastAddress()) {
pushMsgToMain(UDP_HANDLER_MESSAGE_TOAST, "IP地址不是組播地址(224.0.0.0~239.255.255.255)");
return;
}
// 3、讓套接字加入到組播中
mcSocket.joinGroup(groupAddr);
// 4、創建數據報
byte[] data = ("Hi, I am Multicast of UDP".getBytes(Charset.forName("UTF-8")));
DatagramPacket pack = new DatagramPacket(data, data.length, groupAddr, port);
// 5、發送信息
mcSocket.send(pack);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != mcSocket) {
mcSocket.close();
}
}
}
}).start();
}
接收:
private boolean mIsUdpMulticastOn;
private void receiveUdpMulticast() {
final String groupIP = "224.1.1.1";
final int port = 8000;
new Thread(){
@Override
public void run() {
MulticastSocket mcSocket = null;
try {
// 1、創建多播套接字
mcSocket = new MulticastSocket(port);
// 2、創建多播組地址,並校驗
InetAddress groupAddr = InetAddress.getByName(groupIP);
if (!groupAddr.isMulticastAddress()) {
pushMsgToMain(UDP_HANDLER_MESSAGE_TOAST, "IP地址不是組播地址(224.0.0.0~239.255.255.255)");
return;
}
// 3、把套接字加入到多播組中
mcSocket.joinGroup(groupAddr);
// 4、創建數據報
byte[] data = new byte[1024];
DatagramPacket pack = new DatagramPacket(data, data.length);
// 5、接收信息。循環接收信息,並把接收到的數據交給主線程處理
mIsUdpMulticastOn = true;
while (mIsUdpMulticastOn) {
mcSocket.receive(pack);
String rece = new String(data, pack.getOffset(), pack.getLength());
pushMsgToMain(UDP_HANDLER_MESSAGE_DATA, rece);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != mcSocket) {
mcSocket.close();
}
}
}
}.start();
}
經過測試,DatagramPacket中的數據data最大是65507,超過則會在發送的時候報錯:
Exception:sendto failed: EMSGSIZE (Message too long)
接收的data大小,可以超65536(2^16),但一般也沒必要超過發送的最大值65507,最多65536。
發送的測試,自己設計了一個數據填充小算法。使用時,在發送的時候修改data的大小即可。代碼如下:
byte[] data = new byte[65507];
byte[] temp = "abcdefghijklmnopABCDEFGHIJKLMNOP".getBytes(); // 固定為32個
for (int i = 0; i < data.length >> 5; i++) {
System.arraycopy(temp, 0, data, i<<5, temp.length);
}
System.arraycopy(temp, 0, data, data.length - data.length % temp.length, data.length % temp.length);
大小分析:
數據報的長度是指包括報頭和數據部分在內的總字節數。因為報頭的長度是固定的,所以該域主要被用來計算可變長度的數據部分(又稱為數據負載)。數據報的最大長度根據操作環境的不同而各異。從理論上說,包含報頭在內的數據報的最大長度為65535字節。不過,一些實際應用往往會限制數據報的大小,有時會降低到8192字節。(摘自 百度百科UDP)
而報頭又包括IP包頭(20字節)和UDP報文頭(8字節)。

所以,UDP數據的最大值 = 65535 - 20 - 8 = 65507
雖然我測試那麼大數據時OK的,但不是越大越好,建議小於1472。
3.5.2 bind 與connect 的區別1、bind(SocketAddress addr)
將套接字綁定到特定的地址和端口,本地的綁定。
使用示例:
DatagramSocket s = new DatagramSocket(null);
SocketAddress local = new InetSocketAddress(8888);
s.bind(local);
與此句代碼等效:
DatagramSocket s = new DatagramSocket(8888);
使用說明:
DatagramSocket如果綁定了端口,則不能再綁定,否則拋異常。如:DatagramSocket s = new DatagramSocket(8000); s.bind(local); 一般情況下,去綁定地址(就算與本機地址一樣)也將報錯。如:SocketAddress local = new InetSocketAddress("192.168.1.222", 8888); s.bind(local);
2、connect(SocketAddress addr)
將套接字連接到遠程套接字地址(IP地址+端口號),連接對方。
使用示例:
socket = new DatagramSocket(8888);
SocketAddress local = new InetSocketAddress("192.168.1.145", 8000);
socket.connect(local);
byte[] data = "Hello, I am Client".getBytes(Charset.forName("UTF-8"));
DatagramPacket packet = new DatagramPacket(data, data.length);
socket.send(packet);
與此代碼等效:
socket = new DatagramSocket(8888);
SocketAddress socketAddr = new InetSocketAddress(hostIP, port);
byte[] data = "Hello, I am Client".getBytes(Charset.forName("UTF-8"));
DatagramPacket packet = new DatagramPacket(data, data.length, socketAddr);
socket.send(packet);
3.5.3 巧記組播地址組播地址為224.0.0.0~239.255.255.255。怎麼記?
查isMulticastAddress()的源碼:
public boolean isMulticastAddress() {
return ((holder().getAddress() & 0xf0000000) == 0xe0000000);
}
也就是說,只要第一段的高四位為E的IP地址,就是組播地址。
而第一段的最小值E0 = 256 - 32(後五位) = 224
最大值EF = 224 + 15(F) = 239
3.5.4 簡單理解TTLTTL(Time To Live)的作用是限制IP數據包在計算機網絡中的存在的時間。TTL的最大值是255,TTL的一個推薦值是64。
雖然TTL從字面上翻譯,是可以存活的時間,但實際上TTL是IP數據包在計算機網絡中可以轉發的最大跳數。TTL字段由IP數據包的發送者設置,在IP數據包從源到目的的整個轉發路徑上,每經過一個路由器,路由器都會修改這個TTL字段值,具體的做法是把該TTL的值減1,然後再將IP包轉發出去。如果在IP包到達目的IP之前,TTL減少為0,路由器將會丟棄收到的TTL=0的IP包並向IP包的發送者發送 ICMP time exceeded消息。
所以,TTL可以簡單的理解為能達到路由器的個數。
剩下的是一個UDP實例與常見問題。由於實例的代碼太多,還是另外寫一篇吧。。。>>>
Android Configuration橫豎屏切換時Activity生命周期調用
問題:橫豎屏切換時Activity的生命周期?測試環境:華為mate7 package com.virglass.beyond.activity;import
Android進階-Volley-4.Network源碼
Volley的核心結構如下所示:今天主要看Network相關:Network整體功能介紹:通過實現了Network接口的類(這裡是BasicNetwork),執行從Req
android開發步步為營之22:處理Activity中的back按鈕事件
在手機應用中,用戶點擊回退按鈕一般是返回上個頁面,一般頁面不用處理,如果在首頁,點回退,沒任何提示,就把應用給關了,這個用戶體驗就不太好了,所以一般都會給用戶一個確認的提
Android使用criteria選擇合適的地理位置服務實現方法
本文實例講述了Android使用criteria選擇合適的地理位置服務實現方法。分享給大家供大家參考,具體如下:/* LocationActivity.java * @a