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.