Java tutorial
/******************************************************************************* * Copyright 2012 Niklas Ekman * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *******************************************************************************/ package yet.another.hackernews.reader.functions; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; import java.util.Calendar; import org.apache.commons.io.IOUtils; import org.apache.commons.io.LineIterator; import android.content.Context; import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Handler; /** * Downloads text from an internet site. This is perfect to use in conjuction * with a server that echoes JSON arrays. Class features listener, cache and * progress updates (if server sends content-length header).<br> * <br> * An id can be attached to each instance to identify wich DownloadText has * finished.<br> * <br> * If class gives you an error you can identify the error by using a switch and * the public error constants. * * * @author Niklas Ekman * @since 11 FEB 2012 * */ public class DownloadText extends AsyncTask<String, Integer, String> { public static final String CACHE = "CACHE"; public static final String CACHE_LAST_DATE = "DATE"; public static final String CACHE_LAST_TIME = "TIME"; /** * Called when download text is finished. With functions onFinished, * onError, onCancel and onProgressUpdate. * * @author Niklas Ekman * */ public interface OnDownloadTextListener { /** * Called when download text has finished. * * @param text * - Results from download */ public void onFinished(int id, String text); /** * Called if an error occurs. */ public void onError(int error); /** * Called when download text has ben cancelled. */ public void onCancel(); } // Error constants public static final int MALFORMED_URL_ERROR = -1; public static final int IOEXCEPTION_ERROR = -2; // Charset used for stream private Charset charset; // Listener for results private OnDownloadTextListener listener; // ID of instance private int id; // Buffer size for stream private int bufferSize; private boolean useCache; private final Handler handler; private final Context context; private boolean forceUpdate; /** * DownloadImage constructor. * * @param useCache * - Do you want to soft cache results? */ public DownloadText(Context context) { this.handler = new Handler(); this.context = context; // Defaults charset = Charset.forName("UTF-8"); id = 0; bufferSize = 8192; useCache = true; forceUpdate = false; } /** * Force update dispite cache * * @param force */ public void forceUpdate(boolean force) { this.forceUpdate = force; } public void setUseCache(boolean useCache) { this.useCache = useCache; } /** * Attach an interface to be able to track statuses. * * @param listener */ public void setOnDownloadTextListener(OnDownloadTextListener listener) { this.listener = listener; } /** * Set charset used for stream. Charset defaults UTF8. * * @param charset * charset to use for connection stream. * @return returns false if UnsupportedCharsetException is catched. If * false, default will be used. */ public boolean setCharset(String charset) { try { this.charset = Charset.forName(charset); } catch (UnsupportedCharsetException e) { this.charset = Charset.forName("UTF-8"); return false; } return true; } /** * Sets buffered readers buffer size. Good to estimate and set. Defaults * 8192. * * @param bufferSize */ public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; } @Override protected String doInBackground(String... urlAdress) { /* * Core of the class that does the background task. */ id = urlAdress[0].hashCode(); // If we want to use the cache, try and get it first String cacheResults = null; if (useCache) { cacheResults = HardCache.getString(id, context); if (cacheResults != null) { if (!forceUpdate && !Cache.doUpdate(context, id)) { return cacheResults; } } } // Set target url URL url; try { url = new URL(urlAdress[0]); } catch (MalformedURLException e) { // If listener is attached, report error. if (listener != null) { handler.post(new Runnable() { @Override public void run() { listener.onError(MALFORMED_URL_ERROR); } }); } return cacheResults; } // Stream to URL adress BufferedReader bufferedReader = null; LineIterator line = null; // Results, size set to 100 chars final StringBuffer textResults = new StringBuffer(0); try { // Open stream bufferedReader = new BufferedReader(new InputStreamReader(url.openStream(), charset), bufferSize); // Download text line = IOUtils.lineIterator(bufferedReader); while (line.hasNext()) { if (this.isCancelled()) { break; } textResults.append(line.nextLine()); } } catch (IOException e) { if (textResults.length() > 0) { textResults.delete(0, textResults.length()); } if (cacheResults != null) { textResults.append(cacheResults); } if (listener != null) { handler.post(new Runnable() { @Override public void run() { listener.onError(IOEXCEPTION_ERROR); } }); } } finally { // Close reader LineIterator.closeQuietly(line); IOUtils.closeQuietly(bufferedReader); } if (textResults.length() <= 0) { return null; } // If use cache, put it in cache if (useCache && (!textResults.toString().equals(cacheResults))) { HardCache.putString(id, textResults.toString(), context); Cache.saveUpdate(context, id); } return textResults.toString(); } @Override protected void onPostExecute(String result) { /* * Called when background task is finished. Gives a result as argument */ super.onPostExecute(result); // If we have a listener attached and no error occured, report string. if (listener != null) { listener.onFinished(id, result); } } @Override protected void onCancelled() { super.onCancelled(); /* * Called if class is canceled. */ // If we have a listener attached, report cancel. if (listener != null) { listener.onCancel(); } } private static class HardCache { public static void putString(int key, String text, Context context) { FileOutputStream fos = null; try { fos = context.openFileOutput("" + key, Context.MODE_PRIVATE); fos.write(text.getBytes()); fos.flush(); fos.close(); } catch (FileNotFoundException e) { } catch (IOException e) { } finally { try { if (fos != null) { fos.close(); } } catch (IOException e1) { } } } public static String getString(int key, Context context) { if (!context.getFileStreamPath("" + key).exists()) { return null; } FileInputStream fis = null; ByteBuffer buf = null; try { fis = context.openFileInput("" + key); buf = ByteBuffer.allocate(fis.available()); fis.read(buf.array()); } catch (FileNotFoundException e) { } catch (IOException e) { } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { } } } if (buf == null) { return null; } return new String(buf.array()); } } private static class Cache { private static final long INTERVAL = 15 * 60 * 1000; private static long getTime() { Calendar cal = Calendar.getInstance(); return cal.getTimeInMillis(); } private static int getDate() { Calendar cal = Calendar.getInstance(); final int year = cal.get(Calendar.YEAR) * 10000; final int month = cal.get(Calendar.MONTH) * 100; final int day = cal.get(Calendar.DAY_OF_MONTH); return year + month + day; } public static boolean doUpdate(Context context, int key) { SharedPreferences prefs = context.getSharedPreferences(CACHE, Context.MODE_PRIVATE); final int lastUpdateDate = prefs.getInt(CACHE_LAST_DATE + key, getDate()); final long lastUpdateTime = prefs.getLong(CACHE_LAST_TIME + key, getTime()); if (getDate() > lastUpdateDate || (getTime() - INTERVAL) > lastUpdateTime) { return true; } return false; } public static void saveUpdate(Context context, int key) { SharedPreferences prefs = context.getSharedPreferences(CACHE, Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putInt(CACHE_LAST_DATE + key, getDate()); editor.putLong(CACHE_LAST_TIME + key, getTime()); editor.commit(); } } }