post list

2013년 2월 10일

[Android] Wi-Fi Direct


Android Wi-Fi Direct

 이번 포스트는 구글 Connecting with Wi-Fi Direct 에 있는 글을 그대로 번역한 것입니다. 여러분들에게 조금이라도 도움이 되길 바랍니다. (실은 내가 영어 다시 읽어보기 귀찮아서.. ㅋㅋ) 또한 구글에서 배포한 Wi-Fi Direct Example을 포스트 마지막에 올려두겠습니다. 내용과 코드를 참조하시면 많은 도움이 될껍니다. 그럼 시작할께요. 

 Wi-Fi Direct 는 네트워크나 핫스팟에 접속할 필요 없이 근처의 기계들과의 연결을 가능하게 해준다. 블루투스보다 먼 거리에서도 서로 연결할 수 있으며 빠르게 기계간의 상호 연결이 가능하다. 


 Permissions 설정하기

 먼저 Wi-Fi Direct 를 사용하기 위해서는 Manifest에 추가해야할 Permission이 있다. Wi-Fi Direct는 인터넷 연결을 요구하지는 않지만 표준 Java Socket을 사용하기 때문에 Internet Permission을 요구한다. 그래서 우리는 다음의 Permission들을 추가해줘야 한다.

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


 P2P Manager와 Broadcast Receiver 설정하기

  Wi-Fi Direct를 사용하기 위해서, 특정 이벤트가 일어났을 때 그 상황을 우리의 앱에게 말해줄 broadcast intent가 필요하다. 이것을 위해 우리 앱에 IntentFilter를 초기화시켜서 다음의 intent에 연결해줘야한다.

WIFI_P2P_STATE_CHANGED_ACTION
Indicates whether Wi-Fi Peer-To-Peer (P2P) is enabled

WIFI_P2P_PEERS_CHANGED_ACTION
Indicates that the available peer list has changed.

WIFI_P2P_CONNECTION_CHANGED_ACTION
Indicates the state of Wi-Fi P2P connectivity has changed.

WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
Indicates this device's configuration details have changed.

 각각이 뭘 하는지는 적혀 있으니 이제 실제 코드에서 어떻게 구현되는지 살펴보자.

private final IntentFilter intentFilter = new IntentFilter();
...
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    //  Indicates a change in the Wi-Fi Peer-to-Peer status.
    intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);

    // Indicates a change in the list of available peers.
    intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);

    // Indicates the state of Wi-Fi P2P connectivity has changed.
    intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);

    // Indicates this device's details have changed.
    intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);

    ...
}


 이제 onCreate method 마지막에 WifiP2pManager 인스턴스를 생성해보자. 또한 이 인스턴스의 initialize() method를 이용하면 WifiP2pManager.Channel 객체를 리턴받을 수 있다. 이 녀석은 나 중에 Wi-Fi Direct 프레임워크에 우리의 앱이 접속하도록 해준다.

@Override

Channel mChannel;

public void onCreate(Bundle savedInstanceState) {
    ....
    mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
    mChannel = mManager.initialize(this, getMainLooper(), null);
}
 자, 이제 System의 P2P 상태가 변하는 것을 알기 위해서 BroadcastReceiver 클래스를 생성하라. onReceive() 메서드에서 아래의 나열된 P2P 상태 변화를 핸들링하기 위한 코드를 추가하자.



@Override
public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
            // Determine if Wifi Direct mode is enabled or not, alert
            // the Activity.
            int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
            if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
                activity.setIsWifiP2pEnabled(true);
            } else {
                activity.setIsWifiP2pEnabled(false);
            }
        } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

            // The peer list has changed!  We should probably do something about
            // that.

        } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {

            // Connection state changed!  We should probably do something about
            // that.

        } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
            DeviceListFragment fragment = (DeviceListFragment) activity.getFragmentManager()
                    .findFragmentById(R.id.frag_list);
            fragment.updateThisDevice((WifiP2pDevice) intent.getParcelableExtra(
                    WifiP2pManager.EXTRA_WIFI_P2P_DEVICE));

        }
    }
마지막으로 intent filter를 등록하기 위한 코드를 추가하고, 메인 액티비티가 활성화 될때 broadcast receiver를 등록하자. 그리고 그 액티비티가 멈추게 될 때(paused) 등록을 해제하도록 한다. 이것을 위해서 onResume(), onPause() 메서드를 이용하면 된다.

/** register the BroadcastReceiver with the intent values to be matched */
    @Override
    public void onResume() {
        super.onResume();
        receiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this);
        registerReceiver(receiver, intentFilter);
    }

    @Override
    public void onPause() {
        super.onPause();
        unregisterReceiver(receiver);
    }

 Peer (피어) 탐색 초기화하기

 Wi-Fi Direct를 이용해서 근처의 디바이스를 찾기 위해서는 discoverPeers()를 호출해야 한다. 이 메서드는 다음의 것들을 인자로 요구한다.

  • P2P Manager를 초기화했을 때 리턴받은 WiFiP2pManager.Channel
  • 디바이스 찾는 것을 성공했는지 못했는지를 의미하는 메서드를 포함하는 WifiP2pManager.ActionListener의 구현


mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {

        @Override
        public void onSuccess() {
            // Code for when the discovery initiation is successful goes here.
            // No services have actually been discovered yet, so this method
            // can often be left blank.  Code for peer discovery goes in the
            // onReceive method, detailed below.
        }

        @Override
        public void onFailure(int reasonCode) {
            // Code for when the discovery initiation fails goes here.
            // Alert the user that something went wrong.
        }
});
 이 코드에서 명심해야 할 점이 있다. discoverPeers()는 오직 peer discovery만 초기화한다는 것이다. discoverPeers() 메서드는 탐색과정을 시작하고 즉각적으로 그 결과를 리턴한다. 이 시스템은 당신에게 Peer(피어) 탐색 과정이 성공적으로 시작되었는지 아닌지 알려준다. 이 과정은 제공된 ActionListener의 메서드를 호출함으로 이루어진다. 또한, 탐색은 접속이 성공적으로 초기화되던지 P2P group 이 형성이 될때까지 활성화 될 것이다.


 Peer(피어) 목록 가져오기

 자 이제 피어목록을 가져오고 처리하기 위한 코드를 작성해보자. 먼저 Wi-Fi Direct가 발견한 피어들에 대한 정보를 제공하는 WifiP2pManager.PeerListListener 인터페이스를 구현해야 한다. 다음의 코드를 보자.
private List peers = new ArrayList();
    ...

    private PeerListListener peerListListener = new PeerListListener() {
        @Override
        public void onPeersAvailable(WifiP2pDeviceList peerList) {

            // Out with the old, in with the new.
            peers.clear();
            peers.addAll(peerList.getDeviceList());

            // If an AdapterView is backed by this data, notify it
            // of the change.  For instance, if you have a ListView of available
            // peers, trigger an update.
            ((WiFiPeerListAdapter) getListAdapter()).notifyDataSetChanged();
            if (peers.size() == 0) {
                Log.d(WiFiDirectActivity.TAG, "No devices found");
                return;
            }
        }
    }
 먼저 WIFI_P2P_PEERS_CHANGED_ACTION 을 가진 intent를 받았을 때 requestPeers() 메서드를 호출하기 위해서 broadcast receiver의 onReceive() method를 수정하자. 이제 우리는  이 listener를 receiver에게 어떻게든 전해줘야한다. 한가지 방법은 broadcast receiver의 생성자에게 인자로 이것을 넘겨주는 것이다.
public void onReceive(Context context, Intent intent) {
    ...
    else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

        // Request available peers from the wifi p2p manager. This is an
        // asynchronous call and the calling activity is notified with a
        // callback on PeerListListener.onPeersAvailable()
        if (mManager != null) {
            mManager.requestPeers(mChannel, peerListListener);
        }
        Log.d(WiFiDirectActivity.TAG, "P2P peers changed");
    }...
}
 이제, WIFI_P2P_PEERS_CHAGNED_ACTION 을 가진 intent는 업데이트된 피어 목록을 요청하게 될 것이다.


 Peer(피어)에게 접속하기

 피어에게 접속하기 위해서는 WifiP2pConfig 객체를 만들어야 한다. 그리고 우리가 접속하고자 하는 디바이스를 의미하는 WifiP2pDevice로부터 데이터를 얻어서 WifiP2pConfig 에 카피해 줘야 한다.


@Override
    public void connect() {
        // Picking the first device found on the network.
        WifiP2pDevice device = peers.get(0);

        WifiP2pConfig config = new WifiP2pConfig();
        config.deviceAddress = device.deviceAddress;
        config.wps.setup = WpsInfo.PBC;

        mManager.connect(mChannel, config, new ActionListener() {

            @Override
            public void onSuccess() {
                // WiFiDirectBroadcastReceiver will notify us. Ignore for now.
            }

            @Override
            public void onFailure(int reason) {
                Toast.makeText(WiFiDirectActivity.this, "Connect failed. Retry.",
                        Toast.LENGTH_SHORT).show();
            }
        });
    }이 코드에 구현된 WifiP2pManager.ActionListener는 초기화(initiation)이 성공하거나 실패했을 때 당신에게 알려주는 기능만을 갖추고 있다. 그래서 접속 상태의 변화를 알고 싶다면 WifiP2pManager.ConnectionInfoListener 인터페이스를 구현해야한다. 이 인터페이스의 onConnectionInfoAvailable() 콜백은 접속 상태가 변화되었을 때 우리에게 알려줄 것이다. 만약에 여러대의 디바이스가 하나의 디바이스에 접속하고자 할 때에는 (3명이나 그 이상의 플레이어가 즐기는 게임이나 채팅 앱같은), 한 디바이스가 “그룹의 오너”(group owner)가 된다.

@Override
    public void onConnectionInfoAvailable(final WifiP2pInfo info) {

        // InetAddress from WifiP2pInfo struct.
        InetAddress groupOwnerAddress = info.groupOwnerAddress.getHostAddress());

        // After the group negotiation, we can determine the group owner.
        if (info.groupFormed && info.isGroupOwner) {
            // Do whatever tasks are specific to the group owner.
            // One common case is creating a server thread and accepting
            // incoming connections.
        } else if (info.groupFormed) {
            // The other device acts as the client. In this case,
            // you'll want to create a client thread that connects to the group
            // owner.
        }
    } 이번에는 broadcast receiver의 onReceive() 메서드로 돌아가서 WIFI_P2P_CONNECTION_CHANGED_ACTION intent 부분을 수정하자. 이 intent를 받았을 때 requestConnectionInfo()를 호출하도록 하자. 이것은 동기화적인 호출이기 때문에 호출결과는 우리가 파라미터로 제공한 connection info listener에 의해서 받게 될 것이다.

...
        } 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()) {

                // We are connected with the other device, request connection
                // info to find group owner IP

                mManager.requestConnectionInfo(mChannel, connectionListener);
            }
            ...


Download project source code

댓글 없음:

댓글 쓰기