Miko Android自学之路 WifiDirect中文最强详解,如何传输数据,如何设置GroupOwener,如何设置客户端以及服务器端

来源:互联网 发布:数据库功能分类 编辑:程序博客网 时间:2024/05/16 06:06

大家好我是Miko,最近有参加一个比赛,用到了WifiDirect技术,于是翻看官方文档之后,想写一个Demo,Google API Sample已经很老了,还是用的Eclipse,宝宝心好累=_=+///,在CSDN上找了几篇文章,竟然都是些API的国语翻译,程序猿节操何在?这里我将会用自己根据官方Demo重写的Demo来详解WifiDirect的使用。
Android4.0之后开始支持WifiDirect技术,即Wifi直连,做为一种通讯方式,它的优势在于传输速度快传输距离远。
ok
首先上我的DemoGithub地址https://github.com/MikoGodZd/WifiDerect
在上官方文档http://developer.android.com/intl/zh-tw/guide/topics/connectivity/wifip2p.html
官方Demo http://download.csdn.net/detail/yichigo/5516627

通过Wi-Fi Direct查找附近的设备,并与之连接一般包括如下几个骤:
一 设置应用程序权限
二 创建一个广播接收器和对等网络管理器
三 初始化对等点的搜索
四 获取对等点列表
五 连接一个对等点

一、设置权限
我们mainifest中添加如下权限

 <uses-sdk android:minSdkVersion="14" />    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />    <uses-permission android:name="android.permission.INTERNET" />    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />    <uses-permission android:name="android.permission.READ_PHONE_STATE" />    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

注:
android.permission.ACCESS_WIFI_STATE 允许程序访问Wi-Fi网络状态信息(Allows applications to access information about Wi-Fi networks)
android.permission.CHANGE_WIFI_STATE 允许程序改变Wi-Fi连接状态(Allows applications to change Wi-Fi connectivity state)
android.permission.INTERNET 当需要访问网络的时候,需要在AndroidManifest.xml里面添加访问网络的权限

二、创建广播接收器
首先在MainActivity中初始化IntentFilter并让它监听以下动作:
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
* 表示该设备的配置信息发生了改变

 private void initIntentFilter() {        mFilter = new IntentFilter();        mFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);        mFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);        mFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);        mFilter.addAction(WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION);        mFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);    }

然后我们创建一个新的广播类

public class WifiDirectBroadcastReceiver extends BroadcastReceiver {    private WifiP2pManager mManager;    private WifiP2pManager.Channel mChannel;    private Activity mActivity;    private WifiP2pManager.PeerListListener mPeerListListener;    private WifiP2pManager.ConnectionInfoListener mInfoListener;    public WifiDirectBroadcastReceiver(WifiP2pManager manager, WifiP2pManager.Channel channel, Activity activity,                                       WifiP2pManager.PeerListListener peerListListener,                                       WifiP2pManager.ConnectionInfoListener infoListener    ) {        this.mManager = manager;        this.mChannel = channel;        this.mPeerListListener = peerListListener;        this.mActivity = activity;        this.mInfoListener = infoListener;    }    @Override    public void onReceive(Context context, Intent intent) {        String action = intent.getAction();        /*check if the wifi is enable*/        if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {            int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);             if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {                  activity.setIsWifiP2pEnabled(true);              } else {                  activity.setIsWifiP2pEnabled(false);              }          }        /*get the list*/        else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {            mManager.requestPeers(mChannel, mPeerListListener);        }        /*查看当前是否处于查找状态        * get the state of discover*/        else if (WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION.equals(action)) {            int State = intent.getIntExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, -1);            if (State == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED)                Toast.makeText(mActivity, "搜索开启", Toast.LENGTH_SHORT).show();            else if (State == WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED)                Toast.makeText(mActivity, "搜索已关闭", Toast.LENGTH_SHORT).show();        }        /*Respond to new connection or disconnections        *查看是否创建连接*/        else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {            if (mManager == null) {                return;            }            NetworkInfo networkInfo = (NetworkInfo) intent                    .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);            if (networkInfo.isConnected()) {                Log.i("xyz", "已连接");                mManager.requestConnectionInfo(mChannel, mInfoListener);            } else {                Log.i("xyz", "断开连接");                return;            }        }        /*Respond to this device's wifi state changing*/        else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {        }    }}

构造函数中的manager以及channel比较好理解,剩下的主要是在mainActivity中的方法的实现
第一个action用于检测当前设备的wifi是否打开很好理解
第二个action当你开始搜索之后,当设备列表发生变化的时候即触发通过mManager.requestPeers(mChannel, mPeerListListener);方法可以的到列表,这些我们下文详讲
第三个action用于检测当前是否处于搜索状态,
第四个action用于检测两个设备连接状态是否改变
第五个action适用于设备名称发生改变这里我们不讲

三、创建搜索,获得列表
ok 广播搭建好之后,我们就要初始化一个p2p了

首先在MainActivity中创建一个Manager以及一个Channel

private WifiP2pManager mManager;private WifiP2pManager.Channel mChannel;mManager = (WifiP2pManager) getSystemService(WIFI_P2P_SERVICE);mChannel = mManager.initialize(this, Looper.myLooper(), null);

这样我们就开启了direct服务
下面我们定义搜索函数

   private void DiscoverPeers() {        mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {            @Override            public void onSuccess() {            }            @Override            public void onFailure(int reason) {            }        });    }

这里面的success以及failure没有任何实质的信息,只是提醒你调用这个方法是否成功,而不代表开启搜索是否成功,要监听搜索的状态,如上文所述我们要在广播中实现

else if (WifiP2pManager.WIFI_P2P_DISCOVERY_CHANGED_ACTION.equals(action)) {            int State = intent.getIntExtra(WifiP2pManager.EXTRA_DISCOVERY_STATE, -1);            if (State == WifiP2pManager.WIFI_P2P_DISCOVERY_STARTED)                Toast.makeText(mActivity, "搜索开启", Toast.LENGTH_SHORT).show();            else if (State == WifiP2pManager.WIFI_P2P_DISCOVERY_STOPPED)                Toast.makeText(mActivity, "搜索已关闭", Toast.LENGTH_SHORT).show();        }

ok开启搜索服务之后,当找到一个设备后,设备列表就会发生改变,这个时候就会触发广播中的第二个action

else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {            mManager.requestPeers(mChannel, mPeerListListener);        }

通过requestPeers(mChannel, mPeerListListener)再通过监听器中的onPeersAvailable方法就可以得到设备列表,当让我们想在主界面中显示列表,因此监听器我们在activity中实现然后通过传参的方式传入到广播之中,

 WifiP2pManager.PeerListListener mPeerListListerner = new WifiP2pManager.PeerListListener() {            @Override            public void onPeersAvailable(WifiP2pDeviceList peersList) {                peers.clear();                peersshow.clear();                Collection<WifiP2pDevice> aList = peersList.getDeviceList();                peers.addAll(aList);                for (int i = 0; i < aList.size(); i++) {                    WifiP2pDevice a = (WifiP2pDevice) peers.get(i);                    HashMap<String, String> map = new HashMap<String, String>();                    map.put("name", a.deviceName);                    map.put("address", a.deviceAddress);                    peersshow.add(map);                }                mAdapter = new MyAdapter(peersshow);                mRecyclerView.setAdapter(mAdapter);                mRecyclerView.setLayoutManager(new LinearLayoutManager                        (MainActivity.this));                mAdapter.SetOnItemClickListener(new MyAdapter.OnItemClickListener() {                    @Override                    public void OnItemClick(View view, int position) {                        CreateConnect(peersshow.get(position).get("address"),                                peersshow.get(position).get("name"));                    }                    @Override                    public void OnItemLongClick(View view, int position) {                    }                });            }        };

这是在Activity中的实现,得到List的方法有很多,我使用了RecyclerView感觉游侠笨重,一定有其他好的方法=_=///
单击每一个设备我所写的Adapter就会返回出来它的MAC地址,通过地址我们就可以来连接设备了
三、创建连接

创建连接我们调用manager中的connect方法,如果连接之前没有创建一个组,系统会自动创建一个组,并且随机分配谁是GroupOwner即谁是组长,这也关系到谁是客户端谁是服务器,
connect方法官方文档解释
If the current device is part of an existing p2p group or has created
* a p2p group with {@link #createGroup}, an invitation to join the group is sent to
* the peer device.

我们写一个方法来创建Group,在两个设备中谁调用这个方法,谁就是组长这样就实现了设定谁是服务器与客户端

 private void BeGroupOwener() {        mManager.createGroup(mChannel, new WifiP2pManager.ActionListener() {            @Override            public void onSuccess() {            }            @Override            public void onFailure(int reason) {            }        });    }
 private void CreateConnect(String address, final String name) {        WifiP2pDevice device;        WifiP2pConfig config = new WifiP2pConfig();        Log.i("xyz", address);        config.deviceAddress = address;        /*mac地址*/        config.wps.setup = WpsInfo.PBC;        Log.i("address", "MAC IS " + address);        if (address.equals("9a:ff:d0:23:85:97")) {            config.groupOwnerIntent = 0;            Log.i("address", "lingyige shisun");        }        if (address.equals("36:80:b3:e8:69:a6")) {            config.groupOwnerIntent = 15;            Log.i("address", "lingyigeshiwo");        }        Log.i("address", "lingyige youxianji" + String.valueOf(config.groupOwnerIntent));        mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {            @Override            public void onSuccess() {            }            @Override            public void onFailure(int reason) {            }        });    }

Wifip2pconfi这个类官方解释是
A class representing a Wi-Fi P2p configuration for setting up a connection
有道翻译 一个类代表一个wi - fi P2p配置为建立一个连接=_=+///
其实是一个类用来储存p2p设备的信息,我们把其中的Address值改为想要链接设备的Mac地址就可以得到设备的实例,

 mManager.connect(mChannel, config, new WifiP2pManager.ActionListener() {            @Override            public void onSuccess() {            }            @Override            public void onFailure(int reason) {            }        });

调用connect方法,实现连接,如上文所述,这里的sucess,以及failure都是表示函数的成功与否,要看是否连接还要到广播之中

 else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {            if (mManager == null) {                return;            }            NetworkInfo networkInfo = (NetworkInfo) intent                    .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);            if (networkInfo.isConnected()) {                Log.i("xyz", "已连接");                mManager.requestConnectionInfo(mChannel, mInfoListener);            } else {                Log.i("xyz", "断开连接");                return;            }        }

NetWorkInfo方法Describes the status of a network interface. 用来描述网络接口的状态
当网络连接时我们调用此方法mManager.requestConnectionInfo(mChannel, mInfoListener);就可让服务器端开始接受数据了,这里在下文中也会详讲。

四、传输数据前的准备
上文讲到mManager.requestConnectionInfo(mChannel, mInfoListener);方法
第二个参数是ConnectionInfoListener类
Interface for callback invocation when connection info is available用来当connect成功后回掉,跟上一个得List的监听器一样,我们也在MainActivity中实现,然后通过传参到广播之中

        WifiP2pManager.ConnectionInfoListener mInfoListener = new WifiP2pManager.ConnectionInfoListener() {            @Override            public void onConnectionInfoAvailable(final WifiP2pInfo minfo) {                Log.i("xyz", "InfoAvailable is on");                info = minfo;                TextView view = (TextView) findViewById(R.id.tv_main);                if (info.groupFormed && info.isGroupOwner) {                    Log.i("xyz", "owmer start");                    mServerTask = new FileServerAsyncTask(MainActivity.this, view);                    mServerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);                    mDataTask = new DataServerAsyncTask(MainActivity.this, view);                    mDataTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);                } else if (info.groupFormed) {                    SetButtonVisible();                }            }        };

我们看一下下面这核心代码

 if (info.groupFormed && info.isGroupOwner) {                    Log.i("xyz", "owmer start");                    mServerTask = new FileServerAsyncTask(MainActivity.this, view);                    mServerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);                    mDataTask = new DataServerAsyncTask(MainActivity.this, view);                    mDataTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);                }                 else if (info.groupFormed) {                    SetButtonVisible();                }

我们使用的传输方式是UDP模型在这个Demo中只能从客户端向服务器端发送消息
ok知道这个前提之后我们看
第一个判断:如果组已经建立,并且是组长,也就是说当前设备是服务器,我们开启两个AsyncTask分别用来接收图片以及字符串,当然看完这篇博客之后你可以传输各种类型的数据,这里仅以这两个举例
AsyncTask怎么写我们后面再说,
看第二个判断:如果组建立了,但不是组长,也就是说当前设备是客户端,这个时候我们让两个发送Button可见,也就是说一开始所有的设备界面都是一样的没有发送数据的按钮,但判断出谁是客户端之后,我们就将发送按钮展现出来。
上截图
客户端界面
服务器端界面
ok搜索连接都讲完了,下面就是大头传送数据了
五、传输数据
1.客户端 发送服务的编写
Activity中设置发送图片按钮的监听器

 sendpicture.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);                intent.setType("image/*");                startActivityForResult(intent, 20);            }        });

Intent intent = new Intent(Intent.ACTION_GET_CONTENT)
官方文档Allow the user to select a particular kind of data and return it.
这个Intent会打开文件管理器
intent.setType(“image/*”);这个语句决定着以什么方式打开,我们设以图片方式打开,启动Activity后我们选择好图片,之后Activity关闭自动调用onActivityResult

 @Override    protected void onActivityResult(int requestCode, int resultCode, Intent data) {        if (requestCode == 20) {            super.onActivityResult(requestCode, resultCode, data);            Uri uri = data.getData();            Intent serviceIntent = new Intent(MainActivity.this,                    FileTransferService.class);            serviceIntent.setAction(FileTransferService.ACTION_SEND_FILE);            serviceIntent.putExtra(FileTransferService.EXTRAS_FILE_PATH,                    uri.toString());            serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_ADDRESS,                    info.groupOwnerAddress.getHostAddress());            serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_PORT,                    8988);            MainActivity.this.startService(serviceIntent);        }    }

Uri uri = data.getData();获得图片所在位置
serviceIntent.putExtra(FileTransferService.EXTRAS_FILE_PATH,uri.toString());将位置传入Service
serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_ADDRESS,info.groupOwnerAddress.getHostAddress());传入组长的IP地址,用来创建Socket端口
serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_PORT,8988);传入端口port
ok下面我们看服务究竟怎么写

public class FileTransferService extends IntentService {    private static final int SOCKET_TIMEOUT = 5000;    public static final String ACTION_SEND_FILE = "com.example.android.wifidirect.SEND_FILE";    public static final String EXTRAS_FILE_PATH = "sf_file_url";    public static final String EXTRAS_GROUP_OWNER_ADDRESS = "sf_go_host";    public static final String EXTRAS_GROUP_OWNER_PORT = "sf_go_port";    public FileTransferService(String name) {        super(name);    }    public FileTransferService() {        super("FileTransferService");    }    /*     * (non-Javadoc)     *     * @see android.app.IntentService#onHandleIntent(android.content.Intent)     */    @Override    protected void onHandleIntent(Intent intent) {        Context context = getApplicationContext();        if (intent.getAction().equals(ACTION_SEND_FILE)) {            String fileUri = intent.getExtras().getString(EXTRAS_FILE_PATH);            String host = intent.getExtras().getString(                    EXTRAS_GROUP_OWNER_ADDRESS);            Socket socket = new Socket();            int port = intent.getExtras().getInt(EXTRAS_GROUP_OWNER_PORT);            try {                Log.d("xyz", "Opening client socket - ");                socket.bind(null);                socket.connect((new InetSocketAddress(host, port)),                        SOCKET_TIMEOUT);                Log.d("xyz",                        "Client socket - " + socket.isConnected());                /*returns an output stream to write data into this socket*/                OutputStream stream = socket.getOutputStream();                ContentResolver cr = context.getContentResolver();                InputStream is = null;                try {                    is = cr.openInputStream(Uri.parse(fileUri));                } catch (FileNotFoundException e) {                    Log.d("xyz", e.toString());                }                FileServerAsyncTask.copyFile(is, stream);                Log.d("xyz", "Client: Data written");            } catch (IOException e) {                Log.e("xyz", e.getMessage());            } finally {                if (socket != null) {                    if (socket.isConnected()) {                        try {                            socket.close();                        } catch (IOException e) {                            // Give up                            e.printStackTrace();                        }                    }                }            }        }    }}

socket.connect((new InetSocketAddress(host, port)),
SOCKET_TIMEOUT);创建Socket连接

    /*returns an output stream to write data into this socket*/                OutputStream stream = socket.getOutputStream();                ContentResolver cr = context.getContentResolver();                InputStream is = null;                try {                    is = cr.openInputStream(Uri.parse(fileUri));                } catch (FileNotFoundException e) {                    Log.d("xyz", e.toString());                }                FileServerAsyncTask.copyFile(is, stream);

将is copy到stream中ok这样我们的服务就启动成功了,至于怎么接收先不细讲,先看一下AsyncTask
2、服务器端 AsyncTask的编写

public class FileServerAsyncTask extends        AsyncTask<Void, Void, String> {    private Context context;    private TextView statusText;    /**     * @param context     * @param statusText     */    public FileServerAsyncTask(Context context, View statusText) {        this.context = context;        this.statusText = (TextView) statusText;    }    @Override    protected String doInBackground(Void... params) {        try {            Log.i("xyz", "file doinback");            ServerSocket serverSocket = new ServerSocket(8988);            Socket client = serverSocket.accept();            final File f = new File(                    Environment.getExternalStorageDirectory() + "/"                            + "com.miko.zd" + "/wifip2pshared-"                            + System.currentTimeMillis() + ".jpg");            File dirs = new File(f.getParent());            if (!dirs.exists())                dirs.mkdirs();            f.createNewFile();                /*Returns an input stream to read data from this socket*/            InputStream inputstream = client.getInputStream();            copyFile(inputstream, new FileOutputStream(f));            serverSocket.close();            return f.getAbsolutePath();        } catch (IOException e) {            Log.e("xyz", e.toString());            return null;        }    }    /*     * (non-Javadoc)     *     * @see android.os.AsyncTask#onPostExecute(java.lang.Object)     */    @Override    protected void onPostExecute(String result) {        Log.i("xyz", "file onpost");        Toast.makeText(context, "result"+result, Toast.LENGTH_SHORT).show();        if (result != null) {            statusText.setText("File copied - " + result);            Intent intent = new Intent();            intent.setAction(Intent.ACTION_VIEW);            intent.setDataAndType(Uri.parse("file://" + result), "image/*");            context.startActivity(intent);        }    }    /*     * (non-Javadoc)     *     * @see android.os.AsyncTask#onPreExecute()     */    @Override    protected void onPreExecute() {    }    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) {            return false;        }        return true;    }}

AsyncTask原理大家应该很清楚这里不细说,不懂Google=_=+///
ok先看doInbackGround
ServerSocket serverSocket = new ServerSocket(8988);创建Socket
Socket client = serverSocket.accept();这句很关键,
accept方法
Waits for an incoming request and blocks until the connection is opened.
This method returns a socket object representing the just opened connection.
也就说等待连接之前这里是阻塞的,线程停止,当然不会调用下面的onPostExecute,
这一点很关键我们梳理一下,找到设备,点击设备进行连接后ConnectionInfoListener监听器触发,调用方法

onConnectionInfoAvailable{ if (info.groupFormed && info.isGroupOwner) {                    Log.i("xyz", "owmer start");                    mServerTask = new FileServerAsyncTask(MainActivity.this, view);                    mServerTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);                    }                    }

这个时候Task就启动了,当然它会在 Socket client = serverSocket.accept()这里阻塞

这个时候,我们点击发送数据按钮启动服务,socket.connect((new InetSocketAddress(host, port)),
SOCKET_TIMEOUT);
这时候创建连接,阻塞取消,然后就会尽心之后的copy保存了

ok博主是大二党,计算机科学与技术专业,这搞笑的专业,完全自学,每天学一点,好吧这一点有时候是10个钟头,但自己就能感觉到进步,起码打字快了吧=——=,虽然blog很耗费时间,但我坚信这是一种学习的好方法。

14 0
原创粉丝点击