Improved asset moving time by an order of magnitude (3s vs. 25s on my device)
--- 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);
}
}
}