在使用DownloadManager下载时,在Android ListView中显示进度条

7
我有一个ListView,其中每个项目代表一个PDF文件。当用户点击一个项目时,应用程序必须下载该文件到外部存储器上。 现在下载不正常工作,但这不是我的问题。我想要一个ProgressBar,旋转式样,出现在列表中的每个项目旁边,同时文件被下载。
我的问题是:我找不到如何使旋转式进度条出现。在Download Manager之前,我尝试过使用AsyncTask,并且旋转式进度条可以工作。
以下是我的代码:
CategoryActivity.java(ListView的Activity)
@Override
public void onItemClick(AdapterView<?> parent, View view,
    int position, long id) {
    // Récupère les valeurs depuis ListItem
    udl = ((TextView) view.findViewById(R.id.udl)).getText().toString();
    // This is the spinning wheel
    loader = ((ProgressBar) view.findViewById(R.id.spinWheel2));

    filepath = dirpath + udl + ".pdf";
    File file = new File(filepath);
    if (file.exists()) {

        // If the file exists, I open it

    }else{ // Else I download it
        // Setting the spinning wheel to VISIBLE
        loader.setVisibility(View.VISIBLE);

        SharedPreferences codeSaveUrl = getSharedPreferences(PREFS_TEXT,Context.MODE_PRIVATE);
        url2 = codeSaveUrl.getString("defaut", ""); // Code Organisation
        // Constructing the uriString
        uri = url10 + url2 + "&file=" + udl ;

        Uri myuri = Uri.parse(uri);
        DownloadManager mgr=(DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
        mgr.enqueue(new DownloadManager.Request(myuri)
        .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI |DownloadManager.Request.NETWORK_MOBILE)
        .setAllowedOverRoaming(false)
        .setTitle(udl + ".pdf")
        .setDescription("Téléchargement en cours")
        .setDestinationInExternalPublicDir("/Protocols/", (udl+".pdf"))
        .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE));                    

        // Hiding the spinning wheel
        loader.setVisibility(View.GONE);

    }
}

请注意,如果我不让旋转的轮子消失,点击项目后它将一直可见。使用隐藏它的代码行,它甚至不会出现。
编辑:
我添加了一个广播接收器。
在onCreate()中添加以下两行代码:
final DownloadManager mgr=(DownloadManager)getSystemService(Context.DOWNLOAD_SERVICE);
registerReceiver(onComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));

而且添加了这个:
BroadcastReceiver onComplete = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            loader.setVisibility(View.GONE);
        }
    };

    @Override
    public void onDestroy(){
        super.onDestroy();
        unregisterReceiver(onComplete);
    }

编辑2:好的,这里是我做出的一些更改:

我将下载的id保存在一个变量中:

lastDownload = mgr.enqueue(new DownloadManager.Request(myuri)
                .setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI |DownloadManager.Request.NETWORK_MOBILE)
                .setAllowedOverRoaming(false)
                .setTitle(udl + ".pdf")
                .setDescription("Téléchargement en cours")
                .setDestinationInExternalPublicDir("/Protocols/", (udl+".pdf"))
                .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED));

根据下载的状态,我想要做不同的事情:

BroadcastReceiver onComplete = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) {
            Cursor c = mgr.query(new DownloadManager.Query().setFilterById(lastDownload));
            if(c.moveToFirst()){
                int x = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
                switch(x){
                case DownloadManager.STATUS_PAUSED:
                case DownloadManager.STATUS_PENDING:
                case DownloadManager.STATUS_RUNNING:
                    break;
                case DownloadManager.STATUS_SUCCESSFUL:
                    loader.setVisibility(View.GONE);
                    break;
                case DownloadManager.STATUS_FAILED:
                    //TODO: retry download
                    break;
                }
            }

        }
    };

问题是,旋转的轮子只会在listView中最后一个被点击的项目上隐藏。我尝试使用调试模式,但程序的行为是正确的(即每次下载都会调用loader.setVisibility(View.GONE))。我不知道为什么旋转的轮子除了最后一个被点击的项目之外,为什么不会隐藏。
编辑:3 我知道为什么旋转的轮子除了最后一个被点击的项目之外,为什么不会隐藏。
当我点击多个项目时,lastDownload会取最后一个被点击的id。因此,在广播接收器中,它会将最后一个被点击的项目的情况执行多次,即被点击的项目数量乘以最后一个项目的情况。
我尝试将lastDownload更改为长整型的数组/表,并将其与我认为包含在意图中的ID进行比较。
这是新代码(y=0,ld是被点击的项目数):
BroadcastReceiver onComplete = new BroadcastReceiver() {
        public void onReceive(Context context, Intent intent) { 
            long referenceId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
            if(y <ld){
                if(lastDownload[y] == referenceId){

            Cursor c = mgr.query(new DownloadManager.Query().setFilterById(lastDownload[y]));
            if(c.moveToFirst()){
                int x = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
                switch(x){
                case DownloadManager.STATUS_PAUSED:
                case DownloadManager.STATUS_PENDING:
                case DownloadManager.STATUS_RUNNING:
                    break;
                case DownloadManager.STATUS_SUCCESSFUL:
                    loader.setVisibility(View.GONE); // This loader is the one of the last item clicked
                    break;
                case DownloadManager.STATUS_FAILED:
                    //TODO: retry download
                    break;
                }

            }

                       y=y+1;

                   }
            }   
        }
    };

我没有写将变量归零的部分,但目前代码基本上按预期工作。仅剩的问题是我让消失的旋转轮是最后点击的项目的旋转轮,我知道原因。因为这一行:loader = ((ProgressBar) view.findViewById(R.id.spinWheel2));位于onItemClicked方法中。我认为我无法将其放在其他任何地方,因为它所在的视图不是活动的视图。

长话短说:我必须找到一种方法来访问我点击的项/视图的进度条,知道我可以在第一个到达广播接收器之前点击多个项。


编辑:4好的,我做了这个:

yzld一开始都设置为0

当单击一个项目时:

// Loader of the clicked item is made visible
loader[z].setVisibility(View.VISIBLE);

// Construction of the URL
SharedPreferences codeSaveUrl = getSharedPreferences(PREFS_TEXT,Context.MODE_PRIVATE);
url2 = codeSaveUrl.getString("defaut", ""); // Organization code
uri = url10 + url2 + "&file=" + udl ;

// URL parse to URI
Uri myuri = Uri.parse(uri);

// Enqueue file to downloads, with notification. Storage of download id in a table
lastDownload[ld] = mgr.enqueue(new DownloadManager.Request(myuri)
.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI |DownloadManager.Request.NETWORK_MOBILE)
.setAllowedOverRoaming(false)
.setTitle(udl + ".pdf")
.setDescription("Téléchargement en cours")
.setDestinationInExternalPublicDir("/Protocols/", (udl+".pdf"))
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE));

// Increment variables for next downloads
ld=ld+1;
z=z+1;

广播接收器:
// Broadcast Receiver called when a download is finished
BroadcastReceiver onComplete = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        // referenceId is the download's id for which the method is called
        long referenceId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        // If y (=0 at the beginning) is inferior to the number of downloads
        if(y <ld){
            // If the id of the download corresponds to the one for which the method is called
            if(lastDownload[y] == referenceId){
                // We define a cursor depending on the id
                Cursor c = mgr.query(new DownloadManager.Query().setFilterById(lastDownload[y]));
                if(c.moveToFirst()){
                    // Download status recovery
                    int x = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
                    switch(x){
                    // If download is paused, pending or running, we do nothing
                    case DownloadManager.STATUS_PAUSED:
                    case DownloadManager.STATUS_PENDING:
                    case DownloadManager.STATUS_RUNNING:
                        break;
                    // If file has successfully been downloaded, loader is hidden
                    case DownloadManager.STATUS_SUCCESSFUL:
                        loader[y].setVisibility(View.GONE); 
                        // Increment y to go to next download
                        y=y+1;
                        break;
                    // If download failed, it is retried
                    case DownloadManager.STATUS_FAILED:
                        //TODO: retry download
                        break;
                    }
                }
            }
        }
    }
};

正常情况下工作良好,但当下载大文件时点击小文件中的一个项目时会出现问题。小文件会优先处理,下载管理器不再按表格顺序进行,导致加载轮廓图无法消失。


编辑:5

我找到了实现我想要的功能的方法,请查看我的答案。

感谢您的帮助。


这也是这种情况的一个好解决方案。 - JVN
3个回答

2

好的,我成功地用哈希表实现了我想做的事情:

// HashTable to store download id's and loaders
Hashtable<Long, SyncedProgressBar> storeTable = new Hashtable<Long, SyncedProgressBar>();

在我的onClickListener方法中,在loader[z]lastDownload[ld]获取它们的值之后,我将它们放入HashTable中:(下载id将成为键,加载器将成为值)

// Storing download id and loader in HashTable
storeTable.put(lastDownload[ld], loader[z]);

在我的广播接收器的onReceive方法中,不要做这样的操作:
if(lastDownload[y] == referenceId)

我检查 HashTable 是否包含意图的下载 ID:

if(storeTable.containsKey(referenceId))

我将正确的值放入了加载器中:

loader[y] = storeTable.get(referenceId);

那么我只需要在想要的地方将加载器的可见性设置为GONE。这个解决方案对我有效,但如果我发现了新的东西,我会更新它。

以下是我的新代码:

在onClickListener方法中:(此处未完整呈现)

// Loader of the clicked item is made visible
loader[z].setVisibility(View.VISIBLE);

// Construction of the URL
SharedPreferences codeSaveUrl = getSharedPreferences(PREFS_TEXT,Context.MODE_PRIVATE);
url2 = codeSaveUrl.getString("defaut", ""); // Organization code
uri = url10 + url2 + "&file=" + udl ;

// URL parse to URI
Uri myuri = Uri.parse(uri);

// Enqueue file to downloads, with notification. Storage of download id in a table
lastDownload[ld] = mgr.enqueue(new DownloadManager.Request(myuri)
.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI |DownloadManager.Request.NETWORK_MOBILE)
.setAllowedOverRoaming(false)
.setTitle(udl + ".pdf")
.setDescription("Téléchargement en cours")
.setDestinationInExternalPublicDir("/Protocols/", (udl+".pdf"))
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE));

// Storing download id and loader in HashTable
storeTable.put(lastDownload[ld], loader[z]);

// Increment variables for next downloads
ld=ld+1;
z=z+1;

广播接收器:

// Broadcast Receiver called when a download is finished
BroadcastReceiver onComplete = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        // referenceId is the download's id for which the method is called
        long referenceId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
        // If y (=0 at the beginning) is inferior to the number of downloads
        if(y <ld){
            // If the HashTable contains the Key-download-id for which the method is called
            if(storeTable.containsKey(referenceId)){
                // Loader takes the value for the key
                loader[y] = storeTable.get(referenceId);
                // We define a cursor depending on the id
                Cursor c = mgr.query(new DownloadManager.Query().setFilterById(referenceId));
                if(c.moveToFirst()){
                    // Download status recovery
                    int x = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
                    switch(x){
                    // If download is paused, pending or running, we do nothing
                    case DownloadManager.STATUS_PAUSED:
                    case DownloadManager.STATUS_PENDING:
                    case DownloadManager.STATUS_RUNNING:
                        break;
                    // If file has successfully been downloaded, loader is hidden
                    case DownloadManager.STATUS_SUCCESSFUL:
                        loader[y].setVisibility(View.GONE); 
                        // Increment y to go to next download
                        y=y+1;
                        break;
                    // If download failed, it is retried
                    case DownloadManager.STATUS_FAILED:
                        //TODO: retry download
                        break;
                    }
                }
            }
        }
    }
};

1
你好,能否提供完整的解决方案?我想做类似的任务,但我想展示每个下载的进度以及当前正在下载的文件数量。 - Bhavik Mehta
什么是SyncedProgressBar类? - Shahab Saalami

1
请注意,DownloadManager.enqueue是异步的,这意味着mgr.enqeue几乎立即返回,之后您的当前代码将spinner设置为不可见。
要隐藏spinner,您必须注册广播接收器以在下载完成时接收通知。然后,您必须找到相应的spinner并将其隐藏。请注意,下载可能会失败(在这种情况下仍会发送通知)。
CommonsWare发布了一个示例,展示如何使用DownloadManager。

已添加广播接收器。现在当我点击相应的项目时,下拉菜单会变得可见,但有些在下载完成后仍然保持可见状态。 - MAxeF
我编辑了我的问题,但还没有实现下载失败的情况。 - MAxeF
你的代码只在最后一个被点击的项目上隐藏了spinner。这是因为loader在onItemClick中获取了它的值。你需要存储mgr.enqueue返回的下载ID。BroadcastReceiver接收到的Intent包含刚完成的下载的ID。使用这个信息,你可以找到要禁用的spinner。 - Okas
好的,如果我按照你提供的链接这样做,我可以使用lastDownload = mgr.enqueue(...);将下载ID存储在变量中。然后在BroadcastReceiver中,我想我必须使用setFilterById(lastDownload),但是在那种情况下,我不知道在哪里放置loader.setvisibility(View.GONE) - MAxeF

-1
我会使用AsyncTask,然后在onPreCreate和onPostCreate中控制进度条的显示/隐藏。
private class MyDownloader extends AsyncTask<String, Void, File>{

    @Override
    protected void onPreExecute() {
        loader.setVisibility(View.VISIBLE);
    }

    @Override
    protected File doInBackground(String... params) {
        //Download and save file here, and return the result, which will be fed into the onPostExecute method
        return file;
    }

    @Override
    protected void onPostExecute(File file) {
        super.onPostExecute(file);
        loader.setVisibility(View.GONE);
    }
}

然后使用新的MyDownloader().execute("linktopdf")或类似内容启动任务(execute中的内容将被提供给doInBackground方法)


请注意,在doInBackground中,您将不得不以同步方式下载文件,以便它会阻塞AsyncTask。 - Squeazer
做不到,用户必须能够同时下载多个文件(或单击多个文件一个接一个地下载),并且在下载过程中必须能够进行其他操作。我已经尝试使用AsyncTask,但也存在旋转圆圈问题(有时不会消失,有时会克隆到屏幕外的另一个项目上)。 - MAxeF

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