Activity中运用线程的例子
Activity中使用线程的例子转自:http://blog.csdn.net/yihui823/article/details/6741411?今天看到一段很糟
Activity中使用线程的例子
转自:http://blog.csdn.net/yihui823/article/details/6741411
?
今天看到一段很糟糕的代码。于是做了一个工程,模拟这段代码,向大家说明一下线程在使用中要注意的几点。这个例子适合给新手,也欢迎各位高手来指点一下。
首先,上代码。
第一个类LoginService,这是一个模拟类,把业务剥离出去了。只是模拟登录操作而已。
?
?
[java]?view plaincopy
- 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了。
?
?
?
[java]?view plaincopy
- 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();??
- ????}??
- }??
?
[java]?view plaincopy
- <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>??
?
?
?
[java]?view plaincopy
- 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();??
- ????}??
- }??
?
[java]?view plaincopy
- <p><span?style="background-color:?rgb(255,?255,?255);">红色部分代码,就是修改的地方。现在,这段代码可以运行了,而且还貌似不错,是吧。</span></p><p><span?style="background-color:?rgb(255,?255,?255);">但是,一个好的程序,不能只是应付正常情况,还要应付错误情况,是吧。如果登录总是出错怎么样呢?我们把LoginService类略微改动,如下:</span></p>??
?
?
?
[java]?view plaincopy
- 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。
?
?
?
[java]?view plaincopy
- 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:
?
?
[java]?view plaincopy
- 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);??
- ????}??
- }??
?
[java]?view plaincopy
- <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> ?