转自:http://blog.csdn.net/yihui823/article/details/6741411
今天看到一段很糟糕的代码。于是做了一个工程,模拟这段代码,向大家说明一下线程在使用中要注意的几点。这个例子适合给新手,也欢迎各位高手来指点一下。
首先,上代码。
第一个类LoginService,这是一个模拟类,把业务剥离出去了。只是模拟登录操作而已。
- package com.study;
- package com.study;
- /**
- * 虚拟的一个登录服务.
- * @author yihui823
- */
- public class LoginService {
- //单例
- private static LoginService oneInstance = new LoginService();
- /**
- * 得到唯一的一个单例
- * @return 唯一的一个单例
- */
- public static LoginService getInstance() {
- return oneInstance;
- }
- //登录成功标记
- private boolean hadLogin = false;
- /**
- * 模拟登录操作
- * @return true:登录成功
- */
- public boolean login() {
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- }
- hadLogin = true;
- return hadLogin;
- }
- /**
- * 判断是否登录
- * @return true:已经登录
- */
- public boolean isLogin() {
- return hadLogin;
- }
- }
第二个类就是我们的Activity了。
- package com.study;
- import android.app.Activity;
- import android.os.Bundle;
- import android.widget.Toast;
- /**
- * 一个段不好的代码
- * @author yihui823
- */
- public class BadCodeActivity extends Activity {
- //登录服务
- private LoginService lService = LoginService.getInstance();
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- new Thread(new Runnable() {
- @Override
- public void run() {
- while(!lService.isLogin() ) {
- try {
- lService.login();
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- Toast.makeText(BadCodeActivity.this, "登录成功", Toast.LENGTH_LONG).show();
- }
- }).start();
- }
- }
- <span style="background-color: rgb(255, 255, 255);">这个例子呢,显然运行的时候会报错的。请见我的另一篇文章:<a href="http://blog.csdn.net/yihui823/article/details/6722784" target="_blank">Android画面UI中的线程约束</a>。我们在非UI线程里去控制UI界面,就必须使用Handler来发送消息。修改代码如下:</span>
- package com.study;
- import android.app.Activity;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Message;
- import android.widget.Toast;
- /**
- * 一个段不好的代码
- * @author yihui823
- */
- public class BadCodeActivity extends Activity {
- //登录服务
- private LoginService lService = LoginService.getInstance();
- <span style="color:#ff6666;">//外线程访问UI线程的Handle
- private Handler mhandle = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- Toast.makeText(BadCodeActivity.this, "登录成功", Toast.LENGTH_LONG).show();
- }
- };</span>
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- new Thread(new Runnable() {
- @Override
- public void run() {
- while(!lService.isLogin() ) {
- try {
- lService.login();
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- <span style="color:#ff0000;">mhandle.sendEmptyMessage(0);</span>
- }
- }).start();
- }
- }
- <p><span style="background-color: rgb(255, 255, 255);">红色部分代码,就是修改的地方。现在,这段代码可以运行了,而且还貌似不错,是吧。</span></p><p><span style="background-color: rgb(255, 255, 255);">但是,一个好的程序,不能只是应付正常情况,还要应付错误情况,是吧。如果登录总是出错怎么样呢?我们把LoginService类略微改动,如下:</span></p>
- package com.study;
- import android.util.Log;
- /**
- * 虚拟的一个登录服务.
- * @author yihui823
- */
- public class LoginService {
- private static final String TAG = "LoginService";
- //单例
- private static LoginService oneInstance = new LoginService();
- /**
- * 得到唯一的一个单例
- * @return 唯一的一个单例
- */
- public static LoginService getInstance() {
- return oneInstance;
- }
- //登录成功标记
- private boolean hadLogin = false;
- /**
- * 模拟登录操作
- * @return true:登录成功
- */
- public boolean login() {
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- }
- Log.d(TAG, "we are login");
- // hadLogin = true;
- return hadLogin;
- }
- /**
- * 判断是否登录
- * @return true:已经登录
- */
- public boolean isLogin() {
- return hadLogin;
- }
- }
增加了Log,以便查看登录情况。模拟业务代码只改了一行,就是登录永远是失败。现在运行一下呢。停在页面上没有动静了,logcat里也不断的打出:
we are login
这个也不会有什么错误,对吧。但是,我们如果按“返回”键退出页面,再看看logcat呢?
we are login的log还在不停的输出,是吗?
我想现在大家应该知道哪里出了问题了。就是说,我们的线程启动之后,就没法停掉了。
这里我要说一下。我一直认为,
new Thread(new Runnable() {…}().start();
这种代码写的非常的不好。你直接构造了一个对象,但是这个对象你没有任何的变量去指向它。这个线程被你启动之后,你已经无法再去跟踪、调用、管理了。这个线程,只能自生自灭,永远游离在你的控制范围之外。你会不会觉得,这个线程跟僵尸一样?对,这就是僵尸进程,如果它没有停止的条件,就永远在你的系统里消耗你的资源。
所以我觉得使用线程的一个基本认识:生成的线程类,一定要有一个变量去指向它,以便在合适的时候销毁。
这里说到销毁,这就是另一个问题了。Thread类已经废弃了stop方法了,因为线程需要自行去释放该释放的资源,不能光依赖于运行框架的控制。我们需要在Thread里面,加上他自己停止的代码。也就是说,不论如何,线程应该会自己去停止掉,而不应该是无限制的运行。
另外,我们在Android里面,还应该注意Activity的各个状态周转。一般来说,线程的启动在onCreate里是不合适的,我们必须考虑到onResume和onPause的情况。
那么,我们总结下,Activity里使用线程有三个注意:
1, 线程对象一定要有变量指向它,以便我们可以控制。
2, 线程类一定要有停止条件,以便外界通知线程自行停止。
3, 在onResume里启动线程,在onPause里停止线程。
我们根据以上三点,重新写一下Activity。
- package com.study;
- import android.app.Activity;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Message;
- import android.util.Log;
- import android.widget.Toast;
- /**
- * 一个段不好的代码
- * @author yihui823
- */
- public class BadCodeActivity extends Activity {
- private static final String TAG = "BadCodeActivity";
- //登录服务
- private LoginService lService = LoginService.getInstance();
- //外线程访问UI线程的Handle
- private Handler mhandle = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- Toast.makeText(BadCodeActivity.this, "登录成功", Toast.LENGTH_LONG).show();
- }
- };
- //通知停止线程的标记
- private boolean stopFlag = false;
- //登录成功标记
- private boolean loginOk = false;
- /**
- * 登录用的线程类
- */
- private class LoginThread extends Thread {
- @Override
- public void run() {
- while(!stopFlag) {
- loginOk = lService.isLogin();
- if (loginOk) {
- break;
- }
- try {
- lService.login();
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- mhandle.sendEmptyMessage(0);
- }
- /**
- * 通知线程需要停止
- */
- public void stopLogin() {
- stopFlag = true;
- }
- };
- //用来登录的线程
- private LoginThread loginThread = new LoginThread();
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- Log.d(TAG, "BadCodeActivity instance is called onCreate :" + this.hashCode());
- }
- public void onResume() {
- super.onResume();
- Log.d(TAG, "BadCodeActivity instance is called onResume :" + this.hashCode());
- loginThread.start();
- }
- public void onPause() {
- super.onPause();
- loginThread.stopLogin();
- }
- }
现在,我们的线程可以在页面退出的时候正常停止了。
但是这段代码还是有问题的。我们仔细看看,线程在Activity构造的时候就已经创建了,然后在程序进到前台的时候启动,退到后台的时候停止。但是线程有这么一个特性:
一旦线程的run()函数运行结束了,这个线程就销毁了,不能再启动了。
现在我们的程序,在退出后将不可能再次显示,所以系统会马上回收掉Activity。如果我们的页面增加一个按钮,迁移到另一个页面,那么在那个页面返回的时候,就会有异常出现。我们修改一下代码来试试。
增加一个Activity:
- package com.study;
- import android.app.Activity;
- import android.os.Bundle;
- /**
- * 临时页面
- * @author yihui823
- */
- public class TempActivity extends Activity {
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
- }
- <pre class="java" name="code"></pre>
- <p>别忘了修改AndroidManifest.xml,增加Activity的说明:</p>
- <p align="left"> <activity android:name=".TempActivity"<br>
- android:label="@string/app_name"/><br>
- </p>
- <p align="left">修改BadCodeActivity:</p>
- <p><br>
- </p>
- <pre class="java" name="code">package com.study;
- import android.app.Activity;
- import android.content.Intent;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Message;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.Toast;
- /**
- * 一个段不好的代码
- * @author yihui823
- */
- public class BadCodeActivity extends Activity {
- private static final String TAG = "BadCodeActivity";
- //登录服务
- private LoginService lService = LoginService.getInstance();
- //外线程访问UI线程的Handle
- private Handler mhandle = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- Toast.makeText(BadCodeActivity.this, "登录成功", Toast.LENGTH_LONG).show();
- }
- };
- //通知停止线程的标记
- private boolean stopFlag = false;
- //登录成功标记
- private boolean loginOk = false;
- /**
- * 登录用的线程类
- */
- private class LoginThread extends Thread {
- @Override
- public void run() {
- while(!stopFlag) {
- loginOk = lService.isLogin();
- if (loginOk) {
- break;
- }
- try {
- lService.login();
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- mhandle.sendEmptyMessage(0);
- }
- /**
- * 通知线程需要停止
- */
- public void stopLogin() {
- stopFlag = true;
- }
- };
- //用来登录的线程
- private LoginThread loginThread = new LoginThread();
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- <span style="color:#ff0000;"> Button btn = (Button)findViewById(R.id.btn);
- btn.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- startActivity(new Intent(BadCodeActivity.this,TempActivity.class));
- }
- });</span>
- Log.d(TAG, "BadCodeActivity instance is called onCreate :" + this.hashCode());
- }
- public void onResume() {
- super.onResume();
- Log.d(TAG, "BadCodeActivity instance is called onResume :" + this.hashCode());
- loginThread.start();
- }
- public void onPause() {
- super.onPause();
- loginThread.stopLogin();
- }
- }
- </pre>
- <p><br>
- </p>
- <p align="left"> </p>
- <p>其实就是加了一个按钮,做一个页面迁移。别忘了在main.xml里面加上:</p>
- <pre class="html" name="code"><Button
- android:id="@+id/btn"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="@string/hello"
- />
- </pre>
- <p><br>
- </p>
- <p>现在我们运行程序。运行之后,点击按钮,画面闪动一下说明是切换了页面。我们偷了个懒,两个Activity共用一个layout,所以页面没有任何变化。但是没关系,我们看log,we are login已经停止输出了。这个时候,我们再按返回键,应该是切换回BadCodeActivity。这个时候系统报错:</p>
- <p>java.lang.IllegalThreadStateException: Thread already started.</p>
- <p>显然,就是说线程已经启动过了,不能再次被利用。</p>
- <p>我们对代码需要做一点点修改。当然,我们也顺手改掉一个BUG:在退出的时候还会报告登录成功。</p>
- <p>并且,我们把控制变量都放在内部类里,做到变量最小化生存空间。</p>
- <p>修改后如下:</p>
- <pre class="java" name="code">package com.study;
- import android.app.Activity;
- import android.content.Intent;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Message;
- import android.util.Log;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.Toast;
- /**
- * 一个段不好的代码
- * @author yihui823
- */
- public class BadCodeActivity extends Activity {
- private static final String TAG = "BadCodeActivity";
- //登录服务
- private LoginService lService = LoginService.getInstance();
- //外线程访问UI线程的Handle
- private Handler mhandle = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- Toast.makeText(BadCodeActivity.this, "登录成功", Toast.LENGTH_LONG).show();
- }
- };
- /**
- * 登录用的线程类
- */
- private class LoginThread extends Thread {
- //通知停止线程的标记
- private boolean stopFlag = false;
- //登录成功标记
- private boolean loginOk = false;
- @Override
- public void run() {
- while(!stopFlag) {
- loginOk = lService.isLogin();
- if (loginOk) {
- break;
- }
- try {
- lService.login();
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- if (loginOk) {
- mhandle.sendEmptyMessage(0);
- }
- }
- /**
- * 通知线程需要停止
- */
- public void stopLogin() {
- stopFlag = true;
- }
- };
- //用来登录的线程
- private LoginThread loginThread;
- /** Called when the activity is first created. */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- Button btn = (Button)findViewById(R.id.btn);
- btn.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View arg0) {
- startActivity(new Intent(BadCodeActivity.this,TempActivity.class));
- }
- });
- Log.d(TAG, "BadCodeActivity instance is called onCreate :" + this.hashCode());
- }
- public void onResume() {
- super.onResume();
- Log.d(TAG, "BadCodeActivity instance is called onResume :" + this.hashCode());
- <span style="color:#ff0000;">loginThread = new LoginThread();</span>
- loginThread.start();
- }
- public void onPause() {
- super.onPause();
- loginThread.stopLogin();
- }
- }
- </pre>
- <p align="left"><br>
- </p>
- <p>现在,我们点击按钮,进入到TempActivity的时候,登录log停止输出;然后按返回键,回到BadCodeActivity的时候,登录log又继续输出。程序基本完成,没有僵尸线程存在了。红色的那行代码是关键!</p>
- <p>我们总结一下:</p>
- <p><strong>1, 线程对象一定要有变量指向它,以便我们可以控制。</strong></p>
- <p><strong>2, 线程类一定要有停止条件,以便外界通知线程自行停止。</strong></p>
- <p><strong>3, 线程启动之后,不管是不是已经停止了,都是不能再次利用的。</strong></p>
- <p>4, <strong>在onResume里新建和启动线程,在onPause里停止线程。</strong></p>
- <p> </p>
- <p> </p>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
相关推荐
如果你需要在两个Activity之间传递数据,可以在创建`Intent`时使用`putExtra()`方法添加键值对,然后在接收方Activity中使用`getExtra()`来获取这些数据。 在`SecondActivity`中,通常会有一个返回到`FirstActivity...
1. **runOnUiThread()**:这是一个Activity提供的方法,可以直接在非UI线程中调用,将指定的Runnable对象放入主线程的消息队列,等待主线程执行。 2. **View.post(Runnable)**:类似于`runOnUiThread()`,可以将一...
在这个例子中,"Splash Screen"是在应用启动时显示的一个短暂页面,通常用来展示品牌标识、加载资源或者进行初始化工作。为了实现Splash Screen,开发者通常会在后台线程加载数据或资源,同时在主线程更新进度条,让...
在Android应用开发中,Fragment和Activity是两个核心组件,它们共同构建了用户界面。Fragment代表一个可重用的UI部分,而Activity则是程序中的一个窗口,它可以包含一个或多个Fragment。Fragment与Activity之间的...
在这个例子中,我们不需要Progress和Result,所以可以为空。 - 在`doInBackground(Params... params)`方法中,实现网络请求和图片下载。这个方法会在后台线程执行,不会阻塞UI。 - 使用`publishProgress()`方法...
9. **源代码分析**:HandlerDemo可能包含一个主Activity,其中初始化了Handler,并在onCreate()或onResume()中启动计时器。同时,Runnable的run()方法可能包含对计时器行为的逻辑,比如每过一段时间更新UI或者执行...
在这个例子中,`handleMessage()`方法会在UI线程中被执行,从而安全地更新了UI。 #### 总结 线程间的函数调用在Android开发中极为常见。使用`Handler`机制可以在非UI线程中执行耗时任务,并将结果安全地同步回UI...
在这个例子中,我们可能会使用startService(),因为Service将独立于Activity运行,即使Activity被销毁,Service依然会继续执行。 Service内部,我们将开启一个新的线程,避免阻塞主线程。在新线程中,我们可以执行...
在"android 线程间通信显示同步时间"的例子中,我们可以选择使用Handler或LiveData。例如,创建一个后台线程,周期性获取系统时间,然后通过Handler发送一个包含新时间的Message到主线程。主线程的Handler接收到...
在你的例子中,文件名为`MyHandler2`,可能包含了一个自定义的`Handler`实现,可能用于处理更复杂的逻辑,如并发控制、消息分发策略等。 总结来说,Android主线程通过`Handler`和`Looper`机制可以有效地与多个工作...
在这个例子中,3个线程分别执行任务,每个任务完成后调用 `countDown()`。主线程调用 `await()` 阻塞,直到计数器归零,表明所有线程已完成任务,主线程继续执行后续操作。 总的来说,`CountDownLatch` 是一种有效...
在`AsyncTaskDemo`这个项目中,我们可能可以看到一个简单的例子,如何使用`AsyncTask`来加载数据,比如从网络下载文件或者数据库查询。开发者通常会模拟一个耗时操作,在`doInBackground()`中添加睡眠时间,然后在`...
在这个例子中,`myThread`是一个在后台运行的线程,它不断地发送消息给`myHandler`,后者在主线程中接收到消息后更新UI。 ### Timer 和 TimerTask 除了`Thread`和`Handler`外,`Timer`和`TimerTask`也可以用来执行...
例子可能包括了各种Activity状态的处理,如onCreate(), onStart(), onResume(), onPause(), onStop()和onDestroy()等方法的使用,以及如何在不同场景下正确管理它们。 2. **Intent的使用**:Intent用于启动或激活...
它可能演示了如何创建一个工作线程,定义回调接口,并在主线程中使用这个接口来接收和处理来自工作线程的结果。为了运行这个例子,你需要确保正确导入所有必要的库,创建并启动线程,并传递实现了回调接口的对象。 ...
在Android应用开发中,`Service` 是一个用于执行长时间运行操作的组件,它不提供用户界面,但可以与用户界面组件交互。...通过分析和学习这个示例,开发者可以更好地理解和实践在`Service` 中使用线程的技术。
- **示例**:假设Activity中包含一个用于从网络下载数据的后台线程,那么可以在`onCreate()`中启动这个线程,在`onDestroy()`中确保线程被正确停止。 2. **可见生命周期**:从`onStart()`到`onStop()`。这一阶段...
你可以从例子中学习如何创建、启动和绑定Service,以及如何处理生命周期事件。 6. **AsyncTask**和**线程管理**:Android应用通常需要在后台线程执行耗时操作,避免阻塞UI。书中的例子可能会介绍AsyncTask的使用,...
在这个安卓小例子中,我们可能会看到如何处理用户输入、如何构建网络请求以及如何处理响应。 安卓线程的概念在此过程中至关重要。由于Android系统主要在主线程(UI线程)上运行,所有与用户界面交互的任务都必须在...
在Android应用开发中,异步任务(AsyncTask)是一个常用工具,用于在后台线程执行耗时操作,然后在UI线程更新结果,避免阻塞用户界面。AsyncTask的使用和内部机制对于任何Android开发者来说都至关重要。让我们深入...