1. 程式人生 > >安卓 WIFI通訊之聊天小程式

安卓 WIFI通訊之聊天小程式

安卓  WIFI通訊之聊天小程式

一、簡述

      記--使用WIFI實現的一個簡單一對一聊天小程式。一臺裝置開啟WIFI熱點,另外一臺裝置進行連線,然後互相收發資訊。

     例子打包:連結: https://pan.baidu.com/s/1uOGxQJPmfJhtM8S6soqkVQ 提取碼: 5b56

二、效果

                                                       

                                       

三、工程結構

四、原始檔

新增的許可權 (改變網路狀態許可權,改變WIFI狀態許可權,獲取網路狀態許可權,獲取WIFI狀態許可權,網路許可權)

    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

MainActivity.java檔案

package com.liang.wifi;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.DhcpInfo;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;


public class MainActivity extends Activity implements View.OnClickListener {

    private Button btn_create_hostspot;//"建立WIFI熱點"按鈕
    private Button btn_close_hostspot;//"關閉熱點"
    private Button btn_exit;//"退出"
    private Button btn_send;//"傳送資訊"
    private Button btn_search;//搜尋附近熱點資訊
    private TextView tv_rmsg;//顯示連線資訊、接收的資訊
    private TextView tv_state;//顯示WIFI連線狀態
    private EditText edt_smsg;//要傳送的資訊
    private ScrollView sv;//滾動檢視,適配TextView內容,當內容過多,以滾動條形式顯示

    private WifiManager wifiManager;//WIFI管理物件
    private WifiConfiguration config;//WIFI配置
    private int netID;//網路身份ID
    private boolean scan_WIFIHOT = false;//用來控制跳轉到WIFI熱點列表的

    private static final String WIFI_HOTSPOT_SSID = "TEST";//WIFI熱點名稱
    private static final String WIFI_PWD = "12345678";//熱點密碼
    private static final int PORT = 54321;//埠號

    public static final int WIFICIPHER_NOPASS = 1;//熱點無密碼
    public static final int WIFICIPHER_WEP = 2;//熱點加密方式為 WEP
    public static final int WIFICIPHER_WPA = 3;//熱點加密方式為 WPA

    public static final int DEVICE_CONNECTING = 4;//有裝置正在連線熱點
    public static final int DEVICE_CONNECTED = 5;//有裝置連上熱點
    public static final int SEND_MSG_SUCCSEE = 6;//傳送訊息成功
    public static final int SEND_MSG_ERROR = 7;//傳送訊息失敗
    public static final int GET_MSG = 8;//獲取新訊息
    public static final int TOAST_MSG = 10;//彈出toast提示框
    public static final int REQUEST_CONNECT_DEVICE = 11;//用來表示請求連線裝置
    
    private ConnectThread connectThread;//連線執行緒
    private ListenerThread listenerThread;//監聽執行緒

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);//設定主頁面
        initView();//初始化控制元件
        initBroadcastReceiver();//註冊廣播

        //獲取WIFI管理助手物件
        wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        
        //開啟監聽執行緒
        listenerThread = new ListenerThread(PORT, handler);
        listenerThread.start();
    }

    //初始化控制元件,並繫結列表選項的點選事件
    private void initView() 
    {
    	//獲取控制元件控制代碼
        btn_create_hostspot = (Button) findViewById(R.id.btn_create_hostspot);
        btn_close_hostspot = (Button) findViewById(R.id.btn_close_hostspot);
        btn_exit = (Button) findViewById(R.id.btn_exit);
        btn_send = (Button) findViewById(R.id.btn_send);
        btn_search = (Button) findViewById(R.id.btn_search);
        tv_rmsg = (TextView) findViewById(R.id.tv_rmsg);
        tv_state = (TextView) findViewById(R.id.tv_state);
        edt_smsg = (EditText) findViewById(R.id.edt_smsg);
        sv = (ScrollView)findViewById(R.id.sv_list);
        
        //繫結點選事件
        btn_create_hostspot.setOnClickListener(this);
        btn_close_hostspot.setOnClickListener(this);
        btn_exit.setOnClickListener(this);
        btn_send.setOnClickListener(this);
        btn_search.setOnClickListener(this);

    }    

    //連線WIFI熱點,根據WIFI熱點的配置資訊進行連線
    private void connect(WifiConfiguration config) 
    {
    	tv_state.append("連線中...");//顯示當前連線狀態
        netID = wifiManager.addNetwork(config);//根據熱點配置新增網路,返回網路身份ID,如果是-1則新增失敗
        wifiManager.enableNetwork(netID, true);//連線網路(連線成功會有相應的廣播資訊)
        
    }

    
    //自定義廣播接收者
    private BroadcastReceiver receiver = new BroadcastReceiver()
    {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();//收到的廣播動作
            
            //根據廣播動作做出相應的響應
            if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) 
            {
                // wifi已成功掃描到可用wifi熱點
                if(scan_WIFIHOT)//點選了"搜尋"按鈕才跳轉到WIFI列表
                {
                	//獲取掃描的結果(有可能含有SSID為空的資料,尚未處理)
                	ArrayList<ScanResult> scanResultList =  (ArrayList<ScanResult>)wifiManager.getScanResults();
                	
                	Intent scanIntent = new Intent(MainActivity.this, WiFiListActivity.class); //跳轉到WIFI列表介面
                	scanIntent.putParcelableArrayListExtra("SCANRESLIST", scanResultList);//將熱點資料傳遞過去,顯示到ListView
                	startActivityForResult(scanIntent, REQUEST_CONNECT_DEVICE);  //設定返回巨集定義
                    scan_WIFIHOT = false;
                }
            } 
            else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) 
            {//WIIF狀態改變
            	//獲取WIFI狀態
                int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 0);
                switch (wifiState) 
                {
                    case WifiManager.WIFI_STATE_ENABLED:
                        //獲取到wifi開啟的廣播時,開始掃描
                        wifiManager.startScan();
                        break;
                    case WifiManager.WIFI_STATE_DISABLED://wifi關閉發出的廣播
                        Toast.makeText(MainActivity.this, "WiFi已關閉", Toast.LENGTH_SHORT).show();
                        break;
                }
            } 
            else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) 
            {//網路狀態改變
            	//獲取網路資訊
                NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
                if (info.getState().equals(NetworkInfo.State.DISCONNECTED)) 
                {
                	//斷開連線
                	tv_state.setText("提示:連線已斷開。");
                } 
                else if (info.getState().equals(NetworkInfo.State.CONNECTED)) 
                {
                	//已連線網路
                    WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
                    final WifiInfo wifiInfo = wifiManager.getConnectionInfo();
                    tv_state.setText("已連線到熱點:" + wifiInfo.getSSID()+"\n");
                    String local_ip = intToIp(wifiInfo.getIpAddress());//自己的IP
                    DhcpInfo ipinfo = wifiManager.getDhcpInfo();//獲取WIFI熱點的IP 
                	final String ip = intToIp(ipinfo.serverAddress);
                    if (wifiInfo.getSSID().equals("\""+WIFI_HOTSPOT_SSID+"\"") ) 
                    {
                    	tv_state.append("熱點ip:"+ip+"\n");
                    	tv_state.append("本機ip:"+local_ip+"\n");
                    	//開啟連線執行緒
                    	new Thread(){
                            @Override
                            public void run() {
			                	 Socket socket;
								try {
									socket = new Socket(ip, PORT);//建立與熱點通訊的socket
									connectThread = new ConnectThread(socket, handler);
			                        connectThread.start();
								} catch (UnknownHostException e1) {
									sendHandlerMsg("err1");
									e1.printStackTrace();
								} catch (IOException e1) {
									e1.printStackTrace();
									sendHandlerMsg("err2");
								}
						
                            }
                    	}.start();
                    	
                   }
                    
                } 
                else 
                {   
                	//實時網路連線狀態
                    NetworkInfo.DetailedState state = info.getDetailedState();
                    if (state == DetailedState.CONNECTING) {
                    	tv_state.setText("連線中...");
                    } else if (state == DetailedState.AUTHENTICATING) {
                    	tv_state.setText("正在驗證身份資訊...");
                    } else if (state == DetailedState.OBTAINING_IPADDR) {
                    	tv_state.setText("正在獲取IP地址...");
                    } else if (state == DetailedState.FAILED) {
                    	tv_state.setText("連線失敗");
                    }
                }

            }
        }
    };
    
    //int形式的ip轉為字串形式的ip
    private String intToIp(int i) 
    {
        return (i & 0xFF) + "." + ((i >> 8) & 0xFF) + "." + ((i >> 16) & 0xFF) + "."
                + ((i >> 24) & 0xFF);
    }
    
    //初始化廣播並註冊
    private void initBroadcastReceiver() 
    {
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
        intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
        intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);

        registerReceiver(receiver, intentFilter);//註冊廣播
    }

    //按鈕的點選響應事件
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_create_hostspot:
                createWifiHotspot();//開啟WIFI熱點
                break;
            case R.id.btn_close_hostspot:
                closeWifiHotspot();//關閉WIFI熱點
                break;
            case R.id.btn_send://傳送資訊
                if (connectThread != null) 
                {
                	if(!edt_smsg.getText().toString().equals(""))//訊息不為空
                	{
                		connectThread.sendData( edt_smsg.getText().toString() );
                		edt_smsg.setText("");
                	}
                }
                else
                {
                    Toast.makeText(this, "未連線裝置", Toast.LENGTH_SHORT).show();
                }
                break;
            case R.id.btn_search://搜尋周邊熱點資訊
            	searchWiFiHot();
                break;
            case R.id.btn_exit://退出程式
            	showAlertDialog("退出應用", "您確認退出嗎?", "取消" ,"確認", 0);
            	break;
        }
    }

    /**
     * 建立Wifi熱點
     */
    private void createWifiHotspot() {
        if (wifiManager.isWifiEnabled()) {
            //如果wifi處於開啟狀態,則關閉wifi,
            wifiManager.setWifiEnabled(false);
        }
        //熱點設定
        WifiConfiguration config = new WifiConfiguration();
        config.SSID = WIFI_HOTSPOT_SSID;//熱點名稱
        config.preSharedKey = WIFI_PWD;//熱點密碼
        config.hiddenSSID = false;//是否隱藏密碼
        config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);//開放系統認證
        config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
        config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);//設定加密方式
        config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
        config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
        config.allowedPairwiseCiphers
                .set(WifiConfiguration.PairwiseCipher.CCMP);
        config.status = WifiConfiguration.Status.ENABLED;
        //通過反射呼叫設定熱點
        try {
            Method method = wifiManager.getClass().getMethod(
                    "setWifiApEnabled", WifiConfiguration.class, Boolean.TYPE);
            boolean enable = (Boolean) method.invoke(wifiManager, config, true);
            if (enable) {
            	//DhcpInfo info = wifiManager.getDhcpInfo();//熱點本機IP
            	//String ip = intToIp(info.serverAddress);
            	tv_state.setText("熱點已開啟 熱點名稱:" + WIFI_HOTSPOT_SSID +" 密碼:"+WIFI_PWD+"\n");
            } else {
            	tv_state.setText("建立熱點失敗");
            }
        } catch (Exception e) {
            e.printStackTrace();
            tv_state.setText("建立熱點失敗");
        }
    }

    /**
     * 關閉WiFi熱點 (利用反射訪問隱藏的熱點設定函式)
     */
    private void closeWifiHotspot() {
        try {
            Method method = wifiManager.getClass().getMethod("getWifiApConfiguration");
            method.setAccessible(true);
            WifiConfiguration config = (WifiConfiguration) method.invoke(wifiManager);
            Method method2 = wifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
            method2.invoke(wifiManager, config, false);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        tv_state.setText("熱點已關閉");
    }


    /**
     * 搜尋wifi熱點
     */
    private void searchWiFiHot() 
    {
        if (!wifiManager.isWifiEnabled()) //如果還沒有開啟WIFI則開啟
        {
            //開啟wifi
            wifiManager.setWifiEnabled(true);
        }
        wifiManager.startScan();//掃描周邊熱點資訊
        Toast.makeText(this, "正在掃描周邊熱點。。。請稍後!", Toast.LENGTH_SHORT).show();
        scan_WIFIHOT = true;//可以跳轉到WIFI熱點列表
    }
    
    /* 
     * 接收活動結果,響應startActivityForResult()
     */
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
    	
    	switch(requestCode)
    	{
    	case REQUEST_CONNECT_DEVICE://從熱點列表介面返回
    		if (resultCode == RESULT_OK)//選擇一個裝置
    		{
    			//獲取返回的資料(所選擇的熱點資訊)
    			final String ssid = data.getStringExtra("SSID");
    			wifiManager.disconnect();//斷開之前的連線
                String capabilities = data.getStringExtra("CAPABILITIES");
                //判斷WIFI熱點加密型別
                int type = MainActivity.WIFICIPHER_WPA;
                if (!TextUtils.isEmpty(capabilities)) {
                    if (capabilities.contains("WPA") || capabilities.contains("wpa")) {
                        type = MainActivity.WIFICIPHER_WPA;
                    } else if (capabilities.contains("WEP") || capabilities.contains("wep")) {
                        type = MainActivity.WIFICIPHER_WEP;
                    } else {
                        type = MainActivity.WIFICIPHER_NOPASS;
                    }
                }
                //根據熱點的SSID判斷之前是否連線過
                config = isExsits(ssid);
                if (config == null) //沒有連線過
                {
                    if (type != WIFICIPHER_NOPASS)//需要密碼
                    {
                    	//彈出密碼輸入框
                        final EditText editText = new EditText(MainActivity.this);
                        final int finalType = type;
                        new AlertDialog.Builder(MainActivity.this).setTitle("請輸入Wifi密碼").setIcon(
                                android.R.drawable.ic_dialog_info).setView(
                                editText).setPositiveButton("確定", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which)
                            {
                                config = createWifiInfo(ssid, editText.getText().toString(), finalType);
                                connect(config);
                            }
                        })
                        .setNegativeButton("取消", null).show();
                        return;
                    } 
                    else//熱點是開放的(不需要密碼)
                    {
                        config = createWifiInfo(ssid, "", type);
                        connect(config);
                    }
                } 
                else 
                {
                	//之前連線過則直接進行連線
                    connect(config);
                }
            } 
    		break;
    	default:break;
    	}
    }

    //退出程式時釋放資源
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(receiver);//登出廣播註冊
        //關閉執行緒、釋放socket資源等等
    }

    //Handler操作 (收到Hansler訊號做出反應,通常用來處理子執行緒的請求)
    @SuppressLint("HandlerLeak") //jdk版本問題
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) 
        {
            switch (msg.what) {
                case DEVICE_CONNECTING://進行連線(本機開熱點,連線其他手機)
                	//有客戶端連線
                	tv_state.append( "【IP:"+(String)msg.obj +"】");
                	connectThread = new ConnectThread(listenerThread.getSocket(),handler);
                    connectThread.start();
                    break;
                case DEVICE_CONNECTED://有裝置連線成功
                	tv_rmsg.append("***裝置連線成功***\n");
                	sv.scrollTo(0,tv_rmsg.getMeasuredHeight()); //適配內容
	        		break;
                case SEND_MSG_SUCCSEE://成功傳送訊息
                	tv_rmsg.append("(我)" + msg.getData().getString("MSG")+"\n");
                	sv.scrollTo(0,tv_rmsg.getMeasuredHeight()); //適配內容
	        		break;
                case SEND_MSG_ERROR://傳送訊息失敗
                	tv_rmsg.append("傳送失敗:" + msg.getData().getString("MSG")+"\n");
                	sv.scrollTo(0,tv_rmsg.getMeasuredHeight()); //適配內容
	        		break;
                case GET_MSG://收到訊息
                	tv_rmsg.append(msg.getData().getString("MSG")+"\n");
                	sv.scrollTo(0,tv_rmsg.getMeasuredHeight()); //適配內容
	        		break;
                case TOAST_MSG:
                	Toast.makeText(MainActivity.this, (String)msg.obj, Toast.LENGTH_SHORT).show();
                	break;
            }
        }
    };

    //傳送Handler訊息,彈出Toast資訊
    public void sendHandlerMsg(String msg)
    {
    	 Message message = Message.obtain();
         message.what = MainActivity.TOAST_MSG;
         message.obj = msg;
         handler.sendMessage(message);
    }
    
    /**
     * 判斷當前wifi是否有儲存
     * @param SSID 熱點資訊
     * @return
     */
    public WifiConfiguration isExsits(String SSID) {
        List<WifiConfiguration> existingConfigs = wifiManager.getConfiguredNetworks();
        for (WifiConfiguration existingConfig : existingConfigs) {
            if (existingConfig.SSID.equals("\"" + SSID + "\"")) {
                return existingConfig;
            }
        }
        return null;
    }

    //建立WIFI配置資訊
    public WifiConfiguration createWifiInfo(String SSID, String password, int type) {
        WifiConfiguration config = new WifiConfiguration();
        config.allowedAuthAlgorithms.clear();
        config.allowedGroupCiphers.clear();
        config.allowedKeyManagement.clear();
        config.allowedPairwiseCiphers.clear();
        config.allowedProtocols.clear();
        config.SSID = "\"" + SSID + "\"";
        if (type == WIFICIPHER_NOPASS) {
            config.wepKeys[0] = "\"" + "\"";
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            config.wepTxKeyIndex = 0;
        } else if (type == WIFICIPHER_WEP) {
            config.preSharedKey = "\"" + password + "\"";
            config.hiddenSSID = false;
            config.allowedAuthAlgorithms
                    .set(WifiConfiguration.AuthAlgorithm.SHARED);
            config.allowedGroupCiphers
                    .set(WifiConfiguration.GroupCipher.CCMP);
            config.allowedGroupCiphers
                    .set(WifiConfiguration.GroupCipher.TKIP);
            config.allowedGroupCiphers
                    .set(WifiConfiguration.GroupCipher.WEP40);
            config.allowedGroupCiphers
                    .set(WifiConfiguration.GroupCipher.WEP104);
            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            config.wepTxKeyIndex = 0;
        } else if (type == WIFICIPHER_WPA) {
            config.preSharedKey = "\"" + password + "\"";
            config.hiddenSSID = false;
            config.allowedAuthAlgorithms
                    .set(WifiConfiguration.AuthAlgorithm.OPEN);
            config.allowedGroupCiphers
                    .set(WifiConfiguration.GroupCipher.TKIP);
            config.allowedKeyManagement
                    .set(WifiConfiguration.KeyMgmt.WPA_PSK);
            config.allowedPairwiseCiphers
                    .set(WifiConfiguration.PairwiseCipher.TKIP);
            // config.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
            config.allowedGroupCiphers
                    .set(WifiConfiguration.GroupCipher.CCMP);
            config.allowedPairwiseCiphers
                    .set(WifiConfiguration.PairwiseCipher.CCMP);
            config.status = WifiConfiguration.Status.ENABLED;
        } else {
            return null;
        }
        return config;
    }

    //彈出確認對話方塊
    private void showAlertDialog(String title, String content, String negative, String positive, final int action)
    {
    	//建立一個對話方塊
    	AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
    	dialog.setTitle(title);  //對話方塊標題
    	dialog.setMessage(content);//設定對話方塊內容提示
		if(!negative.isEmpty() && negative != "" )//按需要是否新增“取消”按鈕
		{
			//新增"取消按鈕",並且單擊時響應
			dialog.setNegativeButton(negative,new DialogInterface.OnClickListener() {  
	            @Override  
	            public void onClick(DialogInterface dialog, int which) 
	            {}
			});
		}
		
		//新增一個確定按鈕,並且單擊時響應
		dialog.setPositiveButton(positive, new DialogInterface.OnClickListener() {  
            @Override  
            public void onClick(DialogInterface dialog, int which) 
            {  
            	switch(action)
            	{
            	case 0://退出應用操作
            		finish();//關閉本頁面
            		break;
            	case 1://"確認" 
            		break;
            	default:
            		break;
            	}
            }  
            
        });  
		dialog.show();
    }
  
    /**
     * 監聽執行緒 (等待其它裝置來連線)
     */
    public class ListenerThread extends Thread
    {

        private ServerSocket serverSocket = null;//用來監聽本機的某個埠,等待其他裝置的連線
        private Handler handler;//用來將資料、資訊通知主執行緒
        private Socket socket;//通訊socket
        private boolean thread_run;//用來控監聽執行緒的結束(不能立即結束,因為accpept阻塞等待)

        public ListenerThread(int port, Handler handler)
        {
            //setName("ListenerThread");//設定執行緒的名稱
            thread_run = true;
            this.handler = handler;
            try {
                serverSocket = new ServerSocket(port);//監聽本機的port埠
            } catch (IOException e) {}
        }


        @Override
        public void run() {
            while (thread_run)
            {
                try {
                	//阻塞,等待裝置連線
                    socket = serverSocket.accept();
                    
                    sendHandlerMsg("發現裝置");
                    
                    //有裝置連線,使用Handler通知主執行緒
                    Message message = Message.obtain();//獲取訊息物件
                    message.what = MainActivity.DEVICE_CONNECTING;//訊息型別
                    message.obj = socket.getLocalAddress().getHostAddress();//自己的IP
                    handler.sendMessage(message);//傳送訊息
                    
                    //傳送一個“你好”資訊給連線的裝置
                    //outputStream = socket.getOutputStream();
                    //outputStream.write("你好!\n".getBytes());
                    
                    
                } catch (IOException e) {}
            }
        }

        //退出執行緒
        public void close()
        {
        	thread_run = false;
        	try {
				serverSocket.close();
			} catch (IOException e) {}
        }
        
        //獲取通訊socket(主執行緒可以通過此方法獲取通訊的socket)
        public Socket getSocket() 
        {
            return socket;
        }
    }
    
    
    /**
     * 連線執行緒 (用來通訊的執行緒)
     */
    public class ConnectThread extends Thread{

        private final Socket socket;//通訊socket
        private Handler handler;//用來更新UI等(傳送資訊給主執行緒)
        private InputStream inputStream;//資料輸入流
        private OutputStream outputStream;//資料輸出流

        public ConnectThread(Socket socket, Handler handler)
        {
            //setName("ConnectThread");//設定執行緒的名稱
            this.socket = socket;//初始化通訊socket
            this.handler = handler;//初始化handler
        }

        //執行緒執行所執行的函式
        @Override
        public void run() 
        {
            if(socket==null){
                return;
            }
            //傳送不帶資料的handler訊號
            handler.sendEmptyMessage(MainActivity.DEVICE_CONNECTED);//傳送已連線 訊號
            try 
            {
                //獲取資料流
                inputStream = socket.getInputStream();//用來接收訊息
                outputStream = socket.getOutputStream();//用來發送訊息

                byte[] buffer = new byte[1024];
                int lens = 0;
                while (true)
                {
                    //讀取資料
                	lens = inputStream.read(buffer);
                    if (lens > 0) 
                    {
                        final byte[] data = new byte[lens];
                        System.arraycopy(buffer, 0, data, 0, lens);

                        //將資訊顯示到UI
                        Message message = Message.obtain();
                        message.what = MainActivity.GET_MSG;
                        Bundle bundle = new Bundle();
                        bundle.putString("MSG",new String(data));
                        message.setData(bundle);
                        handler.sendMessage(message);


                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        /**
         * 傳送資料
         */
        public void sendData(String msg)
        {
        	//將訊息新增上傳送者IP
        	msg = socket.getLocalAddress().getHostAddress() + ":" + msg;
        	
        	if(outputStream!=null)
            {
        		try {
                	//傳送資訊
                    outputStream.write(msg.getBytes());
                    //提示傳送資訊成功
                    Message message = Message.obtain();
                    message.what = MainActivity.SEND_MSG_SUCCSEE;
                    Bundle bundle = new Bundle();
                    bundle.putString("MSG",new String(msg));
                    message.setData(bundle);
                    handler.sendMessage(message);
                } catch (IOException e) {}
            }
        }
    }

    
}

WiFiListActivity.java檔案 

package com.liang.wifi;

import java.io.Serializable;
import java.util.List;

import android.net.wifi.ScanResult;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import android.app.Activity;
import android.content.Intent;

public class WiFiListActivity extends Activity {

	private ListView listView;//用來顯示搜尋到的WIFI熱點
	private WifiListAdapter wifiListAdapter;//WIFI列表介面卡
    
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		try
		{
			// 建立並顯示視窗
	        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);  //設定視窗顯示模式為視窗方式,有個滾動圈
	        //設定視窗標題
	        setTitle("WiFi熱點列表");
	        //設定介面
			setContentView(R.layout.activity_wifi_list);
			//獲取listView控制元件控制代碼
			listView = (ListView) findViewById(R.id.listView);
	        
			//設定ListView介面卡
	        wifiListAdapter = new WifiListAdapter(this, R.layout.wifi_list_item);
	        listView.setAdapter(wifiListAdapter);
	        
	        //獲取從MainActivity傳遞過來的周邊熱點資料集合
	        List<ScanResult> scanResultList = getIntent().getParcelableArrayListExtra("SCANRESLIST");
	         
	        wifiListAdapter.clear();//清空原來的資料
	        wifiListAdapter.addAll(scanResultList);//填充資料
	        
	        //繫結列表資料項的點選響應事件
	        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
	            @Override
	            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
	            	//拿到選中的熱點資訊
	            	ScanResult scanResult = wifiListAdapter.getItem(position);
	            	
	            	// 設定返回資料 (返回選中的熱點資訊)
	                Intent intent = new Intent();
	                intent.putExtra("SSID", scanResult.SSID);
	                intent.putExtra("CAPABILITIES", scanResult.capabilities);
	                // 設定返回值並結束程式
	                setResult(Activity.RESULT_OK, intent);
	                finish();//關閉本頁面
	            }
	        });
	        
	        //“取消”按鍵響應
	        Button btn_cancel = (Button) findViewById(R.id.btn_cancel);
	        btn_cancel.setOnClickListener(new OnClickListener() {
	            public void onClick(View v) {
	            	finish();//關閉本頁面
	                }
	        });
		}
		catch(Exception e){}
    
	}
}

WifiListAdapter.java檔案 

package com.liang.wifi;

import android.content.Context;
import android.net.wifi.ScanResult;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

//WiFi列表介面卡類 用來填充ListView
public class WifiListAdapter extends ArrayAdapter<ScanResult> {

    private final LayoutInflater mInflater;//xml佈局載入器
    private int mResource;//xml佈局ID號

    public WifiListAdapter(Context context, int resource) {
        super(context, resource);
        mInflater = LayoutInflater.from(context);
        mResource = resource;
    }

    //填充ListView的資料項
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if (convertView == null) {
            convertView = mInflater.inflate(mResource, parent, false);
        }

        //列表中的一個數據項(有兩個TextView用來顯示WIFI熱點名稱和訊號的強弱)
        TextView name = (TextView) convertView.findViewById(R.id.wifi_name);
        TextView signl = (TextView) convertView.findViewById(R.id.wifi_signal);

        //拿到所點選的熱點的相關資訊
        ScanResult scanResult = getItem(position);
        
        //顯示熱點的名稱
        name.setText(scanResult.SSID);

        //顯示熱點訊號的強弱
        int level = scanResult.level;
        if (level <= 0 && level >= -50) {
            signl.setText("訊號很好");
        } else if (level < -50 && level >= -70) {
            signl.setText("訊號較好");
        } else if (level < -70 && level >= -80) {
            signl.setText("訊號一般");
        } else if (level < -80 && level >= -100) {
            signl.setText("訊號較差");
        } else {
            signl.setText("訊號很差");
        }

        return convertView;
    }

}

 佈局檔案

activity_main.xml檔案

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
     <TextView
         android:id="@+id/tv_state"
         android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:layout_weight="0.04"
         android:text="未連線裝置" />
     
    <ScrollView
        android:id="@+id/sv_list"
        android:layout_width="fill_parent"
        android:layout_height="0dp"
        android:layout_weight="0.36"
        android:scrollbars="vertical" >
        
    <TextView
        android:id="@+id/tv_rmsg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    </ScrollView>
    
     <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <EditText
            android:id="@+id/edt_smsg"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:layout_weight="1"
            android:hint="請輸入要傳送的資訊"
            android:shape="rectangle"
            android:inputType="text" >

		</EditText>
        
        <Button
            android:id="@+id/btn_send"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="0.09"
            android:text="@string/send_data" />

    </LinearLayout>
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/btn_create_hostspot"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/create_hostspot" />

        <Button
            android:id="@+id/btn_close_hostspot"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/close_hostspot" />

         <Button
            android:id="@+id/btn_search"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/search" />
         
        <Button
            android:id="@+id/btn_exit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="退出" />
    </LinearLayout>
    
   
</LinearLayout>

 activity_wifi_list.xml檔案

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
   
  
    <ListView android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:stackFromBottom="true"
        android:layout_weight="2"
    />

 

    <Button
        android:id="@+id/btn_cancel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="OnCancel"
        android:text="取消" />

</LinearLayout>

wifi_list_item.xml檔案

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:minHeight="58dp"
    android:padding="5dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:id="@+id/wifi_name"
        android:gravity="center_vertical"
        android:background="#E6E6FA"
        android:textColor="#000"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/wifi_signal"
        android:gravity="center_vertical|right"
        android:background="#E6E6FA"
        android:textColor="#000"/>

</LinearLayout>

五、總結

1、程式通訊的大致流程圖

      裝置A開啟熱點,裝置B搜尋熱點並進行連線。連線成功後,裝置B系統發出已連線網路的廣播資訊,裝置B收到後就可以獲取到熱點的相關資訊(IP),然後可以根據IP,埠號建立與熱點通訊的socket,然後裝置A收到socket通訊的請求,接收連線並取得與裝置B通訊的socket,這樣裝置A與裝置B可以相互收發訊息。

2、WIFI相關操作

       //獲取WIFI管理助手物件 (WIFI重要操作類)
     WifiManager   wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);

WiFi相關操作
操作 程式碼 備註
WIFI是否開啟 wifiManager.isWifiEnabled() 返回true說明WIFI已經開啟,false:未開啟
開啟WIFI wifiManager.setWifiEnabled(true); 成功開啟返回true
關閉WIFI wifiManager.setWifiEnabled(false); 成功關閉返回true
開啟熱點

//熱點設定
        WifiConfiguration config = new WifiConfiguration();
        config.SSID = WIFI_HOTSPOT_SSID;//熱點名稱
        。。。

Method method = wifiManager.getClass().getMethod(
                    "setWifiApEnabled", WifiConfiguration.class, Boolean.TYPE);
 method.invoke(wifiManager, config, true);
            

通過反射訪問隱藏的函式
關閉熱點 Method method = wifiManager.getClass().getMethod("getWifiApConfiguration");
            method.setAccessible(true);
            WifiConfiguration config = (WifiConfiguration) method.invoke(wifiManager);
            Method method2 = wifiManager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
            method2.invoke(wifiManager, config, false);
監聽

ServerSocket serverSocket = new ServerSocket(port);//監聽本機的port埠

//阻塞,等待裝置連線
 Socket socket = serverSocket.accept();

通過socket 進行收發資料
搜尋熱點 wifiManager.startScan();//掃描周邊熱點資訊

如果搜尋到可用的熱點系統就會發出廣播WifiManager.SCAN_RESULTS_AVAILABLE_ACTION

註冊廣播就可以接收到廣播。

自定義廣播接收者

//自定義廣播接收者
    private BroadcastReceiver receiver = new BroadcastReceiver()
    {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();//收到的廣播動作
            
            //根據廣播動作做出相應的響應
            if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) 
            {
                //接收到這個廣播之後,自定義動作響應

                //dosomething()
            } 

     }

}

說明接收到廣播後的動作響應
註冊廣播

IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
        intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
        intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);

        registerReceiver(receiver, intentFilter);//註冊廣播

說明要接收什麼型別的廣播

(程式結束需要登出廣播註冊unregisterReceiver(receiver);//登出廣播註冊)

獲取周邊熱點資訊    // wifi已成功掃描到可用wifi熱點,獲取熱點集合
     ArrayList<ScanResult> scanResultList =  (ArrayList<ScanResult>)wifiManager.getScanResults();
 
有SSID為空的資料,需要自己處理
連線熱點 int netID = wifiManager.addNetwork(config);//根據熱點配置新增網路,返回網路身份ID,如果是-1則新增失敗,config是熱點的相關資訊(熱點名稱,加密型別等)
        wifiManager.enableNetwork(netID, true);//連線網路(連線成功會有相應的廣播資訊NetworkInfo.State.CONNECTED)
連線成功後就加入到區域網了,裝置在區域網中有唯一的身份IP
與熱點建立通訊 Socket socket = new Socket(ip, PORT);//通訊socket,用來與熱點收發資料 ip是熱點的ip,PORT是開啟熱點裝置所監聽的埠
收發訊息 //獲取資料流
InputStream    inputStream = socket.getInputStream();//用來接收訊息
OutputStream     outputStream = socket.getOutputStream();//用來發送訊息

3、多人聊天

      多個裝置連線同一個熱點,熱點最為資訊中轉站 ,使得多個裝置之間可以互相通訊。(熱點可以指定傳送訊息給某個裝置,或者是廣播訊息)

      

4、Activity之間傳遞訊息 (例子中將掃描到的熱點資訊集合傳遞到另外一個頁面顯示,ScanResult已經實現了Parcelable介面)

     傳遞資料(Intent的相關函式)

     

   接收資料 (Intent的相關函式)

     a) 傳遞字串資料        (Intent的putExtra()函式傳遞資料,getStringExtra()獲取資料)

          Intent intent = new Intent(MainActivity.this, WiFiListActivity.class); //從主頁面跳轉到WIFI列表介面
          intent.putExtra("NAME","liang");//將字串"liang"傳遞過去
          startActivityForResult(intent , request_code);  //開始跳轉,request_code:自定義請求碼

         在WiFiListActivity中接收

String name = getIntent().getStringExtra("NAME);//根據鍵值接收

     b) 傳遞物件集合資料 

        對於傳遞物件資料,需要將物件序列化,list集合資料類似
       物件可序列化的前提就是實現了Serializable或者是Parcelable介面。

1  實現Serializable介面
用Serializable方式傳遞Object的語法:bundle.putSerializable(key,object);
用Serializable方式接收Object的語法:object=(Object) getIntent().getSerializableExtra(key);
實現Serializable介面就是把物件序列化,然後再傳輸。

2 需要實現Parcelable介面
用Parcelable方式傳遞Object的語法:bundle.putParcelable(key,object);
用Parcelable方式接收Object的語法:object=(Object) getIntent().getParcelableExtra(key);
實現Parcelable介面的類比較複雜,Parcelable是個什麼東西呢?
Android提供了一種新的型別:Parcel,被用作封裝資料的容器,封裝後的資料可以通過Intent或IPC傳遞。 除了基本型別以外,只有實現了Parcelable介面的類才能被放入Parcel中。
實現Parcelable介面需要實現三個方法: 1)writeToParcel方法。該方法將類的資料寫入外部提供的Parcel中。
宣告:writeToParcel(Parcel dest, int flags)。
2)describeContents方法。直接返回0就可以。
3)靜態的Parcelable.Creator<T>介面,本介面有兩個方法:createFromParcel(Parcel in) 實現從in中創建出類的例項的功能。
newArray(int size) 建立一個型別為T,長度為size的陣列, returnnew T[size];即可。本方法是供外部類反序列化本類陣列使用。
 

5、待完善:很多細節都內有考慮,程式容錯性不高,只是簡單的演示一個流程。包括資源釋放問題都沒有處理。