How to optimize External Storage file search on Android - android

Requirement
Scan all folders and files on external storage to find all apk files and for each apk file, get info and display progress.
How I did it so far
My initial thought is that this is a simple thing to achieve. But well... even it it sort of works, it does not perform as expected.
I use an AsyncTask to perform the scanning in background. Here is how I do:
private ArrayList<String> sdcardAppsPath;
protected class ScanTask extends AsyncTask<Context, Scanner, ArrayList<Apk>> {
private ArrayList<Apk> sdcardApps;
#Override
protected ArrayList<App> doInBackground(Context... params) {
visitAllDirsAndFiles(Environment.getExternalStorageDirectory());
for (String path : sdcardAppsPath) {
//get info about apk file and
publishProgress(values) //show in some textviews number of files scanned, total files
sdcardApps.add(currentScannedItemFoundInfo);
}
return sdcardApps
}
#Override
protected void onProgressUpdate(Scanner... values) {
super.onProgressUpdate(values);
// update some textView texts based on values
}
}
And
// Process all files and directories under dir
public void visitAllDirsAndFiles(File dir) {
if (dir != null) {
if (dir.getAbsoluteFile().toString().endsWith(".apk")) {
sdcardAppsPath.add(dir.getAbsoluteFile().toString());
}
if (dir.isDirectory()) {
String[] children = dir.list();
if (children != null) {
for (int i = 0; i < children.length; i++) {
visitAllDirsAndFiles(new File(dir, children[i]));
}
}
}
}
}
As you can see after vistitAllDirsAndFiles I get an ArrayList<String> with all apk files I need, so I can check for each what I want. The downside is that it might take a lot of time to build this sdcardAppsPath if there are many files on the card and the app shows no progress while the paths are being found.
What I want to achieve
I want to be able in the doInBackground to somehow for each found file, get the info I need, update progress and then continue with scanning. Any ideas how to update my code to work like this ?

I would think that you would want to call publishProgress inside your visitAllDirsAndFiles method if you want to update progress while searching for files, instead of iterating through the list afterwards and calling it then...

Related

External SD card gets corrupted on removal

I'm using the libaums library (version 0.7.0) for reading SD cards via a USB card reader.
The issue I'm having is that if I don't go into the phone storage options and choose to unmount the SD card before removing it, it always becomes corrupted when I take it out, even if I don't actually write anything to it (Windows complains about it when inserted in a Windows machine). Calling device.init() is enough to trigger the problem.
I wonder if I'm perhaps using the library incorrectly or if nothing else, there is a way to unmount the USB card reader automatically from my app once I'm finished with it? As I understand it calling device.close() SHOULD be enough to flush data and secure the card for removal.
This is the code I'm using for accessing the card:
private class FindDevicesTask extends AsyncTask<Integer, Integer, String> {
private WeakReference<Context> mContext;
FindDevicesTask (Context context){
mContext = new WeakReference<>(context);
}
#Override
protected String doInBackground(Integer... dummy) {
Context ctx = mContext.get();
if (ctx == null)
return null; // Context expired
UsbMassStorageDevice[] devices = UsbMassStorageDevice.getMassStorageDevices(ctx);
for (final UsbMassStorageDevice device : devices) {
// If we don't have permission, request it and leave this function (if we get
// permission the function will be called again)
if (!mUsbManager.hasPermission(device.getUsbDevice())) {
mUsbManager.requestPermission(device.getUsbDevice(), mPermissionIntent);
return null;
}
try {
device.init();
// Do work
} catch (IOException e) {
return e.getMessage();
} finally {
device.close();
}
}
return null;
}
#Override
protected void onPostExecute(String result) {
if (result == null || result.isEmpty())
return;
Context ctx = mContext.get();
if (ctx != null)
displayString(result, "USB connection error", ctx);
}
};
Started by calling new FindDevicesTask(context).execute();
I just wanted to report that this issue was not with the libaums library but rather Android itself. My device automatically configures SD cards it sees to work with Android (it adds some system folders and files). When this happens it does something that makes Windows not like the SD card anymore, although it's by no means corrupt (Windows can read it no problem).

Collection was modified in a foreach loop

I'm trying to get notifications from a bluetooth device sending records, and periodically update the UI to display the records. The while loop below sits in its own thread to handle UI updates while the rest of the module takes care of other tasks. gattCallback is an instance of a BluetoothGattCallback class that adds to a list of received records and returns that list when getHistory() is called.
My problem is that when I hit the foreach line, after so many iterations I get an error:
System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
As far as I can tell, history isn't being updated here or anywhere else in my code so I'm confused by the error. I specifically retrieve a copy of the record history through getHistory() to avoid modifying it during the foreach. Can anyone suggest what might be causing it or any tips to find out?
It might be relevant that this has only caused issues since switching to a Moto E4 on Android 7.1.1 from a Moto G Play on Android 6.0.1.
// Periodically check to see what needs updating
while (!finishedDisplayThread)
{
// See if there are any new records to display
int count;
List<Record> history = gattCallback.getHistory();
if (history == null)
{
count = 0;
}
else
{
count = history.Count;
}
// Only update the display if it has changed
if(count != prevCount)
{
prevCount = count;
List<string> recordList = new List<string>();
if (history == null)
{
recordList = new List<string>();
recordList.Add("No history.");
}
else
{
foreach (Record record in history)
{
recordList.Add(record.ToRow());
}
}
//Update the display
RunOnUiThread(() =>
{
ListAdapter = new ArrayAdapter<string>(this,
Resource.Layout.ListItemLayout,
recordList);
recordCountText.Text = "" + count;
});
}
Thread.Sleep(100);
}
I specifically retrieve a copy of the record history through getHistory() to avoid modifying it during the foreach.
Are you certain that you're getting a copy? Imagine this implementation:
public List<Record> getHistory() {
return history;
}
This will not return a copy of history, but a reference directly to history itself. If two different places call this method, any changes to one of the returned values will affect the other returned value.
If you want a copy, you have to do something like this:
public List<Record> getHistory() {
return new ArrayList<>(history);
}

How to prevent app from being cached in android

After some research I found that Android likes to cache some parts of an app while installing to improve the performance while runtime.
Is there a way to prevent Android from caching things from my app?
I am sharing my app via my website and users install and update the app manually. As soon as I update my app some Activities and Code-parts seems to be cached on their devices.
You could delete your cache everytime before you update or before you close you application.
Code to clear the cache:
public static void trimCache(Context context) {
try {
File dir = context.getCacheDir();
if (dir != null && dir.isDirectory()) {
deleteDir(dir);
}
} catch (Exception e) {
// TODO: handle exception
}
}
public static boolean deleteDir(File dir) {
if (dir != null && dir.isDirectory()) {
String[] children = dir.list();
for (int i = 0; i < children.length; i++) {
boolean success = deleteDir(new File(dir, children[i]));
if (!success) {
return false;
}
}
}
// The directory is now empty so delete it
return dir.delete();
}
Call Trimcache when you want to clear the cache (before you update perhaps? or override the onStop() method, to clear the cache when the application is going to close.
The way I see you should also have a cache problem when you're NOT updating your app.
If you want to force a server fetch when you upgrade your app you could store a boolean in Shared Preferences, using a key associated with the app version. So when you install version 1 you fetch and make putsBoolean(version, true) where version == "1". When version 2 is installed you'll find a false, which will trigger a fetch, followed by setting putsBoolean(version, true) - but in this case version == "2".
If you are reusing information that have a different meaning when you update, the solution is storing the information in a different part of the cache, so it's not sent to an activity that is not prepared to display it.
Otherwise the solution by #jordy-dieltjens seems a good one.

Tracking changes on /proc/uid_stat/myAppUID

Does someone know is there a way I can get notified on file change in /proc/uid_stat/myAppUID folder?
I want to track data usage of my app. The file is there and when I read it manually using BufferedReader, I get the data traffic.
I tried using FileObserver class and also RecursiveFileObserver but I don't get any callback when data usage change. My guess is that it doesn't work on virtual file system. I would like to get notified from linux when these files changes, because looping constantly through files is not a valid option for me.
Here is the code I used:
path = "/proc/uid_stat/"+getApplicationInfo().uid;
observer = new FileObserver(path) {
#Override
public void onEvent(int event, String file) {
Toast.makeText(getApplicationContext(), file + " was changed!", Toast.LENGTH_LONG).show();
}
}
};
observer.startWatching();

Dropbox Sync Api. Not seeing the File on Dropbox the first time round

I have a DropboxHelper Class that is handling downloading and uploading from dropbox.
Downloading works fine but when I try to upload from dropbox the first time the code is called. The following Line is false
if (dropboxFileSystem.isFile(dropboxPath)) {
}
It returns false. Tell the app to try again and this time it sees the file and uploads it to the app. Below is some of the code I am using for the class. Debug seems to incdicate the dropbox api has not completing started / synced the first time
public class DropBoxHelper {
public DropBoxHelper(Context pContext) {
context = pContext;
defineVariables();
}
private void defineVariables() {
dropboxAccountManager = DbxAccountManager.getInstance(context.getApplicationContext(), DROPBOX_APP_KEY, DROPBOX_APP_SECRET);
dropboxPath = new DbxPath(DbxPath.ROOT, DROPBOX_FILE_NAME);
}
public boolean importFromDropbox() {
try {
dropboxFileSystem = DbxFileSystem.forAccount(dropboxAccountManager.getLinkedAccount());
if (dropboxFileSystem.isFile(dropboxPath)) {
DbxFile databaseFileonDropbox = dropboxFileSystem.open(dropboxPath);
try {
// Do Copy
} finally {
Log.i(DEBUG_TAG, "Closing File");
databaseFileonDropbox.close();
}
}
Any ideas on why the copy fails first time.
Thanks
I'm not 100% sure, but I believe you need to use dropboxFileSystem.awaitFirstSync() to make sure at least one sync with the server has happened before you try to find the file.
An alternative might be to just call dropboxFileSystem.open(...) directly and handle the exception that's raised if the file doesn't exist.

Categories

Resources