Android Wi-Fi Direct 开发

来源:互联网 发布:淘宝代购能退货吗 编辑:程序博客网 时间:2024/05/06 11:29
一、Wi-Fi Direct 简介
    Wi-Fi Direct标准是指允许无线网络中的设备无需通过无线路由器即可相互连接。与蓝牙技术类似,这种标准允许无线设备以点对点形式互连,而且在传输速度与传输距离方面则比蓝牙有大幅提升。按照定义,Wi-Fi Direct设备是支持对等连接的设备,这种设备既支持基础设施网络,也支持P2P连接。Wi-Fi Direct设备能够作为典型的站点(STA)加入基础设施网络,而且必须支持Wi-Fi Protected Setup加入者功能。Wi-Fi Direct设备通过组建小组(以一对一或一对多的拓扑形式)来建立连接,小组的工作形式与基础设施BSS类似。由一部Wi-Fi Direct设备负责整个小组,包括控制哪部设备加入、小组何时启动和终止等。这种设备对于传统客户设备而言就是一部接入点,能够提供基础设施接入点所提供的部分服务。
​详见百度百科
二、Android中的Wi-Fi Direct 
        从Android4.0(API level 14)开始,开始支持Wi-Fi Direct,它可以在没有热点(hotspot)和网络连接(Internet)的情况下,实现点对点(P2P)的连接。Android的Framework提供了一组API,允许你发现和连接其它支持Wi-Fi Direct的设备。这种连接比蓝牙速度更快,通信距离更远。
      
、部分代码及分析 
        下面通过分析一部分代码来介绍API的使用方法。其中//...代表一些省略掉的代码未显示。
       首先使用Wi-Fi Direct,需要向你的清单文件添加 CHANGE_WIFI_STATE, ACCESS_WIFI_STATE 和 INTERNET 权限。

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

         获取WifiP2pManager和Channel。WifiP2pManager是API中的主类,可以通过系统服务获取。Channel是app和Framework连接的通道,在WifiP2pManager调用initialize()方法初始化后返回一个Channel

public class MainActivity extends Activity implements OnClickListener,        PeerListListener, OnItemClickListener, ConnectionInfoListener {    //...    private WifiP2pManager manager;    private Channel channel;      @Override    protected void onCreate(Bundle savedInstanceState) {          //...        manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);        channel = manager.initialize(MainActivity.this, getMainLooper(), null);          //...    }    //...}



     接下来要要注册一个广播,用来接收Wi-Fi P2P连接时发出的广播。有以下几种广播:

                WIFI_P2P_STATE_CHANGED_ACTION 表明Wi-Fi对等网络(P2P)是否已经启用  
                WIFI_P2P_PEERS_CHANGED_ACTION 表明可用的对等点的列表发生了改变 
                WIFI_P2P_CONNECTION_CHANGED_ACTION 表示Wi-Fi对等网络的连接状态发生了改变  
                WIFI_P2P_THIS_DEVICE_CHANGED_ACTION 表示该设备的配置信息发生了改变

public class WiFiP2PReceiver extends BroadcastReceiver {    private final String TAG = "WiFiP2PReceiver";    private Channel channel;    private WifiP2pManager manager;    private MainActivity activity;    public WiFiP2PReceiver(Channel channel, WifiP2pManager manager,            MainActivity activity) {        super();        this.channel = channel;        this.manager = manager;        this.activity = activity;    }    @Override    public void onReceive(Context context, Intent intent) {        String action = intent.getAction();        if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {            // 确定Wi-Fi Direct模式是否已经启用            int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);            if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {               // ...            } else {               // ...            }        } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {            // 对等点设备列表改变            if (manager != null) {                manager.requestPeers(channel, (PeerListListener) activity);            } else {               // ...            }        } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION                .equals(action)) {            // 连接状态改变            if (manager != null) {                NetworkInfo networkInfo = (NetworkInfo) intent                        .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);                if (networkInfo.isConnected()) {                    manager.requestConnectionInfo(channel,                            (ConnectionInfoListener) activity);                } else {                   // ...                }            } else {              // ...            }        } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION                .equals(action)) {            //连接设备信息改变           // ...        }    }}



        在MainActivity的onResume()方法中注册,在onPause()方法中注销。
public class MainActivity extends Activity implements OnClickListener,        PeerListListener, OnItemClickListener, ConnectionInfoListener {    //...    private final IntentFilter intentFilter = new IntentFilter();    private WiFiP2PReceiver mReceiver;      //...    @Override    protected void onCreate(Bundle savedInstanceState) {          //...        // 表示Wi-Fi对等网络状态发生了改变        intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);        // 表示可用的对等点的列表发生了改变        intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);        // 表示Wi-Fi对等网络的连接状态发生了改变        intentFilter                .addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);        // 设备配置信息发生了改变        intentFilter                .addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);           //...    }    //...    @Override    protected void onResume() {        super.onResume();        mReceiver = new WiFiP2PReceiver(channel,                manager, this);        registerReceiver(mReceiver, intentFilter);    }    @Override    protected void onPause() {        unregisterReceiver(mReceiver);        super.onPause();    }    //...}


      使用WifiP2pManager的discoverPeers()方法搜索对等设备。ActionListener是一个监听接口,其方法在操作成功或失败时自动调用。失败时,带有失败原因reasonCode,其值含义如下:
                WifiP2pManager.BUSY 表示Framework正忙,不能响应你的请求
                WifiP2pManager.ERROR 表示内部错误
                WifiP2pManager.P2P_UNSUPPORTED 表示你的设备不支持Wi-Fi P2P功能

            manager.discoverPeers(channel,new WifiP2pManager.ActionListener() {                @Override                public void onSuccess() {                        //...                }                @Override                public void onFailure(int reasonCode) {                       //...                }            });

 当搜索到对等设备时,我们写好的BroadcastReceiver会收到一个带有 WIFI_P2P_PEERS_CHANGED_ACTION 动作的意图(Intent),此时调用 requestPeers()方法请求获取设备列表。这个方法需要一个PeerListListener接口。

 public void onReceive(Context context, Intent intent) {        String action = intent.getAction();         //...        if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {            // 对等点设备列表改变            if (manager != null) {                manager.requestPeers(channel, (PeerListListener) activity);            } else {                //...            }        }         //...    }

        在本例中,直接用MainActivity继承了此接口。当设备列表发生改变时,就会自动调用该接口中的onPeersAvailable()方法,设备列表在参数中获取。

public class MainActivity extends Activity implements OnClickListener,        PeerListListener, OnItemClickListener, ConnectionInfoListener {    //...    @Override    public void onPeersAvailable(WifiP2pDeviceList peers) {        Collection<WifiP2pDevice> collection = peers.getDeviceList();        if (collection == null || collection.size() == 0) {            //...        } else {            //更新显示列表            //...        }        //...    }    //...}


        获取到设备列表后,我们就可以连接了。调用WifiP2pManager的connect()方法进行连接 第二个参数是WifiP2pConfig对象,用来配置连接信息。其中device是要连接的设备,是一个WifiP2pDevice对象,deviceAddress是设备网卡MAC地址。WPS是Wi-Fi保护设置,这里把他设置成PBC(Push button configuration),和我们连接无线路由器时按下路由器上的wps按键功能类似。

                WifiP2pConfig config = new WifiP2pConfig();                config.deviceAddress = device.deviceAddress;                config.wps.setup = WpsInfo.PBC;                manager.connect(channel,config, new ActionListener() {                    @Override                    public void onSuccess() {                       // ...                    }                    @Override                    public void onFailure(int reason) {                       // ...                    }                });



        当连接成功时,我们写好的BroadcastReceiver会收到一个带有 WIFI_P2P_CONNECTION_CHANGED_ACTION 动作的意图,此时调用 requestConnectionInfo()方法请求获取设备列表。这个方法需要一个ConnectionInfoListener接口。

 public void onReceive(Context context, Intent intent) {        String action = intent.getAction();         //...        if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION                .equals(action)) {            // 连接状态改变            // ...            if (manager != null) {                NetworkInfo networkInfo = (NetworkInfo) intent                        .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);                if (networkInfo.isConnected()) {                   // ...                    manager.requestConnectionInfo(channel,                            (ConnectionInfoListener) activity);                } else {                  //  ...                }            } else {               // ...            }        }        // ...    }

       在本例中,直接用MainActivity继承了此接口。当设备列表发生改变时,就会自动调用该接口中的onConnectionInfoAvailable()方法,设备列表在参数中获取。

public class MainActivity extends Activity implements OnClickListener,        PeerListListener, OnItemClickListener, ConnectionInfoListener {    //...    @Override    public void onConnectionInfoAvailable(WifiP2pInfo info) {       // ...    }   // ...}

        在连接成功后,我们就可以发送和接收数据了,本例中传送的是图片。
        首先来看发送数据,本例定义了一个传送图片的IntentService,利用IntentService的好处是可以在后台服务中自动另开线程完成任务,比Service更方便。发送数据用Socket,所需的目标设备地址、端口号、图片位置都通过Intent传过来。

public class FileTransferService extends IntentService {    private static final String TAG = "FileTransferService";    private static final int SOCKET_TIMEOUT = 5000;    public static final String ACTION_SEND_FILE = "com.ising99.wifidirecttest.SEND_FILE";   // ...    @Override    protected void onHandleIntent(Intent intent) {        if (intent.getAction().equals(ACTION_SEND_FILE)) {// send file            String uri = intent.getExtras().getString("uri");            String host = intent.getExtras().getString("host");            int port = intent.getExtras().getInt("port");            Socket socket = new Socket();            try {                socket.bind(null);                socket.connect(new InetSocketAddress(host, port),                        SOCKET_TIMEOUT);                ContentResolver cr = getApplicationContext()                        .getContentResolver();                OutputStream os = socket.getOutputStream();                InputStream is = cr.openInputStream(Uri.parse(uri));                copyFile(is, os);            } catch (Exception e) {                Log.e(TAG, e.toString());            } finally {                if (socket != null && socket.isConnected()) {                    try {                        socket.close();                    } catch (IOException e) {                        e.printStackTrace();                    }                }            }        }       // ...    }    public static boolean copyFile(InputStream inputStream, OutputStream out) {        byte buf[] = new byte[1024];        int len;        try {            while ((len = inputStream.read(buf)) != -1) {                out.write(buf, 0, len);            }            out.close();            inputStream.close();        } catch (IOException e) {            Log.d(TAG, e.toString());            return false;        }        return true;    }}



        在MainAcitiviy里,我们点击按钮打开图库选择图片。选定好图片后,启动定义好的IntentService发送图片。其中,connectedInfo是前面我们提到的,连接成功时保存的连接信息。

public class MainActivity extends Activity implements OnClickListener,        PeerListListener, OnItemClickListener, ConnectionInfoListener {   // ...    @Override    public void onClick(View v) {        switch (v.getId()) {       // ...        case R.id.btn_send_image:            Intent intent = new Intent(Intent.ACTION_GET_CONTENT);            intent.setType("image/*");            startActivityForResult(intent, 20);            break;        //...        }    }    //...    @Override    public void onActivityResult(int requestCode, int resultCode, Intent data) {        if (requestCode == 20) {            Intent serviceIntent = new Intent(MainActivity.this,                    FileTransferService.class);            Uri uri = data.getData();            serviceIntent.setAction(FileTransferService.ACTION_SEND_FILE);            serviceIntent.putExtra("uri", uri.toString());            serviceIntent.putExtra("host",                    connectedInfo.groupOwnerAddress.getHostAddress());            serviceIntent.putExtra("port", SOCKET_PORT);            startService(serviceIntent);        }    }    //...}

           再来看接收数据,同样利用IntentService。接收数据用ServerSocket。接收的图片保存到本地。接收成功后自动打开图片。

public class FileTransferService extends IntentService {   // ...    public static final String ACTION_RECEIVE_FILE = "com.ising99.wifidirecttest.RECEIVE_FILE";    //...    @Override    protected void onHandleIntent(Intent intent) {        //...        if (intent.getAction().equals(ACTION_RECEIVE_FILE)) {//接收数据            try {                int port = intent.getExtras().getInt("port");                ServerSocket serverSocket = new ServerSocket(port);                Socket client = serverSocket.accept();                final File f = new File(                        Environment.getExternalStorageDirectory()                                + "/iSing99/WifiDirectShared_"                                + System.currentTimeMillis() + ".jpg");                Log.d("FileServerAsyncTask", f.getAbsolutePath());                File dirs = new File(f.getParent());                if (!dirs.exists()) {                    dirs.mkdirs();                }                f.createNewFile();                InputStream inputstream = client.getInputStream();                copyFile(inputstream, new FileOutputStream(                        f));                serverSocket.close();                Toast.makeText(getApplicationContext(), "文件接收完成!",                        Toast.LENGTH_SHORT).show();                                //打开图片                Intent viewIntent = new Intent();                viewIntent.setAction(android.content.Intent.ACTION_VIEW);                viewIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);                viewIntent.setDataAndType(                        Uri.parse("file://" + f.getAbsolutePath()), "image/*");                startActivity(viewIntent);            } catch (IOException e) {                Log.e("FileServerAsyncTask", e.toString());            }        }    }   // ...}

        在MainAcitivity的onConnectionInfoAvailable()方法中,若本机是群主,作为接收端,启动接收数据的IntentService。

    @Override    public void onConnectionInfoAvailable(WifiP2pInfo info) {        connectedInfo = info;        if (info.groupFormed && info.isGroupOwner) {            // 我是群主,作为接收端接收信息            Intent serviceIntent = new Intent(MainActivity.this,                    FileTransferService.class);            serviceIntent.setAction(FileTransferService.ACTION_RECEIVE_FILE);            serviceIntent.putExtra("port", SOCKET_PORT);            startService(serviceIntent);             // ...        } else {            //  ...        }    }



0 0