You need to perform simple networking tasks via HTTP, such as downloading a file, and you want to avoid the performance penalty imposed by the more high-level, much larger and more complex Apache HttpClient implementation.
URL url = new URL("http://www.example.com/");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
...
conn.disconnect();
HttpURLConnection inherits from the more generic URLConnection,how does URL know what kind of connection to return? The simple answer is that it depends on the URL’s scheme (such as http). A protocol handler class looks at the scheme and tries to find a matching connection implementation.
URLConnection uses TCP sockets and the standard java.io stream classes. That means I/O is blocking, so remember to never run them on the main UI thread.
For this to work, we have to place a text file containing the update notes somewhere on a web server, download and read the file, and display its text in a message dialog.
The plan is to write an AsyncTask that establishes a connection to an HTTP server via HttpURLConnection and download the file containing the update notes text. We then send this text via a Handler object to our main activity so we can show an AlertDialog with that text. Let’s first look at the MyMovies activity class, which contains the callback for the handler to show the pop-up dialog.
Androidmanifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="example.mymovieswithupdatenotice"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="15" />
<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:anyDensity="true" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/MyMoviesTheme" >
<activity
android:name=".SplashScreen"
android:label="@string/title_activity_main"
android:theme="@style/SplashScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MyMovies" />
</application>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
</manifest>
values/movies.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="movies">
<item>The Shawshank Redemption</item>
......
<item>The Apartment</item>
<item>Gladiator</item>
<item>The Sting</item>
<item>Slumdog Millionaire</item>
</string-array>
</resources>
values/movie_thumbs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="movie_thumbs">
<item>http://ia.media-imdb.com/images/M/MV5BMTg4MDA2MDM5Nl5BMl5BanBnXkFtZTcwOTU5MTQ2Mg@@._V1._CR328,0,1392,1392_SS90_.jpg
</item>
...........
<item>http://ia.media-imdb.com/images/M/MV5BMTk4MDk2NDI5Nl5BMl5BanBnXkFtZTcwMDM3MjcwMg@@._V1._CR120,0,481,481_SS90_.jpg
</item>
</string-array>
</resources>
values/strings.xml
<resources>
<string name="app_name">MyMoviesWithUpdateNotice</string>
<string name="hello_world">Hello world!</string>
<string name="menu_settings">Settings</string>
<string name="title_activity_main">MainActivity</string>
</resources>
values/colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="list_background">#A000</color>
</resources>
values/styles.xml
<resources>
<style name="MyMoviesTheme" parent="@android:style/Theme.Black">
<item name="android:listViewStyle">@style/MyMoviesListView</item>
<item name="android:windowBackground">@drawable/film_bg</item>
</style>
<style name="MyMoviesListView" parent="@android:Widget.ListView">
<item name="android:background">@color/list_background</item>
<item name="android:listSelector">@drawable/list_selector</item>
<item name="android:cacheColorHint">@android:color/transparent</item>
<item name="android:fastScrollEnabled">true</item>
<item name="android:footerDividersEnabled">false</item>
</style>
<style name="SplashScreen" parent="@android:style/Theme.Black">
<item name="android:windowNoTitle">true</item>
</style>
</resources>
layout/splash_screen.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<ImageView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scaleType="fitXY"
android:src="@drawable/splash" />
</merge>
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ImageView android:src="@drawable/title"
android:layout_width="fill_parent"
android:layout_height="75px"
android:scaleType="fitXY" />
<ListView android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</LinearLayout>
movie_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
>
<ImageView
android:id="@+id/movie_icon"
android:layout_width="50dip"
android:layout_height="50dip"
android:scaleType="centerCrop"
/>
<CheckedTextView android:id="@android:id/text1"
android:layout_width="0px"
android:layout_height="fill_parent"
android:layout_weight="0.9"
android:gravity="center_vertical"
android:paddingLeft="6dip"
android:paddingRight="6dip"
android:checkMark="?android:attr/listChoiceIndicatorMultiple"
/>
</LinearLayout>
list_footer.xml
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:text="Back to top"
android:onClick="backToTop"
/>
SplashScreen.class
public class SplashScreen extends Activity {
public static final int SPLASH_TIMEOUT=2000;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.splash_screen);
new Timer().schedule(new TimerTask(){
@Override
public void run() {
// TODO Auto-generated method stub
proceed();
}
}, SPLASH_TIMEOUT);
}
private void proceed(){
if(this.isFinishing())
return;
startActivity(new Intent(SplashScreen.this,MyMovies.class));
}
public boolean onTouchEvent(MotionEvent event){
if(event.getAction()==MotionEvent.ACTION_DOWN)
proceed();
return super.onTouchEvent(event);
}
}
MovieAdapter.class
public class MovieAdapter extends ArrayAdapter<String> {
private HashMap<Integer,Boolean> movieCollection=new HashMap<Integer,Boolean>();
private String[] moiveIconUrls;
public MovieAdapter(Context context) {
super(context,R.layout.movie_item,android.R.id.text1,context.getResources().getStringArray(R.array.movies));
// TODO Auto-generated constructor stub
moiveIconUrls=context.getResources().getStringArray(R.array.movie_thumbs);
}
public void toggleMovie(int position){
if(!isInCollection(position))
movieCollection.put(position, true);
movieCollection.put(position, false);
}
public boolean isInCollection(int position){
return movieCollection.get(position)==Boolean.TRUE;
}
public View getView(int position,View convertView,ViewGroup parent){
View listItem=super.getView(position, convertView, parent);
CheckedTextView checkMark=(CheckedTextView)listItem.findViewById(android.R.id.text1);
checkMark.setChecked(isInCollection(position));
ImageView imageView=(ImageView)listItem.findViewById(R.id.movie_icon);
imageView.setImageDrawable(null);
imageView.setTag(position); MyMovies
String imageUrl=this.moiveIconUrls[position];
new DownloadTask(position,imageView).execute(imageUrl);
return listItem;
}
}
DownloadTask.class
public class DownloadTask extends AsyncTask<String, Void, Bitmap> {
private int position;
private ImageView imageView;
private Drawable placeHolder;
public DownloadTask(int position,ImageView imageView){
this.position=position;
this.imageView=imageView;
Resources resources=imageView.getContext().getResources();
placeHolder=resources.getDrawable(android.R.drawable.gallery_thumb);
}
protected void onPreExecute(){
imageView.setImageDrawable(placeHolder);
}
@Override
protected Bitmap doInBackground(String... inputUrls) {
// TODO Auto-generated method stub
try{
URL url=new URL(inputUrls[0]);
return BitmapFactory.decodeStream(url.openStream());
}catch(Exception e){
e.printStackTrace();
return null;
}
}
protected void onPostExecute(Bitmap result){
int forPosition=(Integer)imageView.getTag();
if(forPosition==this.position)
this.imageView.setImageBitmap(result);
}
}
MyMovies.class
public class MyMovies extends ListActivity implements Callback {
private MovieAdapter adapter;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ListView listView=getListView();
Button backToTop=(Button)getLayoutInflater().inflate(R.layout.list_footer, null);
backToTop.setCompoundDrawablesWithIntrinsicBounds(getResources().getDrawable(android.R.drawable.ic_menu_upload),
null, null, null);
listView.addFooterView(backToTop);
this.adapter=new MovieAdapter(this);
listView.setAdapter(adapter);
listView.setItemsCanFocus(false);
new UpdateNoticeTask(new Handler(this)).execute();//starts new download task
}
public void backToTop(View view) {
getListView().setSelection(0);
}
protected void onListItemClick(ListView l, View v, int position, long id) {
this.adapter.toggleMovie(position);
this.adapter.notifyDataSetInvalidated();
}
@Override
public boolean handleMessage(Message msg) {
// TODO Auto-generated method stub
String updateNotice=msg.getData().getString("text");//reads update text
AlertDialog.Builder dialog=new AlertDialog.Builder(this);
dialog.setTitle("What's new");
dialog.setMessage(updateNotice);//set update text
dialog.setIcon(android.R.drawable.ic_dialog_info);
dialog.setPositiveButton(getString(android.R.string.ok), new OnClickListener(){
@Override
public void onClick(DialogInterface dialog, int which) {
// TODO Auto-generated method stub
dialog.dismiss();
}
});
dialog.show();
return false;
}
}
UpdateNoticeTask.class
public class UpdateNoticeTask extends AsyncTask<Void,Void,String> {
private static final String UPDATE_URL =
"http://android-in-practice.googlecode.com/files/update_notice.txt";
private HttpURLConnection connection;
private Handler handler;
public UpdateNoticeTask(Handler handler){
this.handler=handler;
}
@Override
protected String doInBackground(Void... params) {
// TODO Auto-generated method stub
try{
URL url=new URL(UPDATE_URL);
//get instance of HttpURLConnection
connection=(HttpURLConnection)url.openConnection();
//configure request
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "text/plain");
connection.setReadTimeout(10);
connection.setConnectTimeout(10);
//establish connection
connection.connect();
int statusCode=connection.getResponseCode();
if(statusCode!=HttpURLConnection.HTTP_OK)
//handl non-200 reply
return "error: Failed getting update notes";
return readTextFromServer();//reand text from response
}catch(Exception e){
return "Error: " + e.getMessage();
}finally{
if(connection!=null)
//close connection
connection.disconnect();
}
}
private String readTextFromServer() throws IOException{
InputStreamReader isr=new InputStreamReader(connection.getInputStream());
BufferedReader br=new BufferedReader(isr);
StringBuilder sb=new StringBuilder();
String line=br.readLine();
while(line!=null){
sb.append(line+"\n");
line=br.readLine();
}
return sb.toString();
}
//pass retrive text to activity
protected void onPostExecute(String updateNotice){
Message message=new Message();
Bundle data=new Bundle();
data.putString("text", updateNotice);
message.setData(data);
handler.sendMessage(message);
}
}
The request, response, and the mechanisms to send and receive them are merged into a single class, often leaving you wondering which methods to use to process which part of this triplet.
It’s not a beaming example of good object-oriented class design.
HttpURLConnection in Apache Harmony has bugs—serious bugs. To summarize, HttpURLConnection is a simply structured, but low-level way of doing
HTTP messaging. A few negative aspects about it stand out:
Its clunky interface makes it difficult to use.
Its monolithic design and lack of object-orientation impede testability and configuration/ customization
It suffers from bugs that can turn out to be show stoppers.
For simple tasks such as the file download shown here, it’s fine and comes with the least overhead (it doesn’t take a sledgehammer to crack a nut). But if you want to do
more complex things such as request interception, connection pooling, or multipart file uploads, then don’t bother with it.