简体   繁体   中英

Is it possible to use a non-final in an enclosing scope like a runnable (no.)

The plan:

I created a little PreferenceActivity (don't hate me, I'm supporting API 10 and up) and need to display the current usage of local storage data by my app. I did this using a specialized class (a pretty big one, as of the moment) that handles all file operations (it's called FileOperations.java for a reason). Inside this class file there is a method getSize(File file) {...} which does just that. It gets the size of a file (or folder) with this little piece of code:

public long getSize(File file) {
    long size = 0;

    if(file.isDirectory()) {
        for(File child : file.listFiles()) {
            size += getSize(child);
        }
    }
    size = file.length();

    return size;
}

The general idea was to use this in a background Thread so it doesn't clog the UI even the slightest bit. (I am really annoyed by lagging apps and suffer from them daily)

The problem:

This works just fine. However, as soon as I purge the folder the app stores it's data in using this beauty:

private void purgeLocalStorage() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();
            Log.i("ActivityPrefsLocalStorage.purgeLocalStorage.Thread.Runnable.run", "Started to run");
            final String         directory = context.getResources().getString(R.string.app_name);
            final String         usedData  = context.getResources().getString(R.string.ActivityPrefsLocalStorage_usedData);
            final File           file      = new File(Environment.getExternalStorageDirectory()+"/"+directory);
            final FileOperations FO        = new FileOperations(context);
            Log.i("ActivityPrefsLocalStorage.purgeLocalStorage.Thread.Runnable.run", "deleting folder: "+file);
            if(FO.delete(file)) {
                Log.i("ActivityPrefsLocalStorage.purgeLocalStorage.Thread.Runnable.run", file+" deleted");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(context, R.string.ActivityPrefsLocalStorage_deleteSucces, Toast.LENGTH_SHORT).show();
                        setTotalLocalDataTexts(usedData+" "+context.getResources().getString(R.string.pref_totalData_default), "");
                        getUsedStorage();
                    }
                });
            } else {
                Log.e("ActivityPrefsLocalStorage.purgeLocalStorage.Thread.Runnable.run", "could not delete "+file);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(context, R.string.ActivityPrefsLocalStorage_deleteError, Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }
    }).start();
}

Things hit the fan...

See, the problem is that my method for reading the size of the folder does not want to function properly when called by the previous method.

Here's a snippet:

private void getUsedStorage() {
    new Thread(new Runnable() {
        @Override
        public void run() {
            Log.i("ActivityPrefsLocalStorage.getUsedStorage.Thread.Runnable.run", "Started to run");
            final String         directory = context.getResources().getString(R.string.app_name);
            final String         usedData  = context.getResources().getString(R.string.ActivityPrefsLocalStorage_usedData);
            final File           file      = new File(Environment.getExternalStorageDirectory()+"/"+directory);
            final FileOperations FO        = new FileOperations(context);
            final DataUsage      DU        = new DataUsage(context);
            Log.i("ActivityPrefsLocalStorage.getUsedStorage.Thread.Runnable.run", "Checking filesize of folder: "+file);
                  long           fileSize  = FO.getSize(file);
                  String         usedUnits = DU.getUnit(fileSize, true, false, false);
                  String         usedBytes = DU.getUnit(fileSize, true, true, true);
            Log.i("ActivityPrefsLocalStorage.getUsedStorage.Thread.Runnable.run", "filesize of "+file+": "+usedUnits);
            setTotalLocalDataTexts(usedData+" "+usedUnits, usedBytes);
        }
    }).start();
}

However, a quick and easy workaround would be to place it on the UI thread like so:

...blabla code you already read above.
                  long           fileSize  = FO.getSize(file);
                  String         usedUnits = DU.getUnit(fileSize, true, false, false);
                  String         usedBytes = DU.getUnit(fileSize, true, true, true);
            Log.i("ActivityPrefsLocalStorage.getUsedStorage.Thread.Runnable.run", "filesize of "+file+": "+usedUnits);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    setTotalLocalDataTexts(usedData+" "+usedUnits, usedBytes);
                }
            });
        }
    }).start();
}

And that's where it starts getting interesting. I cannot use non-finals inside the new Runnable() , and I cannot make them final since I want the value to update and not remain stuck at eg. 32MiB (while it has just been purged).

Possible fixes:

I should man up and just use a final. The user will understand they need to refresh the page manually. (oh no...)

Hire... erm. Extend an AsyncTask<Void, Void, Void> to do the work.


My ideal fix:

Someone giving me an awesome snippet of code for free that does all the magic. no, seriously though, I would really appreciate anything apart from my list of possible fixes. There has to be some way to pass the new Runnable() a variable without creating classes and implementing the entire universe? Or is that what I am trying to achieve really a new thing?

TL;DR:
Things go wrong as soon as I call getUsedStorage() from within a new Runnable() . This function is also a background task inside a Runnable, but updates the UI using a private void function that sets it. It only passes variables to this function. and then things fly off the handle(r).

Edit: grammar.
Edit2: Also a pretty interesting thing to note here, I used something similar in another PreferenceActivity, and that one works. (but that one does not update at the press of a button that calls another private something functionName() {new Thread(new Runnable() {public void run() {...});} )

There are a couple of ways to use non-finals inside of a Runnable or other enclosed classes.

The first is to change your variables to be members of an enclosing class. This will allow you to use the variables inside the Runnable. An example follows:

public class Foo {
    private long time;

    public void run() {
        time = System.currentTimeMillis();

        new Thread(new Runnable() {
            @Override
            public void run() {
                time += 1;
                System.out.println("Our current time is: " + time);
            }
        });
    }
}

The other option, and it is quite hacky, is to use a final array with a length of 1. An example of that follows:

public class Foo {
    public void run() {
        final long[] time = { System.currentTimeMillis() };

        new Thread(new Runnable() {
            @Override
            public void run() {
                time[0] += 1;
                System.out.println("Our current time is: " + time[0]);
            }
        });
    }
}

As gonemad16 on reddit.com/r/androiddev pointed out, my issue had nothing to do with final vs non final. That was not the reason I was getting an old value. All my variables are given a value, sent to setTotalLocalDataTexts and then go out of scope... nothing is updating their values.. so there is no harm in them being final and no benefit to them being non final...

It was an an issue in getSize()

I thought I had a correct loop there using if file.isDirectory() {...}. It created a directory tree and executed itself using the children it has found. When all items have been scanned the value returns to the function calling it.

This was working just fine for me while I was still running all of my code on the ui thread. Everything was slow. But it worked.

However, I forgot that I removed a very crucial ...} else {...

I believe I removed that one because it caused a stack overflow at some point, so I removed it and I guess forgot to put it back...

And here I was thinking my first SO question wouldn't be a noobish question...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM