Android线程处理简述
附件工程是有关Android线程的,里面对的&错的方式都有。遇到报错那就对了,先熟悉了,以后才更清楚不是吗^^。
还有,运行结果就不都截图了,懒人一个T^T。
一、基础篇
1)UI线程概念
Android为单线程模型。当一个程序第一次启动时,Android会自动创建一个对应的主线程(Main Thread)。它负责把事件分派到相应的控件,用于用户与Android控件进行交互。所以通常又被叫做
UI线程。
在开发Android应用时必须遵守单线程模型的原则:
Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。
简单表述为:1、不要阻塞UI线程;2、只能在主线程操作UI。
详见Android帮助文档Dev Guide,左侧栏Processes and Threads。
2)UI线程示例
2.1)UI线程阻塞
不考虑Android单线程模型,将所有任务都在该线程中执行,尤其是某些耗时操作,会使得整个用户界面被阻塞。从用户角度来看,就是按键或触屏后响应很慢甚至毫无响应。而且当应用程序阻塞的时间过长时,Android还会向用户提示一个无响应的对话框(不截了==)。
2.2)非主线程更新UI
1、LogCat会报如下的错误消息:
Uncaught handler: thread Thread-9 exiting due to uncaught exception。
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
2、Android会向用户提示一个强行关闭的对话框(又不截了==)。
2.3)非主线程更新UI
三种方式:①Handler;②View.post(Runnable);③Activity.runOnUiThread(Runnable)。
2.4)好了,开始动手测试吧^^
- public class UIThreadActivity extends BaseActivity {
-
-
- private TextView textView;
-
-
- private Runnable runnable = new Runnable() {
-
- @Override
- public void run() {
- logThreadId();
- textView.setText(tag + ", I'm Join!");
- }
- };
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.ui_thread);
- tag = "UIThreadActivity";
- textView = (TextView) findViewById(R.id.textView);
- }
-
-
- public void blockUi(View v) {
- tag = "blockUi";
- logThreadId();
-
-
- textView.setText("开始读取网页!");
- StringBuffer document = loadHtml("http://www.google.com"); // 耗时操作:读取网页源码
- textView.setText(null == document ? "读取失败!" : document.toString());
-
-
-
-
- }
-
-
- public void updateUi(View v) {
- tag = "updateUi";
- new Thread(runnable).start();
- }
-
-
- public void methodOne(View v) {
- tag = "methodOne";
-
-
- final Handler handler = new Handler();
- new Thread(new Runnable() {
- @Override
- public void run() {
- logThreadId();
- handler.post(runnable);
-
- }
- }).start();
- }
-
-
- public void methodTwo(View v) {
- tag = "methodTwo";
- new Thread(new Runnable() {
- @Override
- public void run() {
- logThreadId();
- textView.post(runnable);
-
- }
- }).start();
- }
-
-
- public void methodThree(View v) {
- tag = "methodThree";
- new Thread(new Runnable() {
- @Override
- public void run() {
- logThreadId();
- runOnUiThread(runnable);
- }
- }).start();
- }
-
-
-
-
- private StringBuffer loadHtml(String urlStr) {
- try {
- StringBuffer doc = new StringBuffer();
- URL url = new URL(urlStr);
- URLConnection conn = url.openConnection();
- BufferedReader reader = new BufferedReader(new InputStreamReader(
- conn.getInputStream()));
- String line = null;
- while ((line = reader.readLine()) != null)
- doc.append(line);
- reader.close();
- return doc;
- } catch (IOException e) {
- e.printStackTrace();
- }
- return null;
- }
-
- }
亲,记得看Log日志出的线程id哦。(卖下萌,不介意吧?)
二、提高篇
1)Android消息处理机制
熟悉Android开发的可能知道其应用程序都是由消息驱动的。参考了Windows的消息循环机制,Android系统通过Handler、Thread、Looper以及MessageQueue实现了这种消息机制。相关的类都被定义在了package android.os。
推荐这篇博客咯—— 解析Android消息处理机制。(这个,那个,还建模==)
2)实用的消息处理方式
2.1)Thread实现消息循环
-
- public void methodOne(View v) {
- tag = "methodOne";
-
-
- LooperThread thread = new LooperThread();
-
- thread.start();
-
-
- Message msg = new Message();
- Bundle bundle = new Bundle();
- bundle.putString("key", "1.1 Thread实现消息循环");
- msg.setData(bundle);
-
- thread.mHandler.sendMessage(msg);
- }
-
-
- private class LooperThread extends Thread {
- public Handler mHandler;
-
- public void run() {
- Looper.prepare();
-
- mHandler = new Handler() {
- public void handleMessage(Message msg) {
-
- logThreadId();
- Log.e(tag, msg.getData().getString("key"));
- }
- };
-
- Looper.loop();
- }
- }
如果不使用Looper.prepare()及loop()的话,就不能创建Handler将消息处理加入到Looper中。LogCat会报“Can't create handler inside thread that has not called Looper.prepare();”的错误信息。
2.2)Handler与Looper相关联
如果构造一个无参的Handler对象,它将自动与当前运行线程相关联。可以注意Handler相关例子中打印出的当前线程ID信息。
而与Looper相关联,需要创建一个带参数的Handler对象。注意:此时线程类应该是一个HandlerThread类,一个Looper类的Thread类。
-
- public void methodTwo(View v) {
- tag = "methodTwo";
-
-
- HandlerThread thread = new HandlerThread("MyThread");
-
- thread.start();
-
- MyHandler myhandler = new MyHandler(thread.getLooper());
-
- Message msg = myhandler.obtainMessage();
-
- Bundle bundle = new Bundle();
- bundle.putString("key", "1.2 Handler与Looper相关联");
- msg.setData(bundle);
-
- msg.sendToTarget();
- }
-
-
- private class MyHandler extends Handler {
-
-
- public MyHandler(Looper looper) {
- super(looper);
- }
-
- @Override
- public void handleMessage(Message msg) {
-
- logThreadId();
- Log.e(tag, msg.getData().getString("key"));
- }
- }
所以,Wifi的HandlerThread,WifiHandler可以这样实例化(推荐博客有述):
- HandlerThread wifiThread = new HandlerThread("WifiService");
- wifiThread.start();
- mWifiHandler = new WifiHandler(wifiThread.getLooper());
2.3)Hanlder与Thread实现异步
在新线程中执行耗时操作,结束后通过Handler来更新UI。
-
- public void methodThree(View v) {
- tag = "methodThree";
-
- progressBar.setProgress(0);
- progressBar.setVisibility(View.VISIBLE);
-
- new ProgressThread(0).start();
- }
-
-
- private Handler myHandler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- progressBar.setProgress(msg.getData().getInt("key"));
- }
- };
-
-
- private class ProgressThread extends Thread {
-
- private int progress;
-
- public ProgressThread(int progress) {
- this.progress = progress;
- }
-
- @Override
- public void run() {
-
- try {
- while (progress <= 100) {
-
- progress += 5;
-
-
- Thread.sleep(100);
-
-
- Message msg = myHandler.obtainMessage();
-
- Bundle b = new Bundle();
-
- b.putInt("key", progress);
- msg.setData(b);
- myHandler.sendMessage(msg);
- }
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
3)Android异步线程——AsyncTask
当任务需要复杂操作并频繁更新UI时,上述的非主线程访问UI方法会使得代码结构复杂和难以理解。所以Android1.5提供了一个工具类AsyncTask,用以创建与用户界面交互的长时间任务。也在被定义在了package android.os。
AsyncTask定义了3种泛型类型: Params, Progress and Result;4个执行步骤:onPreExecute, doInBackground, onProgressUpdate and onPostExecute。
3.1)泛型类型
1) Params,发送给后台任务处理的参数类型
2) Progress,后台任务过程中发布的进度单位
3) Result,后台任务结束后返回的结果类型
想了解泛型类型的话,可参见我的 Java泛型应用浅析一文^^。
3.2)执行步骤
1) onPreExecute(),该方法将在执行实际的后台操作前被UI thread调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条。
2) doInBackground(Params...),将在onPreExecute方法执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作。可以调用 publishProgress方法来更新实时的任务进度。该方法是抽象方法,子类必须实现。
3) onProgressUpdate(Progress...),在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
4) onPostExecute(Result),在doInBackground执行完成后,onPostExecute方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread。
onPostExecute的参数为doInBackground的返回值,类型由第三个泛型类型所指定。例子里仅是String,也可以是你自己的实体类、接口什么的。
3.3)使用准则
1) Task的实例必须在UI thread中创建
2) execute方法必须在UI thread中调用
3) 不要手动的调用onPreExecute(), onPostExecute(Result),
doInBackground(Params...), onProgressUpdate(Progress...)这几个方法
4) 该task只能被执行一次,否则多次调用时将会出现异常
3.4)任务取消
一个任务在任何时候都能够通过调用cancel(boolean)而取消。调用这个方法,将使得随后调用的isCancelled()返回true。并且在执行完 doInBackground(Object[])后,会去调用onCancelled(Object)而不再是onPostExecute(Object)方法。
为了确保任务能够尽快的被取消,可以在doInBackground(Object[])内定期校验isCancelled()的返回值(例如在循环判断中)。
3.5)样例程序
-
- public void methodFour(View v) {
- tag = "methodFour";
- new LoadHtmlTask(this).execute("http://www.baidu.com"); // 执行读取网页任务
-
-
- }
-
-
- private class LoadHtmlTask extends AsyncTask<String, Integer, String> {
-
- private Context mContext;
- private ProgressDialog dialog;
-
- public LoadHtmlTask(Context context) {
- this.mContext = context;
- initDialog();
- }
-
-
- private void initDialog() {
- dialog = new ProgressDialog(mContext);
- dialog.setMax(100);
- dialog.setTitle("Loading...");
- dialog.setCancelable(false);
- dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
-
- dialog.setButton("取消", new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int i) {
- dialog.dismiss();
- cancel(true);
- }
- });
- }
-
-
- @Override
- protected void onPreExecute() {
- logThreadId("onPreExecute()");
- dialog.show();
- }
-
-
- @Override
- protected String doInBackground(String... params) {
- logThreadId("doInBackground");
-
- if (null == params || params.length <= 0) {
- return "请确认输入了网址参数!";
- }
- try {
-
- HttpGet httpGet = new HttpGet(params[0]);
-
-
- HttpResponse response = new DefaultHttpClient()
- .execute(httpGet);
-
- if (response.getStatusLine().getStatusCode() == 200) {
- HttpEntity entity = response.getEntity();
-
- long length = entity.getContentLength();
- boolean getLen = length > 0 ? true : false;
-
- InputStream is = entity.getContent();
- if (is != null) {
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- byte[] buf = new byte[128];
- int ch = -1;
- long count = 0;
- while ((ch = is.read(buf)) != -1 && !isCancelled()) {
- baos.write(buf, 0, ch);
- count += ch;
- if (getLen) {
-
- publishProgress((int) ((count / (float) length) * 100));
- }
- }
- is.close();
- baos.close();
- return new String(baos.toByteArray());
- }
- return "无返回内容!";
- } else {
- return "服务器未响应或失败!";
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- return "程序异常啦!";
- }
-
-
- @Override
- protected void onProgressUpdate(Integer... values) {
-
- dialog.setProgress(values[0]);
- }
-
-
- @Override
- protected void onPostExecute(String result) {
- logThreadId("onPostExecute(result)");
- dialog.dismiss();
- showMsg(result);
- }
-
-
- @Override
- protected void onCancelled() {
- logThreadId("onCancelled");
- showMsg("用户取消了该任务!");
- }
-
-
- private void showMsg(String message) {
-
-
- new AlertDialog.Builder(mContext)
- .setTitle("消息")
- .setMessage(message)
- .setNegativeButton("关闭",
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog,
- int which) {
- dialog.dismiss();
- }
- }).show();
- }
-
- }
三、扩展篇
1)Service中的Toast
Service中不能直接使用Toast提示信息,推荐如下方式:
- private Handler handler;
-
- Override
- public void onCreate() {
- Log.i("onCreate", "==onCreate==");
- super.onCreate();
-
- handler = new Handler(Looper.getMainLooper());
- }
-
-
-
-
- private void showToast(final int resId, final Object... formatArgs) {
- handler.post(new Runnable() {
- public void run() {
- Toast.makeText(getApplicationContext(),
- getString(resId, formatArgs), Toast.LENGTH_SHORT)
- .show();
- }
- });
-
-
-
-
- }
2)Java线程池
一些简单常用的线程池,只需使用Executors类里面提供了一些静态工厂,如下:
1)newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2)newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3)newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4)newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
5)newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
或者使用ThreadPoolExecutor,更加定制化地构造线程池。它们都被定义在package java.util.concurrent。
线程池技术是为了减少频繁创建和销毁线程的系统开销,适用情况有:1)单个任务时间很短、处理请求巨大;2)有突发性大量任务请求;3)需要迅速响应的性能要求等。
- public class TestPool implements Runnable {
-
- private static final String TAG = "TestPool";
-
- private ExecutorService service;
-
- public TestPool() {
-
- service = Executors.newFixedThreadPool(3);
- }
-
-
- public void addTask() {
- service.execute(this);
- }
-
-
- @Override
- public void run() {
- log(1);
- try {
- Thread.sleep(5 * 1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- log(2);
- }
-
-
- private void log(int which) {
- String result = 1 == which ? ",任务开始!" : ",任务结束!";
- Log.e(TAG, "线程:" + String.valueOf(Thread.currentThread().getId())
- + result);
- }
-
- }
3)Java线程同步
多线程并发访问同一数据时,就会有同步的需求。Java内在的同步机制包含:同步块(或方法)和 volatile 变量。同步块(或方法)通过synchronized关键字声明,而volatile可被看做是轻量级的synchronized。
Java为每个object分配了一个monitor,相关方法如下:
1)obj.wait()方法将使本线程挂起,并释放obj对象的monitor。只有其他线程调用obj对象的notify()或notifyAll()时,才可以被唤醒。
2)obj.notifyAll()方法唤醒所有该obj对象相关的沉睡线程,然后被唤醒的众多线程开始竞争obj对象的monitor占有权,最终得到的那个线程会继续执行下去,但其他线程还将继续等待。
3)obj.notify()方法是随机唤醒一个沉睡线程。
4)wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用。
没多线程不需要同步,有多线程不一定需要同步。
- public class TestSync {
-
- private Product product;
- private Consumer ConsumerA, ConsumerB;
-
- public TestSync() {
- product = new Product();
- ConsumerA = new Consumer("小怪兽A", product);
- ConsumerB = new Consumer("小怪兽B", product);
- }
-
- public void produce() {
- synchronized (product) {
-
- Log.e("TestSync", "\\(^o^)/,投掷一个果冻!");
- product.plus();
-
-
-
-
- product.notifyAll();
- }
- }
-
- public void start() {
- ConsumerA.start();
- ConsumerB.start();
- }
-
- public void stop() {
- ConsumerA.stopEating();
- ConsumerB.stopEating();
-
- synchronized (product) {
- product.notifyAll();
- }
- }
-
- }
-
-
- class Product {
-
- private int count = 0;
-
- public Product() {
- }
-
-
- public boolean isNull() {
- return count <= 0 ? true : false;
- }
-
-
- public void plus() {
- count++;
- }
-
-
- public void minus() {
- count--;
- }
-
- }
-
-
- class Consumer extends Thread {
-
- private String name;
- private Product product;
- private int count = 0;
-
- private boolean waitEating = true;
-
- public Consumer(String name, Product product) {
- this.name = name;
- this.product = product;
- }
-
- @Override
- public void run() {
- while (waitEating) {
-
- synchronized (product) {
-
- while (product.isNull() && waitEating) {
- try {
- Log.e(name, "(¯﹃¯),等待果冻中...");
- product.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- if (waitEating) {
- Log.e(name, "~\\(≧▽≦)/~,抢到了果冻!");
- product.minus();
- count++;
- }
- }
- }
- Log.e(name, "(~ o ~)~zZ,吃不下了。计:" + count);
- }
-
- public void stopEating() {
- waitEating = false;
- }
-
- }
四、后记
好吧,最后再截点图--!
怎么一直是小怪兽A抢到的果冻?不应该啊T^T。
ps:UI线程不太好阻塞,可以找个门户网站的主页(全是图==)、或者观察延迟时间。那个问题 “该步可能未显示,为什么?”不知道可以试下看看^^,为什么呢?