Android网络通信之WiFi Direct

使用Wi-Fi Direct技术可以让具备硬件支持的设备在没有中间接入点的情况下进行直接互联。Android 4.0(API版本14)及以后的系统都提供了对Wi-Fi Direct的API支持。通过对这些API的使用,开发者可以实现支持Wi-Fi Direct的设备间进行相互探测和连接,从而获得较之蓝牙更远距离的高速数据通信效果。对于诸如多人游戏、图片共享等需要在用户之间传输数据的应用而言,这一技术无疑是十分有价值的。
关于Wi-Fi Direct的API函数的使用需要注意一下几个要点:
·用于探测(discover)对等设备(peers)、向对等设备发起请求(request)以及建立连接(connect)的方法定义在类WifiP2pManager中。
·通过设置监听器(Listener)可以获知WifiP2pManager中方法调用的成功与否。监听器以参数的形式传递给被调用的方法。
·当发现新对等设备或链接丢失的时候,Wi-Fi Direct系统(framework)以意向(Intent)的方式根据检测到的不同事件做出相应的通知。
开发中,以上三点的配合使用相当普遍。简单举个例子,定义一个监听器WifiP2pManager.ActionListener并调用函数discoverPeers(),当相应事件发生的时候就会在ActionListener.onSuccess()和ActionListener.onFailure()两个方法中得到通知。当discoverPeers()方法检测到了对等设备列表变化的时候,可以收到由系统广播(broadcast)发出一个WIFI_P2P_PEERS_CHANGED_ACTION意向。
创建广播接收器以处理Wi-Fi Direct意向的基本步骤如下:
1、创建一个继承BroadcastReceiver类的新类。构造函数的参数分别传递WifiP2pManager,WifiP2pManager.Channel,以及在这个广播接收器中需要注册的活动(activity)。这是一种最常见的参数设置模式,它让广播接收器能够引起活动作出更新,同时又能在必要时使用Wi-Fi硬件和通信信道。
2、在广播接收器的onReceive()函数中,针对感兴趣的特定意向可以执行相应的动作(actions)。例如,当广播接收器收到了意向WIFI_P2P_PEERS_CHANGED_ACTION,就可以调用requestPeers()方法来列举出当前探测到的对等设备。
创建Wi-Fi Direct应用:
完整的Wi-Fi Direct应用包含创建并注册广播接收器、检测对等设备、连接对等设备以及在对等设备间传输数据几个方面的功能。下面将详细介绍如何实现。
准备工作:
在使用Wi-Fi Direct API之前,首先要确保应用程序能够访问硬件,并且设备支持Wi-Fi Direct协议。如果这些条件都满足,就可以获取一个WifiP2pManager实例,创建并注册广播接收器,最后就是使用Wi-Fi Direct API了。
在Android manifest文件中加入以下内容,允许使用Wi-Fi设备上的硬件并声明应用程序正确支持了调用API所需的最低SDK版本。
<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” />
检查Wi-Fi Direct支持并已开启。推荐在广播接收器收到WIFI_P2P_STATE_CHANGED_ACTION意向的时候进行检测。检测结果需要通告相应的活动并做出处理:
@Override
public void onReceive(Context context, Intent intent) {

String action = intent.getAction();
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) {
// Wifi Direct is enabled
} else {
// Wi-Fi Direct is not enabled
}
}

}
在活动的onCreate()方法中获取WifiP2pManager对象的一个实例,通过该对象的initialize()方法向Wi-Fi Direct系统注册当前的应用程序。注册成功后,会返回一个WifiP2pManager.Channel,通过它,应用程序就能和Wi-Fi Direct系统交互。WifiP2pManager和WifiP2pManager.Channel对象以及一个活动的引用最后都被作为参数传递给自定义的广播接收器。这样,该活动就能够响应广播接收器的通知并作出相应的更新。当然,这样做也使程序具备了操纵设备Wi-Fi状态的能力:
WifiP2pManager mManager;
Channel mChannel;
BroadcastReceiver mReceiver;
……
@Override
protected void onCreate(Bundle savedInstanceState){

mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mManager.initialize(this, getMainLooper(), null);
mReceiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this);

}
创建一个意向过滤器(intent filter),其中添加的意向种类和广播接收器中的保持一致:
IntentFilter mIntentFilter;
……
@Override
protected void onCreate(Bundle savedInstanceState){
……
mIntentFilter = new IntentFilter();
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
……
}
在活动的onResume()方法中注册广播接收器,并在活动的onPause()方法中注销它:
/* register the broadcast receiver with the intent values to be matched */
@Override
protected void onResume() {
super.onResume();
registerReceiver(mReceiver, mIntentFilter);
}
/* unregister the broadcast receiver */
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(mReceiver);
}
一旦成功获取WifiP2pManager.Channel并创建了广播接收器,应用程序就已经具备了使用Wi-Fi Direct相关函数和接收Wi-Fi Direct意向的能力。尽管放手使用WifiP2pManager为你提供的方法,让程序也拥有Wi-Fi Direct的特殊能力吧!
探测对等设备(Discovering peers)
调用discoverPeers()函数可以探测到有效距离内的对等设备。它是一个异步函数,调用成功与否会在程序所创建WifiP2pManager.ActionListener监听器的onSuccess()和onFailure()中给出通知。值得注意的是,onSuccess()方法只会对成功探测到对等设备这一事件做出通知,而并不会提供任何关于已发现的对等设备的具体信息:
manager.discoverPeers(channel, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {

}
@Override
public void onFailure(int reasonCode) {

}
});
当成功检测到对等设备存在的时候,系统会广播WIFI_P2P_PEERS_CHANGED_ACTION意向。程序接收到该意向后,通过调用requestPeers()方法,就能获得已经探测到对等设备的清单。下面代码将展示如何实现这一过程:
PeerListListener myPeerListListener;
……
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 (manager != null) {
manager.requestPeers(channel, myPeerListListener);
}
}
requestPeers()方法同样是一个异步函数,当它准备好一份对等设备列表的时候,就会通知监听器WifiP2pManager.PeerListListener中定义的onPeersAvailable()方法。而onPeersAvailable()方法中所能获取到的对等设备列表以WifiP2pDeviceList形式存储,通过遍历这个列表可以选择出希望连接的设备。
连接对等设备(Connecting to peers)
确定了要连接的设备,还需调用connect()方法建立连接。该方法的其中一个参数是WifiP2pConfig对象,它提供了要连接设备的相关信息。连接的成功与否需要通过监听器WifiP2pManager.ActionListener获取通知。下面的代码将示范如何建立设备连接:
//obtain a peer from the WifiP2pDeviceList
WifiP2pDevice device;
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
manager.connect(channel, config, new ActionListener() {
@Override
public void onSuccess() {
//success logic
}
@Override
public void onFailure(int reason) {
//failure logic
}
});
传输数据(Transferring data)
连接一旦建立成功,数据传输也就是顺理成章的事情。以下是通过socket发送数据的基本步骤:
1、创建ServerSocket。它将被用于监听特定端口,等待客户端发起的连接请求。该操作需要在后台线程中实现。
2、创建客户端Socket。客户端通过ServerSocket对应的IP和端口连接到服务设备。
3、客户端向服务器发生数据。客户socket成功连接到服务socket后,就能以字节流的形式向服务器发生数据了。
4、服务器socket通过accept()方法等待客户端数据连接的到来。该方法在收到客户端数据之前一直处于阻塞状态。因此,需要在单独的线程中调用它。数据连接一旦建立,服务设备就能接收到客户端的数据。这时要做的就是施以相应的动作,例如将数据保存到文件,或者是直接显示到用户界面上,等等。
以下代码修改自SDK自带的示例Wi-Fi Direct Demo。它演示了如何建立一对客户端-服务器连接,并由客户端向服务器发送JPEG图片。若需完整的演示工程,只需编译并运行SDK示例Wi-Fi Direct Demo即可。
public static class FileServerAsyncTask extends AsyncTask {
private Context context;
private TextView statusText;
public FileServerAsyncTask(Context context, View statusText) {
this.context = context;
this.statusText = (TextView) statusText;
}
@Override
protected String doInBackground(Void… params) {
try {
/* Create a server socket and wait for client connections. This call blocks until a connection is accepted from a client */
ServerSocket serverSocket = new ServerSocket(8888);
Socket client = serverSocket.accept();
/*If this code is reached, a client has connected and transferred data Save the input stream from the client as a JPEG file */
final File f = new File(Environment.getExternalStorageDirectory() + “/”
+ context.getPackageName() + “/wifip2pshared-”
+ System.currentTimeMillis()  + “.jpg”);
File dirs = new File(f.getParent());
if (!dirs.exists())
dirs.mkdirs();
f.createNewFile();
InputStream inputstream = client.getInputStream();
copyFile(inputstream, new FileOutputStream(f));
serverSocket.close();
return f.getAbsolutePath();
} catch (IOException e) {
Log.e(WiFiDirectActivity.TAG, e.getMessage());
return null;
}
}
// Start activity that can handle the JPEG image
@Override
protected void onPostExecute(String result) {
if (result != null) {
statusText.setText(“File copied – ” + result);
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(“file://” + result), “image/*”);
context.startActivity(intent);
}
}
}
On the client, connect to the server socket with a client socket and transfer data. This example transfers a JPEG file on the client device’s file system:
Context context = this.getApplicationContext();
String host;
int port;
int len;
Socket socket = new Socket();
byte buf[]  = new byte[1024];

try {
/* Create a client socket with the host,  port, and timeout information.*/
socket.bind(null);
socket.connect((new InetSocketAddress(host, port)), 500);
/* Create a byte stream from a JPEG file and pipe it to the output stream  of the socket. This data will be retrieved by the server device. */
OutputStream outputStream = socket.getOutputStream();
ContentResolver cr = context.getContentResolver();
InputStream inputStream = null;
inputStream = cr.openInputStream(Uri.parse(“path/to/picture.jpg”));
while ((len = inputStream.read(buf)) != -1) {
outputStream.write(buf, 0, len);
}
outputStream.close();
inputStream.close();
} catch (FileNotFoundException e) {
//catch logic
} catch (IOException e) {
//catch logic
}
/* Clean up any open sockets when done transferring or if an exception occurred. */
finally {
if (socket != null) {
if (socket.isConnected()) {
try {
socket.close();
} catch (IOException e) {
//catch logic
}
}
}
}

Android – Broadcast Receivers

Broadcast Receivers simply respond to broadcast messages from other applications or from the system itself. These messages are sometime called events or intents. For example, applications can also initiate broadcasts to let other applications know that some data has been downloaded to the device and is available for them to use, so this is broadcast receiver who will intercept this communication and will initiate appropriate action.

There are following two important steps to make BroadcastReceiver works for the system broadcasted intents −

  • Creating the Broadcast Receiver.
  • Registering Broadcast Receiver

There is one additional steps in case you are going to implement your custom intents then you will have to create and broadcast those intents.

Creating the Broadcast Receiver

A broadcast receiver is implemented as a subclass of BroadcastReceiverclass and overriding the onReceive() method where each message is received as a Intent object parameter.

public class MyReceiver extends BroadcastReceiver {
   @Override
   public void onReceive(Context context, Intent intent) {
      Toast.makeText(context, "Intent Detected.", Toast.LENGTH_LONG).show();
   }
}

Registering Broadcast Receiver

An application listens for specific broadcast intents by registering a broadcast receiver in AndroidManifest.xml file. Consider we are going to register MyReceiver for system generated event ACTION_BOOT_COMPLETED which is fired by the system once the Android system has completed the boot process.

broadcast

BROADCAST-RECEIVER

<application
   android:icon="@drawable/ic_launcher"
   android:label="@string/app_name"
   android:theme="@style/AppTheme" >
   <receiver android:name="MyReceiver">
   
      <intent-filter>
         <action android:name="android.intent.action.BOOT_COMPLETED">
         </action>
      </intent-filter>
   
   </receiver>
</application>

Now whenever your Android device gets booted, it will be intercepted by BroadcastReceiver MyReceiver and implemented logic inside onReceive()will be executed.

There are several system generated events defined as final static fields in the Intent class. The following table lists a few important system events.

Event Constant Description
android.intent.action.BATTERY_CHANGED Sticky broadcast containing the charging state, level, and other information about the battery.
android.intent.action.BATTERY_LOW Indicates low battery condition on the device.
android.intent.action.BATTERY_OKAY Indicates the battery is now okay after being low.
android.intent.action.BOOT_COMPLETED This is broadcast once, after the system has finished booting.
android.intent.action.BUG_REPORT Show activity for reporting a bug.
android.intent.action.CALL Perform a call to someone specified by the data.
android.intent.action.CALL_BUTTON The user pressed the “call” button to go to the dialer or other appropriate UI for placing a call.
android.intent.action.DATE_CHANGED The date has changed.
android.intent.action.REBOOT Have the device reboot.

Broadcasting Custom Intents

If you want your application itself should generate and send custom intents then you will have to create and send those intents by using thesendBroadcast() method inside your activity class. If you use thesendStickyBroadcast(Intent) method, the Intent is sticky, meaning theIntent you are sending stays around after the broadcast is complete.

public void broadcastIntent(View view)
{
   Intent intent = new Intent();
   intent.setAction("com.tutorialspoint.CUSTOM_INTENT");
   sendBroadcast(intent);
}

This intent com.tutorialspoint.CUSTOM_INTENT can also be registered in similar way as we have regsitered system generated intent.

<application
   android:icon="@drawable/ic_launcher"
   android:label="@string/app_name"
   android:theme="@style/AppTheme" >
   <receiver android:name="MyReceiver">
   
      <intent-filter>
         <action android:name="com.tutorialspoint.CUSTOM_INTENT">
         </action>
      </intent-filter>
   
   </receiver>
</application>

Example

This example will explain you how to create BroadcastReceiver to intercept custom intent. Once you are familiar with custom intent, then you can program your application to intercept system generated intents. So let’s follow the following steps to modify the Android application we created inHello World Example chapter −

Step Description
1 You will use Android studio to create an Android application and name it as My Application under a package com.example.My Application as explained in the Hello World Example chapter.
2 Modify main activity file MainActivity.java to add broadcastIntent()method.
3 Create a new java file called MyReceiver.java under the packagecom.example.My Application to define a BroadcastReceiver.
4 An application can handle one or more custom and system intents without any restrictions. Every indent you want to intercept must be registered in your AndroidManifest.xml file using <receiver…/> tag
5 Modify the default content of res/layout/activity_main.xml file to include a button to broadcast intent.
6 No need to modify the string file, Android studio take care of string.xml file.
7 Run the application to launch Android emulator and verify the result of the changes done in the application.

Following is the content of the modified main activity filesrc/com.example.My Application/MainActivity.java. This file can include each of the fundamental life cycle methods. We have addedbroadcastIntent() method to broadcast a custom intent.

package com.example.My Application;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.content.Intent;
import android.view.View;

public class MainActivity extends Activity {

   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      }
   
   @Override
   public boolean onCreateOptionsMenu(Menu menu) {
      getMenuInflater().inflate(R.menu.activity_main, menu);
      return true;
      }
   
   // broadcast a custom intent. 
   public void broadcastIntent(View view){
      Intent intent = new Intent();
      intent.setAction("com.tutorialspoint.CUSTOM_INTENT");
      sendBroadcast(intent);
   }
}

Following is the content of src/com.example.My Application/MyReceiver.java:

package com.example.My Application;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class MyReceiver extends BroadcastReceiver {
   @Override
   public void onReceive(Context context, Intent intent) {
      Toast.makeText(context, "Intent Detected.", Toast.LENGTH_LONG).show();
   }
}

Following will the modified content of AndroidManifest.xml file. Here we have added <service…/> tag to include our service:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.example.My Application"
   android:versionCode="1"
   android:versionName="1.0" >
   
   <uses-sdk
      android:minSdkVersion="8"
      android:targetSdkVersion="22" />
   
   <application
       android:icon="@drawable/ic_launcher"
       android:label="@string/app_name"
       android:theme="@style/AppTheme" >
       
       <activity
          android:name=".MainActivity"
          android:label="@string/title_activity_main" >
       
          <intent-filter>
             <action android:name="android.intent.action.MAIN" />
             <category android:name="android.intent.category.LAUNCHER"/>
          </intent-filter>
          
       </activity>
       
       <receiver android:name="MyReceiver">
       
       <intent-filter>
          <action android:name="com.tutorialspoint.CUSTOM_INTENT">
          </action>
       </intent-filter>
       
       </receiver>
       
   </application>
</manifest>

Following will be the content of res/layout/activity_main.xml file to include a button to broadcast our custom intent −

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
   android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
   android:paddingRight="@dimen/activity_horizontal_margin"
   android:paddingTop="@dimen/activity_vertical_margin"
   android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
   
   <TextView
      android:id="@+id/textView1"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Example of Broadcast"
      android:layout_alignParentTop="true"
      android:layout_centerHorizontal="true"
      android:textSize="30dp" />
      
   <TextView
      android:id="@+id/textView2"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Tutorials point "
      android:textColor="#ff87ff09"
      android:textSize="30dp"
      android:layout_above="@+id/imageButton"
      android:layout_centerHorizontal="true"
      android:layout_marginBottom="40dp" />
      
   <ImageButton
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:id="@+id/imageButton"
      android:src="@drawable/abc"
      android:layout_centerVertical="true"
      android:layout_centerHorizontal="true" />
      
   <Button
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:id="@+id/button2"
      android:text="Broadcast Intent"
      android:onClick="broadcastIntent"
      android:layout_below="@+id/imageButton"
      android:layout_centerHorizontal="true" />

</RelativeLayout>

Following will be the content of res/values/strings.xml to define two new constants −

<resources>    
    <string name="menu_settings">Settings</string>
    <string name="title_activity_main">My Application</string>
</resources>

Let’s try to run our modified Hello World! application we just modified. I assume you had created your AVD while doing environment set-up. To run the app from Android studio, open one of your project’s activity files and click Run Eclipse Run Icon icon from the tool bar. Android Studio installs the app on your AVD and starts it and if everything is fine with your set-up and application, it will display following Emulator window −

Android Broadcast Demo

Now to broadcast our custom intent, let’s click on Broadcast Intent button, this will broadcast our custom intent “com.tutorialspoint.CUSTOM_INTENT”which will be intercepted by our registered BroadcastReceiver i.e. MyReceiver and as per our implemented logic a toast will appear on the bottom of the the simulator as follows −

Android Broadcast Intent

You can try implementing other BroadcastReceiver to intercept system generated intents like system boot up, date changed, low battery etc.

Stop a thread

public class IndexProcessor implements Runnable {

    private static final Logger LOGGER = LoggerFactory.getLogger(IndexProcessor.class);
    private volatile boolean running = true;

    public void terminate() {
        running = false;
    }

    @Override
    public void run() {
        while (running) {
            try {
                LOGGER.debug("Sleeping...");
                Thread.sleep((long) 15000);

                LOGGER.debug("Processing");
            } catch (InterruptedException e) {
                LOGGER.error("Exception", e);
                running = false;
            }
        }

    }
}

Synchronized Methods

The Java programming language provides two basic synchronization idioms: synchronized methods and synchronized statements. The more complex of the two, synchronized statements, are described in the next section. This section is about synchronized methods.

To make a method synchronized, simply add the synchronized keyword to its declaration:

public class SynchronizedCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}

If count is an instance of SynchronizedCounter, then making these methods synchronized has two effects:

  • First, it is not possible for two invocations of synchronized methods on the same object to interleave. When one thread is executing a synchronized method for an object, all other threads that invoke synchronized methods for the same object block (suspend execution) until the first thread is done with the object.
  • Second, when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocationof a synchronized method for the same object. This guarantees that changes to the state of the object are visible to all threads.

Note that constructors cannot be synchronized — using the synchronized keyword with a constructor is a syntax error. Synchronizing constructors doesn’t make sense, because only the thread that creates an object should have access to it while it is being constructed.


Warning: When constructing an object that will be shared between threads, be very careful that a reference to the object does not “leak” prematurely. For example, suppose you want to maintain a List called instances containing every instance of class. You might be tempted to add the following line to your constructor:

instances.add(this);

But then other threads can use instances to access the object before construction of the object is complete.


Synchronized methods enable a simple strategy for preventing thread interference and memory consistency errors: if an object is visible to more than one thread, all reads or writes to that object’s variables are done through synchronized methods. (An important exception: finalfields, which cannot be modified after the object is constructed, can be safely read through non-synchronized methods, once the object is constructed) This strategy is effective, but can present problems with liveness, as we’ll see later in this lesson.

java.io.IOException – How to solve IOException

In this example we are going to talk about a very common exception that many java developers stumble upon when dealing with IO operations in their program : IOException. This exception occurs when an IO operation has failed for some reason. It is also a checked exception which means that your program has to handle it. All bult-in Java IO methods that might cause an IOException, explicitly throw it so that your program can handle it. It is also worth noting that the IOException object that your program receives is accompanied by a String message that can inform you as accurately as possible for the cause of the exception.

Also, IOException is the most general class that can describe anIOException. Several subclass of it exist in order to reveal the problem that occurred in a more specific and detailed manner. Some of the most well know sub classes are FileNotFoundException,EOFException, UnsupportedEncodingException, SocketException, SSLException

1. A simple case of IOException

Let’s see a very simple case of an IOException. In the following example we are going to try to read some lines of text form a file that does not exist:

IOExceptionExample.java:

package com.javacodegeeks.core.io.ioexception;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class IOExceptionExample {

	private static String filepath = "C:\\Users\\nikos\\Desktop\\TestFiles\\testFile2.txt";
	
	public static void main(String[] args) {
		
		BufferedReader br = null;
		String curline;
		try {
			br = new BufferedReader(new FileReader(filepath));

			while ((curline = br.readLine()) != null) {
				System.out.println(curline);
			}

		} catch (IOException e) {
		
			System.err.println("An IOException was caught :"+e.getMessage());
		
		}finally{
			
			try {
				
				if(br != null)
					br.close();
			
			} catch (IOException e) {
				
				e.printStackTrace();
			}
		}

	}

}

Now, when I run this program, because the file C:\\Users\\nikos\\Desktop\\TestFiles\\testFile2.txt does not exist, it will output:

An IOException was caught :C:\Users\nikos\Desktop\TestFiles\testFile2.txt (The system cannot find the file specified)

2. How to solve IOException

As you’ve seen IOException is a very general exception that occurs when an IO operations fails. So you can understand that there is no standard way on how to solve this exception. The best thing you can do is to explicitly handle the exception in a try-catch block and print out the message of the exception. Then you can take the correct actions to solve this situation. For example, in the previous code snippet, the message clearly states that the file does not exist. So you should go ahead and create the file. It can also say :”Permission Denied”. This means that you should check if you have the permission to perform the action you requested in that file. If you are working with streams and especially with sockets and the stream is closed in the middle of your session you could see a message like :”Stream Closed” that designates the problem exactly .

Download Source Code

This was an example on java.io.IOException. Download the source code of this example here : IOExceptionExample.zip

Understanding AsyncTask – Once and Forever

Motivation

Android modifies the user interface via one thread, the so called UI Thread. If you perform a long running operation directly on the UI Thread, for example downloading a file from the internet, the user interface of your application will “freeze” until the corresponding task is finished. When this happens it is very easy for the user to perceive your application as slow.

As a concrete example of a bad implementation and what happens when a long running operation is done on the UI Thread, I want to refer to one of my previous tutorials: Creating A Simple RSS Application in Android. Well, that application is working fine, and it does what it is supposed to do – parse an XML feed and display the headlines in a ListView. The “vulnerability” of that application is that the network access is done directly on the UI Thread which makes the application to “freeze” while the XML feed is downloaded (take a look at point number 5 to see).
When I created that tutorial I wanted to make it as simple as possible without dealing with more advanced topics like asynchronous tasks. The intent of tutorial was to show the working process with feeds on a high level. But I promise you, by the end of this article you will be able to fix it and have a Cool Rss App that runs smoothly! 🙂

To provide a good user experience all long running operations in an Android application should run asynchronously. To achieve this we will be using the AsyncTask class.

What does AsyncTask do?

AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers. 

In order to use the AsyncTask class, you must extend it and override at least thedoInBackground() method.

The most common methods you will need to implement are these:

   1. onPreExecute() – called on the UI thread before the thread starts running. This method is usually used to setup the task, for example by displaying a progress bar.

   2. doInBackground(Params…) – this is the method that runs on the background thread. In this method you should put all the code you want the application to perform in background. Referring to our Simple RSS Aplication, you would put here the code that downloads the XML feed and does the parsing. The doInBackground() is called immediately after onPreExecute(). When it finishes, it sends the result to the onPostExecute().

   3. onProgressUpdate() – called when you invoke publishProgress() in the doInBackground().

   4. onPostExecute(Result) – called on the UI thread after the background thread finishes. It takes as parameter the result received from doInBackground().

AsyncTask is a generic class, it uses 3 types: AsyncTask<Params, Progress, Result>.

  1. Params – the input. what you pass to the AsyncTask
  2. Progress – if you have any updates, passed to onProgressUpdate()
  3. Result – the output. what returns doInBackground()

Once a task is created, it can be executed like this:
new DownloadTast().execute(url1, url2, urln);

Code Example

This is a simple skeleton of an AsyncTask implementation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class AsyncTaskTestActivity extends Activity {
   @Override
   public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.main);
      //Starting the task. Pass an url as the parameter.
      new PostTask().execute("http://feeds.pcworld.com/pcworld/latestnews");
   }
   // The definition of our task class
   private class PostTask extends AsyncTask<String, Integer, String> {
   @Override
   protected void onPreExecute() {
      super.onPreExecute();
      displayProgressBar("Downloading...");
   }
   @Override
   protected String doInBackground(String... params) {
      String url=params[0];
      // Dummy code
      for (int i = 0; i <= 100; i += 5) {
        try {
          Thread.sleep(50);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
         publishProgress(i);
      }
      return "All Done!";
   }
   @Override
   protected void onProgressUpdate(Integer... values) {
      super.onProgressUpdate(values);
      updateProgressBar(values[0]);
   }
   @Override
   protected void onPostExecute(String result) {
      super.onPostExecute(result);
      dismissProgressBar();
   }
   }
}

AsyncTasks are great for performing tasks in a separate thread, they have however one weakness. While the AsyncTask is in the middle of the work and the screen of device is rotated, you’ll notice that the application crashes. This happens because when rotating the device screen a configuration change occurs, which will trigger the Activity to restart. The AsyncTask reference to the Activity is invalid, an onPostExecute() will have no effect on the new Activity. How to handle this sort of issues is described in: Dealing with AsyncTask and Screen Orientation, which I highly recommend reading it if you are concerned to deliver stable Android applications.

How to build a server in Java: Allowing multiple users to connect

Servers with multiple connections

Right now, our server allows one user to connect and then just prints out what that user has sent to the server. Now that’s cool but it isn’t really very useful. Wouldn’t it be awesome if multiple connections would be allowed and we could actually chat? Well then, let’s do it!

If you haven’t already please check out Part 1 and Part 2 of this tutorial series.

So what do we need to add?

Well, in order to allow multiple connections to our server we need to add something called threads. Threads essentially are the things that allow multitasking on our computers and let us run more than one thing at a time. For example, you can listen to music while writing your essay, there is a thread running the music player and a thread running your word processor and then can perform their own functions separately. So, we can set up a thread for each person who connects to our server which will allow us to have tons of people to connect to our server. If you haven’t the slightest clue what a thread is then unfortunately this tutorial might not work out so well for you, try researching threads online (there are tons of tutorials out there about them) and then come back here.

Threads

Here is some code that will show you how we will be using threads to allow multiple connections:

1
2
3
Socket socket = sSocket.accept();
Thread socketThread = new ThreadClass(socket);
socketThread.start();

We will run this code every time someone connects and this will essentially place their connection in it’s own thread. This thread will handle everything for that one client. Unfortunately we can’t just say “RUN THE SOCKET” and it will suddenly magically receive connections from the client, print them out, and send them to all the other clients. So, we essentially create our own custom thread. Here is some code that will outline how this will work:

//We need to use "implements Runnable to tell Java that this is a thread
 class ClientThread implements Runnable {
 //This run method is what is executed when the thread starts
 public void run()
 {
 //Set up the PrintWriter and BufferedReader here
 while(true) {
 //Get info sent from client
 String clientInput = input.nextLine();
 //Here would would have a for loop that would send the
 //client's message to every other client connected.
 }
 }
 }
Okay, now that you know a little about threads and what we’re going to do with them lets redo the server code.

The New Server Code

import java.net.*;
import java.util.*;
import java.io.*;

public class Server
{
    public static void main(String[] args)
    {
        new Server();
    }

    public Server()
    {
        //We need a try-catch because lots of errors can be thrown
        try {
            ServerSocket sSocket = new ServerSocket(5000);
            System.out.println("Server started at: " + new Date());


            //Loop that runs server functions
            while(true) {
                //Wait for a client to connect
                Socket socket = sSocket.accept();



                //Create a new custom thread to handle the connection
                ClientThread cT = new ClientThread(socket);

                //Start the thread!
                new Thread(cT).start();

            }
        } catch(IOException exception) {
            System.out.println("Error: " + exception);
        }
    }

    //Here we create the ClientThread inner class and have it implement Runnable
    //This means that it can be used as a thread
    class ClientThread implements Runnable
    {
        Socket threadSocket;

        //This constructor will be passed the socket
        public ClientThread(Socket socket)
        {
            //Here we set the socket to a local variable so we can use it later
            threadSocket = socket;
        }

        public void run()
        {
            //All this should look familiar
            try {
                //Create the streams
                PrintWriter output = new PrintWriter(threadSocket.getOutputStream(), true);
                BufferedReader input = new BufferedReader(new InputStreamReader(threadSocket.getInputStream()));

                //Tell the client that he/she has connected
                output.println("You have connected at: " + new Date());

                while (true) {
                    //This will wait until a line of text has been sent
                    String chatInput = input.readLine();
                    System.out.println(chatInput);
                }
            } catch(IOException exception) {
                System.out.println("Error: " + exception);
            }
        }
    }
}

The Explanation

So for this new code you can see that we only have to change the server’s code. You can use the client code from the last tutorial to connect to the server. The main thing now is that, each time a socket connection is accepted, that socket is passed off to a thread which runs the socket. Once that socket has been passed off to the thread the server then waits for a new socket connection.

Defining and Starting a Thread

An application that creates an instance of Thread must provide the code that will run in that thread. There are two ways to do this:

  • Provide a Runnable object. The Runnable interface defines a single method, run, meant to contain the code executed in the thread. The Runnable object is passed to the Thread constructor, as in the HelloRunnable example:
    public class HelloRunnable implements Runnable {
    
        public void run() {
            System.out.println("Hello from a thread!");
        }
    
        public static void main(String args[]) {
            (new Thread(new HelloRunnable())).start();
        }
    
    }
    
  • Subclass Thread. The Thread class itself implements Runnable, though its run method does nothing. An application can subclassThread, providing its own implementation of run, as in the HelloThread example:
    public class HelloThread extends Thread {
    
        public void run() {
            System.out.println("Hello from a thread!");
        }
    
        public static void main(String args[]) {
            (new HelloThread()).start();
        }
    
    }
    

Notice that both examples invoke Thread.start in order to start the new thread.

Which of these idioms should you use? The first idiom, which employs a Runnable object, is more general, because the Runnable object can subclass a class other than Thread. The second idiom is easier to use in simple applications, but is limited by the fact that your task class must be a descendant of Thread. This lesson focuses on the first approach, which separates the Runnable task from the Thread object that executes the task. Not only is this approach more flexible, but it is applicable to the high-level thread management APIs covered later.

The Thread class defines a number of methods useful for thread management. These include static methods, which provide information about, or affect the status of, the thread invoking the method. The other methods are invoked from other threads involved in managing the thread and Thread object. We’ll examine some of these methods in the following sections.

What Is a Socket?

Normally, a server runs on a specific computer and has a socket that is bound to a specific port number. The server just waits, listening to the socket for a client to make a connection request.

On the client-side: The client knows the hostname of the machine on which the server is running and the port number on which the server is listening. To make a connection request, the client tries to rendezvous with the server on the server’s machine and port. The client also needs to identify itself to the server so it binds to a local port number that it will use during this connection. This is usually assigned by the system.

A client's connection requestIf everything goes well, the server accepts the connection. Upon acceptance, the server gets a new socket bound to the same local port and also has its remote endpoint set to the address and port of the client. It needs a new socket so that it can continue to listen to the original socket for connection requests while tending to the needs of the connected client.

The connection is madeOn the client side, if the connection is accepted, a socket is successfully created and the client can use the socket to communicate with the server.

The client and server can now communicate by writing to or reading from their sockets.


Definition:A socket is one endpoint of a two-way communication link between two programs running on the network. A socket is bound to a port number so that the TCP layer can identify the application that data is destined to be sent to.


An endpoint is a combination of an IP address and a port number. Every TCP connection can be uniquely identified by its two endpoints. That way you can have multiple connections between your host and the server.

The java.net package in the Java platform provides a class, Socket, that implements one side of a two-way connection between your Java program and another program on the network. The Socket class sits on top of a platform-dependent implementation, hiding the details of any particular system from your Java program. By using the java.net.Socket class instead of relying on native code, your Java programs can communicate over the network in a platform-independent fashion.

Additionally, java.net includes the ServerSocket class, which implements a socket that servers can use to listen for and accept connections to clients. This lesson shows you how to use the Socket and ServerSocket classes.

If you are trying to connect to the Web, the URL class and related classes (URLConnection, URLEncoder) are probably more appropriate than the socket classes. In fact, URLs are a relatively high-level connection to the Web and use sockets as part of the underlying implementation. See Working with URLs for information about connecting to the Web via URLs.

Network Service Discovery / Bonjour – Client

Last post we set up a Android Network Service Discovery server that will receive text messages from our client. Now we are ready to create the client that we will use to create the text messages and send to the server.

When creating the project choose Sdk version 16, Jelly Bean 4.1.

or

Edit the Manifest first to avoid errors when creating the MainActivity class.

Replace minimum with android:minSdkVersion=”16″

and add

1
2
"true" android:name="android.permission.INTERNET"/>
    "android.permission.ACCESS_WIFI_STATE" />

This will all be done in one file for simplicity.

 import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
 
import org.json.JSONException;
import org.json.JSONObject;
 
import android.app.Activity;
import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.format.Formatter;
import android.util.Log;
import android.view.Menu;
import android.widget.Toast;
 
public class MainActivity extends Activity {
 
 private String SERVICE_NAME = "Client Device";
 private String SERVICE_TYPE = "_letstalk._tcp.";
 
 private InetAddress hostAddress;
 private int hostPort;
 private NsdManager mNsdManager;
 
 private int SocketServerPort = 6000;
 private static final String REQUEST_CONNECT_CLIENT = "request-connect-client";
 
 private static final String TAG = "NSDClient";
 
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.main);
 
 mNsdManager = (NsdManager) getSystemService(Context.NSD_SERVICE);
 mNsdManager.discoverServices(SERVICE_TYPE,
 NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
 }
 
 NsdManager.DiscoveryListener mDiscoveryListener = new NsdManager.DiscoveryListener() {
 
 // Called as soon as service discovery begins.
 @Override
 public void onDiscoveryStarted(String regType) {
 Log.d(TAG, "Service discovery started");
 }
 
 @Override
 public void onServiceFound(NsdServiceInfo service) {
 // A service was found! Do something with it.
 Log.d(TAG, "Service discovery success : " + service);
 Log.d(TAG, "Host = "+ service.getServiceName());
 Log.d(TAG, "port = " + String.valueOf(service.getPort()));
 
 if (!service.getServiceType().equals(SERVICE_TYPE)) {
 // Service type is the string containing the protocol and
 // transport layer for this service.
 Log.d(TAG, "Unknown Service Type: " + service.getServiceType());
 } else if (service.getServiceName().equals(SERVICE_NAME)) {
 // The name of the service tells the user what they'd be
 // connecting to. It could be "Bob's Chat App".
 Log.d(TAG, "Same machine: " + SERVICE_NAME);
 } else {
 Log.d(TAG, "Diff Machine : " + service.getServiceName());
 // connect to the service and obtain serviceInfo
 mNsdManager.resolveService(service, mResolveListener);
 }
 }
 
 @Override
 public void onServiceLost(NsdServiceInfo service) {
 // When the network service is no longer available.
 // Internal bookkeeping code goes here.
 Log.e(TAG, "service lost" + service);
 }
 
 @Override
 public void onDiscoveryStopped(String serviceType) {
 Log.i(TAG, "Discovery stopped: " + serviceType);
 }
 
 @Override
 public void onStartDiscoveryFailed(String serviceType, int errorCode) {
 Log.e(TAG, "Discovery failed: Error code:" + errorCode);
 mNsdManager.stopServiceDiscovery(this);
 }
 
 @Override
 public void onStopDiscoveryFailed(String serviceType, int errorCode) {
 Log.e(TAG, "Discovery failed: Error code:" + errorCode);
 mNsdManager.stopServiceDiscovery(this);
 }
 };
 
 NsdManager.ResolveListener mResolveListener = new NsdManager.ResolveListener() {
 
 @Override
 public void onServiceResolved(NsdServiceInfo serviceInfo) {
 Log.d(TAG, "Resolve Succeeded. " + serviceInfo);
 
 if (serviceInfo.getServiceName().equals(SERVICE_NAME)) {
 Log.d(TAG, "Same IP.");
 return;
 }
 
 // Obtain port and IP
 hostPort = serviceInfo.getPort();
 hostAddress = serviceInfo.getHost();
 
 /* Once the client device resolves the service and obtains
 * server's ip address, connect to the server and send data
 */
 
 connectToHost();
 }
 
 
 @Override
 public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
 // Called when the resolve fails. Use the error code to debug.
 Log.e(TAG, "Resolve failed " + errorCode);
 Log.e(TAG, "serivce = " + serviceInfo);
 }
 };
 
 private void connectToHost() {
 
 if (hostAddress == null) {
 Log.e(TAG, "Host Address is null");
 return;
 }
 
 String ipAddress = getLocalIpAddress();
 JSONObject jsonData = new JSONObject();
 
 try {
 jsonData.put("request", REQUEST_CONNECT_CLIENT);
 jsonData.put("ipAddress", ipAddress);
 } catch (JSONException e) {
 e.printStackTrace();
 Log.e(TAG, "can't put request");
 return;
 }
 
 new SocketServerTask().execute(jsonData);
 }
 
 private String getLocalIpAddress() {
 WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
 String ip = Formatter.formatIpAddress(wm.getConnectionInfo().getIpAddress());
 return ip;
 }
 
 private class SocketServerTask extends AsyncTask<JSONObject, Void, Void> {
 private JSONObject jsonData;
 private boolean success;
 
 @Override
 protected Void doInBackground(JSONObject... params) {
 Socket socket = null;
 DataInputStream dataInputStream = null;
 DataOutputStream dataOutputStream = null;
 jsonData = params[0];
 
 try {
 // Create a new Socket instance and connect to host
 socket = new Socket(hostAddress, SocketServerPort);
 
 dataOutputStream = new DataOutputStream(
 socket.getOutputStream());
 dataInputStream = new DataInputStream(socket.getInputStream());
 
 // transfer JSONObject as String to the server
 dataOutputStream.writeUTF(jsonData.toString());
 Log.i(TAG, "waiting for response from host");
 
 // Thread will wait till server replies
 String response = dataInputStream.readUTF();
 if (response != null && response.equals("Connection Accepted")) {
 success = true;
 } else {
 success = false; 
 }
 
 } catch (IOException e) {
 e.printStackTrace();
 success = false;
 } finally {
 
 // close socket
 if (socket != null) {
 try {
 Log.i(TAG, "closing the socket");
 socket.close();
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 
 // close input stream
 if (dataInputStream != null) {
 try {
 dataInputStream.close();
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 
 // close output stream
 if (dataOutputStream != null) {
 try {
 dataOutputStream.close();
 } catch (IOException e) {
 e.printStackTrace();
 }
 }
 }
 return null;
 }
 
 @Override
 protected void onPostExecute(Void result) {
 if (success) {
 Toast.makeText(MainActivity.this, "Connection Done", Toast.LENGTH_SHORT).show();
 } else {
 Toast.makeText(MainActivity.this, "Unable to connect", Toast.LENGTH_SHORT).show();
 }
 }
 }
 
 protected void onPuase() {
 if (mNsdManager != null) {
 mNsdManager.stopServiceDiscovery(mDiscoveryListener);
 }
 super.onPause();
 }
 
 @Override
 protected void onResume() {
 super.onResume();
 if (mNsdManager != null) {
 mNsdManager.discoverServices(
 SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
 }
 
 }
 
 @Override
 protected void onDestroy() {
 if (mNsdManager != null) {
 mNsdManager.stopServiceDiscovery(mDiscoveryListener);
 }
 super.onDestroy();
 }
 
}

SERVICE_NAME is a constant that we use to set the device name, the Google example doesn’t use these it picks the name for you which is the service name if more than one device has the same service name, which it will, the 2nd device has a (1) added to it and the 3rd would have (2) added to it and so on. Not sure if this is what made the Google version so flaky so I don’t use it, I give the server a name and the client a name, and use that.

SERVICE_TYPE is a custom name you make up (that must conform to a standard). This is the name of your app on the network, it doesn’t have to be the same name as your app, just every device that you want to see each other must use the same one. The service type specifies which protocol and transport layer the application uses. The syntax is “_[protocol]._[transportlayer].” You can name the protocol anything you want but leave the transportlayer the way it is.

Note: If you plan on publishing an app to the app store that uses NSD you should register your protocol to the International Assigned Numbers Authority (IANA). They manage a centralized, authoritative list of service types used by service discovery protocols such as NSD and Bonjour. If you intend to use a new service type, you should reserve it by filling out the IANA Ports and Service registration form.

REQUEST_CONNECT_CLIENT is a constant we use to tell the server what we want to do. At first this will be the only option, but later we will add a display message option.

Now with our tools laid out let’s walk through the logic.

We display our screen with setContentView(R.layout.main);

We create an instance of NsdManager called mNsdManager to use to discover and connect to our server.

We then use NsdManager to discover our service (and server) on the network using our SERVICE_TYPE, we define what type of protocol we are using (for NSD you use NsdManager.PROTOCOL_DNS_SD) and where to go after the service is successfully registered (a interface callback we create called mDiscoveryListener).

Next we define, initialize and implement the DiscoveryListener interface call back. We use this to get all the devices that have registered the service. Since it is an interface we have methods() we must implement (@Override) to receive the NsdServiceInfo for all the servers we discovered.

The methods are:

public void onDiscoveryStarted(String regType) {}
public void onServiceFound(NsdServiceInfo service) {}
public void onServiceLost(NsdServiceInfo service) {}
public void onDiscoveryStopped(String serviceType) {}
public void onStartDiscoveryFailed(String serviceType, int errorCode) {}
public void onStopDiscoveryFailed(String serviceType, int errorCode) {}

The second one is the only one we will really use, the rest are used for logging purposes.

@Override
public void onServiceFound(NsdServiceInfo service) {
    // A service was found! Do something with it.
    Log.d(TAG, "Service discovery success : " + service);
    Log.d(TAG, "Host = "+ service.getServiceName());
    Log.d(TAG, "port = " + String.valueOf(service.getPort()));

    if (!service.getServiceType().equals(SERVICE_TYPE)) {
        // Service type is the string containing the protocol and
        // transport layer for this service.
        Log.d(TAG, "Unknown Service Type: " + service.getServiceType());
    } else if (service.getServiceName().equals(SERVICE_NAME)) {
        // The name of the service tells the user what they'd be
        // connecting to. It could be "Bob's Chat App".
        Log.d(TAG, "Same machine: " + SERVICE_NAME);
    } else {
        Log.d(TAG, "Diff Machine : " + service.getServiceName());
        // connect to the service and obtain serviceInfo
        mNsdManager.resolveService(service, mResolveListener);
    }
}

When any service is found (even if it is not ours we show all the NsdServiceInfo that we discovered and then we point out the service name (which is “Server Device” for what we are looking for) and port number for the service, even if it is not ours (if you have printers on the network or webcams you will see those here too).

Now we start parsing the NsdServiceInfo to find what we want. First we check the SERVICE_TYPE if it is NOT (notice the ! at the beginning of the if statement) _letstalk._tcp. then we log it and keep discovering.

If the SERVICE_TYPE IS _letstalk._tcp. we check the SERVICE_NAME. If the SERVICE_NAME is the same as ours, Client Device, we know we have discovered ourself, so we keep looking.

If the SERVICE_NAME is different than we have found another device that we can connect to. In our case it should be the server. This is where you would create a List if you wanted to make a list of several devices you could connect to, but for simplicity sake if we find our server we immediately resolve the service so we can connect.

To resolve the service we use our instance of NsdManager to start the resolveService() method which takes two parameters the first is the NsdServiceInfo of the device we are wanting to resolve(get IPAddress for), and the second parameter is where to send that information when we get it, which is the ResolveListener interface call back.

Now we define, initialize and implement the ResolveListener interface call back. It has two methods we must implement.

public void onServiceResolved(NsdServiceInfo serviceInfo) {}
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {}

onServiceResolved(NsdServiceInfo serviceInfo) is the one we care about, it gives us the NsdServiceInfo that includes the server IP address this time.

First we double check to make sure we didn’t get our own device’s address by checking the SERVICE_NAME, if we did we return, which goes back to resolving the next device we found.

If we did get the information for our server, we set our local variables that we will use to connect to the server, hostPort is the server port number and hostAddress is the servers IP address.

1
2
3
// Obtain port and IP
hostPort = serviceInfo.getPort();
hostAddress = serviceInfo.getHost();

We then run a custom method(), connectToHost();


private void connectToHost() {

    if (hostAddress == null) {
        Log.e(TAG, "Host Address is null");
        return;
    }

    String ipAddress = getLocalIpAddress();
    JSONObject jsonData = new JSONObject();

    try {
        jsonData.put("request", REQUEST_CONNECT_CLIENT);
        jsonData.put("ipAddress", ipAddress);
    } catch (JSONException e) {
        e.printStackTrace();
        Log.e(TAG, "can't put request");
        return;
    }

    new SocketServerTask().execute(jsonData);
}

We double check to make sure we have a IP address to connect to, if we don’t we jump out of the connectToHost() method so we don’t try to connect and crash.

If we do have a IP Address we run another custom method(), getLocalIpAddress(); Let’s jump there and then come back.

1
2
3
4
5
private String getLocalIpAddress() {
    WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
    String ip = Formatter.formatIpAddress(wm.getConnectionInfo().getIpAddress());
    return ip;
}

First of all when you get any kind of Wifi information you are going to need request permissions from the user which means we will need a permission in the Manifest file.

1
"android.permission.ACCESS_WIFI_STATE" />

Then in the getLocalIpAddress() method we initialize our WifiManager which we will use to get our local IP address, wm.getConnectionInfo().getIpAddress().

Then we take that IP address that is not in a format we can use and run it through the Formatter class which contains a method() that turns the IP Address we retrieved into a String that we can use, Formatter.formatIpAddress().

And we send it back to the connectToHost() method and save it as ipAddress.

Now we are back in the connectToHost() method, we create a JSONObject that we can use to send data to the server.

There are lots of things we could do wrong, so we surround our actions with a try and catch and then start to put data in our JSONObject (jsonData). We use (key, value) pairs to do this so the command ends up looking like this:

1
2
jsonData.put("request", REQUEST_CONNECT_CLIENT);
jsonData.put("ipAddress", ipAddress);

The request “key” contains our REQUEST_CONNECT_CLIENT “value” which is request-connect-client. This will tell the server that we want to connect.

We also send a ipAddress “key” that contains our ipAddress “value” which is the IP address of our device.

We catch our JSONObject errors and print out our stack trace (e.printStackTrace();) if there were any, and jump out of our try with return;

But for fun’s sake, let’s say it worked and then we create a new custom Thread called SocketServerTask() and run it with our jsonData (.execute(jsonData);).

1
new SocketServerTask().execute(jsonData);

makes this:


private class SocketServerTask extends AsyncTask<JSONObject, Void, Void> {
    private JSONObject jsonData;
    private boolean success;

    @Override
    protected Void doInBackground(JSONObject... params) {
        Socket socket = null;
        DataInputStream dataInputStream = null;
        DataOutputStream dataOutputStream = null;
        jsonData = params[0];

        try {
            // Create a new Socket instance and connect to host
            socket = new Socket(hostAddress, SocketServerPort);

            dataOutputStream = new DataOutputStream(
                    socket.getOutputStream());
            dataInputStream = new DataInputStream(socket.getInputStream());

            // transfer JSONObject as String to the server
            dataOutputStream.writeUTF(jsonData.toString());
            Log.i(TAG, "waiting for response from host");

            // Thread will wait till server replies
            String response = dataInputStream.readUTF();
            if (response != null && response.equals("Connection Accepted")) {
                success = true;
            } else {
                success = false;
            }

        } catch (IOException e) {
            e.printStackTrace();
            success = false;
        } finally {

            // close socket
            if (socket != null) {
                try {
                    Log.i(TAG, "closing the socket");
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            // close input stream
            if (dataInputStream != null) {
                try {
                    dataInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            // close output stream
            if (dataOutputStream != null) {
                try {
                    dataOutputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    @Override
    protected void onPostExecute(Void result) {
        if (success) {
            Toast.makeText(MainActivity.this, "Connection Done", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(MainActivity.this, "Unable to connect", Toast.LENGTH_SHORT).show();
        }
    }
}

As you noticed probably quite quickly we are using a AsyncTask class to send the data. AsyncTask is not a bare bones Thread it is actually a “helper” class that uses a Thread to do it’s work

Some detailed, but very good documentation from googles AsyncTask web page.

AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.

An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread. An asynchronous task is defined by 3 generic types, called Params, Progress and Result, and 4 steps, called onPreExecute, doInBackground, onProgressUpdate and onPostExecute.

So in summary, AsyncTask uses a Thread and Handler, so you don’t have to, and posts it’s results to the UI Thread. It is also best for short tasks, like sending data, not listening for connections, like the server does.

private class SocketServerTask extends AsyncTask<JSONObject, Void, Void> {

The three parameters are used like this:

JSONObject is the type of Object we are passing in.
The first Void is the type of value you want to pass back to calculate the progress bar.
The second Void is the type of value you want to send back to the UI Thread to display.

Since we are using the AsyncTask to only do work, because what we are doing is going to happen so fast, we won’t be returning any values for the progress bar, and we don’t need to update the UI Thread because we will know it’s complete when the song starts playing.

We use the boolean value (true or false) success in multiple methods() in the SocketServerTask class so we define the variable here so we can get to it. We set it in our doInBackground() method and we retrieve it in our onPostExecute() method.

When we run the .execute() method we are actually running:

protected Void doInBackground(JSONObject... params) {}

protected means that it can not be called from outside the SocketServerTask class.

Void means we will not be passing any values to the onPostExecute() method. And also means we will be returning null from the doInBackground() method.

JSONObject… The … means array, so the doInBackground() method can take several JSONObjects in an array format.

params is the JSONObject (jsonData) we passed in from the connectToHost() method in the .execute(jsonData); command.

Now we do the same think like we did in our Server Thread:

We initialize our Socket, used to communicate with the server, and set it’s value to null. You may wonder why we initialize it here instead of farther down when we give it an actual value. When we are done using the socket we are going to try to close it from the finally {} section and if we don’t initialize it here we won’t be able to “see” it.

We initialize our DataInputStream, that receives incoming data, and set it to null.

We initialize our DataOutputStream, used to send data to clients, and set it to null.

We create a JSONObject jsonData and set it to the value in index 0 (the first value), which was passed in the params array of JSONObjects.

protected Void doInBackground(JSONObject... params) {}

Now we are about to do some pretty complicated stuff, Android makes it pretty easy, but if everything is not set up just right, it could easily fail, so we use a try and catch.

We create a new socket to talk to the server using the server IP Address we acquired and a port that we manually agreed upon.

Once we have the socket we can use it to create a DataInputStream and a DataOutputStream.

Now we can send data to the server.

dataOutputStream.writeUTF(jsonData.toString());

We take our dataOutputStream and write to the socket in a UTF-8 format which is a way to format text similar to the .xml files used for our layout objects, you will notice in the main.xml file at the beginning:

We convert our jsonData Object to a String, not exactly sure what it would look like, but it will contain two key/value pairs something like request request-connect-client ipAddress 192.168.1.98

then we run

String response = dataInputStream.readUTF();

Which blocks the Thread (which would cause our UI Thread to crash if we were running it there) and we wait until the server responds. Once we have a response, since we know it is only returning one string, not a JSONObject or any other object we can just compare the response to a text string.

if (response != null && response.equals(“Connection Accepted”)) {

We could have used a constant like:

final String CONNECTION_VALUE = “Connection Accepted” and then we could check if:

if (response != null && response.equals(CONNECTION_VALUE)) {

but that is a little more than we need at this point.

If the response was “Connection Accepted” then everything was successful and we can set the boolean value success to true, if the response didn’t equal “Connection Accepted” then we set the boolean value success to false.

Then we have our catch statement if something went wrong while performing I/O Input/Output and we can print our stackTrace if needed.

Finally which means after the Thread has performed it’s task we can try to close our socket, DataInputStream and our DataOutputStream. If we run into issues we catch the IOExceptions and print out our stackTrace for debugging.

After all that we have to return something because doInBackground() must have a return value, we are able to set it to Void like we did in this instance as a work around, but we still have to return something so we return null; which doesn’t go anywhere. Since we are returning Void the parameter we take in on the onPostExecute() method needs to be Void so it doesn’t expect a real value.

1
2
3
4
5
6
7
8
@Override
        protected void onPostExecute(Void result) {
            if (success) {
                Toast.makeText(MainActivity.this, "Connection Done", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(MainActivity.this, "Unable to connect", Toast.LENGTH_SHORT).show();
            }
        }

Then we check our success variable and Toast a message to the screen accordingly.

For a better example of the onPostExecute() method let’s say that we are returning a Bitmap from the doInBackground() method.

This:

private class SocketServerTask extends AsyncTask<JSONObject, Void, Void> {

Would become this:

private class SocketServerTask extends AsyncTask<JSONObject, Void, Bitmap> {

This:

protected Void doInBackground(JSONObject... params) {

Would become this:

protected Bitmap doInBackground(JSONObject... params) {

This:

return null;

Would become this:

return bitmap; //bitmap being a Bitmap Object

This:

protected void onPostExecute(Void result) {

Would become this:

protected void onPostExecute(Bitmap result) {

To finish out this project we define the onPause(), onResume(), onDestroy() methods. These are all protected so you can only run them from this class. They all make sure mNsdManager has been created because if you try to stop a service when it doesn’t exist the program will crash.

onPause() will stop service discovery because if the app is not running there is no need to waste resources discovering things we won’t use (memory leak). Then the default onPause() method will run.

onResume() will continue discovering services, and reconnect to our server in this case, on resuming, after the default onResume() method runs.

onDestroy() will stop service discovery because if the app is not running there is no need to waste resources discovering things we won’t use (memory leak). Then the default onDestroy() method will run.

Here is the main.xml file

    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center"
    android:orientation="vertical" >

Now you can compile and run the server on one device and the client on the other, make sure both devices are on the same network. When you run the client you should see Toasts appear on both devices.

For my next post I will add the ability to send text from the client to the server to this project.