【攻克Android(24)】线程模型

[b][size=large]本文围绕以下三个部分展开: [/size][/b]

[b][size=large]一、UI单线程原则[/size][/b]
[b][size=large]二、自定义线程[/size][/b]
[b][size=large]三、消息机制[/size][/b]
[b][size=large]案例一、非UI线程不能更新UI[/size][/b]
[b][size=large]案例二、通过runOnUiThread,在非UI线程中更新UI[/size][/b]
[b][size=large]案例三、通过Handler+Message--handlerMessage(消息机制)更新UI[/size][/b]


[b][size=large]一、UI单线程原则[/size][/b]

[size=medium][b](1)[/b][/size]

[size=medium]当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread)。Main Thread 也叫 UI Thread,也即 UI线程。[/size]

[size=medium]主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理。[/size]

[size=medium][b](2)[/b][/size]

[size=medium]系统不会为每个组件单独创建线程,在同一个进程里的UI组件都会在UI线程里实例化,系统对每一个组件的调用都从UI线程分发出去。结果就是,响应系统回调的方法(比如响应用户动作的onKeyDown()和各种生命周期回调)永远都是在UI线程里运行。[/size]

[size=medium][b](3)[/b][/size]

[size=medium]当App做一些比较重(intensive)的工作的时候,除非合理地实现,否则单线程模型的performance会很poor。[/size]

[size=medium]特别的是,如果所有的工作都在UI线程,做一些比较耗时的工作比如访问网络或者数据库查询,都会阻塞UI线程,导致事件停止分发(包括绘制事件)。对于用户来说,应用看起来像是卡住了,更坏的情况是,如果UI线程blocked的时间太长(大约超过5秒),用户就会看到ANR(application not responding)的对话框。[/size]

[size=medium][b](4)[/b][/size]

[size=medium]Android UI Thread并不是线程安全的,所以不能从非UI线程来操纵UI组件。必须把所有的UI操作放在UI线程里。[/size]

[size=medium][b](5)UI单线程原则(Android 单线程模型的两条原则):[/b][/size]

[size=medium]1)不要阻塞UI线程。[/size]

[size=medium]2)不要在UI线程之外访问Android UI Toolkit(主要是这两个包中的组件:android.widget 和 android.view)[/size]


[b][size=large]二、自定义线程[/size][/b]

[size=medium][b]自定义线程的两种方法:[/b][/size]

[size=medium](1)继承 Thread 类[/size]

[size=medium](2)实现 Runnable 接口[/size]


[b][size=large]三、消息机制[/size][/b]

[size=medium][b]1. Handler[/b][/size]

[size=medium](1)[/size]

[size=medium]Handler用于处理线程中的消息队列。当Looper对象从消息队列中获取消息后,会把消息派发给Handler对象。[/size]

[size=medium]一个线程中只能有一个Handler对象,可以通过该对象向所在线程发送消息。因此,只要拥有其它线程中Handler对象的引用,就可以向其发送消息;除了给别的线程发送消息外,还可以给本线程发送消息。 [/size]

[size=medium]通过Handler你可以发布或者处理一个消息或者是一个Runnable的实例。每个Handler都会与唯一的一个线程以及该线程的消息队列管理绑定。[/size]

[size=medium](2)Handler一般有两种用途:[/size]

[size=medium]1)实现一个定时任务[/size]

[size=medium]2)在线程间传递数据[/size]

[size=medium][b]2. Message[/b][/size]

[size=medium]Message是一个描述消息的数据结构类。[/size]

[size=medium]Message包含很多成员变量和方法,但对于简单的消息处理,一般仅需了解3项,分别是:[/size]

[size=medium](1)int what:这是用户自定义的一个整型值,用于区分消息类型。[/size]

[size=medium](2)int arg1:这是额外消息参数。[/size]

[size=medium](3)int arg2:同arg1。[/size]

[size=medium][b]3. Message Queue[/b][/size]

[size=medium]Message Queue是一个消息队列,用来存放通过Handler发布的消息。[/size]

[size=medium]Android在第一次启动程序时会默认会为UI Thread创建一个关联的消息队列,可以通过Looper.myQueue()得到当前线程的消息队列,用来管理程序的一些上层组件,如activities,broadcast receivers 等等。你可以在自己的子线程中创建Handler与UI Thread通讯。[/size]

[size=medium][b]4. Looper[/b][/size]

[size=medium]Looper扮演着一个Handler和消息队列之间通讯桥梁的角色。程序组件首先通过Handler把消息传递给Looper,Looper把消息放入队列。Looper也把消息队列里的消息广播给所有的Handler,Handler接受到消息后调用handleMessage进行处理。
[/size]


[b][size=large]案例一、非UI线程不能更新UI[/size][/b]

[size=medium]MainActivity。运行的时候,会直接退出。[/size]

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

/*当在非UI线程中操作UI组件的时候,会无法运行*/
// 启动匿名的线程类 (非UI线程不能更新UI)
new Thread(){
@Override
public void run() {
Toast.makeText(getApplicationContext(),
"在非UI线程中不能访问UI", Toast.LENGTH_LONG).show();
}
}.start();
}



[b][size=large]案例二、通过runOnUiThread,在非UI线程中更新UI[/size][/b]

[size=medium][b]效果描述:[/b][/size]

[size=medium]刚进入界面是这样的:[/size]

[align=center][img]http://dl2.iteye.com/upload/attachment/0110/2736/2d076391-96d0-37df-b55a-bca55513aefc.png[/img][/align]

[size=medium]当点击“启动线程”按钮的时候,文本框显示当前时间,并且秒数在时刻随时间变化。当点击“停止线程”按钮的时候,文本框显示的时间停止变化。[/size]

[align=center][img]http://dl2.iteye.com/upload/attachment/0110/2738/9e97ec88-a2ef-3a0c-be17-e1bb6b1cbaad.png[/img][/align]

[size=medium][b](1)strings.xml[/b][/size]

<resources>
<string name="app_name">HandlerUI</string>

<string name="action_settings">Settings</string>

<string name="btn_start">启动线程</string>
<string name="btn_stop">停止线程</string>
</resources>


[size=medium][b](2)activity_main.xml。布局[/b][/size]

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">

<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="@string/btn_start" />

<Button
android:id="@+id/btnStop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/btnStart"
android:onClick="onClick"
android:text="@string/btn_stop" />

<TextView
android:id="@+id/tvState"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/btnStop"
android:text="@string/app_name"
android:textSize="30sp" />

</RelativeLayout>

<!-- 2.两个 Button,一个 TextView-->


[size=medium][b](3)MainActivity。通过runOnUiThread,在非UI线程中更新UI[/b][/size]

package com.android.handlerui;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import java.text.SimpleDateFormat;
import java.util.Date;


public class MainActivity extends Activity {
private static final String TAG = "UIThread";
// 4 创建一个 Handler 对象
private Handler handler = new Handler();
// 5.1 将要执行的操作写在线程对象的 run 方法当中
// 使用匿名的 Runnable接口
private Runnable action = new Runnable() {
@Override
public void run() {
// 模拟下载
Log.v(TAG, "下载中...");
// 5.4 在 run 方法内部,执行 postDelayed 方法:
// 每隔1秒执行一次(日志输出 下载中...)。
handler.postDelayed(action, 1000);

// 6. 在非 UI 线程中更新 UI
// 调用 runOnUiThread:调用后,相当于又唤醒了 UI线程。
// 这样,外面的非 UI线程控制时间,UI线程同时在更新界面
/**
* 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.
*/
runOnUiThread(new Runnable() {
private TextView tvState = (TextView) findViewById(R.id.tvState);
@Override
public void run() {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String text = sdf.format(date);
tvState.setText(text);
}
});
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

// 3.
public void onClick(View view) {
switch (view.getId()) {
case R.id.btnStart:
// 5.2 调用 Handler 的 post 方法,将要执行的线程对象添加到消息队列当中,
// 那么队列就会执行线程,调用 run() 方法
// post方法:线程只执行一次就结束了。
handler.post(action);
break;
case R.id.btnStop:
// 5.3 从队列中移除线程对象
handler.removeCallbacks(action);
break;
}
}

// --------------------------------------------------------------
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}
}



[b][size=large]案例三、通过Handler+Message--handlerMessage(消息机制)更新UI[/size][/b]

[size=medium][b]效果描述:[/b][/size]

[size=medium]刚进入界面是这样的:[/size]

[align=center][img]http://dl2.iteye.com/upload/attachment/0110/2758/2a9a87b8-8e0f-33c9-8153-e01f5736ebfc.png[/img][/align]

[size=medium]当点击按钮后,跳到另一个界面,然后数字和当前时间同时在每秒增加 1。[/size]

[align=center][img]http://dl2.iteye.com/upload/attachment/0110/2760/ecca0103-03fa-3d5e-b89b-82fafafbdc48.png[/img][/align]

[size=medium][b](1)strings.xml[/b][/size]

<string name="btn_update_ui">通过Handler更新UI</string>


[size=medium][b](2)创建一个 Activity :UpdateUIActivity [/b][/size]

[size=medium][b](3)activity_main.xml。主界面按钮。[/b][/size]

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity">

<Button
android:id="@+id/btnUpdateUI"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClick"
android:text="@string/title_activity_update_ui" />

</RelativeLayout>


[size=medium][b](4)MainActivity。主界面按钮的点击事件。[/b][/size]

    public void onClick(View view) {
startActivity(new Intent(this, UpdateUIActivity.class));
}


[size=medium][b](5)activity_update_ui.xml。跳转后的界面上,写一个文本,用于显示 UI 变化。[/b][/size]

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.android.handlerui.UpdateUIActivity">

<TextView
android:id="@+id/tvState"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/btn_update_ui"
android:textSize="30sp" />

</RelativeLayout>


[size=medium][b](6)UpdateUIActivity。通过Handler+Message--handlerMessage(消息机制)更新UI[/b][/size]

package com.android.handlerui;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;

import java.text.SimpleDateFormat;
import java.util.Date;

public class UpdateUIActivity extends Activity {
// 定义常量:消息代号
private static final int MSG_CODE = 100;
// 1.3
private TextView tvState;
// 1.2
private Handler handler;
// 当写上 static后,count的生命周期就和 app的生命周期一样了。
// 这样的话,当返回主界面,再进入后,统计数字就不会是从0开始的了。
// private int count; -- 当返回主界面,再进入后,统计数字就是从0开始的。
private static int count;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_update_ui);
// 1.3
tvState = (TextView) findViewById(R.id.tvState);

// 2.1 调用更新界面的方法
updateUI();
}

private void updateUI() {
handler = new Handler() {
/**
* 子类只有重写此方法才能接收消息
* @param msg 接收的消息
*/
@Override
public void handleMessage(Message msg) {
// 2.3 根据消息代码处理消息
// 用户指定的消息代码
switch (msg.what) {
case MSG_CODE:
// 2.4 当接收到消息后,handle自己再延时 1s发送消息
sendMessageDelayed(obtainMessage(MSG_CODE), 1000);
// 处理消息
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 显示count数字自动增长,以及时间
String text = String.format("%d\n%s", (count++), sdf.format(date));
tvState.setText(text);
break;
}
}
};
}

/**
* onResume():活动到前台,UI获得焦点,进入运行状态
* <p/>
* 而 onStart():活动进入可见状态
*/
@Override
protected void onResume() {
super.onResume();

// 2.2 handle & msg 不为空时,延迟 1s发送消息
// handler肯定不为空:上面已经 new 出来了:handler = new Handler(){}
if (handler != null) {
// 设置发送消息的代号为 MSG_CODE
Message msg = handler.obtainMessage(MSG_CODE);
if (msg != null) {
handler.sendMessageDelayed(msg, 1000);
}
}
}

/**
* 2.5 暂停的时候,移除消息
* <p/>
* onPause():另一个活动即将到前台获得焦点(透明或没有完全覆盖屏幕),当前活动仍
* 可见但不可交互;当系统资源不足时,会被销毁。
*/
@Override
protected void onPause() {
super.onPause();

if (handler != null) {
// 移除消息
handler.removeMessages(MSG_CODE);
}
}

// ------------------------------------------------------------
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_update_ui, menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值