Android中多线程下载列表的封装实现(含进度反馈)

来源:http://blog.csdn.net/u011638883/article/details/17347015

实现了一下Android中的文件多线程下载模块,支持自定义线程数、断点续传、下载任务的删除,添加等功能,这里封装了一下,功能已全部实现。不过由于使用的是最简单的手动线程数组及消息通知实现,可能还存在某些小问题。笔者会在后面的使用过程中再进行优化完善。先看一下程序测试效果,这里指定了5个下载任务,以及2个下载线程,具体如下:

要运行以上Demo需要自己搭建服务器,和简单,只需要把所需的文件拷贝到Tomcat中的../webapps/ROOT文件夹下即可,以下是笔者的电脑:

其中的0.mp3,1.mp3....11.mp3及本Demo的测试数据。

除此之外还需要引入Afinal类库,已包含在工程下载中了(下载地址在本文最后)

主要类介绍

主界面MainActivity,负责初始化任务参数,用户可以单击列表中的按钮来删除、暂停、继续任务。可以看到有一个refreshHandler负责根据不同的Message来对列表进行不同的刷新操作。

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

package com.wly.filedownloader;

import java.util.List;

import com.wly.filedownloader.dao.BeanHelper;

import net.tsz.afinal.FinalDb;

import net.tsz.afinal.exception.AfinalException;

import android.os.Bundle;

import android.os.Handler;

import android.os.Message;

import android.app.Activity;

import android.app.AlertDialog;

import android.content.DialogInterface;

import android.graphics.Bitmap.Config;

import android.view.LayoutInflater;

import android.view.Menu;

import android.view.View;

import android.view.View.OnClickListener;

import android.view.ViewGroup;

import android.widget.BaseAdapter;

import android.widget.Button;

import android.widget.ListAdapter;

import android.widget.ListView;

import android.widget.ProgressBar;

import android.widget.RelativeLayout;

import android.widget.TextView;

public class MainActivity extends Activity{

    DownloadHelper downHelper;

    private LayoutInflater inflater;

    private ListView lv;

    

    //最大支持同时进行的线程数

    private int maxThread = 2;

    

    //测试下载任务数量

    private int taskSize = 5;

        

    private DynamicArray<bean> waitArray; //等待列表

    private DynamicArray<bean> workArray; //用来存放已完成及下载中的任务

    List<bean> list;

    

    //专门负责刷新界面的Handler

    private Handler refreshHandler = new Handler() {

        @Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

            switch(msg.what) {

            case Conf.State_FINISH:

                myAdapter.notifyDataSetChanged();

                break;

            case Conf.State_CANCEL:

                for(int i=0;i<workarray.size();i++) bean="" beanarray="new" bundle="" case="" cell="" conf.msg_statechanged:="" conf.state_download:="" conf.state_fileerror:="" firstv="lv.getFirstVisiblePosition();" for="" i="firstV;" int="" lastv="lv.getLastVisiblePosition();" lv="(ListView)" override="" protected="" view="" void="" workarray="new">();

        waitArray = new DynamicArray<bean>();

        

        for(int i=0;i<beanarray.length;i++) .mp3="" 10.0.2.2:8080="" aa_="" bean="" http:="" i=""> list = FinalDb.create(this).findAll(Bean.class);

        if(list.size() == 0) {

            for(int i=0;i<beanarray.length;i++) baseadapter="" bean="" button="" cancelbtn="(Button)convertView.findViewById(R.id.download_btn_cancel);" convertview="inflater.inflate(R.layout.download_list_item," czise:="" downhelper="new" download_level="(RelativeLayout)convertView.findViewById(R.id.download_level);" downloader="" else="" final="" finish_level="(RelativeLayout)convertView.findViewById(R.id.finish_level);" guid="bean.getGuid();" id:="" inflater="LayoutInflater.from(this);" int="" isenabled:="" isfinished:="" list="FinalDb.create(this).findAll(Bean.class);" myadapter="new" override="" pausebtn="(Button)convertView.findViewById(R.id.download_btn_pause);" percent:="" position="" progressbar="" protected="" public="" relativelayout="" resumebtn="(Button)convertView.findViewById(R.id.download_btn_resume);" string="" textview="" title="(TextView)convertView.findViewById(R.id.download_title);" title:="" url:="" view="" viewgroup="" viewholder="" void="" waitcancelbtn="(Button)convertView.findViewById(R.id.wait_btn_cancel);" waiting_level="(RelativeLayout)convertView.findViewById(R.id.waiting_level);"> 暂停/继续

                download_level.setVisibility(View.VISIBLE);

                waiting_level.setVisibility(View.INVISIBLE);

                finish_level.setVisibility(View.INVISIBLE);

                if(downloader.getstate() == Conf.State_DOWNLOAD) { //下载中

                    resumeBtn.setVisibility(View.INVISIBLE);

                    pauseBtn.setVisibility(View.VISIBLE);

                } else { //暂停中

                    resumeBtn.setVisibility(View.VISIBLE);

                    pauseBtn.setVisibility(View.INVISIBLE);

                    resumeBtn.setText(继续);

                }

            } else if(bean.getIsFinished() == Conf.TRUE) { //对应下载进度100 => 完成

                download_level.setVisibility(View.INVISIBLE);

                waiting_level.setVisibility(View.INVISIBLE);

                finish_level.setVisibility(View.VISIBLE);

            } else { //对应下载进度0 => 开始/等待

                //检查是否处于等待状态

                System.out.println(--开始/等待--);

                if(downloader != null) {

                    download_level.setVisibility(View.VISIBLE);

                    waiting_level.setVisibility(View.INVISIBLE);

                    finish_level.setVisibility(View.INVISIBLE);

                    

                    resumeBtn.setText(开始);

                } else {

                    download_level.setVisibility(View.INVISIBLE);

                    waiting_level.setVisibility(View.VISIBLE);

                    finish_level.setVisibility(View.INVISIBLE);

                }

            }

            

            pauseBtn.setOnClickListener(new View.OnClickListener() {

                

                @Override

                public void onClick(View v) {

                    downloader.pause();

                }

            });

            

            resumeBtn.setOnClickListener(new View.OnClickListener() {

                

                @Override

                public void onClick(View v) {

                    if(downloader != null && downloader.isActive()) {

                        downloader.recovery();

                        System.out.println(--recovery--);

                    } else {

                        downloader.start();

                        System.out.println(--start--);

                    }

                }

            });

            

            //下载中取消按钮

            cancelBtn.setOnClickListener(new View.OnClickListener() {

                

                @Override

                public void onClick(View v) {

                    downloader.cancel();

                }

            });

            

            //等待中取消按钮

            waitCancelBtn.setOnClickListener(new View.OnClickListener() {

                

                @Override

                public void onClick(View v) {

                    if(position < workArray.size()) {

                        downloader.cancel();

                    } else {

                        waitArray.delete(position-workArray.size());

                        BeanHelper.delete(MainActivity.this,bean);

                        myAdapter.notifyDataSetChanged();

                    }

                }

            });

            

            convertView.setId(position);

            return convertView;

        }

        

        @Override

        public long getItemId(int position) {

            return 0;

        }

        

        @Override

        public Object getItem(int position) {

            if(position < workArray.size()) {

                return workArray.getObjectAt(position);

            } else {

                return waitArray.getObjectAt(position-workArray.size());

            }

        }

        

        @Override

        public int getCount() {

            return waitArray.size() + workArray.size();

        }

    };

}

      </beanarray.length;i++)></beanarray.length;i++)></bean></workarray.size();i++)></bean></bean></bean>

下载器调度类DownloaderHelper,负责检查任务队列和下载器数组,并将下载任务分配给空闲的下载器。

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

package com.wly.filedownloader;

import com.wly.filedownloader.Downloader.MyDownloadListener;

import android.app.AlertDialog;

import android.content.Context;

import android.content.DialogInterface;

import android.os.Handler;

import android.os.Message;

/**

 * 本类中包含两个队列一个是等待中的实体Bean,另一个是下载中的任务队列, 当添加一个任务实体Bean时,会先检查是否有空余的下载器没有使用,如果有,就

 * 使用refreshDownloaderArray将该任务Bean从等待下载队列移动到下载中队列

 * 同样的,当一个任务下载成功后,先从下载中队列移除,然后得到一个空闲的下载器,最后调用插入流程,将一个新的任务Bean 添加到到下载中队列中去

 * 最后是删除(取消),如果发生在等待队列则删除数据即可,如果发生在下载中队列则复用下载完成逻辑即可。

 *

 * @author wly

 *

 */

public class DownloadHelper {

    private DynamicArray<bean> waitArray;

    private DynamicArray<bean> workArray;

    private int maxThread; // 最大同时进行任务数量,默认是1

    private Context mContext;

    /**

     * 下载器队列

     */

    private Downloader[] downladerArray;

    private Handler mHandler; // Activity中的Handler

    private long lastRefreshTime = 0;

    private int refresh_time = 0; // 每次刷新进度的最少间隔时间

    // 做一层过滤,控制界面刷新频率

    private Handler handler = new Handler() {

        @Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

            if (msg.what == Conf.State_FILEERROR) {

                System.out.println(--文件异常,发送重置对应下载任务界面显示信息!!!--);

            } else {

                // 每隔refresh_time时间发送一次刷新界面信息

                if ((System.currentTimeMillis() - lastRefreshTime) > refresh_time) {

                    mHandler.sendEmptyMessage(Conf.State_DOWNLOAD);

                    lastRefreshTime = System.currentTimeMillis();

                }

            }

        }

    };

    private MyDownloadListener myDownloadListener = new MyDownloadListener() {

        @Override

        public void finished(Bean bean) {

            // 1.尝试新的任务给空闲的下载器

            refreshDownloaderArray();

            // 2.刷新界面

            Message msg = new Message();

            msg.what = Conf.State_FINISH;

            mHandler.sendMessage(msg);

            System.out.println(--finished);

        }

        @Override

        public void started() {

            Message msg = new Message();

            msg.what = Conf.MSG_STATECHANGED;

            mHandler.sendMessage(msg);

        }

        @Override

        public void paused() {

            Message msg = new Message();

            msg.what = Conf.MSG_STATECHANGED;

            mHandler.sendMessage(msg);

        }

        @Override

        public void resumed() {

            Message msg = new Message();

            msg.what = Conf.MSG_STATECHANGED;

            mHandler.sendMessage(msg);

        }

        @Override

        public void canceled(String guid) {

            // 发送消息给Activity

            Message msg = new Message();

            msg.what = Conf.State_CANCEL;

            msg.obj = guid;

            mHandler.sendMessage(msg);

        }

        /**

         * 准备就绪(主要是文件校验过程),可以开始下载了,

         */

        @Override

        public void ready(String guid) {

            System.out.println(--准备就绪,可以开始下载了--);

        }

    };

    /**

     * 创建实体Bean的队列

     *

     * @param context

     * @param maxThread

     * @param beans

     * @param handler

     */

    public DownloadHelper(Context context, int maxThread,

            DynamicArray<bean> dArray, DynamicArray<bean> workArray,

            Handler handler) {

        this.maxThread = maxThread;

        this.mContext = context;

        this.waitArray = dArray;

        this.workArray = workArray;

        this.mHandler = handler;

        downladerArray = new Downloader[maxThread];

    }

    /**

     * 添加任务到队列

     *

     * @param bean

     */

    public void insert(Bean bean) {

        waitArray.insert(bean);

        refreshDownloaderArray();

    }

    

    /**

     * 初始启动Activity时,为下载器数组中的下载器分配下载任务

     */

    public void assignTaskToDownloaders() {

        int i = 0;

        while (i < maxThread && !waitArray.isEmpty()) {

            // 为下载器分配下载任务

            Bean bean = waitArray.poll();

            workArray.insert(bean); // 不管任务完成与否都插入到工作队列中去

            if (bean.getIsFinished() == Conf.FALSE) { // 只为未完成的任务分配下载器

                downladerArray[i] = new Downloader(mContext);

                downladerArray[i].assignTask(bean, myDownloadListener, handler);

                i++;

            }

        }

    }

    /**

     * 取消整个队列任务

     */

    public void kill() {

        for (Downloader d : downladerArray) {

            if (d != null) {

                d.kill();

            }

        }

    }

    /**

     * 检查下载器队列的状态,负责刷新下载器中的任务。通常在新增、更新、删除任务后调用

     */

    public void refreshDownloaderArray() {

        for (int i = 0; i < maxThread; i++) {

            if (downladerArray[i] != null && downladerArray[i].isIdle()) {

                if (!waitArray.isEmpty()) {

                    Bean bean = waitArray.poll();

                    workArray.insert(bean);

                    // 为下载器分配下载任务

                    downladerArray[i].assignTask(bean, myDownloadListener,

                            handler);

                    // 开始下载

                    downladerArray[i].recovery();

                } else {

                    downladerArray[i].kill();

                }

            }

        }

    }

    /**

     * 根据guid查询得到其对应的实体bean对象

     *

     * @param guid

     * @return

     */

    public Downloader getDownloaderByGuid(String guid) {

        for (Downloader d : downladerArray) {

            if (d != null && d.getBean().getGuid().equals(guid)) {

                return d;

            }

        }

        return null;

    }

    // /**

    // * 检查网络环境

    // */

    // public boolean isNetAvailable() {

    // return true;

    // }

}

</bean></bean></bean></bean>

下载器类Downloader,负责下载数据,将下载状态进行持久化。需要特别说明一下的是,这里的下载器在启动后会进入一个下载循环,即使当前任务断开服务器连接,进入暂停状态,本下载器不会停止,只是进入了wait状态。除此之外,下载器中还包含自定义接口MyDownloadListener,用于向Activity反馈当前的下载状态,以更新界面。

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

package com.wly.filedownloader;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.IOException;

import java.io.InputStream;

import java.io.RandomAccessFile;

import java.net.HttpURLConnection;

import java.net.MalformedURLException;

import java.net.ProtocolException;

import java.net.URL;

import com.wly.filedownloader.dao.BeanHelper;

import android.app.AlertDialog;

import android.content.Context;

import android.content.DialogInterface;

import android.os.Handler;

import android.os.Message;

/**

 * 下载器

 * @author wly

 *

 */

public class Downloader extends Thread {

    private Bean mBean;

    private MyDownloadListener mListener;

    

    //用于表征当前下载器是否处于空闲状态,如果是的话,可以为其分配新的下载任务,进入空闲状态只有两种方法:下载完成和取消下载

    private boolean isIdle;

    

    private boolean isAlive = false; //表示当前下载器是否已经激活,默认未激活

    

    private Handler handler;

    

    private Context mContext;

    

    private int beanPercent;   

    

    private int state;

    /**

     * 创建对象

     * @param context

     */

    public Downloader(Context context) {

        isIdle = true;

        this.mContext = context;

        state = Conf.State_WAITTASK;

    }

    public boolean isPause() {

        return (state == Conf.State_PAUSE);

    }

    

    public boolean isRun() {

        return (state == Conf.State_DOWNLOAD);

    }

    

    /**

     * 返回当前下载器的激活状态

     * @return

     */

    public boolean isActive() {

        return isAlive;

    }

    /**

     * 等到当前下载器状态

     * @return

     */

    public int getstate() {

        return state;

    }

    

    /**

     * 分配任务

     * @param id

     * @param bean

     * @param listener

     * @param handler

     */

    public void assignTask(Bean bean,MyDownloadListener listener,Handler handler) {

        this.mBean = bean;

        this.mListener = listener;

        this.handler = handler;

        isIdle = false;

    }

    

    public Bean getBean() {

        return this.mBean;

    }

    /**

     * 取消下载任务

     */

    public void cancel() {

        

            //先暂停当前任务

            if(!isPause()) {

                pause();

            }

            AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);

            dialog.setTitle(提示)

                .setMessage(确定要取消当前选中的任务吗?)

                .setPositiveButton(是的, new DialogInterface.OnClickListener() {

                    

                    @Override

                    public void onClick(DialogInterface dialog, int which) {

                        if(state == Conf.State_WAITTASK || state == Conf.State_PAUSE) {

                            isIdle = true;

                            BeanHelper.delete(mContext, mBean);

                            mListener.canceled(mBean.getGuid());

                            mBean.setPercent(0);

                            state = Conf.State_WAITTASK;

                        } else {

                            state = Conf.State_CANCELING;

                        }

                    }

                }).setNegativeButton(取消, new DialogInterface.OnClickListener() {

                    

                    @Override

                    public void onClick(DialogInterface dialog, int which) {

                        recovery(); //恢复下载

                    }

                }).show();

    }

    

    /**

     * 暂停任务

     */

    public void pause() {

        state = Conf.State_PAUSE;

        mListener.paused();

    }

    

    public void recovery() {

        state = Conf.State_DOWNLOAD;

        mListener.resumed();

        interrupt();

    }

    

    public boolean isPaused() {

        return state == Conf.State_PAUSE;

    }

    

    /**

     * 取消当前任务

     */

    public void kill() {

        System.out.println(Kill the downloader with guid: + mBean.getGuid());

        state = Conf.State_WAITTASK;

        isAlive = false;

    }

    

    public boolean isIdle() {

        return isIdle;

    }

    

    @Override

    public void run() {

        synchronized (this){

            long fileSize = 0;

            RandomAccessFile randomAccessFile = null;

            long completedSize = 0;

            isAlive = true;

            isIdle = false;

            state = Conf.State_DOWNLOAD;

            InputStream is = null;

            HttpURLConnection conn = null;

            File file = null;

            mBean.setIsEnabled(Conf.TRUE);

            while(isAlive) {

                //如果有可下载任务,就进行下载

                if(state == Conf.State_DOWNLOAD) {

                    //1.读取当前下载任务信息,如果是断点继续下载,则进行文件连续性判断            

                    beanPercent = mBean.getPercent();

                    completedSize = mBean.getCompleteSize();

                    //检查本地文件是否存在

                    File pathFile = new File(Conf.getSaveDir(mContext));

                    if (!pathFile.exists()) {

                        pathFile.mkdirs();

                    }

                    file = new File(Conf.getSaveDir(mContext)

                            + File.separator  + mBean.getTitle());

                    System.out.println(新任务: + mBean.getTitle());

                    if(!file.exists()) {

                        if(mBean.getCompleteSize() == 0) {//新任务

                            try {

                                file.createNewFile();

                            } catch (IOException e) {

                                e.printStackTrace();

                            }

                        } else { //暂停任务,但是文件不存在,则重置下载任务

                            mBean = new Bean(mBean.getGuid(), mBean.getUrl(), mBean.getTitle(), Conf.State_WAITTASK, 0);

                            BeanHelper.update(mContext, mBean);

                            beanPercent = mBean.getPercent();

                            completedSize = mBean.getCompleteSize();

                            

                            Message msg = new Message();

                            msg.what = Conf.State_FILEERROR;

                            handler.sendMessage(msg);

                        }

                    }

                    

                    

                    //2.与服务器检验文件的统一性

                    //verifyFileWithServer();

                    

                    //从当前进度点开始数据下载

                    URL url;

                    try {

                        url = new URL(mBean.getUrl());

                        conn = (HttpURLConnection) url.openConnection();

                        conn.setDoInput(true);

                        conn.setDoOutput(true);

                        conn.setConnectTimeout(Conf.NET_TIMEOUT_CONNECT);

                        conn.setReadTimeout(Conf.NET_TIMEOUT_READ);

                        conn.setRequestMethod(GET);

                        

                        fileSize = conn.getContentLength(); //得到文件尺寸

                        

                        try {

                            randomAccessFile = new RandomAccessFile(file, rwd);

                            randomAccessFile.setLength(mBean.getCompleteSize());

                            beanPercent = (int)((double)(completedSize) / fileSize * 100);

                            randomAccessFile.seek((long)beanPercent);

                        } catch (FileNotFoundException e) {

                            e.printStackTrace();

                            //重置下载任务

                            

                        } catch (IOException e) {

                            e.printStackTrace();

                            //重置下载任务

                        }

                        

                        //准备就绪

                        mListener.started();

                        mBean.setIsEnabled(Conf.TRUE);

                        mListener.ready(mBean.getGuid());  

                    } catch (MalformedURLException e1) {

                        e1.printStackTrace();

                    } catch (ProtocolException e) {

                        e.printStackTrace();

                    } catch (IOException e) {

                        e.printStackTrace();

                    }

                    

                    //开始下载

                    try {

                        is = conn.getInputStream();

                        int length = -1;

                        byte[] buffer = new byte[2048];

                        while(state == Conf.State_DOWNLOAD &&

                                    (length=is.read(buffer)) != -1) {

                            randomAccessFile.write(buffer, 0, length);

                            completedSize += length;

                            beanPercent = (int)((double)(completedSize) / fileSize * 100);

                            mBean.setPercent(beanPercent);

                            

                            Message msg = new Message();

                            handler.sendMessage(msg);

                            

                            System.out.println(percent: + beanPercent);

                            //当前任务已完成,进入空闲状态

                            if(beanPercent == 100) {

                                System.out.println(--一个下载任务完成--);

                                mBean.setIsFinished(Conf.TRUE);

                                mBean.setIsEnabled(Conf.FALSE);

                                //1.数据持久化动作

                                mBean.setPercent(beanPercent);

                                mBean.setCompleteSize(completedSize);

                                BeanHelper.update(mContext, mBean);

                                System.out.println(---更新状态完毕---);

                            

                                //2.下载完毕,发送通知,刷新界面,等待新任务

                                //告知下载队列,请求新任务

                                isIdle = true;

                                state = Conf.State_WAITTASK;

                                mListener.finished(mBean);

                                beanPercent = 0;

                                completedSize = 0;

//                              try {

//                                  sleep(1000);

//                                  System.out.println(--等待分配新的任务--);

//                              } catch (InterruptedException e) {

//                                  e.printStackTrace();

//                              }

                                break;

                            }

                        }

                    } catch (IOException e1) {

                        e1.printStackTrace();

                    }

                }

                

                if(state == Conf.State_PAUSE) {

                    //1.断开连接

                    if(conn != null) {

                        try {

                            is.close();

                            conn.disconnect();

                        } catch (IOException e) {

                            e.printStackTrace();

                        }

                        System.out.println(---断开连接----);

                        //2.进行数据持久化

                        mBean.setIsEnabled(Conf.TRUE);

                        mBean.setPercent(beanPercent);

                        mBean.setCompleteSize(completedSize);

                        BeanHelper.update(mContext, mBean);

                        System.out.println(---更新状态---);

                    }

                }

                

                if(state == Conf.State_CANCELING) {

                    //1.删除数据库数据

                    BeanHelper.delete(mContext, mBean);

                    //2.刷新界面

                    isIdle = true;

                    mListener.canceled(mBean.getGuid());

                    state = Conf.State_WAITTASK;

                }

//              try {

//                  System.out.println(下载器 + mBean.getGuid() + 正处于暂停/空闲状态);

//                  sleep(1000);

//              } catch (InterruptedException e) {

//                  e.printStackTrace();

//              }

                try {

                    wait();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

            System.out.println(This Downloader + mBean.getGuid() +  has been killed!!!);

        }

        

    }

    

    /**

     * 从文件/网路路径中解析出文件名

     * @param filePath

     * @return

     */

    public String getFileNameFromPath(String filePath) {

        String[] ss = filePath.split(//);

        return ss[ss.length-1];

    }

    

    /**

     * 下载过程的生命周期回调接口,用于刷新界面。注意:界面刷新是在数据更改之后的

     * @author wly

     *

     */

    public interface MyDownloadListener {

        /**

         * 下载完成,发送通知Helper,Helper再通过Handler通知Activity

         * @param downloaderID 下载器在下载器数组中的位置索引

         */

        public void finished(Bean bean);

        /**

         * 已经开始下载,下载中,本方法的调用在ready()之后

         * @param id

         */

        public void started();

        /**

         * 已经暂停下载

         * @param id

         */

        public void paused();

        /**

         * 已经恢复下载

         * @param id

         */

        public void resumed();

        /**

         * 已经取消下载

         * @param guid

         */

        public void canceled(String guid);

        /**

         * 已经准备就绪(文件校验,断点读取等),可以开始下载

         * @param guid

         */

        public void ready(String guid);

    }

}

自定义动态数组类DynamicArray,是下载任务容器,集合了队列和数组的特性。这是由模块的需求决定的,下载任务应该用优先队列(FIFO特性)来做,但是由于下载队列又需要支持用户的删除某个指定任务的功能(数组访问指定索引特性)。

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

package com.wly.filedownloader;

/**

 * 动态数组,结合队列(FIFO)和数组(根据索引进行删除元素)的特性

 *

 * @author wly

 *

 */

public class DynamicArray<t> {

    private T[] elems;

    private int mRight; // 右侧有内容索引值,即队列尾

    private int mLeft; // 左侧有内容索引值,即队列首

    private int INCREATE_STEP = 12;

    // public static void main(String[] args) {

    // DynamicArray<student> array = new DynamicArray<student>();

    // array.insert(new Student(A));

    // array.insert(new Student(B));

    // array.insert(new Student(C));

    // array.insert(new Student(D));

    // array.insert(new Student(E));

    // array.insert(new Student(F));

    //

    // array.poll();

    // array.peek();

    // array.delete(2);

    // array.getObjectAt(2);

    // System.out.println(array.size());

    //

    // }

    static class Student {

        String name;

        public Student(String name) {

            this.name = name;

        }

    }

    public DynamicArray() {

        elems = (T[]) new Object[INCREATE_STEP];

        mLeft = 0;

        mRight = 0;

    }

    /**

     * 插入一个元素到数组

     *

     * @param t

     */

    public void insert(T t) {

        // 扩展数组

        if (mRight >= elems.length) {

            T[] temp = (T[]) new Object[elems.length + INCREATE_STEP];

            for (int i = 0; i < elems.length; i++) {

                temp[i] = elems[i];

            }

            elems = temp;

            temp = null;

        }

        if (elems[mRight] == null) {

            elems[mRight++] = t;

        } else {

            elems[mRight++] = t;

        }

    }

    public T peek() {

        if (!isEmpty()) {

            return elems[mLeft];

        }

        return null;

    }

    /**

     * 弹出一个元素,将数组起点到p之间的元素都往右移动一位

     *

     * @return

     */

    public T poll() {

        if (mLeft == mRight) {

            System.out.println(数组为空,无法移除);

            return null;

        } else {

            T t = elems[mLeft];

            elems[mLeft++] = null;

            return t;

        }

    }

    /**

     * 删除mLeft和mRight之间的元素,从0开始

     *

     * @param p

     */

    public void delete(int p) {

        p = p + mLeft;

        if (p >= mRight) {

            System.out.println(无效的索引值,无法进行删除);

        } else {

            for (int i = p; i > mLeft; i--) {

                elems[i] = elems[i - 1];

            }

            elems[mLeft] = null;

        }

        mLeft++;

    }

    /**

     * 返回数组实际保存的有效个数

     *

     * @return

     */

    public int size() {

        return (mRight - mLeft);

    }

    /**

     * 得到mLeft和mRight之间第p个元素,从0开始

     *

     * @param p

     * @return

     */

    public T getObjectAt(int p) {

        p = p + mLeft;

        if (p >= mRight) {

            System.out.println(无效的索引值,无法进行查找);

            return null;

        } else {

            return elems[p];

        }

    }

    /**

     * 数组是否为空

     *

     * @return

     */

    public boolean isEmpty() {

        return (mRight <= mLeft);

    }

}

</student></student></t>

实体Bean类

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

package com.wly.filedownloader;

import net.tsz.afinal.annotation.sqlite.Id;

import net.tsz.afinal.annotation.sqlite.Table;

/**

 * 下载任务实体类

 * @author wly

 *

 */

@Table(name=Bean)

public class Bean {

    @Id

    private int id; //数据表查询主键

    private String url;

    private String title;

    

    private int isEnabled; //对应percent=(0,100),0表示false,1表示true

    private int isFinished; //对应percent=100,0表示false,1表示true

    

    private int percent; //表示当前下载任务的进度,需要持久化

    

    private long completeSize;

    

    private String guid; //下载实体类唯一性标识id

    

    /**

     * 默认构造函数,必须有

     */

    public Bean() {

        

    }

    

    /**

     * 新建任务,没有状态和进度,默认isEnabled、isFinished都为false

     */

    public Bean(String url,String title) {

        this.url = url;

        this.title = title;

        this.percent = 0;

        this.completeSize = 0;

        this.isEnabled = Conf.FALSE;

        this.isFinished = Conf.FALSE;

    }

    /**

     * 从本地持久化处新建对象,包含状态和进度

     * @param url

     * @param title

     * @param state 0初始状态,1下载中,2暂停中

     * @param percent

     */

    public Bean(String guid,String url, String title, int state, int percent) {

        super();

        this.guid = guid;

        this.url = url;

        this.title = title;

        this.percent = percent;

    }

    public String getUrl() {

        return url;

    }

    public void setUrl(String url) {

        this.url = url;

    }

    public String getTitle() {

        return title;

    }

    public void setTitle(String title) {

        this.title = title;

    }

    public int getPercent() {

        return percent;

    }

    public void setPercent(int percent) {

        this.percent = percent;

    }

    public int getId() {

        return id;

    }

    public void setId(int id) {

        this.id = id;

    }

    public long getCompleteSize() {

        return completeSize;

    }

    public void setCompleteSize(long completeSize) {

        this.completeSize = completeSize;

    }

    public String getGuid() {

        return guid;

    }

    public void setGuid(String guid) {

        this.guid = guid;

    }

    public int getIsEnabled() {

        return isEnabled;

    }

    public void setIsEnabled(int isEnabled) {

        this.isEnabled = isEnabled;

    }

    public int getIsFinished() {

        return isFinished;

    }

    public void setIsFinished(int isFinished) {

        this.isFinished = isFinished;

    }

}

最后,配置参数类Conf

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

package com.wly.filedownloader;

import java.io.File;

import android.content.Context;

import android.os.Environment;

/**

 * 看出配置类

 * @author wly

 *

 */

public class Conf {

    public final static int NET_TIMEOUT_READ  = 5000; //读取超时

    public final static int NET_TIMEOUT_CONNECT = 20000; //连接超时

    

    public final static int State_DOWNLOAD = 1; //下载中状态

    public final static int State_PAUSE = 2; //暂停状态

    public final static int State_FINISH = 3;//完成状态

    public final static int State_CANCEL = 4; //DownloadBean被取消

    public final static int State_WAITTASK = 5; //等待下载任务状态,可能刚下载完一个任务,可能还没还是下载任务

    

    public final static int State_CANCELING = 7; //任务取消中

    

    public final static int State_FILEERROR = 6; //文件异常,文件版本不连续

    

    public final static int MSG_STATECHANGED = 9;//表示列表状态发生概念,通知Adapter刷新界面

    

    public final static int RETRY_TIMES = 3; //网络请求重试次数

    

    public final static int FALSE = 0;

    public final static int TRUE = 1;

    

    /**

     * 得到本地文件保存路径

     * @return

     */

    public static String getSaveDir(Context context) {

        

        String fileDir;

        

        if (android.os.Environment.getExternalStorageState().equals(

                android.os.Environment.MEDIA_MOUNTED)) { //判断SD卡是否存在

            File f = context.getExternalCacheDir();

            if (null == f) {

                fileDir = Environment.getExternalStorageDirectory().getPath()

                        + File.separator + context.getPackageName()

                        + File.separator + cache;

            } else {

                fileDir = f.getPath();

            }

        } else {

            File f = context.getCacheDir();

            fileDir = f.getPath();

        }

        return fileDir;

    }

}

几个实现过程中需要注意的地方

1、列表的进度反馈,因为列表需要支持拖动的同时刷新进度,但是如果简单的使用BaseAdapter.notifyDataSetChanged()来刷新的话,会出现两个问题: 一、列表拖动过程中有卡顿现象;二、列表上的按钮将变得难以响应单击事件。那么怎么解决这个问题呢?可以这么思考,如果不使用notifyDataSetChanged来刷新整张列表,而是直接取得列表项上的组件的引用,然后直接修改组件属性的话,就不会出现上述两个问题了。

解决:Google了很多,讲的多少使用ListView.getFirstVisiablePosition和getChildAt()配合使用,我也试了下,发现在拖动列表时会出现显示错乱的情况(主要问题是getChildAt得到的View并不是期望的View)。于是只能再自己想办法了,还好咱够聪明,难不倒咱。这里在getView中使用当前的为每个contentView分配一个id:

?


1

2

3

4

5

6

public View getView(final int position, View convertView, ViewGroup parent) {

        .......

                 .......       

        convertView.setId(position);

        return convertView;

    }

然后在需要刷新时根据id得到对应的列表项convertView对象,在取得其中的ProgressBar并为其设置进度即可。

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

//刷新可见区域列表进度值

                int firstV = lv.getFirstVisiblePosition();

                int lastV = lv.getLastVisiblePosition();

                for (int i = firstV; i <= lastV; i++)  {

                    

                    View cell = lv.findViewById(i);

                    cell.getId();

                    if(cell != null) {

                        Bean bean = workArray.getObjectAt(i);

                        if(bean != null) {

                            ((ProgressBar)cell

                                    .findViewById(R.id.download_progress))

                                    .setProgress(bean.getPercent());

                        }

                    }

                }

2、下载器队列的安排,因为有这样特定的需求,所以需要手动管理等待中队列和下载中队列。

3、下载模块的实现,可能不是很难,不过还是有一些需要注意的地方的。

时间: 2024-08-27 10:51:01

Android中多线程下载列表的封装实现(含进度反馈)的相关文章

Android中 多线程下载原理

计算每个线程的下载起始终止位置公式如下 文件读写方式4中类型 工程源码目录 package cn.itcast.download; import java.io.File; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; public class MulThreadDownloader { public stat

Android中多线程编程(四)AsyncTask类的详细解释(附源码)

Android中多线程编程中AsyncTask类的详细解释 1.Android单线程模型 2.耗时操作放在非主线程中执行 Android主线程和子线程之间的通信封装类:AsyncTask类 1.子线程中更新UI 2.封装.简化异步操作. 3.AsyncTask机制:底层是通过线程池来工作的,当一个线程没有执行完毕,后边的线程是无法执行的.必须等前边的线程执行完毕后,后边的线程才能执行. AsyncTask类使用注意事项: 1.在UI线程中创建AsyncTask的实例 2.必须在UI线程中调用As

[Android学习笔记]Android中多线程开发的一些概念

线程安全: 在多线程的情况下,不会因为线程之间的操作而导致数据错误. 线程同步: 同一个资源,可能在同一时间被多个线程操作,这样会导致数据错误.这是一个现象,也是一个问题,而研究如何解决此类问题的相关工作就叫做线程同步. android中,处理线程同步的手段就是:锁 一般分为公平锁和非公平锁: synchronized(内部锁,互斥锁):synchronized是JVM提供的线程同步机制,如果出现问题,JVM能捕获异常,并释放资源,具体实现机制需要查看JVM源码 synchronized的使用特

Android中网络框架的简单封装

个人博客 http://www.milovetingting.cn Android中网络框架的简单封装 前言 Android作为一款主要应用在移动终端的操作系统,访问网络是必不可少的功能.访问网络,最基本的接口有:HttpUrlConnection,HttpClient,而在后续的发展中,出现了Volley,OkHttp,Retrofit等网络封装库.由于各种原因,在实际的项目开发中,我们可能会需要在项目的版本迭代中,切换网络框架.如果对于网络框架没有好的封装,那么当需要切换网络框架时,可能就会

Android开发--多线程下载加断点续传

文件下载在App应用中也用到很多,一般版本更新时多要用的文件下载来进行处理,以前也有看过很多大神有过该方面的博客,今天我也自己来实践一下,写的一般,还请大家多提意见,共同进步.主要思路: 1.多线程下载: 首先通过下载总线程数来划分文件的下载区域:利用int range = fileSize / threadCount:得到每一段下载量:每一段的位置是i * range到(i + 1) * rang  - 1,注意最后一段的位置是到filesize - 1: 通过Http协议的Range字段实现

Android之——多线程下载示例(一)

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/46883927 一.概述 说到Android中的文件下载,Android API中明确要求将耗时的操作放到一个子线程中执行,文件的下载无疑是需要耗费时间的,所以要将文件的下载放到子线程中执行.下面,我们一起来实现一个Android中利用多线程下载文件的小例子. 二.服务端准备 在这个小例子中我以下载有道词典为例,在网上下载有道词典的安装包,在eclipse中新建项目web,将下载的

Android之——多线程下载演示样例

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/46883927 一.概述 说到Android中的文件下载.Android API中明白要求将耗时的操作放到一个子线程中运行,文件的下载无疑是须要耗费时间的.所以要将文件的下载放到子线程中运行. 以下,我们一起来实现一个Android中利用多线程下载文件的小样例. 二.服务端准备 在这个小样例中我下面载有道词典为例.在网上下载有道词典的安装包,在eclipse中新建项目web.将下载

Android版多线程下载器核心代码分享

首先给大家分享多线程下载核心类: 1 package com.example.urltest; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.RandomAccessFile; 6 import java.net.HttpURLConnection; 7 import java.net.MalformedURLException; 8 import java.net.URL; 9 im

Android 之多线程下载原理

在Android之中呢,对于多线程的操作很是平凡,所以对于多线程的理解越深,那么对于自己的程序便能够很好的运行 这也是对于Android开发是一个重要的知识点,那么我们现在来了解多线程的下载原理. android 多线程下载 多线程下载步骤: 1.本地创建一个跟服务器一样的大小一样的文件 临时文件. 2.计算分配几个线程去下载服务器上的资源 每个文件下载的位置. 3.开启线程,每一个线程下载对应的文件. 4.如果所有的线程都把自己的数据下载完成了,服务器上的资源就被下载到本地了 如图所示:(假设