安卓- 下载文件 + 状态栏通知会使手机变慢

5

我目前拥有一个 asynctask,它可以从服务器下载 MP3。当用户开始下载时,会创建一个状态栏通知。这将实时显示下载的进度。我的唯一担忧是手机几乎被拖慢了。是否有延迟显示进度或加快代码速度的方法?谢谢。

以下是代码:

public class DownloadFile extends AsyncTask<String, String, String> {
    CharSequence contentText;
    Context context;
    CharSequence contentTitle;
    PendingIntent contentIntent;
    int HELLO_ID = 1;
    long time;
    int icon;
    CharSequence tickerText;
    File file;

    public void downloadNotification() {
        String ns = Context.NOTIFICATION_SERVICE;
        notificationManager = (NotificationManager) getSystemService(ns);

        icon = R.drawable.sdricontest;
        //the text that appears first on the status bar
        tickerText = "Downloading...";
        time = System.currentTimeMillis();

        notification = new Notification(icon, tickerText, time);

        context = getApplicationContext();
        //the bold font
        contentTitle = "Your download is in progress";
        //the text that needs to change
        contentText = "0% complete";
        Intent notificationIntent = new Intent(Intent.ACTION_VIEW);
        notificationIntent.setType("audio/*");
        contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);

        notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
        notificationManager.notify(HELLO_ID, notification);
    }

    @Override
    protected void onPreExecute() {
        //execute the status bar notification
        downloadNotification();
        super.onPreExecute();
    }

    @Override
    protected String doInBackground(String... url) {
        int count;
        try {
            URL url2 = new URL(sdrUrl);
            HttpURLConnection connection = (HttpURLConnection) url2.openConnection();
            connection.setRequestMethod("GET");
            connection.setDoOutput(true);
            connection.connect();

            int lengthOfFile = connection.getContentLength();

            //make the stop drop rave folder
            File sdrFolder = new File(Environment.getExternalStorageDirectory() + "/StopDropRave");
            boolean success = false;

            if (!sdrFolder.exists()) {
                success = sdrFolder.mkdir();
            }
            if (!success) {
                String PATH = Environment.getExternalStorageDirectory()
                        + "/StopDropRave/";
                file = new File(PATH);
                file.mkdirs();
            } else {
                String PATH = Environment.getExternalStorageDirectory()
                        + "/StopDropRave/";
                file = new File(PATH);
                file.mkdirs();
            }

            String[] path = url2.getPath().split("/");
            String mp3 = path[path.length - 1];
            String mp31 = mp3.replace("%20", " ");
            String sdrMp3 = mp31.replace("%28", "(");
            String sdrMp31 = sdrMp3.replace("%29", ")");
            String sdrMp32 = sdrMp31.replace("%27", "'");

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

            InputStream input = connection.getInputStream();

            byte[] data = new byte[1024];
            long total = 0;
            while ((count = input.read(data)) != -1) {
                total += count;
                publishProgress("" + (int) (total * 100 / lengthOfFile));
                fos.write(data, 0, count);
            }
            fos.close();
            input.close();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalStateException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void onProgressUpdate(String... progress) {
        contentText = Integer.parseInt(progress[0]) + "% complete";
        notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
        notificationManager.notify(HELLO_ID, notification);
        super.onProgressUpdate(progress);
    }
}
4个回答

5

我看到了类似的结果,你需要减少更新通知的频率,在onProgressUpdate中跟踪上一次调用notify的时间,并只在超过上一次调用100ms,或者达到最大值时再调用notify,从而将更新频率降低到每秒几次。


是的,您发布了太多的通知。请尝试仅在百分比实际变化至少1%后进行更新。 - dmon
听起来不错。你有示例代码或者实现的大致思路吗? - Splitusa
@dmon - 我该如何追踪这个问题? - Splitusa
4
在调用 publishProgress(...) 之前,添加一个整型变量,并使用以下代码:if ((int) (total * 100 / lengthOfFile) > previousProgress) { previousProgress = (int) (total * 100 / lengthOfFile); publishProgress(...); } 这将确保进度只会在整数百分比更改时更新,并通过 previousProgress 变量记录先前的进度。 - dmon
所以我在while循环中添加了以下内容:int prevprogress = (int) (total * 100 / lengthOfFile); if ((int) (total * 100 / lengthOfFile) > prevprogress) { prevprogress = (int) (total * 100 / lengthOfFile); publishProgress(""+(int) (total * 100 / lengthOfFile)); fos.write(data, 0, count); 程序仍然运行得非常慢。我做得对吗? - Splitusa
不要只使用百分比,而是同时使用百分比和时间值。如果你只在下载小文件时处理百分比,那么你会推送过多的通知更新。因此,同时使用这两个条件是最好的选择。 - Moss

5

我曾遇到类似的问题,我使用CountDownTimer解决了它。

像@superfell建议的那样,你可以在下载文件时定期调用AsyncTask的进度更新。并且仅在特定间隔调用通知管理器。

调用CountDownTimer的start()后,它将在固定时间间隔后调用onTick()函数,而且当计时器超时或被显式调用时,将调用onFinish()方法。 cancel()函数仅取消计时器,不会调用onFinish()方法。

class DownloadMaterial extends AsyncTask<String, String, String> {

    CountDownTimer cdt;
    int id = i;
    NotificationManager mNotifyManager;
    NotificationCompat.Builder mBuilder;

    @Override
    protected void onPreExecute() {
        /**
         * Create custom Count Down Timer
         */
        cdt = new CountDownTimer(100 * 60 * 1000, 500) {
            public void onTick(long millisUntilFinished) {
                mNotifyManager.notify(id, mBuilder.build());
            }

            public void onFinish() {
                mNotifyManager.notify(id, mBuilder.build());
            }
        };
    }

    @Override
    protected String doInBackground(String... strings) {
        /**
         * Start timer to update Notification
         * Set Progress to 20 after connection
         * Build Notification
         * Increment Progress
         * Download and Save file
         */
        try {
            mNotifyManager =
                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            mBuilder = new NotificationCompat.Builder(context);
            mBuilder.setContentTitle("Downloading File")
                    .setContentText(file_name)
                    .setProgress(0, 100, false)
                    .setOngoing(true)
                    .setSmallIcon(R.mipmap.ic_launcher)
                    .setPriority(Notification.PRIORITY_LOW);

            // Initialize Objects here
            publishProgress("5");
            mNotifyManager.notify(id, mBuilder.build());
            cdt.start();

            // Create connection here
            publishProgress("20");

            // Download file here
            while ((count = input.read(data)) != -1) {
                total += count;
                publishProgress("" + (int) (20 + (total * 80 / fileLength)));
                output.write(data, 0, count);
            }
        } catch (Exception e) {
            return "Failed";
        }
        return "Success";
    }

    @Override
    protected void onProgressUpdate(String... values) {
        /**
         * Update Download Progress
         */
        mBuilder.setContentInfo(values[0] + "%")
                .setProgress(100, Integer.parseInt(values[0]), false);
    }

    @Override
    protected void onPostExecute(String s) {

        String title;
        if (s.equals("Success")) {
            title = "Downloaded";
        } else {
            title = "Error Occurred";
        }
        mBuilder.setContentTitle(title)
                .setContentInfo("")
                .setOngoing(false)
                .setProgress(0, 0, false);
        cdt.onFinish();
        cdt.cancel();
    }
}

最佳实践是先调用 onFinish(),然后再调用 cancel()


谢谢,但是小图标丢失了...所以你可能想要添加例如".setSmallIcon(R.mipmap.ic_launcher)" - Martin Pfeffer
@MartinPfeffer 谢谢。已做出相应修改。 - Lalit Umbarkar

2

我也遇到过这个问题。我更新进度条的频率太高了(即使进度没有改变),以下是我如何解决这个问题:

        // While loop from generic download method.
        int previousProgress = 0;
        while ((count = inputStream.read(buff)) != -1) {
            outputStream.write(buff, 0, count);
            totalBytesDownloaded += count;
            int prog = (int) (totalBytesDownloaded * 100 / contentLength);
            if (prog > previousProgress) {
                // Only post progress event if we've made progress.
                previousProgress = prog;
                myPostProgressMethod(prog);

            }
        }

现在应用程序运行良好,用户仍然会收到进度通知。

然而,如果下载服务器过快,这仍然不足够。 - Vahid Amiri
@VSG24,什么?那更新UI/进度通知的频率再降低一些怎么样?说实话,这不是“太快”的服务器的问题,很可能是你的代码。 - Sakiboy

0
我曾经遇到过同样的问题,即使间隔3秒,也无法更新进度条通知。经过数小时的研究,我意识到当我们更新通知时,RemoteView对象必须重新实例化并重新初始化为Notification对象的contentView。这样做后,我能够在一个很长的时间内以100ms-500ms的间隔更新通知进度条,而不会遇到任何UI阻塞。
注意:如果您不同意,可以注释掉标记的行并运行此片段进行验证,看看有何不同。这可能需要大约5分钟才能开始严重的UI阻塞,这将导致设备发热并可能停止正常工作。 我尝试了一下搭载Android 4.2.2的S3 mini手机,并且updateNotification(....)方法是从一个服务中的工作线程调用的。此外,我已经仔细检查过,并且不知道当使用Notification.Builder来达到相同目的时会发生什么。
注意:之所以在问题提出3年后写下这个答案,是因为我惊讶地发现连一个stackoverflow回答或其他博客文章都没有涉及到这个严重问题,而解决方案却非常简单明了。
希望这个答案对其他像我一样的新手有所帮助。 愉快编程!

这是我复制粘贴的代码,您可以直接使用... 我使用相同的代码来更新包含两个ProgressBar和四个TextView的通知布局,频率为500ms-100ms。

//long mMaxtTimeoutNanos = 1000000000 // 1000ms.
long mMinTimeNanos     = 100000000;//100ms minimum update limit. For fast downloads.
long mMaxtTimeoutNanos = 500000000;//500ms maximum update limit. For Slow downloads
long mLastTimeNanos = 0;
private void updateNotification(.....){
    // Max Limit
    if (mUpdateNotification || ((System.nanoTime()-mLastTimeNanos) > mMaxtTimeoutNanos)) {
        // Min Limit
        if (((System.nanoTime() - mLastTimeNanos) > mMinTimeNanos)) {
            mLastTimeNanos = System.nanoTime();
            // instantiate new RemoteViews object.
            // (comment out this line and instantiate somewhere
            // to verify that the above told answer is true)
            mRemoteView = new RemoteViews(getPackageName(),
                    R.layout.downloader_notification_layout);
            // Upate mRemoteView with changed data
            ...
            ...
            // Initialize the already existing Notification contentView
            // object with newly instatiated mRemoteView.
            mNotification.contentView = mRemoteView;
            mNotificationManager.notify(mNotificatoinId, mNotification);
            mUpdateNotification = false;
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接