Paras Sidhu
Paras Sidhu

Reputation: 670

Handling Multiple Downloads With Notifications Using Retrofit Library

I'm following a tutorial to download files using the Retrofit library.

My app's UI has several different buttons which download different files using the above method. The problem is when someone presses another button after pressing the first, it's queued and starts after the first finishes. I want it to start right away simultaneously.

Here's the code for the DownloadService:

public class DownloadService extends IntentService {

    public DownloadService() {
        super("Download Service");
    }

    private int totalFileSize;
    private NotificationCompat.Builder notificationBuilder;
    private NotificationManager notificationManager;

    @Override
    protected void onHandleIntent(Intent intent) {
        MyApp x = (MyApp)getApplicationContext();
        notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        notificationBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_file_download_deep_orange_a400_18dp)
                .setContentTitle("Downloading")
                .setContentText("Please wait...")
                .setAutoCancel(true);
        notificationManager.notify(x.ID, notificationBuilder.build());
        Log.i("Paras", "onHandleIntent: " + x.filename + x.url);
        initDownload(x.filename,x.url,x.ID);

    }

    private void initDownload(String filename, String url, int id) {

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://dl.dropboxusercontent.com/")
                .build();

        RequestInterface.RetrofitInterface retrofitInterface = retrofit.create(RequestInterface.RetrofitInterface.class);

        Call<ResponseBody> request = retrofitInterface.downloadFile(url);
        try {

            downloadFile(request.execute().body(),filename,id);

        } catch (IOException e) {

            e.printStackTrace();
            Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show();

        }
    }

    private void downloadFile(ResponseBody body, String filename,int id) throws IOException {

        int count;
        byte data[] = new byte[1024 * 4];
        long fileSize = body.contentLength();
        InputStream bis = new BufferedInputStream(body.byteStream(), 1024 * 8);
        File outputFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), filename);
        OutputStream output = new FileOutputStream(outputFile);
        long total = 0;
        long startTime = System.currentTimeMillis();
        int timeCount = 1;

        while ((count = bis.read(data)) != -1) {
            total += count;
            totalFileSize = (int) (fileSize / (Math.pow(1, 2))) / 1000;
            double current = Math.round(total / (Math.pow(1, 2))) / 1000;

            int progress = (int) ((total * 100) / fileSize);

            long currentTime = System.currentTimeMillis() - startTime;

            Download download = new Download();
            download.setTotalFileSize(totalFileSize);

            if (currentTime > 1000 * timeCount) {
                download.setCurrentFileSize((int) current);
                download.setProgress(progress);
                sendNotification(download,id);
                timeCount++;
            }

            output.write(data, 0, count);
        }

        onDownloadComplete(filename,id);
        output.flush();
        output.close();
        bis.close();

    }

    private void sendNotification(Download download, int id) {

        sendIntent(download);
        notificationBuilder.setProgress(100, download.getProgress(), false);
        notificationBuilder.setContentText("Downloading file " + download.getCurrentFileSize() + "/" + totalFileSize + " KB");
        notificationManager.notify(id, notificationBuilder.build());
    }

    private void sendIntent(Download download) {

        Intent intent = new Intent(subject.MESSAGE_PROGRESS);
        intent.putExtra("download", download);
        LocalBroadcastManager.getInstance(DownloadService.this).sendBroadcast(intent);
    }

    private void onDownloadComplete(String filename,int id) {
        try {

            Download download = new Download();
            download.setProgress(100);
            sendIntent(download);

            notificationManager.cancel(id);
            notificationBuilder.setProgress(0, 0, false);
            notificationBuilder.setContentText("Tap to open");
            notificationManager.notify(id, notificationBuilder.build());

            String path1 = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/" + filename;

            File file = new File(path1);
            Uri uri_path = Uri.fromFile(file);
            String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension
                    (MimeTypeMap.getFileExtensionFromUrl(path1));

            Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
            intent.setType(mimeType);
            intent.setDataAndType(uri_path, mimeType);
            PendingIntent pIntent = PendingIntent.getActivity(this, (int) System.currentTimeMillis(), intent, 0);
            String string = filename;
            notificationBuilder
                    .setContentIntent(pIntent)
                    .setAutoCancel(true)
                    .setContentTitle(string + " Downloaded");
            Log.i("Paras", "onDownloadComplete: " + string);
            notificationManager.notify(id, notificationBuilder.build());
        } catch (Exception ex) {

        }
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {

    }
}

Then I read about IntentService and Service classes. Do Service classes allow simultaneous downloads? I tried something like this:

public class DownloadService extends Service {

    public DownloadService() {
        super();
    }

    private Looper mServiceLooper;
    private ServiceHandler mServiceHandler;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            MyApp x = (MyApp)getApplicationContext();
            notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

            notificationBuilder = new NotificationCompat.Builder(getBaseContext())
                    .setSmallIcon(R.drawable.ic_file_download_deep_orange_a400_18dp)
                    .setContentTitle("Downloading")
                    .setContentText("Please wait...")
                    .setAutoCancel(true);
            notificationManager.notify(x.ID, notificationBuilder.build());
            Log.i("Paras", "onHandleIntent: " + x.filename + x.url);
            initDownload(x.filename,x.url,x.ID);
        }
    }

    @Override
    public void onCreate() {
        // Start up the thread running the service.  Note that we create a
        // separate thread because the service normally runs in the process's
        // main thread, which we don't want to block.  We also make it
        // background priority so CPU-intensive work will not disrupt our UI.
        HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();

        // Get the HandlerThread's Looper and use it for our Handler
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        // For each start request, send a message to start a job and deliver the
        // start ID so we know which request we're stopping when we finish the job
        MyApp x = (MyApp)getApplicationContext();
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj= intent.putExtra("ID",x.ID);
        mServiceHandler.sendMessage(msg);

        // If we get killed, after returning from here, restart
        return START_STICKY;
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private int totalFileSize;
    private NotificationCompat.Builder notificationBuilder;
    private NotificationManager notificationManager;

    private void initDownload(String filename, String url, int id) {

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://dl.dropboxusercontent.com/")
                .build();

        RequestInterface.RetrofitInterface retrofitInterface = retrofit.create(RequestInterface.RetrofitInterface.class);

        Call<ResponseBody> request = retrofitInterface.downloadFile(url);
        try {

            downloadFile(request.execute().body(),filename,id);

        } catch (IOException e) {

            e.printStackTrace();
            Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show();

        }
    }

    private void downloadFile(ResponseBody body, String filename,int id) throws IOException {

        int count;
        byte data[] = new byte[1024 * 4];
        long fileSize = body.contentLength();
        InputStream bis = new BufferedInputStream(body.byteStream(), 1024 * 8);
        File outputFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), filename);
        OutputStream output = new FileOutputStream(outputFile);
        long total = 0;
        long startTime = System.currentTimeMillis();
        int timeCount = 1;

        while ((count = bis.read(data)) != -1) {
            total += count;
            totalFileSize = (int) (fileSize / (Math.pow(1, 2))) / 1000;
            double current = Math.round(total / (Math.pow(1, 2))) / 1000;

            int progress = (int) ((total * 100) / fileSize);

            long currentTime = System.currentTimeMillis() - startTime;

            Download download = new Download();
            download.setTotalFileSize(totalFileSize);

            if (currentTime > 1000 * timeCount) {
                download.setCurrentFileSize((int) current);
                download.setProgress(progress);
                sendNotification(download,id);
                timeCount++;
            }

            output.write(data, 0, count);
        }

        onDownloadComplete(filename,id);
        output.flush();
        output.close();
        bis.close();

    }

    private void sendNotification(Download download, int id) {

        sendIntent(download);
        notificationBuilder.setProgress(100, download.getProgress(), false);
        notificationBuilder.setContentText("Downloading file " + download.getCurrentFileSize() + "/" + totalFileSize + " KB");
        notificationManager.notify(id, notificationBuilder.build());
    }

    private void sendIntent(Download download) {

        Intent intent = new Intent(subject.MESSAGE_PROGRESS);
        intent.putExtra("download", download);
        LocalBroadcastManager.getInstance(DownloadService.this).sendBroadcast(intent);
    }

    private void onDownloadComplete(String filename,int id) {
        try {

            Download download = new Download();
            download.setProgress(100);
            sendIntent(download);

            notificationManager.cancel(id);
            notificationBuilder.setProgress(0, 0, false);
            notificationBuilder.setContentText("Tap to open");
            notificationManager.notify(id, notificationBuilder.build());

            String path1 = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/" + filename;

            File file = new File(path1);
            Uri uri_path = Uri.fromFile(file);
            String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension
                    (MimeTypeMap.getFileExtensionFromUrl(path1));

            Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
            intent.setType(mimeType);
            intent.setDataAndType(uri_path, mimeType);
            PendingIntent pIntent = PendingIntent.getActivity(this, (int) System.currentTimeMillis(), intent, 0);
            String string = filename;
            notificationBuilder
                    .setContentIntent(pIntent)
                    .setAutoCancel(true)
                    .setContentTitle(string + " Downloaded");
            Log.i("Paras", "onDownloadComplete: " + string);
            notificationManager.notify(id, notificationBuilder.build());
        } catch (Exception ex) {

        }
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {

    }
}

But it didn't work. Any clues what I should do? I'm ready to give more details if required.

Edit 1: DownloadService runs on a function "startDownload" which is executed by various buttons. As you can see in 2nd code, class Extends Service. There is one thread which handles all those button clicks. If you look in comments, it's suggested that I should use Service and different threads for all those clicks. Now how can I make so many threads programmatically. There are almost 40 buttons which make use of DownloadService.

Upvotes: 2

Views: 3283

Answers (1)

Paras Sidhu
Paras Sidhu

Reputation: 670

Thanks to @Lxu, I have got it working. So IntentServiceis meant to do one task at a time and can't do multiple tasks simultaneously. We should use Serviceinstead. It allows multiple tasks to be performed simultaneously. We can create multiple threads inside the Service which will be executed simultaneously. My problem got solved by putting all the code of onCreate() to onStartCommand(). When a service is called for the first time, onCreate() is called and after that, it's not called anymore no matter how many times service is called. On every service call, onStartCommand() is executed which creates new thread every time. That's it.

Here's the complete code:

public class DownloadService extends Service {

    public DownloadService() {
        super();
    }
    private Looper mServiceLooper;
    private ServiceHandler mServiceHandler;

    int id1;
    int id2;
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
            MyApp x = (MyApp)getApplicationContext();
            notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

            notificationBuilder = new NotificationCompat.Builder(getApplicationContext())
                    .setSmallIcon(R.drawable.ic_file_download_deep_orange_a400_18dp)
                    .setContentTitle("Downloading")
                    .setContentText("Please wait...")
                    .setAutoCancel(true);
            notificationManager.notify(x.ID, notificationBuilder.build());
            Log.i("Paras", "onHandleIntent: " + x.filename + x.url + "  " + x.ID);
            initDownload(x.filename,x.url,x.ID);
        }
    }
    @Override
    public void onCreate() {
        // Get the HandlerThread's Looper and use it for our Handler
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // For each start request, send a message to start a job and deliver the
        // start ID so we know which request we're stopping when we finish the job
        HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
        MyApp x = (MyApp)getApplicationContext();
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        mServiceHandler.sendMessage(msg);


        // If we get killed, after returning from here, restart
        return START_STICKY;
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {

        return null;
    }

    private int totalFileSize;
    private NotificationCompat.Builder notificationBuilder;
    private NotificationManager notificationManager;

    private void initDownload(String filename, String url, int id) {

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://dl.dropboxusercontent.com/")
                .build();

        RequestInterface.RetrofitInterface retrofitInterface = retrofit.create(RequestInterface.RetrofitInterface.class);

        Call<ResponseBody> request = retrofitInterface.downloadFile(url);
        try {

            downloadFile(request.execute().body(),filename,id);

        } catch (IOException e) {

            e.printStackTrace();
            Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show();

        }
    }

    private void downloadFile(ResponseBody body, String filename,int id) throws IOException {

        int count;
        byte data[] = new byte[1024 * 4];
        long fileSize = body.contentLength();
        InputStream bis = new BufferedInputStream(body.byteStream(), 1024 * 8);
        File outputFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), filename);
        OutputStream output = new FileOutputStream(outputFile);
        long total = 0;
        long startTime = System.currentTimeMillis();
        int timeCount = 1;
        while ((count = bis.read(data)) != -1) {

            total += count;
            totalFileSize = (int) (fileSize / (Math.pow(1, 2))) / 1000;
            double current = Math.round(total / (Math.pow(1, 2))) / 1000;

            int progress = (int) ((total * 100) / fileSize);

            long currentTime = System.currentTimeMillis() - startTime;

            Download download = new Download();
            download.setTotalFileSize(totalFileSize);

            if (currentTime > 1000 * timeCount) {

                download.setCurrentFileSize((int) current);
                download.setProgress(progress);
                sendNotification(download,id);
                timeCount++;
            }

            output.write(data, 0, count);
        }
        onDownloadComplete(filename,id);
        output.flush();
        output.close();
        bis.close();

    }

    private void sendNotification(Download download, int id) {

        sendIntent(download,id);
        notificationBuilder.setProgress(100, download.getProgress(), false)
                            .setContentTitle("Downloading");
        notificationBuilder.setContentText("Downloading file " + download.getCurrentFileSize() + "/" + totalFileSize + " KB");
        notificationManager.notify(id, notificationBuilder.build());
    }

    private void sendIntent(Download download, int id) {

        Intent intent = new Intent(subject.MESSAGE_PROGRESS);
        intent.putExtra("download", download);
        LocalBroadcastManager.getInstance(DownloadService.this).sendBroadcast(intent);
    }

    private void onDownloadComplete(String filename,int id) {
        try {

            Download download = new Download();
            download.setProgress(100);
            sendIntent(download,id);

            notificationManager.cancel(id);
            notificationBuilder.setProgress(0, 0, false);
            notificationBuilder.setContentText("Tap to open");
            notificationManager.notify(id, notificationBuilder.build());

            String path1 = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + "/" + filename;

            File file = new File(path1);
            Uri uri_path = Uri.fromFile(file);
            String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension
                    (MimeTypeMap.getFileExtensionFromUrl(path1));

            Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
            intent.setType(mimeType);
            intent.setDataAndType(uri_path, mimeType);
            PendingIntent pIntent = PendingIntent.getActivity(this,(int) System.currentTimeMillis(), intent, 0);
            String string = filename;
            notificationBuilder
                    .setContentIntent(pIntent)
                    .setAutoCancel(true)
                    .setContentTitle(string + " Downloaded");
            Log.i("Paras", "onDownloadComplete: " + string);
            notificationManager.notify(id, notificationBuilder.build());
        }catch (Exception ex){

        }
    }
    @Override
    public void onTaskRemoved(Intent rootIntent) {

    }
}

Upvotes: 3

Related Questions