Improved asset moving time by an order of magnitude (3s vs. 25s on my device)
authorMedo <smaxein@googlemail.com>
Thu, 05 Jul 2012 22:22:48 +0200
changeset 7318 a446eafcddeb
parent 7316 f7b49b2c5d84
child 7320 e704706008d4
Improved asset moving time by an order of magnitude (3s vs. 25s on my device)
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadAssets.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java
project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Utils.java
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadAssets.java	Thu Jul 05 00:33:24 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadAssets.java	Thu Jul 05 22:22:48 2012 +0200
@@ -1,8 +1,7 @@
 package org.hedgewars.hedgeroid.Downloader;
 
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -15,86 +14,69 @@
 import org.hedgewars.hedgeroid.Datastructures.Team;
 import org.hedgewars.hedgeroid.Datastructures.Weapon;
 
-import android.content.Context;
 import android.content.res.AssetManager;
 import android.os.AsyncTask;
 import android.util.Log;
 
 public class DownloadAssets extends AsyncTask<Object, Long, Long>{
-	
-	private MainActivity act;
-	private static byte[] buffer = null;
+	private final MainActivity act;
 	
 	public DownloadAssets(MainActivity _act){
 		act = _act;
 	}
 	
-	public static Long copyFileOrDir(Context c, String path) {
-	    AssetManager assetManager = c.getAssets();
-	    String assets[] = null;
-	    try {
-	        assets = assetManager.list(path);
-	        if (assets.length == 0) {
-	            return DownloadAssets.copyFile(c, path);
-	        } else {
-	            String fullPath = Utils.getCachePath(c) + path;
-	            File dir = new File(fullPath);
-	            if (!dir.exists())
-	                dir.mkdir();
-	            for (int i = 0; i < assets.length; ++i) {
-	                Long result = DownloadAssets.copyFileOrDir(c, path + "/" + assets[i]);
-	                if(result > 0) return 1l;
-	            }
-	        }
-	    } catch (IOException ex) {
-	    	ex.printStackTrace();
-	        Log.e("tag", "I/O Exception", ex);
-	        return 1l;
-	    }
-	    return 0l;
+	private static void copyFileOrDir(AssetManager assetManager, File target, String assetPath) throws IOException {
+		try {
+			copyFile(assetManager, target, assetPath);
+		} catch(FileNotFoundException e) {
+			/*
+			 * I can't find a better way to figure out whether an asset entry is
+			 * a file or a directory. Checking if assetManager.list(assetPath)
+			 * is empty is a bit cleaner, but SLOW.
+			 */
+			if (!target.isDirectory() && !target.mkdir()) {
+				throw new IOException("Unable to create directory "+target);
+			}
+			for (String asset : assetManager.list(assetPath)) {
+				DownloadAssets.copyFileOrDir(assetManager, new File(target, asset), assetPath + "/" + asset);
+			}
+		}
 	}
 	
-	private static Long copyFile(Context c, String filename) {
-	    AssetManager assetManager = c.getAssets();
-
-	    InputStream in = null;
-	    OutputStream out = null;
-	    try {
-	        in = assetManager.open(filename);
-	        in = new BufferedInputStream(in, 8192);
-	        
-	        String newFileName = Utils.getCachePath(c) + filename;
-	        out = new FileOutputStream(newFileName);
-	        out = new BufferedOutputStream(out, 8192);
-
-	        int read;
-	        while ((read = in.read(buffer)) != -1) {
-	            out.write(buffer, 0, read);
-	        }
-	        in.close();
-	        in = null;
-	        out.flush();
-	        out.close();
-	        out = null;
-	    } catch (Exception e) {
-	    	e.printStackTrace();
-	        Log.e("tag", e.getMessage());
-	        return 1l;
-	    }
-	    return 0l;
-
-	}
-
-	protected Long doInBackground(Object... params) {
-		Utils.resRawToFilesDir(act,R.array.schemes, Scheme.DIRECTORY_SCHEME);
-		Utils.resRawToFilesDir(act, R.array.weapons, Weapon.DIRECTORY_WEAPON);
-		Utils.resRawToFilesDir(act, R.array.teams, Team.DIRECTORY_TEAMS);
-		buffer = new byte[8192];//allocate the buffer
-		return DownloadAssets.copyFileOrDir(act, "Data");
+	private static void copyFile(AssetManager assetManager, File target, String assetPath) throws IOException {
+		InputStream is = null;
+		OutputStream os = null;
+		byte[] buffer = new byte[8192];
+		try {
+			is = assetManager.open(assetPath);
+			os = new FileOutputStream(target);
+			int size;
+			while((size=is.read(buffer)) != -1) {
+				os.write(buffer, 0, size);
+			}
+			os.close(); // Important to close this non-quietly, in case of exceptions when flushing
+		} finally {
+			Utils.closeQuietly(is);
+			Utils.closeQuietly(os);
+		}
 	}
 	
+	@Override
+	protected Long doInBackground(Object... params) {
+		try {
+			Utils.resRawToFilesDir(act, R.array.schemes, Scheme.DIRECTORY_SCHEME);
+			Utils.resRawToFilesDir(act, R.array.weapons, Weapon.DIRECTORY_WEAPON);
+			Utils.resRawToFilesDir(act, R.array.teams, Team.DIRECTORY_TEAMS);
+			DownloadAssets.copyFileOrDir(act.getAssets(), Utils.getDataPathFile(act), "Data");
+			return 0l;
+		} catch(IOException e) {
+			Log.e("org.hedgewars.hedgeroid", e.getMessage(), e);
+			return 1l;
+		}
+	}
+	
+	@Override
 	protected void onPostExecute(Long result){
 		act.onAssetsDownloaded(result == 0);
-		buffer = null;
 	}
 }
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java	Thu Jul 05 00:33:24 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java	Thu Jul 05 22:22:48 2012 +0200
@@ -50,11 +50,9 @@
 		downloader.setOnClickListener(downloadClicker);
 		startGame.setOnClickListener(startGameClicker);
 
-
-		String cacheDir = Utils.getCachePath(this);
-		if(cacheDir == null){
+		if(!Utils.isDataPathAvailable()){
 			showDialog(0);
-		}else{
+		} else {
 			int versionCode = 0;
 			try {
 				versionCode = this.getPackageManager().getPackageInfo(this.getPackageName(), 0).versionCode;
--- a/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Utils.java	Thu Jul 05 00:33:24 2012 +0200
+++ b/project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Utils.java	Thu Jul 05 22:22:48 2012 +0200
@@ -19,59 +19,85 @@
 
 package org.hedgewars.hedgeroid;
 
-import java.io.BufferedOutputStream;
+import java.io.Closeable;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.List;
 
 import android.annotation.TargetApi;
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.os.Build;
 import android.os.Environment;
 import android.util.Log;
 
 public class Utils {
+	private static final String ROOT_DIR = "Data";
+	private static final String TAG = "org.hedgewars.hedgeroid";
 
-	private static final String ROOT_DIR = "Data/";
-
+	/**
+	 * @return true if the data path is currently available. However, it can vanish at any time so
+	 * normally you should just try to use it and rely on the exceptions.
+	 */
+	public static boolean isDataPathAvailable() {
+		return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
+	}
+	
 	/**
 	 * get the path to which we should download all the data files
 	 * @param c context 
-	 * @return absolute path
+	 * @return The directory
+	 * @throws FileNotFoundException if external storage is not avaliable at the moment
 	 */
-	public static String getCachePath(Context c){
+	public static File getCachePath(Context c) throws FileNotFoundException {
+		File cachePath = null;
 		if(Build.VERSION.SDK_INT < 8){//8 == Build.VERSION_CODES.FROYO
-			return PreFroyoSDCardDir.getDownloadPath(c) + '/';
-		}else{
-			return FroyoSDCardDir.getDownloadPath(c) + '/';
+			cachePath = PreFroyoSDCardDir.getDownloadPath(c);
+		} else {
+			cachePath = FroyoSDCardDir.getDownloadPath(c);
+		}
+		if(cachePath==null) {
+			throw new FileNotFoundException("External storage is currently unavailable");
+		} else {
+			return cachePath;
 		}
 	}
 
-	public static String getDataPath(Context c){
-		return getCachePath(c) + ROOT_DIR;
+	public static File getDataPathFile(Context c) throws FileNotFoundException {
+		return new File(getCachePath(c), ROOT_DIR);
+	}
+	
+	// TODO Several callers are unaware that this may fail, so it throws an RTE now.
+	// Should be handled better though.
+	@Deprecated
+	public static String getDataPath(Context c) {
+		try {
+			return getDataPathFile(c).getAbsolutePath()+"/";
+		} catch(FileNotFoundException e) {
+			throw new RuntimeException(e);
+		}
 	}
 
 	@TargetApi(8)
-	static class FroyoSDCardDir{
-		public static String getDownloadPath(Context c){
-			File f =  c.getExternalCacheDir();
-			if(f != null){
-				return f.getAbsolutePath();
-			}else{
-				return null;
-			}	
+	private static class FroyoSDCardDir{
+		public static File getDownloadPath(Context c){
+			return c.getExternalCacheDir();
 		}
 	}
 
-	static class PreFroyoSDCardDir{
-		public static String getDownloadPath(Context c){
+	private static class PreFroyoSDCardDir{
+		public static File getDownloadPath(Context c){
 			if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
-				if(Environment.getExternalStorageDirectory() != null)
-					return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Hedgewars/";				
+				File extStorageDir = Environment.getExternalStorageDirectory();
+				if(extStorageDir != null) {
+					return new File(extStorageDir, "Hedgewars");
+				}
 			}
 			return null;
 		}
@@ -166,58 +192,58 @@
 	}
 
 	/**
+	 * Close a resource (possibly null), ignoring any IOException.
+	 */
+	public static void closeQuietly(Closeable c) {
+		if(c!=null) {
+			try {
+				c.close();
+			} catch(IOException e) {
+				Log.w(TAG, e);
+			}
+		}
+	}
+	
+	/**
+	 * Write all data from the input stream to the file, creating or overwriting it.
+	 * The input stream will be closed.
+	 * 
+	 * @throws IOException
+	 */
+	public static void writeStreamToFile(InputStream is, File file) throws IOException {
+		OutputStream os = null;
+		byte[] buffer = new byte[8192];
+		try {
+			os = new FileOutputStream(file);
+			int size;
+			while((size=is.read(buffer)) != -1) {
+				os.write(buffer, 0, size);
+			}
+			os.close(); // Important to close this non-quietly, in case of exceptions when flushing
+		} finally {
+			Utils.closeQuietly(is);
+			Utils.closeQuietly(os);
+		}
+	}
+	
+	/**
 	 * Moves resources pointed to by sourceResId (from @res/raw/) to the app's private data directory
 	 * @param c
 	 * @param sourceResId
 	 * @param directory
 	 */
-	public static void resRawToFilesDir(Context c, int sourceResId, String directory){
-		byte[] buffer = new byte[1024];
-		InputStream bis = null;
-		BufferedOutputStream bos = null;
-		File schemesDirFile = new File(c.getFilesDir().getAbsolutePath() + '/' + directory);
-		schemesDirFile.mkdirs();
-		String schemesDirPath = schemesDirFile.getAbsolutePath() + '/';
+	public static void resRawToFilesDir(Context c, int sourceResId, String directory) throws IOException {
+		File targetDir = new File(c.getFilesDir(), directory);
+		targetDir.mkdirs();
 
 		//Get an array with the resource files ID
-		TypedArray ta = c.getResources().obtainTypedArray(sourceResId);
-		int[] resIds = new int[ta.length()];
+		Resources resources = c.getResources();
+		TypedArray ta = resources.obtainTypedArray(sourceResId);
 		for(int i = 0; i < ta.length(); i++){
-			resIds[i] = ta.getResourceId(i, 0);
-		}
-
-		for(int id : resIds){
-			String fileName = c.getResources().getResourceEntryName(id);
-			File f = new File(schemesDirPath + fileName);
-			try {
-				if(!f.createNewFile()){
-					f.delete();
-					f.createNewFile();
-				}
-
-				bis = c.getResources().openRawResource(id);
-				bos = new BufferedOutputStream(new FileOutputStream(f), 1024);
-				int read = 0;
-				while((read = bis.read(buffer)) != -1){
-					bos.write(buffer, 0, read);
-				}
-
-			} catch (IOException e) {
-				e.printStackTrace();
-			}finally{
-				if(bis != null)
-					try { 
-						bis.close();
-					} catch (IOException e) {
-						e.printStackTrace();
-					}
-					if(bos != null)
-						try {
-							bos.close();
-						} catch (IOException e) {
-							e.printStackTrace();
-						}
-			}
+			int resId =  ta.getResourceId(i, 0);
+			String fileName = resources.getResourceEntryName(resId);
+			File f = new File(targetDir, fileName);
+			writeStreamToFile(resources.openRawResource(resId), f);
 		}
 	}
 }