在一个Android 程序开始运行的时候,会单独启动一个Process。默认的状况下,全部这个程序中的Activity或者Service(Service和 Activity只是Android提供的Components中的两种,除此以外还有Content Provider和Broadcast Receiver)都会跑在这个Process
一个Android 程序默认状况下也只有一个Process,但一个Process下却能够有许多个Thread。在这么多Thread当中,有一个Thread,咱们称之为UI Thread。UI Thread在Android程序运行的时候就被建立,是一个Process当中的主线程Main Thread,主要是负责控制UI界面的显示、更新和控件交互。在Android程序建立之初,一个Process呈现的是单线程模型,全部的任务都在一个线程中运行。java
UI Thread所执行的每个函数,所花费的时间都应该是越短越好。而其余比较费时的工做(访问网络,下载数据,查询数据库等),都应该交由子线程去执行,以避免阻塞主线程。在android的设计思想中,为了确保用户顺滑的操做体验。一些耗时的任务不可以在UI线程中运行,像访问网络就属于这类任务。所以咱们必需要从新开启一个后台线程运行这些任务。然而,每每这些任务最终又会直接或者间接的须要访问和控制UI控件。例如访问网络获取数据,而后须要将这些数据处理显示出来。就出现了上面所说的状况。本来这是在正常不过的现象了,可是android规定除了UI线程外,其余线程都不能够对那些UI控件访问和操控。为了解决这个问题,下面将探讨这UI线程于子线程之间的五种交互方式。android
handler数据库
Activity.runOnUIThread(Runnable)数组
View.Post(Runnable)安全
View.PostDelayed(Runnabe,long)网络
AsyncTask多线程
解释:当应用程序启动时,Android首先会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件, 进行事件分发, 好比说, 你要是点击一个 Button ,Android会分发事件到Button上,来响应你的操做。 若是此时须要一个耗时的操做,例如: 联网读取数据, 或者读取本地较大的一个文件的时候,你不能把这些操做放在主线程中,若是你放在主线程中的话,界面会出现假死现象, 若是5秒钟尚未完成的话,会收到Android系统的一个错误提示 "强制关闭"。 这个时候咱们须要把这些耗时的操做,放在一个子线程中,由于子线程涉及到UI更新,,Android主线程是线程不安全的, 也就是说,更新UI只能在主线程中更新,子线程中操做是危险的。 这个时候,Handler就出现了。,来解决这个复杂的问题 ,因为Handler运行在主线程中(UI线程中), 它与子线程能够经过Message对象来传递数据, 这个时候,Handler就承担着接受子线程传过来的(子线程用sedMessage()方法传弟)Message对象,(里面包含数据) , 把这些消息放入主线程队列中,配合主线程进行更新UIapp
子类须要继承Hendler类,并重写handleMessage(Message msg) 方法, 用于接受线程数据。
如下为一个实例,它实现的功能为:经过线程修改界面Button的内容异步
public class MyHandlerActivity extends Activity { Button button; MyHandler myHandler; protected void onCreate(Bundle savedInstanceState) { super。onCreate(savedInstanceState); setContentView(R.layout.handlertest); button = (Button) findViewById(R.id.button); myHandler = new MyHandler(); // 当建立一个新的Handler实例时, 它会绑定到当前线程和消息的队列中,开始分发数据 // Handler有两个做用, (1) : 定时执行Message和Runnalbe 对象 // (2): 让一个动做,在不一样的线程中执行。 // 它安排消息,用如下方法 // post(Runnable) // postAtTime(Runnable, long) // postDelayed(Runnable, long) // sendEmptyMessage(int) // sendMessage(Message); // sendMessageAtTime(Message, long) // sendMessageDelayed(Message, long) // 以上方法以 post开头的容许你处理Runnable对象 //sendMessage()容许你处理Message对象(Message里能够包含数据) MyThread m = new MyThread(); new Thread(m).start(); } /** * 接受消息,处理消息 ,此Handler会与当前主线程一块运行 * */ class MyHandler extends Handler { public MyHandler() { } public MyHandler(Looper L) { super(L); } // 子类必须重写此方法,接受数据 @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub Log.d("MyHandler", "handleMessage。。。。。。"); super.handleMessage(msg); // 此处能够更新UI Bundle b = msg.getData(); String color = b.getString("color"); MyHandlerActivity.this.button.append(color); } } class MyThread implements Runnable { public void run() { try { Thread.sleep(10000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } Log.d("thread......", "mThread......"); Message msg = new Message(); Bundle b = new Bundle();// 存放数据 b.putString("color","个人"); msg.setData(b); MyHandlerActivity.this.myHandler.sendMessage(msg); // 向Handler发送消息,更新UI } } }
编写后台线程,这回你能够直接调用UI控件ide
建立后台线程的实例
调用UI线程对应的Activity的
runOnUIThread
方法,将后台线程实例做为参数传入其中。注意:无需调用后台线程的start方法
该方法和方法二基本相同,只是在后台线程中能操控的UI控件被限制了,只能是指定的UI控件View。方法以下
编写后台线程,这回你能够直接调用UI控件,可是该UI控件只能是View
建立后台线程的实例
调用UI控件View的post方法,将后台线程实例做为参数传入其中。
如下是官方文档对该方法的注释及源码。(postDelayed
相似,再也不赘述)
Causes the Runnable to be added to the message queue.The runnable >will be run on the user interface thread.
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; }
意思是将任务添加到消息队列中,保证在UI线程执行。从本质上说,它仍是依赖于以Handler、Looper、MessageQueue、Message为基础的异步消息处理机制。相对于新建Handler进行处理更加便捷。下面举一个经常使用的例子,好比在onCreate方法中获取某个view的宽高,而直接View#getWidth获取到的值是0。要知道View显示到界面上须要经历onMeasure、onLayout和onDraw三个过程,而View的宽高是在onLayout阶段才能最终肯定的,而在Activity#onCreate中并不能保证View已经执行到了onLayout方法,也就是说Activity的声明周期与View的绘制流程并非一一绑定。那为何调用post方法就能起做用呢?首先MessageQueue是按顺序处理消息的,而在setContentView()后队列中会包含一条询问是否完成布局的消息,而咱们的任务经过View#post方法被添加到队列尾部,保证了在layout结束之后才执行。
该方法是方法三的补充,long参数用于制定多少时间后运行后台进程
这是一种能够建立多线程消息的函数
使用方法:
1,首先建立一个Handler对象
Handler handler=new Handler();
2,而后建立一个Runnable对象
Runnable runnable=new Runnable(){ @Override public void run() { // TODO Auto-generated method stub //要作的事情,这里再次调用此Runnable对象,以实现每两秒实现一次的定时器操做 handler.postDelayed(this, 2000); } };
3,使用PostDelayed方法,两秒后调用此Runnable对象
handler.postDelayed(runnable, 2000);
实际上也就实现了一个2s的一个定时器
4,若是想要关闭此定时器,能够这样操做handler.removeCallbacks(runnable);
固然,你也能够作一个闹钟提醒延时的函数试试,好比,先用MediaPlayer播放闹钟声音, 若是不想起,被中止播放以后,下次就5分钟后再播放,再被中止的话,下次就4分钟后播放, ……………… 只要更改延时的时间就能够实现了,用一个static对象的话会比较容易操做
public class SplanshActivity extends AppCompatActivity { private Handler handler; private Boolean isFirst = true; private SharedPreferences sp ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_splansh); sp = getPreferences(MODE_PRIVATE); handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { isFirst = sp.getBoolean("isFirst",true); Intent intent = new Intent();//意图 if(isFirst){ sp.edit().putBoolean("isFirst",false).commit(); //引导界面 intent.setClass(SplanshActivity.this,GuideActivity.class); }else{ //主界面 intent.setClass(SplanshActivity.this,MainActivity.class); } startActivity(intent); finish(); } },3000); } }
AsyncTask是一个专门用来处理后台进程与UI线程的工具。经过AsyncTask,咱们能够很是方便的进行后台线程和UI线程之间的交流。
那么AsyncTask是如何工做的哪。
AsyncTask拥有3个重要参数
Params
rogress
Result
Params是后台线程所需的参数。在后台线程进行做业的时候,他须要外界为其提供必要的参数,就好像是一个用于下载图片的后台进程,他须要的参数就是图片的下载地址。
Progress是后台线程处理做业的进度。依旧上面的例子说,就是下载图片这个任务完成了多少,是20%仍是60%。这个数字是由Progress提供。
Result是后台线程运行的结果,也就是须要提交给UI线程的信息。按照上面的例子来讲,就是下载完成的图片。
AsyncTask还拥有4个重要的回调方法。
一、onPreExecute
二、doInBackground
三、onProgressUpdate
四、onPostExecute
onPreExecute
运行在UI线程,主要目的是为后台线程的运行作准备。当他运行完成后,他会调用doInBackground方法。doInBackground
运行在后台线程,他用来负责运行任务。他拥有参数Params,而且返回Result。在后台线程的运行当中,为了可以更新做业完成的进度,须要在doInbackground
方法中调用PublishProgress
方法。该方法拥有参数Progress。经过该方法能够更新Progress的数据。而后当调用完PublishProgress方法,他会调用onProgressUpdate
方法用于更新进度。onProgressUpdate
运行在UI线程,主要目的是用来更新UI线程中显示进度的UI控件。他拥有Progress参数。在doInBackground中调用PublishProgress以后,就会自动调onProgressUpdate方法onPostExecute
运行在UI线程,当doInBackground方法运行完后,他会调用onPostExecute方法,并传入Result。在onPostExecute方法中,就能够将Result更新到UI控件上。
明白了上面的3个参数和4个方法,你要作的就是
编写一个继承AsyncTask的类,并声明3个参数的类型,编写4个回调方法的内容。
而后在UI线程中建立该类(必须在UI线程中建立)。
最后调用AsyncTask的execute方法,传入Parmas参数(一样必须在UI线程中调用)。
这样就大功告成了。
另外值得注意的2点就是,千万不要直接调用那四个回调方法。还有就是一个AsyncTask实例只能执行一次,不然就出错哦
。
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> { //在这里声明了Params、Progress、Result参数的类型 //由于这里不须要使用onPreExecute回调方法,因此就没有加入该方法 //后台线程的目的是更具URL下载数据 protected Long doInBackground(URL... urls) { int count = urls.length;//urls是数组,不止一个下载连接 long totalSize = 0;//下载的数据 for (int i = 0; i < count; i++) { //Download是用于下载的一个类,和AsyncTask无关,你们能够忽略他的实现 totalSize += Downloader.downloadFile(urls[i]); publishProgress((int) ((i / (float) count) * 100));//更新下载的进度 // Escape early if cancel() is called if (isCancelled()) break; } return totalSize; } //更新下载进度 protected void onProgressUpdate(Integer... progress) { setProgressPercent(progress[0]); } //将下载的数据更新到UI线程 protected void onPostExecute(Long result) { showDialog("Downloaded " + result + " bytes"); } }
有了上面的这个类,接下你要作的就是在UI线程中建立实例,并调用execute方法,传入URl参数就能够了。这上面的5种方法各有优势。可是究其根本,其实后面四种方法都是基于handler方法的包装。在通常的情形下后面四种彷佛更值得推荐。可是当情形比较复杂,仍是推荐使用handler