# HG changeset patch # User Medo # Date 1341519768 -7200 # Node ID a446eafcddebc1aeddaa2db7e5de25b6eb47e572 # Parent f7b49b2c5d842e78a4d16942946b42be11360aa9 Improved asset moving time by an order of magnitude (3s vs. 25s on my device) diff -r f7b49b2c5d84 -r a446eafcddeb project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/Downloader/DownloadAssets.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{ - - 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; } } diff -r f7b49b2c5d84 -r a446eafcddeb project_files/Android-build/SDL-android-project/src/org/hedgewars/hedgeroid/MainActivity.java --- 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; diff -r f7b49b2c5d84 -r a446eafcddeb 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/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); } } }