android线程技术,Android线程处理简述

Android线程处理简述

附件工程是有关Android线程的,里面对的&错的方式都有。遇到报错那就对了,先熟悉了,以后才更清楚不是吗^^。

还有,运行结果就不都截图了,懒人一个T^T。

一、基础篇

1)UI线程概念

Android为单线程模型。当一个程序第一次启动时,Android会自动创建一个对应的主线程(Main Thread)。它负责把事件分派到相应的控件,用于用户与Android控件进行交互。所以通常又被叫做UI线程。

在开发Android应用时必须遵守单线程模型的原则:AndroidUI操作并不是线程安全的并且这些操作必须在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)好了,开始动手测试吧^^

publicclassUIThreadActivityextendsBaseActivity {

/** 标签 */

privateTextView textView;

/** Runnable */

privateRunnable runnable =newRunnable() {

@Override

publicvoidrun() {

logThreadId(); // 打印当前线程ID

textView.setText(tag + ", I'm Join!");// 执行UI操作

}

};

@Override

protectedvoidonCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.ui_thread);

tag = "UIThreadActivity";

textView = (TextView) findViewById(R.id.textView);

}

/** 1.1 UI线程阻塞 */

publicvoidblockUi(View v) {

tag = "blockUi";

logThreadId(); // 打印当前线程ID

/* 读取网页内容并显示 */

textView.setText("开始读取网页!");// 该步可能未显示,为什么?

StringBuffer document = loadHtml("http://www.google.com"); // 耗时操作:读取网页源码

textView.setText(null== document ?"读取失败!": document.toString());// 显示网页源码

/**

* 1.观察按钮呈按下状态持续时间

* 2.尝试在按钮呈按下状态时,进行按键和触屏操作

*/

}

/** 1.2 非主线程更新UI */

publicvoidupdateUi(View v) {

tag = "updateUi";

newThread(runnable).start();

}

/** 2.1 方法1:Handler */

publicvoidmethodOne(View v) {

tag = "methodOne";

// Can't create handler inside thread that has not called

// Looper.prepare();

finalHandler handler =newHandler();

newThread(newRunnable() {

@Override

publicvoidrun() {

logThreadId(); // 打印当前线程ID

handler.post(runnable);

// handler.postDelayed(runnable, 1000);

}

}).start();

}

/** 2.2 方法2: View.post(Runnable) */

publicvoidmethodTwo(View v) {

tag = "methodTwo";

newThread(newRunnable() {

@Override

publicvoidrun() {

logThreadId(); // 打印当前线程ID

textView.post(runnable);

// textView.postDelayed(runnable, 1000);

}

}).start();

}

/** 2.3 方法3:Activity.runOnUiThread(Runnable) */

publicvoidmethodThree(View v) {

tag = "methodThree";

newThread(newRunnable() {

@Override

publicvoidrun() {

logThreadId(); // 打印当前线程ID

runOnUiThread(runnable);

}

}).start();

}

/**

* 读取网页源码

*/

privateStringBuffer loadHtml(String urlStr) {

try{

StringBuffer doc = newStringBuffer();

URL url = newURL(urlStr);

URLConnection conn = url.openConnection();

BufferedReader reader = newBufferedReader(newInputStreamReader(

conn.getInputStream()));

String line = null;

while((line = reader.readLine()) !=null)

doc.append(line);

reader.close();

returndoc;

} catch(IOException e) {

e.printStackTrace();

}

returnnull;

}

}

亲,记得看Log日志出的线程id哦。(卖下萌,不介意吧?)

二、提高篇

1)Android消息处理机制

熟悉Android开发的可能知道其应用程序都是由消息驱动的。参考了Windows的消息循环机制,Android系统通过Handler、Thread、Looper以及MessageQueue实现了这种消息机制。相关的类都被定义在了package android.os。

推荐这篇博客咯——解析Android消息处理机制。(这个,那个,还建模==)

2)实用的消息处理方式

2.1)Thread实现消息循环

/** 1.1 Thread实现消息循环 */

publicvoidmethodOne(View v) {

tag = "methodOne";

// 创建一个LooperThread对象,实现了消息循环

LooperThread thread = newLooperThread();

// 必须启动这个线程

thread.start();

// 创建一个消息对象并设置信息

Message msg = newMessage();

Bundle bundle = newBundle();

bundle.putString("key","1.1 Thread实现消息循环");

msg.setData(bundle);

// 发送消息对象

thread.mHandler.sendMessage(msg);

}

/** 实现消息循环的线程(在Android索引文档android.os.Looper的概述里有介绍) */

privateclassLooperThreadextendsThread {

publicHandler mHandler;

publicvoidrun() {

Looper.prepare();

mHandler = newHandler() {

publicvoidhandleMessage(Message msg) {

// process incoming messages here

logThreadId(); // 打印当前线程ID

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类。

/** 1.2 Handler与Looper相关联 */

publicvoidmethodTwo(View v) {

tag = "methodTwo";

// 生成一个HandlerThread对象,使用Looper来处理消息队列

HandlerThread thread = newHandlerThread("MyThread");

// 必须启动这个线程

thread.start();

// 将一个线程绑定到Handler对象上,则该Handler对象就可以处理线程的消息队列

MyHandler myhandler = newMyHandler(thread.getLooper());

// 从Handler中获取消息对象

Message msg = myhandler.obtainMessage();

// 设置消息对象信息

Bundle bundle = newBundle();

bundle.putString("key","1.2 Handler与Looper相关联");

msg.setData(bundle);

// 将消息对象发送给目标对象Handler

msg.sendToTarget();

}

/** 重写Handler的消息处理方法 */

privateclassMyHandlerextendsHandler {

// 带有参数的构造函数

publicMyHandler(Looper looper) {

super(looper);

}

@Override

publicvoidhandleMessage(Message msg) {

// process incoming messages here

logThreadId(); // 打印当前线程ID

Log.e(tag, msg.getData().getString("key"));

}

}

所以,Wifi的HandlerThread,WifiHandler可以这样实例化(推荐博客有述):

HandlerThread wifiThread =newHandlerThread("WifiService");

wifiThread.start();

mWifiHandler = newWifiHandler(wifiThread.getLooper());

2.3)Hanlder与Thread实现异步

在新线程中执行耗时操作,结束后通过Handler来更新UI。

/** 1.3 Hanlder与Thread实现异步 */

publicvoidmethodThree(View v) {

tag = "methodThree";

/* 初始化进度条 */

progressBar.setProgress(0);

progressBar.setVisibility(View.VISIBLE);

// 新线程执行某操作

newProgressThread(0).start();

}

/** 更新UI */

privateHandler myHandler =newHandler() {

@Override

publicvoidhandleMessage(Message msg) {

progressBar.setProgress(msg.getData().getInt("key"));

}

};

/** 新线程任务 */

privateclassProgressThreadextendsThread {

privateintprogress;

publicProgressThread(intprogress) {

this.progress = progress;

}

@Override

publicvoidrun() {

try{

while(progress <=100) {

progress += 5;// 进度+5

// doSomething(); // 执行耗时操作

Thread.sleep(100);

// 从Handler中获取消息对象

Message msg = myHandler.obtainMessage();

// 设置消息对象信息

Bundle b = newBundle();

// 向Handler发送消息,更新UI

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)样例程序

/** 2 Android异步线程——AsyncTask */

publicvoidmethodFour(View v) {

tag = "methodFour";

newLoadHtmlTask(this).execute("http://www.baidu.com"); // 执行读取网页任务

// new LoadHtmlTask(this).execute("http://www.google.com"); // 获取不到内容长度

// new LoadHtmlTask(this).execute("http://www.sina.com"); // 获取大量网页数据

}

/** 读取网页任务 */

privateclassLoadHtmlTaskextendsAsyncTask {

privateContext mContext;

privateProgressDialog dialog;// 进度框

publicLoadHtmlTask(Context context) {

this.mContext = context;

initDialog(); // 初始化进度对话框

}

/** 初始化进度对话框 */

privatevoidinitDialog() {

dialog = newProgressDialog(mContext);

dialog.setMax(100);// 设置最大进度值

dialog.setTitle("Loading...");// 设置标题

dialog.setCancelable(false);// 设为返回键不可取消

dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); // 设为进度条样式

// 增加取消按钮及其事件

dialog.setButton("取消",newDialogInterface.OnClickListener() {

publicvoidonClick(DialogInterface dialog,inti) {

dialog.dismiss(); // 取消显示

cancel(true);// 取消并中断任务

}

});

}

/** doInBackground之前,在主线程执行 */

@Override

protectedvoidonPreExecute() {

logThreadId("onPreExecute()");// 打印当前线程ID

dialog.show(); // 显示进度对话框

}

/** onPreExecute()之后,在后台线程执行 */

@Override

protectedString doInBackground(String... params) {

logThreadId("doInBackground");// 打印当前线程ID

// 未传入参数直接返回

if(null== params || params.length <=0) {

return"请确认输入了网址参数!";

}

try{

// 创建HttpGet对象,params[0]为url

HttpGet httpGet = newHttpGet(params[0]);

// 发送Http Get请求,并返回HttpResponse对象

HttpResponse response = newDefaultHttpClient()

.execute(httpGet);

// 判断响应状态,200表示成功响应

if(response.getStatusLine().getStatusCode() ==200) {

HttpEntity entity = response.getEntity(); // 获取返回结果

longlength = entity.getContentLength();// 获取内容长度(google获取不到)

booleangetLen = length >0?true:false;// 判断是否获取了内容长度

InputStream is = entity.getContent(); // 获取响应内容

if(is !=null) {

ByteArrayOutputStream baos = newByteArrayOutputStream();

byte[] buf =newbyte[128];

intch = -1;

longcount =0;

while((ch = is.read(buf)) != -1&& !isCancelled()) {// 同时还未取消

baos.write(buf, 0, ch);

count += ch;

if(getLen) {// 获取了内容长度时

// 调用publishProgress()更新进度

publishProgress((int) ((count / (float) length) *100));

}

}

is.close();

baos.close();

returnnewString(baos.toByteArray());// 返回结果

}

return"无返回内容!";

} else{

return"服务器未响应或失败!";

}

} catch(Exception e) {

e.printStackTrace();

}

return"程序异常啦!";

}

/** 调用了publishProgress(),在主线程执行 */

@Override

protectedvoidonProgressUpdate(Integer... values) {

// logThreadId("onProgressUpdate"); // 打印当前线程ID

dialog.setProgress(values[0]);

}

/** 未调用了cancel(),doInBackground()结束后,在主线程执行 */

@Override

protectedvoidonPostExecute(String result) {

logThreadId("onPostExecute(result)");// 打印当前线程ID

dialog.dismiss(); // 取消显示

showMsg(result); // 显示结果

}

/** 调用了cancel(),doInBackground()结束后,在主线程执行 */

@Override

protectedvoidonCancelled() {

logThreadId("onCancelled");// 打印当前线程ID

showMsg("用户取消了该任务!");

}

/** 提示框显示消息 */

privatevoidshowMsg(String message) {

// message内容过多时,提示框显示会延迟,例如sina

newAlertDialog.Builder(mContext)

.setTitle("消息")

.setMessage(message)

.setNegativeButton("关闭",

newDialogInterface.OnClickListener() {

@Override

publicvoidonClick(DialogInterface dialog,

intwhich) {

dialog.dismiss();

}

}).show();

}

}

三、扩展篇

1)Service中的Toast

Service中不能直接使用Toast提示信息,推荐如下方式:

privateHandler handler;// Handler

Override

publicvoidonCreate() {

Log.i("onCreate","==onCreate==");

super.onCreate();

handler = newHandler(Looper.getMainLooper());// 使用应用的主消息循环

}

/**

* Toast提示(service中toast不能直接显示)

*/

privatevoidshowToast(finalintresId,finalObject... formatArgs) {

handler.post(newRunnable() {

publicvoidrun() {

Toast.makeText(getApplicationContext(),

getString(resId, formatArgs), Toast.LENGTH_SHORT)

.show();

}

});

// 以下方式只能显示一次

// Looper.prepare();

// Toast.makeText(this, resId, Toast.LENGTH_SHORT).show();

// Looper.loop();

}

2)Java线程池

一些简单常用的线程池,只需使用Executors类里面提供了一些静态工厂,如下:

1)newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

2)newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

3)newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

4)newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

5)newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

或者使用ThreadPoolExecutor,更加定制化地构造线程池。它们都被定义在package java.util.concurrent。

线程池技术是为了减少频繁创建和销毁线程的系统开销,适用情况有:1)单个任务时间很短、处理请求巨大;2)有突发性大量任务请求;3)需要迅速响应的性能要求等。

publicclassTestPoolimplementsRunnable {

privatestaticfinalString TAG ="TestPool";// 标记

privateExecutorService service;// 线程池

publicTestPool() {

// service = Executors.newSingleThreadExecutor(); // 创建一个单任务线程池

service = Executors.newFixedThreadPool(3);// 创建最多同时运行3个任务线程池

}

/** 增加一个线程任务 */

publicvoidaddTask() {

service.execute(this);

}

/** 线程任务 */

@Override

publicvoidrun() {

log(1);// 显示日志

try{

Thread.sleep(5*1000);

} catch(InterruptedException e) {

e.printStackTrace();

}

log(2);// 显示日志

}

/** 显示日志 */

privatevoidlog(intwhich) {

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只能在同步控制方法或者同步控制块里面使用。

没多线程不需要同步,有多线程不一定需要同步。

publicclassTestSync {

privateProduct product;

privateConsumer ConsumerA, ConsumerB;

publicTestSync() {

product = newProduct();

ConsumerA = newConsumer("小怪兽A", product);

ConsumerB = newConsumer("小怪兽B", product);

}

publicvoidproduce() {

synchronized(product) {

Log.e("TestSync","\\(^o^)/,投掷一个果冻!");

product.plus(); // 增加一个产品

/* 优先使用notifyAll(),更容易让jvm找到最适合被唤醒的线程 */

// product.notify(); // 唤醒一个线程

product.notifyAll(); // 唤醒所有线程

}

}

publicvoidstart() {

ConsumerA.start();

ConsumerB.start();

}

publicvoidstop() {

ConsumerA.stopEating();

ConsumerB.stopEating();

synchronized(product) {

product.notifyAll(); // 唤醒所有线程

}

}

}

/** 产品 */

classProduct {

privateintcount =0;// 产品数量

publicProduct() {

}

/** 是否无产品 */

publicbooleanisNull() {

returncount <=0?true:false;

}

/** 增加产品 */

publicvoidplus() {

count++;

}

/** 减少产品 */

publicvoidminus() {

count--;

}

}

/** 消费者 */

classConsumerextendsThread {

privateString name;// 消费者

privateProduct product;// 产品

privateintcount =0;// 数量

privatebooleanwaitEating =true;// 标记

publicConsumer(String name, Product product) {

this.name = name;

this.product = product;

}

@Override

publicvoidrun() {

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);

}

publicvoidstopEating() {

waitEating = false;

}

}

四、后记

好吧,最后再截点图--!

5219274f9893fc9cacf2a2ad54590c8b.png194726d3b7457f9a45f422dedbae23d2.png

42cf6c157018578cd9c5d14eb26de7e9.png

怎么一直是小怪兽A抢到的果冻?不应该啊T^T。

ps:UI线程不太好阻塞,可以找个门户网站的主页(全是图==)、或者观察延迟时间。那个问题 “该步可能未显示,为什么?”不知道可以试下看看^^,为什么呢?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值