毕业设计:下载
一、主要思路
访问网络资源并且下载下来的功能是通过OkHttp来实现的,具体操作在之前的文章中有。最麻烦的是各组件之间的交互问题。例如,我设置一个存放线程的列表,每建立一个下载事项就新建一个对应线程,并将其放到下载列表中,然后线程会自动执行到文件下载完毕。现在问题来了,我需要将下载进程实时反馈到“任务”面板上,并且还要能通过“任务”面板上的暂停/开始按钮操作线程,这怎么实现?
似乎能想到的唯一办法就是通过Handler实现。我在主线程里面定义一个Handler,初始化的时候改变这个Handler的handleMessage()方法,让它每接收到一个值为1的Message就致使adapter执行notifyDataSetChanged()方法。当然这里的这个adapter是和“任务”面板的listView绑定起来的。然后我想方设法地把这个handler通过参数传递到线程列表的Thread对象里面,然后每当Thread执行的进度发生变化(也就是progress向前推进),就通过这个handler发送一个值为1的Message,于是这两者就关联起来了。至于暂停/开始按钮的点击事件,是在adapter的getView()方法写好的,一旦触发就会去找线程列表,找到对应的线程并执行暂停/开始操作。
二、线程和线程的死锁问题
因为原来的suspend()方法和stop()方法容易导致死锁,所以在这个版本被废弃了,想要实现类似的功能只能自己通过设置标志位、wait()方法和synchronized来实现。
如代码中所示,resume(),suspend(),和stop()方法都只是修改运行状态(status)。在run()方法中,首先判断是否为stop,如果为假则再判断是否为suspend,如果为真则执行wait()方法给其他线程让路,如果为假则执行所需执行的内容。方法本身是不复杂的,我为了给程序添加对应的功能让其看起来有些复杂。
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import okhttp3.Call; import okhttp3.Callback; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import android.os.Environment; import android.os.Handler; import android.os.Message; import com.example.vcloud_3_25.MainActivity; import com.example.vcloud_3_25.utils.L; import com.example.vcloud_3_25.utils.TaskManager; public class DownloadThread implements Runnable { public static final int STOP = -1; public static final int SUSPEND = 0; public static final int RUNNING = 1; protected int status = 1; private String name; private String fileName; private int fileId; private int progress; private String fileType; private Handler mHandler;// private MainActivity activity; public DownloadThread(MainActivity context,Handler handler, String fileName, int fileId,String fileType) { this.mHandler = handler; this.activity = context; TaskManager.count++; this.name = "DownloadThread-" + TaskManager.count; this.fileId = fileId; this.fileName = fileName; this.fileType = fileType; progress = 0; } public String getFileType(){ return this.fileType; } public int getProgress() { return progress; } public String getName() { return name; } public String getFileName() { return fileName; } public void setFileName(String fileName) { this.fileName = fileName; } public int getFileId() { return fileId; } public void setFileId(int fileId) { this.fileId = fileId; } @Override public boolean equals(Object o) { DownloadThread t2 = (DownloadThread) o; return t2.getName() == this.getName(); } @Override public void run() { OkHttpClient client = new OkHttpClient(); final Request request = new Request.Builder().url( "http://192.168.23.1:8080/VCloud/files/" + fileId).build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { L.d("on response"); long sum = 0; long total; int len = 0; InputStream is = response.body().byteStream(); try { total = response.body().contentLength(); File file = new File(Environment .getExternalStorageDirectory(), "VcloudDownload/" + fileName); byte[] buf = new byte[128]; FileOutputStream fos = new FileOutputStream(file); L.d("start download"); while (status != STOP & (len = is.read(buf)) != -1) { if (status == SUSPEND) { try { synchronized (this) { wait(500); } } catch (InterruptedException e) { L.d(e.getMessage()); } } else { fos.write(buf, 0, len); sum += len; double temp = sum / (double) total; int recentProgress = (int) (temp * 100); if (recentProgress != progress) { progress = recentProgress; L.d("sum:" + sum + ", progress:" + progress); Message msg = mHandler.obtainMessage(1); mHandler.sendMessage(msg); } } } fos.flush(); fos.close(); is.close(); } catch (IOException ioe) { ioe.printStackTrace(); L.d(ioe.getMessage()); } L.d("download success!"); progress = 100; mHandler.sendMessage(mHandler.obtainMessage(1)); } @Override public void onFailure(Call call, IOException e) { L.d("enqueue failure!"); L.d(e.getMessage()); } }); } public synchronized void resume() { status = RUNNING; notifyAll(); L.d("Thread " + name + "resume!"); } public void suspend() { status = SUSPEND; L.d("Thread " + name + "suspend!"); } public void stop() { status = STOP; L.d("Thread " + name + "stop!"); } }
三、DownloadListAdapter
这个ListViewAdapter比较特殊,它需要一个List<DownloadThread>来构造,而这个List就是从主线程里面传过来的。它里面的item和List里面的Thread是一一对应的,因此不仅数据随时同步(通过notifyDataSetChanged就能实现),而且对按钮绑定监听器也能直接对每一个线程进行控制。
ViewHolder其实是比较难理解的一个东西。之前我使用的adapter是直接继承自SimpleAdapter所以没有使用到这个方法。根据我的理解,holder应该是一个指向ListView中item的指针,每次新增加一个item这个指针就会指向新增加的item,所以对holder中的成员操作实际就是对item中的成员操作。值得注意的是,对button设置监听器的时候,对button的操作一定是要基于onClick(View v)中的v,而不是holder.button,否则不管触发的是哪个item的button,都只会有最后一个item作出反应。
package com.example.vcloud_3_25.utils; import java.util.HashMap; import java.util.List; import java.util.Map; import thread.DownloadThread; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import com.example.vcloud_3_25.MainActivity; import com.example.vcloud_3_25.R; public class DownloadListAdapter extends BaseAdapter { private List<DownloadThread> mList; private MainActivity context; private ViewHolder holder = null; public DownloadListAdapter(MainActivity context) { this.mList = context.mTaskManager.mDownList; this.context = context; } @Override public int getCount() { return mList.size(); } @Override public Object getItem(int position) { return mList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { View view = convertView; if (view == null) { LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflater.inflate(R.layout.task_item, parent, false); holder = new ViewHolder(); holder.fileName = (TextView) view .findViewById(R.id.textView_taskName); holder.fileImag = (ImageView) view.findViewById(R.id.imag_task); holder.progressBar = (ProgressBar) view .findViewById(R.id.progressBar_task); holder.progressCount = (TextView) view .findViewById(R.id.textView_progress); holder.button = (Button) view.findViewById(R.id.button_task); holder.complete = (TextView) view .findViewById(R.id.textView_complete); view.setTag(holder); } else { holder = (ViewHolder) view.getTag(); } final DownloadThread thread = (DownloadThread) getItem(position); if (thread != null) { holder.fileName.setText(thread.getFileName()); holder.fileImag.setImageResource(FileHelper.getFileImag("other")); holder.progressBar.setProgress(thread.getProgress()); holder.fileImag.setImageResource(FileHelper.getFileImag(thread .getFileType())); holder.progressBar.setMax(100); holder.progressCount.setText(thread.getProgress() + "%"); holder.button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (((TextView) v).getText().equals("暂停")) { ((TextView) v).setText("开始"); // 暂停该线程 thread.suspend(); } else { ((TextView) v).setText("暂停"); // 继续执行该线程 thread.resume(); } } }); if (thread.getProgress() == 100) { holder.button.setVisibility(View.INVISIBLE); holder.complete.setVisibility(View.VISIBLE); } } return view; } public class ViewHolder { public TextView fileName; public ImageView fileImag; public ProgressBar progressBar; public TextView progressCount; public Button button; public TextView complete; } }
四、TaskManager
TaskManager其实是一个管理所有任务的类,所以添加线程到下载列表之类的操作都是基于这个类实现的。目前还在实现上传功能,所以给上传列表留了一个空。
package com.example.vcloud_3_25.utils; import java.util.ArrayList; import java.util.List; import thread.DownloadThread; import thread.UploadThread; import com.example.vcloud_3_25.MainActivity; public class TaskManager { public List<DownloadThread> mDownList; public List<UploadThread> mUpList; private MainActivity context; public static int count = 0; public TaskManager(MainActivity activity) { context = activity; init(); } public void init() { mDownList = new ArrayList<DownloadThread>(); mUpList = new ArrayList<UploadThread>(); } }
五、MainActivity
核心方法都说过了,所以只贴Handler的部分。
public Handler mHandler; mHandler = new Handler(){ @Override public void handleMessage(Message msg) { if(msg.what==1){ downloadAdapter.notifyDataSetChanged(); } } };
六、手机如何访问电脑上的Tomcat
电脑端下载一个免费WIFI,我使用的是腾讯管家的。打开免费WIFI,让手机连到这个网上,至此手机和电脑就连到同个局域网了。打开电脑的网络管理能看到这个局域网中电脑的IP,例如我的电脑端是192.168.23.1,而手机端是192.168.23.2。打开服务器,手机端访问192.168.23.1:8080,如果出现tomcat欢迎页面即成功。
如果电脑能访问到tomcat页面而手机端不能,多半是电脑防火墙的问题,关了它。
如果电脑端都不能访问,多半是tomcat配置出错了,最好的方法是重新解压重新绑定到Eclipse。