編輯:關於Android編程
Java中能接收其他通信實體連接請求的類是ServerSocket, ServerSocket對象用於監聽來 自客戶端的Socket連接,如果沒有連接,它將一直處於等待狀態。ServerSocket包含一個監聽來自客戶端連接請求的方法。
1) Socket accept():如果接收到一個客戶端Socket的連接請求,該方法將返回一個與連接客戶端Socket對應的Socket;否則該方法將一直處於等待狀態,線程也被阻塞。
創建ServerSocket對象,ServerSocket類提供了如下幾個構造器:
2) ServerSocket(int port):用指定的端口 port 來創建一個ServerSocket。該端口應該是有一個有效的端口整數值:0?65 535。
3) ServerSocket(int port,int backlog):增加一個用來改變連接隊列長度的參數backlog。
4) ServerSocket(int port.int backlog,lnetAddress localAdd():在機器存在多個 IP 地 址的情況下,允許通過localAddr這個參數來指定將ServerSocket綁定到指定的IP地址。
注:當ServerSocket使用完畢後,應使用ServerSocket的close()方法來關閉該ServerSocket。通常情況下,服務器不應該只接收一個客戶端請求,而應該不斷地接收來自客戶端的所有請求。如下面代碼所示:
//創建一個ServerSocket,用於監聽客戶端的連接請求
ServerSocket ss=new ServerSocket(1566);
//不停地從接收來自客戶端的請求
while (true) {
//每當接受一個來自客戶端的Socket的請求,服務器端也對應產生一個Socket
Socket s=ss.accept();
//下面就可以使用Socket進行通信了
//..........
}
客戶端通常可使用Socket的構造器來連接到指定服務器,Socket通常可使用如下兩個構造器。
1) Socket(lnetAddress/String remoteAddress, int port):創建連接到指定遠程主機、遠程端口的Socket,該構造器沒有指定本地地址、本地端口,默認使用本地主機的默認IP地址,默認使用系統動態指定的IP地址。
2) Socket(lnetAddress/String remoteAddress, int port, InetAddress localAddr, int localPort):創建連接到指定遠程主機、遠程端口的Socket,並指定本地IP地址 和本地端口號,適用於本地主機有多個IP地址的情形。
上面兩個構造器中指定遠程主機時既可使用InetAddress來指定,也可直接使用String對象來指定,但程序通常使用String對象(如211.158.6.26)來指定遠程IP。當本地主機只有—個IP地址時,使用第一個方法更為簡單。如:
Socket socket=new Socket("169.254.77.36", 8888);
//下面就可以和服務器進行通信了
當程序執行上面代碼中的粗體字代碼時,該代碼將會連接到指定服務器,讓服務器端的ServerSocket的accept()方法向下執行,於是服務器端和客戶端就產生一對互相連接的Socket。
當客戶端、服務器端產生了對應的Socket之後,程序無須再區分服務器、客戶端,而是通過各自的Socket進行通信。Socket提供如下兩個方法來獲取輸入流和輸出流:
1) InputStream getlnputStream():返回該Socket對象對應的輸入流,讓程序通過該輸入流從Socket中取出數據。
2) OutputStream getOutputStream():返回該Socket對象對應的輸出流,讓程序通過該輸出流向Socket中輸出數據。
服務器端:
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
//創建一個ServerSocket,用於監聽客戶端的連接請求
ServerSocket ss=new ServerSocket(8888);
//不停地從接收來自客戶端的請求
while (true) {
//每當接受一個來自客戶端的Socket的請求,服務器端也對應產生一個Socket
Socket s=ss.accept();
//下面就可以使用Socket進行通信了
OutputStream os=s.getOutputStream();
os.write("來自服務器端的消息:你好,今天天氣不錯,騷年外出散散心吧!".getBytes("utf-8"));
//關閉輸出流
os.close();
//關閉Socket
s.close();
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
注:上面的程序並未把OutputStream流包裝成PrintStream ,然後使用 PrintStream直接輸出整個字符串,這是因為該服務器端程序運行於Windows 主機上,當直接使用PrintStream輸出字符串時默認使用系統平台的字符串(即 GBK )進行編碼;但該程序的客戶端是Android應用,運行於Linux平台(Android 是Linux內核的),因此當客戶端讀取網絡數據時默認使用UTF-8字符集進行解碼,這樣勢必引起亂碼。為了保證客戶端能正常解析到數據,此處手動控制字符串的編碼,強行指定使用UTF-8字符集進行編碼,這樣就可以避免亂碼問
客戶端:
edtMsg=(EditText)findViewById(R.id.edtMsg);
//創建並啟動一個新線程,向服務器發送TCP請求
new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
//創建一個Socket用於向IP為169.254.77.36的服務器的8888端口發送請求
Socket s;
try {
s = new Socket();
//如果超過10s還沒連接到服務器則視為超時
s.connect(new InetSocketAddress("169.254.77.36", 8888),10000);
//設置客戶端與服務器建立連接的超時時長為30秒
s.setSoTimeout(30000);
//將Socket對應的輸入流封裝成BufferedReader對象
BufferedReader br=new BufferedReader(new
InputStreamReader(s.getInputStream()));
String msg=br.readLine();
edtMsg.setText(msg);
br.close();
s.close();
//捕捉SocketTimeoutException異常
}catch (SocketTimeoutException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}.start();
最後別忘記為程序添加訪問網絡的權限:
程序運行效果圖:
上面的程序為了突出通過ServerSocket和Socket建立連接並通過底層 IO流進行通信的主題,程序沒有進行異常處理,也沒有使用finally塊來關閉資源。
實際應用中,程序可能不想讓執行網絡連接、讀取服務器數據的進程一直阻塞,而是希 望當網絡連接、讀取操作超過合理時間之後,系統自動認為該操作失敗,這個合理時間就是 超時時長。Socket對象提供了一個setSoTimeout(int timeout)來設置超時時長,如下面的代碼 片段所示:
//設置客戶端與服務器建立連接的超時時長為30秒
s.setSoTimeout(30000);
為Socket對象指定了超時時長之後,如果使用Socket進行讀、寫操作完成之前已經超出了該時間限制,那麼這些方法就會拋出SocketTimeoutException異常,程序可以對該異常進行捕捉,並進行適當處理,如以下代碼所示:
Socket s;
try {
s = new Socket();
//如果超過10s還沒連接到服務器則視為超時
s.connect(new InetSocketAddress("169.254.77.36", 8888),10000);
//設置客戶端與服務器建立連接的超時時長為30秒
s.setSoTimeout(30000);
//將Socket對應的輸入流封裝成BufferedReader對象
BufferedReader br=new BufferedReader(new
InputStreamReader(s.getInputStream()));
String msg=br.readLine();
edtMsg.setText(msg);
br.close();
s.close();
//捕捉SocketTimeoutException異常
}catch (SocketTimeoutException e) {
//進行異常處理
}
假設程序需要為Socket連接服務器時指定超時時長:即經過指定時間後,如果該Socket 還未連接到遠程服務器,則系統認為該Socket連接超時。但Socket的所有構造器裡都沒有提供指定超時時長的參數,所以程序應該先創建一個無連接Socket,再調用Socket的connect() 方法來連接遠程服務器,connect()方法就可以接受一個超時時長參數。如以下代碼所示:
//創建一個無連接的Socket
Socket s= new Socket();
//如果超過10s還沒連接到服務器則視為超時
s.connect(new InetSocketAddress("169.254.77.36", 8888),10000);
前面服務器端和客戶端只是進行了簡單的通信操作:服務器接收到客戶端連接之後,服 務器向客戶端輸出一個字符串,而客戶端也只是讀取服務器的字符串後就退出了。實際應用 中的客戶端則可能需要和服務器端保持長時間通信,即服務器需要不斷地讀取客戶端數據, 並向客戶端寫入數據;客戶端也需要不斷地讀取服務器數據,並向服務器寫入數據。
當使用傳統BufferedReader的readLine()方法讀取數據時,當該方法成功返回之前,線程被阻塞,程序無法繼續執行。考慮到這個原因,服務器應該為每個Socket單獨啟動一條線程,每條線程負責與一個客戶端進行通信。
客戶端讀取服務器數據的線程同樣會被阻塞,所以系統應該單獨啟動一條線程,該線程 專門負責讀取服務器數據。
下面考慮實現一個簡單的C/S聊天室應用,服務器端則應該包含多條線程,每個Socket 對應一條線程,該線程負責讀取Socket對應輸入流的數據(從客戶端發送過來的數據),並 將讀到的數據向每個Socket輸出流發送一遍(將一個客戶端發送的數據“廣播”給其他客戶 端),因此需要在服務器端使用List來保存所有的Socket。
下面是服務器端的實現代碼,程序為服務器提供了兩個類,一個是創建ServerSocket監 聽的主類,另一個是負責處理每個Socket通信的線程類。
代碼清單:
服務器端:
ServerSocket監聽的主類:
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
/**
* Description:
* 創建ServerSocket監聽的主類
* @author jph
*/
public class MyServer
{
// 定義保存所有Socket的ArrayList
public
static ArrayList
= new ArrayList
public static void main(String[] args)
throws IOException
{
ServerSocket ss = new ServerSocket(30000);
while(true)
{
// 此行代碼會阻塞,將一直等待別人的連接
Socket s = ss.accept();
socketList.add(s);
// 每當客戶端連接後啟動一條ServerThread線程為該客戶端服務
new Thread(new ServerThread(s)).start();
}
}
}
負責處理每一個Socket通信的線程類:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
/**
* Description:
* 負責處理每一個Socket通信的線程類
* @author jph
*/
// 負責處理每個線程通信的線程類
public class ServerThread implements Runnable
{
// 定義當前線程所處理的Socket
Socket s = null;
// 該線程所處理的Socket所對應的輸入流
BufferedReader br = null;
public ServerThread(Socket s)
throws IOException
{
this.s = s;
// 初始化該Socket對應的輸入流
br = new BufferedReader(new InputStreamReader(
s.getInputStream() , "utf-8")); //②
}
public void run()
{
try
{
String content = null;
// 采用循環不斷從Socket中讀取客戶端發送過來的數據
while ((content = readFromClient()) != null)
{
// 遍歷socketList中的每個Socket,
// 將讀到的內容向每個Socket發送一次
for (Socket s : MyServer.socketList)
{
OutputStream os = s.getOutputStream();
os.write((content + "\n").getBytes("utf-8"));
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
// 定義讀取客戶端數據的方法
private String readFromClient()
{
try
{
return br.readLine();
}
// 如果捕捉到異常,表明該Socket對應的客戶端已經關閉
catch (IOException e)
{
// 刪除該Socket。
MyServer.socketList.remove(s); //①
}
return null;
}
}
上面的服務器端線程類不斷讀取客戶端數據,程序使用readFromCHent()方法來讀取客戶端數據,如果讀取數據過程中捕獲到IOException異常,則表明該Socket對應的客戶端Socket 出現了問題(到底什麼問題我們不管,反正不正常),程序就將該Socket從socketList中刪除, 如readFromClient()方法中①號代碼所示。
當服務器線程讀到客戶端數據之後,程序遍歷socketList集合,並將該數據向socketList 集合中的每個Socket發送一次一該服務器線程將把從Socket中讀到的數據向socketList中 的每個Socket轉發一次,如run()線程執行體中的粗體字代碼所示。
注:
上面的程序中②號粗體字代碼將網絡的字節榆入流轉換為字符輸入流時,指定了轉換所用的字符串:UTF-8,這也是由於客戶端寫過來的數據是采用UTF-8 字符集進行編碼的,所以此處的服務器端也要使用UTF-8字符集進行解碼。當需 要編寫跨平台的網絡通信程序時,使用UTF-8字符集進行編碼、解碼是一種較好的解決方案。
每個客戶端應該包含兩條線程:一條負責生成主界面,並響應用戶動作,並將用戶輸入 的數據寫入Socket對應的輸出流中:另一條負責讀取Socket對應輸入流中的數據(從服務器 發送過來的數據),並負責將這些數據在程序界面上顯示出來。
客戶端:
客戶端程序同樣是一個Android應用,因此需要創建一個Android項目,這個Android 應用的界面中包含兩個文本框:一個用於接收用戶輸入,另一個用於顯示聊天信息:界面中 還有一個按鈕,當用戶單擊該按鈕時,程序向服務器發送聊天信息。該程序的界面布局代碼 如下。
/**
* 客戶端:
* */
public class MultiThreadClient extends Activity
{
// 定義界面上的兩個文本框
EditText input;
TextView show;
// 定義界面上的一個按鈕
Button send;
Handler handler;
// 定義與服務器通信的子線程
ClientThread clientThread;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
input = (EditText) findViewById(R.id.input);
send = (Button) findViewById(R.id.send);
show = (TextView) findViewById(R.id.show);
handler = new Handler() //①
{
@Override
public void handleMessage(Message msg)
{
// 如果消息來自於子線程
if (msg.what == 0x123)
{
// 將讀取的內容追加顯示在文本框中
show.append("\n" + msg.obj.toString());
}
}
};
clientThread = new ClientThread(handler);
// 客戶端啟動ClientThread線程創建網絡連接、讀取來自服務器的數據
new Thread(clientThread).start(); //①
send.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
try
{
// 當用戶按下發送按鈕後,將用戶輸入的數據封裝成Message,
// 然後發送給子線程的Handler
Message msg = new Message();
msg.what = 0x345;
msg.obj = input.getText().toString();
clientThread.revHandler.sendMessage(msg);
// 清空input文本框
input.setText("");
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
}
}
代碼分析:
當用戶單擊該程序界而中的“發送”按鈕之後,程序將會把input輸入框中的的內容發 送該clientThread的revHandler對象,clientThread將負責將用戶輸入的內容發送給服務器。
為了避免UI線程被阻塞,該程序將建立網絡連接、與網絡服務器通信等工作都交給 ClientThread線程完成。因此該程序在①號代碼處啟動ClientThread線程。
由於Android不允許子線程訪問界面組件,因此上面的程序定義了一個Handler來處理 來自子線程的消息,如程序中②號粗體字代碼所示。
ClientThread子線程負責建立與遠程服務器的連接,並負責與遠程服務器通信,讀到數 據之後便通過Handler對象發送一條消息:當ClientThread子線程收到UI線程發送過來的消 息(消息攜帶了用戶輸入的內容)之後,還負責將用戶輸入的內容發送給遠程服務器。該子 線程代碼如下:
public class ClientThread implements Runnable
{
private Socket s;
// 定義向UI線程發送消息的Handler對象
private Handler handler;
// 定義接收UI線程的消息的Handler對象
public Handler revHandler;
// 該線程所處理的Socket所對應的輸入流
BufferedReader br = null;
OutputStream os = null;
public ClientThread(Handler handler)
{
this.handler = handler;
}
public void run()
{
try
{
//192.168.191.2為本機的ip地址,30000為與MultiThreadServer服務器通信的端口
s = new Socket("192.168.191.2", 30000);
br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
os = s.getOutputStream();
// 啟動一條子線程來讀取服務器響應的數據
new Thread()
{
@Override
public void run()
{
String content = null;
// 不斷讀取Socket輸入流中的內容。
try
{
while ((content = br.readLine()) != null)
{
// 每當讀到來自服務器的數據之後,發送消息通知程序界面顯示該數據
Message msg = new Message();
msg.what = 0x123;
msg.obj = content;
handler.sendMessage(msg);
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}.start();
// 為當前線程初始化Looper
Looper.prepare();
// 創建revHandler對象
revHandler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
// 接收到UI線程中用戶輸入的數據
if (msg.what == 0x345)
{
// 將用戶在文本框內輸入的內容寫入網絡
try
{
os.write((msg.obj.toString() + "\r\n")
.getBytes("utf-8"));
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
};
// 啟動Looper
Looper.loop();
}
catch (SocketTimeoutException e1)
{
System.out.println("網絡連接超時!!");
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
實例分析:
上面線程的功能也非常簡單,它只是不斷獲取Socket輸入流中的內容,當讀到Socket 輸入流中的內容後,便通過Handler對象發送一條消息,消息負責攜帶讀到數據,除此之外,該子線程還負責讀取UI線程發送的消到消息之後,該子線程負責將消息中攜帶的數據發送給遠程服務器。
先運行上面程序中的MyServer類,該類運行後只是作為服務器,看不到任何輸出。接 著可以運行Android客戶端一相當於啟動聊天室客戶端登錄該服務器,接著可以看到在任 何一個Android客戶端輸入一些內容後單擊“發送”按鈕,將可看到所有客戶端(包括自己) 都會收到他剛剛輸入的內容,如上圖所示,這就粗略實現了一個C/S結構聊天室的功能。
新建項目,新建一個java類OtherScreenActivity 繼承自 Activity類package com.wuyudong.twoactivity;impor
一、概述android系統電池部分的驅動程序,繼承了傳統linux系統下的Power Supply驅動程序架構,Battery驅動程序通過Power Supply驅動程序
通知使用權打開方式設置——提示音和通知——通知使用權。詳細界面如圖:存在需要擁有通知使用權應用時:不存在需要擁有通知使用權
最近在搗鼓android 自定義控件屬性,學到了TypedArray以及attrs。在這其中看了一篇大神博客Android 深入理解Android中的自定義屬性。我就更加