Saturday, May 28, 2011

Tackling the async download

My goal is to get done *very soon* -as in today - the part of my app that downloads the mp3 audio files and puts them on the SD card.

Google download - ah, there's another problem I need to look after - keeping them from showing up when you use astro or
something like that.

http://androinica.com/2009/08/how-to-hide-non-music-audio-files-from-appearing-in-android-media-players/

1. On your computer, open Notepad or any other text editor

2. Save the blank file as “.nomedia”
Make sure that the Save as type is set as “All Files” instead of “Text documents”

3. Copy that file to the folder on your SD card containing audio files you don’t want to show up.
(Example: If you want to block CoPilot sound files from coming up, place the .nomedia file in the sdcard/copilot folder)

4. Reboot your phone and the files should no longer be viewable

This same trick works for folders containing images or videos that you don’t want to appear in the Gallery app. The files will still be accessible from your original app or an explorer like Astro or Linda, but you’ll finally be able to stop hearing “Turn left” announcements after a great song.

Let's see if this works.

First, create the .notepad on say desktop.

Check.

How again do you copy a file from your desktop to a directory?

I think it must be adb push.

Open terminal.

Ok, I'm doing adb shell, and for some reason it's not given me a not authorized when I cd around anymore. Well, that's good.

Well, I copied it, but it doesn't work. I'm noticing that it says open it it in notepad and save the file as type All Files, but I'm using textmate on a mac. So, now what do I do?

google all types mac?


Ok, we'll get back to that.


Here's something the looks useful...

public void mp3load() {
URL url = new URL(url);
HttpURLConnection c = (HttpURLConnection) url.openConnection();
c.setRequestMethod("GET");
c.setDoOutput(true);
c.connect();

String PATH = Environment.getExternalStorageDirectory()
+ "/download/";
Log.v(LOG_TAG, "PATH: " + PATH);
File file = new File(PATH);
file.mkdirs();

String fileName = "test.mp3";


File outputFile = new File(file, fileName);
FileOutputStream fos = new FileOutputStream(outputFile);

InputStream is = c.getInputStream();

byte[] buffer = new byte[1024];
int len1 = 0;
while ((len1 = is.read(buffer)) != -1) {
fos.write(buffer, 0, len1);
}
fos.close();
is.close();
}

I think it's a zip file, and all the files are in these individual files - where did I put it?

Ok, it was in a folder called "japanrelated/jlpt_named_folders".

Let's upload it. Ok, I need to reconnect the ftp. It's 1 113 MB file. That's nothing unless you have no SD card at all.

Ok. It's going, but not that fast. 57 KB = 400 kb /sec upload, approximately. Still, not bad for a coffee shop.

I think I will code it as an an async task. While I'm waiting I'll get the code for an async task.

For now, I'll just add a button to the start activity which the user will press to activate the download. I will do it automatically later.

There's some code from this link http://appfulcrum.com/?p=126

This should be a one step at a time thing. Going with TDD spirit, implement the smallest possible action.

Create a download button in StartActivity.xml.

Hmm, I actually never renamed the start activity layout from the previous quiz. Ok - rename.

Hmm...crash. I need to call initialize, I think, which I commented out yesterday.

Crash again. Why isn't the default value getting set? Risk a debug.

It's just sitting there waiting for the debugger - for some reason it won't attach.

Run, and It's crashing on line 61.

mJlptLevel.setText(new Integer(appState.jlptLevel).toString());

It can only be mjlptLevel or appstate.jlptLevel.

D/QuizStartActiity( 1491): >>>>>>>>>>>>>>> mJlptLevel: null

Ok, there it is. So...do I have to clean the project? Yup - that seemed to do it. Never make assumptions.

Ok, getting back to the "test" - add a download button to start layout.

Ok, finally, I've got the button going on. Now, let's add a Toast display.

It's something like onButtonClick. Well, let's work off the example.

Ok, that's done.

Now, the actual purpose of the button is to kick off an asynchronous activity.

It looks like there's a good example in the developer docs:

http://developer.android.com/reference/android/os/AsyncTask.html

private class DownloadFilesTask extends AsyncTask {
protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
}
return totalSize;
}

protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}

protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}


Ok. It looks like this actually takes a list of urls. Given the odd file names, and the thousands of files, I'll just download one zip file and contend with that.

It apparently is intended to be used a a private task. I won't fight city hall, although I would prefer it as a separate class.

Frustratingly, the example doesn't provide the implementation of setProgressPercent. Well, drop that for a moment. We're doing the minimum here, which will in this case execute a toast in the doInBackground.

private class DownloadFilesTask extends AsyncTask {
protected void doInBackground(URL... urls) {

// some toast code
}

protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}

protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}


As the docs say,

The three types used by an asynchronous task are the following:

Params, the type of the parameters sent to the task upon execution.
Progress, the type of the progress units published during the background computation.
Result, the type of the result of the background computation.

So, what's going on with this?

'AsyncTask enables proper and easy use of the UI thread.

This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers."

Ok, no problem.

"An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread. "

Ok, but how to publish the results?

"An asynchronous task is defined by 3 generic types, called Params, Progress and Result, and 4 steps, called onPreExecute, doInBackground, onProgressUpdate and onPostExecute."

Ok, so the Params, Progress and Result actually are the input params to the Async Task, and you can define what they are. The class you define is a subclass of Async Task, and it step through the 4 steps defined. I'm not quit sure how the on progress update works.

"Usage

AsyncTask must be subclassed to be used. The subclass will override at least one method (doInBackground(Params...)), and most often will override a second one (onPostExecute(Result).)"

Ok, so it definitely has to override doinBackground(Params...), and also do something once that part is completed.


"Here is an example of subclassing:

private class DownloadFilesTask extends AsyncTask {

// so the URL is input to the doInBackground; the Long is returned by that, and passed into postExecute. The Integer is passed
// into onProgressUpdate - by the publishProgress?

protected Long doInBackground(URL... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
totalSize += Downloader.downloadFile(urls[i]);
publishProgress((int) ((i / (float) count) * 100));
}
return totalSize;
}

// This is called back, probably by the publishProgress...where is setProgress percent defined?
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}

// this is the post execute
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}

"Once created, a task is executed very simply:

new DownloadFilesTask().execute(url1, url2, url3);"

Ok, that makes sense.

Try running with toast, but that kills it.

"AsyncTask's generic types

The three types used by an asynchronous task are the following:

Params, the type of the parameters sent to the task upon execution.
Progress, the type of the progress units published during the background computation.
Result, the type of the result of the background computation.

Not all types are always used by an asynchronous task. To mark a type as unused, simply use the type Void:

private class MyTask extends AsyncTask { ... }"


private class DownloadFilesTask extends AsyncTask {
protected Long doInBackground(URL... urls) {


So, when you give something like URL above, it's saying doInBackground will receive a list of Urls. The syntax "URL... urls" is probably a Java 1.5 thing and clearly specifies a List with the named list afterwards.


"The 4 steps

When an asynchronous task is executed, the task goes through 4 steps:

onPreExecute(), invoked on the UI thread immediately after the task is executed. This step is normally used to setup the task, for instance by showing a progress bar in the user interface."

Ok, I would like an example of the progress bar.

"2, doInBackground(Params...), invoked on the background thread immediately after onPreExecute() finishes executing. This step is used to perform background computation that can take a long time. The parameters of the asynchronous task are passed to this step. The result of the computation must be returned by this step and will be passed back to the last step. This step can also use publishProgress(Progress...) to publish one or more units of progress. These values are published on the UI thread, in the onProgressUpdate(Progress...) step."

Right, as I suspected, it's a call to the onProgressUpdate. Although in the example, it doesn't explain why the primitive type is converted into the the first value in an array of integers.

"onProgressUpdate(Progress...), invoked on the UI thread after a call to publishProgress(Progress...). The timing of the execution is undefined. This method is used to display any form of progress in the user interface while the background computation is still executing. For instance, it can be used to animate a progress bar or show logs in a text field."

The big thing I will need for this is to figure out how far along the file is in being downloaded; wait, I think it's loop driven by bytes. That's the ticket.

"onPostExecute(Result), invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter."

This would be unzipping the file, and maybe showing some kind of download complete method.

Ok, I got the "calling time service message to display.

Now, I have to figure out how to incorporate the file download.

Save that for later!

No comments:

Post a Comment