三更灯火五更鸡,正是男儿读书时。黑发不知勤学早,白首方悔读书迟。——颜真卿
因为内容还没学太深,这篇就是已学到的知识做个总结,还没做太多个人的思考研究,做下记录留给自己看以后好补充。
一、简单方法在WORK线程中更新UI
学习一段时间了,渐渐知道网络连接、IO操作之类应该放在线程中运行,而有时候这类操作过程中我们也许在某一步之后获得数据希望将这个数据显示到界面的时候,就会有些问题,你会发现若是在线程中使用setText这类影响UI的方法时程序就会产生异常,因为我们Android的UI是线程不安全的,所以我们要想更新UI就必须在主线程(UI线程)。但是正如上面举例的情况,我们有时必须要在线程中根据其中运行获得的数据更新UI,这时我们有几种比较好的解决方案:Handler、AsyncTask.....但也许你的线程内的操作并不复杂,或是只想简单的把运行结果显示到UI上,可以用下面的几个简单的方法。
⑴Activity.runOnUiThread(Runnable action)
正如这段英文的意思“运行在UI线程”,不用多说啦,它的使用方法就是:
new Thread(new Runnable() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
mTestTextView.setText("啦啦啦");
}
});
}
}).start();
这样就简单的在线程中更新了TextView。当然你可以在runOnUiThread的前后进行其它操作。我们可以看看它的源码:
/**
* Runs the specified action on the UI thread. If the current thread is the UI
* thread, then the action is executed immediately. If the current thread is
* not the UI thread, the action is posted to the event queue of the UI thread.
*
* @param action the action to run on the UI thread
*/
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
可知该方法会首先判断现在的线程是不是UI线程,是则已,不是则实际使用了Handler的post方法来处理了我们写在runOnUiThread的这段操作。
⑵View.post(Runnable action)
这个方法很尴尬的,我在做类似runOnUiThread那样的操作的时候失败了(因为我是在onCreate方法中写的线程,下面会说为啥不行),这个方法是从网上看到的和runOnUiThread一起被列出的,为啥没效果呢?我稍微研究了一下,记录一下我的错误使用,先来看看它的源码:
/**
* Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.
*
* @param action The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*
* @see #postDelayed
* @see #removeCallbacks
*/
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
首先可以看到该方法会先判断AttachInfo对象的内容是否为空,若不为空,则同样是用了Handler方法来处理我们的runnable,若为空,可以看到下面的注释“假设post最后能成功”(什么鬼?)首先ViewRootImpl这个类与视图的绘制有关——扩展阅读ViewRootImpl,然后可以看到其利用了其内部方法得到运行队列,且其post方法是其静态最终内部类RunQueue中自定义的post方法,而其中也有postDelayed方法,参数为(Runnable action, long delayMillis) 第二个参数就是延迟毫秒数。总之看起来View.post()方法看起来就像Handler的post方法,但为什么我像runOnUiThread那样使用却无效呢,我百度了一下查到一句这样的话“This method can be invoked from outside of the UI thread only when this View is attached to a window.”意思大概是该方法要想影响到UI线程必须等到视图(view)附加到窗口(window)。什么鬼?最后参考了一百度了一下这个“attached to a window”了解到Activity中还有个onAttachedToWindow ()方法——扩展阅读他人博客—onAttachedToWindow ()。我们可以知道我们要使用View.post()这个方法就必须等onAttachedToWindow ()这个方法执行过了才行,且启动程序时这个onAttachedToWindow ()运行在onResume()之后,而我在onCreate中开线程异步操作可能在TextView还没attached to a window就post了,所以没有产生丝毫的效果。有兴趣的朋友可以看上面的大佬的博客多了解一下,我理解的可能不太对。(这个方法我最后实验成功是把线程写在一个按钮的点击监听器里,我点击的时候自然已经“attached to a window”了)
⑶View.postDelayed(Runnable action, long delayMillis)
这个就是上一个方法的延时方法,第二个参数写要延时的毫秒数就行了,1000就是1秒。
二、利用线程进行简单的下载(就是做个笔记)
直接上代码吧:
DownloadActivity.class
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.net.URLConnection;
public class DownloadActivity extends AppCompatActivity {
public static final String TAG = DownloadActivity.class.getSimpleName() + "-TAG";
private Button mDownloadButton;
private ProgressBar mDownloadProgressBar;
private Handler mHandler = new DownloadHandler(this);
private TextView mProgressTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_download);
findViews();
mDownloadButton.setOnClickListener(myClick);
}
private void findViews() {
mDownloadButton = (Button) findViewById(R.id.btn_download);
mDownloadProgressBar = (ProgressBar) findViewById(R.id.progressBar_download);
mProgressTextView = (TextView) findViewById(R.id.textView_progress);
}
private View.OnClickListener myClick = new View.OnClickListener() {
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_download:
new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL("http://download.qidian.com/epub/3666197.epub");//下载链接
URLConnection connection = url.openConnection();
InputStream downloadStream = connection.getInputStream();
int contentLength = connection.getContentLength();//获得文件长度
Log.i(TAG, "contentLength: " + contentLength);
if (contentLength <= 0) {
return;
}
//文件存放路径
String downloadFolderName = Environment.getExternalStorageDirectory() + File.separator + "qdf" + File.separator;
Log.i(TAG, "下载路径 " + downloadFolderName);
File file = new File(downloadFolderName);
if (!file.exists()) {
//如果目录不存在则创建目录
file.mkdir();
}
//文件目录+文件名
String fileName = downloadFolderName + "test.txt";
File contentFile = new File(fileName);
if (contentFile.exists()) {
contentFile.delete();
}
int downloadSize = 0;
byte[] bytes = new byte[1024];
int length;
//字节输出流
OutputStream outputStream = new FileOutputStream(fileName);
while ((length = downloadStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, length);
downloadSize += length;
int progress = downloadSize * 100 / contentLength;
//更新UI
Message message = mHandler.obtainMessage();
message.what = 0;
message.obj = progress;
mHandler.sendMessage(message);
//ProgressBar可以在线程中操作
mDownloadProgressBar.setProgress(progress);
}
Log.i(TAG, "download success");
downloadStream.close();
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
break;
}
}
};
public TextView getProgressTextView() {
return mProgressTextView;
}
public static class DownloadHandler extends Handler {
public final WeakReference mDownloadActivityWeakReference;
public DownloadHandler(DownloadActivity downloadActivity) {
mDownloadActivityWeakReference = new WeakReference<>(downloadActivity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
DownloadActivity activity = mDownloadActivityWeakReference.get();
switch (msg.what) {
case 0:
int progress = (int) msg.obj;
String progress2 = progress + "%";
activity.getProgressTextView().setText(progress2);
if (progress == 100) {
Toast.makeText(activity, "下载成功", Toast.LENGTH_SHORT).show();
}
break;
}
}
}
}
网络连接、IO操作自然是要在线程中啦,连接网络后通过URLConnection 对象来获得输入流,并可以利用getContentLength()方法来获得文件大小。最后利用字节输出流输出文件,其中根据已下载的大小与总大小之比来更新ProgressBar,因为是在线程中获取数据后立马更新UI所以使用了Handler。