安卓主线程在多线程上崩溃 - >文件下载



我已经创建了一个自定义类来从我们的服务器下载一个音频文件。单个下载工作没有任何问题,但现在我需要包括下载多个音频文件的功能,无论是在一次或在一行..

问题是应用程序崩溃与我的下载执行:"应用程序没有响应。你想关闭它吗?[wait] [quit]"

我不确定是什么导致的问题,但我已经读到,NotificationManager可能是问题,所以我已经实现了它在一个教程,说它会修复它,但它没有。所以我相信这一定是线程的问题。

AudioDownload类:

public class AudioDownload implements Runnable {
private static Boolean bIAmBusy = false;
private Activity activity;
OnAudioDownloadFinishedListener mycallback;
private String Folder;
private String FileName;
private String FileUrl;
public AudioDownload(String COURSE_FLAG, String AUDIO, String language, Activity a){
    Folder = COURSE_FLAG;
    FileName = AUDIO;
    FileUrl = "http://SOMEURL/"+ language + "/" + COURSE_FLAG + "/" + FileName;
    activity = a;
}

@Override
public void run() {
    bIAmBusy = true;
    File localFile = new File(activity.getFilesDir() + "/" + Folder + "/" + FileName);
    if(localFile.exists()){
        //don't download
    }
    else{
        /*
        In older android versions, your Notification has to have a content Intent,
        so that when the user clicks your Notification, something happens. This means you must:
        Make an Intent object, pointing somewhere, such as your MainActivity.
        Make sure to add the Intent.FLAG_ACTIVITY_NEW_TASK flag.
        */
        Intent intent = new Intent(activity, CoursesActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        PendingIntent pendingIntent = PendingIntent.getActivity(activity,0,intent,PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationManager notificationManager = (NotificationManager) activity.getSystemService(activity.getApplicationContext().NOTIFICATION_SERVICE);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(activity);
        builder.setContentTitle("Audio Download");
        builder.setContentText("Download in Progress");
        builder.setSmallIcon(R.drawable.btn_download);
        builder.setTicker("Audio Download");
        builder.setContentIntent(pendingIntent);
        int id = 1;
        BufferedInputStream in = null;
        FileOutputStream out = null;
        boolean saved = false;
        try {
            URL url = new URL(FileUrl);
            URLConnection conn = url.openConnection();
            int size = conn.getContentLength();
            in = new BufferedInputStream(new URL(FileUrl).openStream());
            final File dir = new File(activity.getFilesDir() + "/" + Folder);
            dir.mkdirs();
            final File file = new File(dir,FileName);
            out = new FileOutputStream(file);
            final byte data[] = new byte[1024];
            int count;
            int sumCount = 0;

            while ((count = in.read(data, 0, 1024)) != -1) {
                sumCount += count;
                builder.setProgress(size,sumCount,false);
                notificationManager.notify(id, builder.build());
                out.write(data, 0, count);
            }
            saved = true;
            builder.setContentText("Download Complete");
            builder.setProgress(0,0,false);
            notificationManager.notify(id, builder.build());
        }catch (IOException e){
        }
        finally {
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        final boolean finalSaved = saved;
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mycallback.OnAudioDownloadThreadFinished(finalSaved);
            }
        });
    }
    bIAmBusy = false;
}

在我的活动上点击按钮,我正在尝试排队的线程。

public void onClick(View view) {
            Thread t = new Thread(){
                @Override
                public void run() {
                    Log.d("Audio", "Downloading all the audio for the chapter");
                    Toast.makeText(context, "Downloading all the audio for the chapter", Toast.LENGTH_SHORT).show();
                    List<AudioDownload> downloadList = new ArrayList<AudioDownload>();
                    for (int child = 0; child < chapters.get(groupPosition).getSubChapters().size(); child++) {
                        String Language = "en";
                        String FileName = subChapters.get(chapters.get(group)).get(child).getAudio();
                        audioDownload = new AudioDownload(Course, FileName, Language, activity);
                        audioDownload.setOnAudioDownloadFinishedListener(new AudioDownload.OnAudioDownloadFinishedListener() {
                            @Override
                            public void OnAudioDownloadThreadFinished(Boolean success) {
                                if (success) {
                                    Toast.makeText(context, "Download was successful", Toast.LENGTH_SHORT).show();
                                }
                            }
                        });
                        downloadList.add(audioDownload);
                    }
                    for (int i = 0; i < downloadList.size(); i++) {
                        while (downloadList.get(i).isBusy()) {
                            //wait
                        }
                        if (downloadList.get(i).isBusy() == false) {
                            new Thread(downloadList.get(i), "AudioDownload_" + i).start();
                        }
                    }
                }
            };
            t.start();

等待线程完成的while循环应该工作,因为bIamBusy被声明为静态->从我的角度来看,预期的行为是每个AudioDownload在另一个完成后被启动/启动。

不幸的是,这个应用程序在多次下载时崩溃了。如前所述,如果我将这个类用于单个音频下载,App/UIThread不会崩溃。有人知道为什么会这样吗?

应用程序崩溃只有当我拉下NotificationManager

ThreadLog……->似乎不需要等待启动它们

  17    30669   Native  21  6   AudioDownload_6 
  18    30666   Native  19  7   AudioDownload_4 
  19    30657   Native  20  7   AudioDownload_3 
  20    30673   Native  18  6   AudioDownload_10    
  21    30677   Native  20  8   AudioDownload_12    
异常堆栈

:

09-16 01:43:57.235      405-440/? E/ActivityManager﹕ ANR in smapp.com.smapp (smapp.com.smapp/.Controller.Screen_4.ChapterOverviewActivity)
Reason: keyDispatchingTimedOut
Load: 1.12 / 0.76 / 0.51
CPU usage from 10166ms to 4102ms ago:
36% 30551/smapp.com.smapp: 35% user + 1.3% kernel / faults: 2463 minor
17% 481/com.android.systemui: 16% user + 0.8% kernel / faults: 5726 minor
9.5% 405/system_server: 8.2% user + 1.3% kernel / faults: 483 minor
1.8% 121/surfaceflinger: 0.6% user + 1.1% kernel
0.2% 124/mediaserver: 0% user + 0.2% kernel / faults: 3 minor
0.9% 114/local_opengl: 0% user + 0.9% kernel
0.3% 60/adbd: 0% user + 0.3% kernel / faults: 34 minor
0.3% 112/vinput: 0% user + 0.3% kernel
0% 28602/flush-8:16: 0% user + 0% kernel
0% 29589/logcat: 0% user + 0% kernel
70% TOTAL: 60% user + 8.8% kernel + 1.1% softirq
CPU usage from 257ms to 760ms later:
59% 30551/smapp.com.smapp: 59% user + 0% kernel
55% 30551/smapp.com.smapp: 55% user + 0% kernel
3.8% 31130/AudioDownload_5: 1.9% user + 1.9% kernel
1.9% 31121/AudioDownload_2: 1.9% user + 0% kernel
1.9% 31122/AudioDownload_3: 1.9% user + 0% kernel
24% 481/com.android.systemui: 20% user + 4% kernel / faults: 657 minor
16% 481/ndroid.systemui: 16% user + 0% kernel
4% 575/Binder_3: 2% user + 2% kernel
2% 519/GC: 2% user + 0% kernel
2% 526/Binder_2: 2% user + 0% kernel
10% 405/system_server: 8% user + 2% kernel
2% 405/system_server: 2% user + 0% kernel
2% 415/Binder_1: 0% user + 2% kernel
2% 416/Binder_2: 0% user + 2% kernel
2% 440/InputDispatcher: 0% user + 2% kernel
2% 662/Binder_5: 2% user + 0% kernel
1.7% 60/adbd: 0% user + 1.7% kernel
1.7% 121/surfaceflinger: 0% user + 1.7% kernel
1.7% 303/VSyncThread: 0% user + 1.7% kernel
100% TOTAL: 90% user + 10% kernel

我现在已经解决了这个问题,谢谢大家的支持。CountDownLatch是我的解决方案。

现在的工作代码是:

public class AudioDownload implements Runnable {
private static Boolean bIAmBusy = false;
private Activity activity;
OnAudioDownloadFinishedListener mycallback;
private final CountDownLatch latch;
private String Folder;
private String FileName;
private String FileUrl;
public AudioDownload(String COURSE_FLAG, String AUDIO, String language, Activity a, CountDownLatch latch){
    Folder = COURSE_FLAG;
    FileName = AUDIO;
    FileUrl = "http://xxx.xx/xxxxx/xxxx/"+ language + "/" + COURSE_FLAG + "/" + FileName;
    activity = a;
    this.latch = latch;
}

@Override
public void run() {
    bIAmBusy = true;
    File localFile = new File(activity.getFilesDir() + "/" + Folder + "/" + FileName);
    if(localFile.exists()){
        //don't download
    }
    else{
        /*
        In older android versions, your Notification has to have a content Intent,
        so that when the user clicks your Notification, something happens. This means you must:
        Make an Intent object, pointing somewhere, such as your MainActivity.
        Make sure to add the Intent.FLAG_ACTIVITY_NEW_TASK flag.
        */
        Intent intent = new Intent(activity, CoursesActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        PendingIntent pendingIntent = PendingIntent.getActivity(activity,0,intent,PendingIntent.FLAG_UPDATE_CURRENT);

        NotificationManager notificationManager = (NotificationManager) activity.getSystemService(activity.getApplicationContext().NOTIFICATION_SERVICE);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(activity);
        builder.setContentTitle("Audio Download");
        builder.setContentText("Download in Progress");
        builder.setSmallIcon(R.drawable.btn_download);
        builder.setTicker("Audio Download");
        builder.setContentIntent(pendingIntent);
        int id = 1;
        BufferedInputStream in = null;
        FileOutputStream out = null;
        boolean saved = false;
        try {
            URL url = new URL(FileUrl);
            URLConnection conn = url.openConnection();
            int size = conn.getContentLength();
            in = new BufferedInputStream(new URL(FileUrl).openStream());
            final File dir = new File(activity.getFilesDir() + "/" + Folder);
            dir.mkdirs();
            final File file = new File(dir,FileName);
            out = new FileOutputStream(file);
            final byte data[] = new byte[1024];
            int count;
            int sumCount = 0;

            while ((count = in.read(data, 0, 1024)) != -1) {
                sumCount += count;
                builder.setProgress(size,sumCount,false);
                notificationManager.notify(id, builder.build());
                out.write(data, 0, count);
            }
            saved = true;
            builder.setContentText("Download Complete");
            builder.setProgress(0,0,false);
            notificationManager.notify(id, builder.build());
        }catch (IOException e){
        }
        finally {
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        final boolean finalSaved = saved;
        activity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mycallback.OnAudioDownloadThreadFinished(finalSaved);
            }
        });
    }
    latch.countDown();
    bIAmBusy = false;
}
public interface OnAudioDownloadFinishedListener{
    void OnAudioDownloadThreadFinished(Boolean success);
}
public void setOnAudioDownloadFinishedListener(OnAudioDownloadFinishedListener listener){
    mycallback = listener;
}
public Boolean isBusy() {
    return bIAmBusy;
}
}
在我的onClick方法中:
public void onClick(View view) {
    Thread t = new Thread(){
                @Override
                public void run() {
                    Log.d("Audio", "Downloading all the audio for the chapter");
                    activity.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(context, "Downloading all the audio for the chapter", Toast.LENGTH_SHORT).show();
                        }
                    });
                    final CountDownLatch latch = new CountDownLatch(chapters.get(groupPosition).getSubChapters().size());
                    Executor ex;
                    for (int child = 0; child < chapters.get(groupPosition).getSubChapters().size(); child++) {
                        String Language = "en";
                        String FileName = subChapters.get(chapters.get(group)).get(child).getAudio();
                        audioDownload = new AudioDownload(Course, FileName, Language, activity, latch);
                        audioDownload.setOnAudioDownloadFinishedListener(new AudioDownload.OnAudioDownloadFinishedListener() {
                            @Override
                            public void OnAudioDownloadThreadFinished(Boolean success) {
                                if (success) {
                                    activity.runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            Toast.makeText(context, "Download was successful", Toast.LENGTH_SHORT).show();
                                        }
                                    });
                                }
                            }
                        });
                        ex = new Executor() {
                            @Override
                            public void execute(Runnable command) {
                                command.run();
                            }
                        };
                        ex.execute(audioDownload);
                    }
                    try{
                        latch.await();
                        Log.d("multipleAudioDownload", "all Threads are finished");
                        Toast.makeText(context, "Downloading was successful", Toast.LENGTH_SHORT).show();
                    }catch (InterruptedException e){
                        Log.e("multipleAudioDownload",e.getMessage());
                    }
                }
            };
            t.start();
        }

如果你想让线程等待,直到其他线程先完成一个' CountDownLatch '将是一个巨大的帮助。下面是如何使用它的快速sudocode示例。

' CountDownLatch l = new CountDownLatch(n) 'N是需要等待的任务数

´l.await()´在后台线程中调用它,直到所有n个任务完成,它将阻塞

´l.countdown()´在每个任务完成后调用,减少这个锁存器等待的任务数一旦它达到0,await方法将被释放,之后的代码将在该点执行

最新更新