majidgolshadi / Android-Download-Manager-Pro

Android/Java download manager library help you to download files in parallel mechanism in some chunks.
MIT License
1.61k stars 317 forks source link

onDownloadfinished called directly after connectionlost on lolipop 5.0 #6

Open hassan379 opened 9 years ago

hassan379 commented 9 years ago

hi , thx for this great library but i am facing issue , in lolipop 5.0 ondownloadFinished Called Directly after connectionlost , but on other apis it works fine even on 5.1 , this issue appear only in 5.0 i noticed that an error raised 07-31 16:50:06.941 1670-1804/com.filmov.itech.movies E/AndroidRuntime﹕ FATAL EXCEPTION: Thread-229 Process: com.filmov.itech.movies, PID: 1670 java.lang.NullPointerException: Attempt to invoke virtual method 'int java.io.FileInputStream.read(byte[])' on a null object reference at tools.majid.core.chunkWorker.Rebuilder.run(Rebuilder.java:52)

daniaDlbani commented 9 years ago

I have the same problem when I pause a resumable task: dm.pause(token); it called the sequence : onpause → rebuild → rebuild finish → complete so I can`t resume the download any more

daniaDlbani commented 9 years ago

this happened only with android lolipop 5.0.1 in earlier versions it works perfectly..

majidgolshadi commented 9 years ago

Did you check that on handheld device or on an emulator?

daniaDlbani commented 9 years ago

@majidgolshadi thanks for the fast response, I am working on real device.

hassan379 commented 9 years ago

i tested it on real device and emulator and both the same result , please fix this issue your library is very great and i only faced this issue on it

hassan379 commented 9 years ago

after days of hardworking i have found the source of the error , in Asyncworker :

 if (!this.isInterrupted()) {
     observer.rebuild(chunk);
    Log.i("isInterrupted()","yes");
}

for some reason this if statement become true after we call pausedownload , and for this reason observer call rebuild to finish the download , i saw this issue only on 5.0 , i compared this on other apis , the if statement was false when we call pausedownload sorry bad english UPDATE: i notice something that .isInterrupted() return false even after we interrupt every chunk thread , but in other apis return true

hassan379 commented 9 years ago

FIXED : I fixed this issue by adding flag inside AsyncWorker , i name it as "isInturrputed" , the default value of this flag is false and became true when trying to inturrput every thread chunk , the code now became like this AsyncWorker Class :

package tools.majid.core.chunkWorker;

import android.os.Build;
import android.util.Log;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.SocketTimeoutException;
import java.net.URL;

import tools.majid.Utils.helper.FileUtils;
import tools.majid.database.elements.Chunk;
import tools.majid.database.elements.Task;

/**
 * Created by Majid Golshadi on 4/14/2014.
 */
public class AsyncWorker extends Thread{

    private final int BUFFER_SIZE = 1024;

    private final Task task;
    private final Chunk chunk;
    private final Moderator observer;
    private byte[] buffer;
    private ConnectionWatchDog watchDog;
    private Boolean isInterrupted=false;

    public boolean stop = false;

    public AsyncWorker(Task task, Chunk chunk, Moderator moderator){
        buffer = new byte[BUFFER_SIZE];

        this.task = task;
        this.chunk = chunk;
        this.observer = moderator;
    }

    public void setInterrupt()
    {
        isInterrupted=true;
    }

    @Override
    public void run() {
        try {

            URL url = new URL(task.url);
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setConnectTimeout(50000);
            connection.setReadTimeout(50000);
            if (chunk.end != 0) // support unresumable links
                connection.setRequestProperty("Range", "bytes=" + chunk.begin + "-" + chunk.end);

            connection.connect();

            File cf = new File(FileUtils.address(task.save_address, String.valueOf(chunk.id)));
            InputStream remoteFileIn = connection.getInputStream();
            FileOutputStream chunkFile = new FileOutputStream(cf, true);

            int len = 0;
            // set watchDoger to stop thread after 1sec if no connection lost
            watchDog = new ConnectionWatchDog(5000, this);
            watchDog.start();

                while (!this.isInterrupted() &&
                        (len = remoteFileIn.read(buffer)) > 0) {

                    watchDog.reset();
                    chunkFile.write(buffer, 0, len);
                    process(len);
                }

            chunkFile.flush();
            chunkFile.close();
            watchDog.interrupt();
            connection.disconnect();

            if(Build.VERSION.SDK_INT==21)
            {

                if(!isInterrupted)
                {
                    observer.rebuild(chunk);
                    Log.i("isInterrupted()","yes");
                }
            }
            else
            {
                if (!this.isInterrupted()) {
                    observer.rebuild(chunk);
                    Log.i("isInterrupted()","yes");
                }

            }

        }catch (SocketTimeoutException e) {
            e.printStackTrace();

            observer.connectionLost(task.id);
            puaseRelatedTask();

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return;
    }

    private void process(int read) {
        observer.process(chunk.task_id, read);
    }

    private void puaseRelatedTask() {
        observer.pause(task.id);
    }

    private boolean flag = true;
    public void connectionTimeOut(){
        if (flag) {
            watchDog.interrupt();
            flag = false;
            observer.connectionLost(task.id);
            puaseRelatedTask();
        }

    }

}

and the block of code that handle inturrputing every thread chunk in Moderator Class became like this :

   List<Chunk> taskChunks =
                    chunksDataSource.chunksRelatedTask(task.id);
            for (Chunk chunk : taskChunks) {
                Thread worker = workerList.get(chunk.id);
                if (worker != null) {
                    worker.interrupt();
                    if(Build.VERSION.SDK_INT==21)
                        ((AsyncWorker)worker).setInterrupt();
                    workerList.remove(chunk.id);
                    Log.i("workerListremove",String.valueOf(chunk.id));
                }
            }

thx to me for fixing this issue :P

majidgolshadi commented 9 years ago

Send your bug fix with pull request (pr)

nthuat commented 8 years ago

I had a similar bug reported from crashlytics. But I couldn't reproduce this. How can I encounter this with some steps?