不良代码展示-Activity中使用线程的例子

转自http://blog.csdn.net/yihui823/article/details/6741411

一段坏代码如何变成优雅代码

 

原创文章,如有转载,请注明出处:http://blog.csdn.net/yihui823/article/details/6741411

 

今天看到一段很糟糕的代码。于是做了一个工程,模拟这段代码,向大家说明一下线程在使用中要注意的几点。这个例子适合给新手,也欢迎各位高手来指点一下。

首先,上代码。

第一个类LoginService,这是一个模拟类,把业务剥离出去了。只是模拟登录操作而已。

 

 

[java] view plaincopy
 
  1. package com.study;  
  2. package com.study;  
  3.   
  4. /** 
  5.  * 虚拟的一个登录服务. 
  6.  * @author yihui823 
  7.  */  
  8. public class LoginService {  
  9.   
  10.     //单例  
  11.     private static LoginService oneInstance = new LoginService();  
  12.       
  13.     /** 
  14.      * 得到唯一的一个单例 
  15.      * @return 唯一的一个单例 
  16.      */  
  17.     public static LoginService getInstance() {  
  18.         return oneInstance;  
  19.     }  
  20.       
  21.     //登录成功标记  
  22.     private boolean hadLogin = false;  
  23.       
  24.     /** 
  25.      * 模拟登录操作 
  26.      * @return true:登录成功 
  27.      */  
  28.     public boolean login() {  
  29.         try {  
  30.             Thread.sleep(2000);  
  31.         } catch (InterruptedException e) {  
  32.         }  
  33.         hadLogin = true;  
  34.         return hadLogin;  
  35.     }  
  36.       
  37.     /** 
  38.      * 判断是否登录 
  39.      * @return true:已经登录 
  40.      */  
  41.     public boolean isLogin() {  
  42.         return hadLogin;  
  43.     }  
  44. }  


第二个类就是我们的Activity了。

 

 

 

[java] view plaincopy
 
  1. package com.study;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.widget.Toast;  
  6.   
  7. /** 
  8.  * 一个段不好的代码 
  9.  * @author yihui823 
  10.  */  
  11. public class BadCodeActivity extends Activity {  
  12.       
  13.     //登录服务  
  14.     private LoginService lService = LoginService.getInstance();  
  15.       
  16.     /** Called when the activity is first created. */  
  17.     @Override  
  18.     public void onCreate(Bundle savedInstanceState) {  
  19.         super.onCreate(savedInstanceState);  
  20.         setContentView(R.layout.main);  
  21.   
  22.         new Thread(new Runnable() {  
  23.             @Override  
  24.             public void run() {  
  25.                 while(!lService.isLogin() ) {  
  26.                     try {  
  27.                         lService.login();  
  28.                         Thread.sleep(1000);  
  29.                     } catch (InterruptedException e) {  
  30.                         e.printStackTrace();  
  31.                     }  
  32.                 }  
  33.                 Toast.makeText(BadCodeActivity.this, "登录成功", Toast.LENGTH_LONG).show();  
  34.             }  
  35.         }).start();  
  36.     }  
  37. }  

 

[java] view plaincopy
 
  1. <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
 
  1. package com.study;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.os.Handler;  
  6. import android.os.Message;  
  7. import android.widget.Toast;  
  8.   
  9. /** 
  10.  * 一个段不好的代码 
  11.  * @author yihui823 
  12.  */  
  13. public class BadCodeActivity extends Activity {  
  14.       
  15.     //登录服务  
  16.     private LoginService lService = LoginService.getInstance();  
  17.       
  18.     <span style="color:#ff6666;">//外线程访问UI线程的Handle  
  19.     private Handler mhandle = new Handler(){  
  20.         @Override  
  21.         public void handleMessage(Message msg) {  
  22.             Toast.makeText(BadCodeActivity.this, "登录成功", Toast.LENGTH_LONG).show();  
  23.         }  
  24.     };</span>  
  25.       
  26.       
  27.     /** Called when the activity is first created. */  
  28.     @Override  
  29.     public void onCreate(Bundle savedInstanceState) {  
  30.         super.onCreate(savedInstanceState);  
  31.         setContentView(R.layout.main);  
  32.   
  33.         new Thread(new Runnable() {  
  34.             @Override  
  35.             public void run() {  
  36.                 while(!lService.isLogin() ) {  
  37.                     try {  
  38.                         lService.login();  
  39.                         Thread.sleep(1000);  
  40.                     } catch (InterruptedException e) {  
  41.                         e.printStackTrace();  
  42.                     }  
  43.                 }  
  44.                 <span style="color:#ff0000;">mhandle.sendEmptyMessage(0);</span>  
  45.             }  
  46.         }).start();  
  47.     }  
  48. }  

 

[java] view plaincopy
 
  1. <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
 
  1. package com.study;  
  2.   
  3. import android.util.Log;  
  4.   
  5. /** 
  6.  * 虚拟的一个登录服务. 
  7.  * @author yihui823 
  8.  */  
  9. public class LoginService {  
  10.   
  11.     private static final String TAG = "LoginService";  
  12.       
  13.     //单例  
  14.     private static LoginService oneInstance = new LoginService();  
  15.       
  16.     /** 
  17.      * 得到唯一的一个单例 
  18.      * @return 唯一的一个单例 
  19.      */  
  20.     public static LoginService getInstance() {  
  21.         return oneInstance;  
  22.     }  
  23.       
  24.     //登录成功标记  
  25.     private boolean hadLogin = false;  
  26.       
  27.     /** 
  28.      * 模拟登录操作 
  29.      * @return true:登录成功 
  30.      */  
  31.     public boolean login() {  
  32.         try {  
  33.             Thread.sleep(2000);  
  34.         } catch (InterruptedException e) {  
  35.         }  
  36.         Log.d(TAG, "we are login");  
  37. //      hadLogin = true;  
  38.           
  39.         return hadLogin;  
  40.     }  
  41.       
  42.     /** 
  43.      * 判断是否登录 
  44.      * @return true:已经登录 
  45.      */  
  46.     public boolean isLogin() {  
  47.         return hadLogin;  
  48.     }  
  49. }  


增加了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
 
  1. package com.study;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5. import android.os.Handler;  
  6. import android.os.Message;  
  7. import android.util.Log;  
  8. import android.widget.Toast;  
  9.   
  10. /** 
  11.  * 一个段不好的代码 
  12.  * @author yihui823 
  13.  */  
  14. public class BadCodeActivity extends Activity {  
  15.   
  16.     private static final String TAG = "BadCodeActivity";  
  17.       
  18.     //登录服务  
  19.     private LoginService lService = LoginService.getInstance();  
  20.       
  21.     //外线程访问UI线程的Handle  
  22.     private Handler mhandle = new Handler(){  
  23.         @Override  
  24.         public void handleMessage(Message msg) {  
  25.             Toast.makeText(BadCodeActivity.this, "登录成功", Toast.LENGTH_LONG).show();  
  26.         }  
  27.     };  
  28.       
  29.     //通知停止线程的标记  
  30.     private boolean stopFlag = false;  
  31.       
  32.     //登录成功标记  
  33.     private boolean loginOk = false;  
  34.       
  35.     /** 
  36.      * 登录用的线程类 
  37.      */  
  38.     private class LoginThread extends Thread {  
  39.           
  40.         @Override  
  41.         public void run() {  
  42.             while(!stopFlag) {  
  43.                 loginOk = lService.isLogin();  
  44.                 if (loginOk) {  
  45.                     break;  
  46.                 }  
  47.                 try {  
  48.                     lService.login();  
  49.                     Thread.sleep(1000);  
  50.                 } catch (InterruptedException e) {  
  51.                     e.printStackTrace();  
  52.                 }  
  53.             }  
  54.             mhandle.sendEmptyMessage(0);  
  55.         }  
  56.           
  57.         /** 
  58.          * 通知线程需要停止 
  59.          */  
  60.         public void stopLogin() {  
  61.             stopFlag = true;  
  62.         }  
  63.     };  
  64.   
  65.     //用来登录的线程  
  66.     private LoginThread loginThread = new LoginThread();  
  67.       
  68.       
  69.     /** Called when the activity is first created. */  
  70.     @Override  
  71.     public void onCreate(Bundle savedInstanceState) {  
  72.         super.onCreate(savedInstanceState);  
  73.         setContentView(R.layout.main);  
  74.         Log.d(TAG, "BadCodeActivity instance is called onCreate :" + this.hashCode());  
  75.     }  
  76.       
  77.     public void onResume() {  
  78.         super.onResume();  
  79.         Log.d(TAG, "BadCodeActivity instance is called onResume :" + this.hashCode());  
  80.         loginThread.start();  
  81.     }  
  82.       
  83.     public void onPause() {  
  84.         super.onPause();  
  85.         loginThread.stopLogin();  
  86.     }  
  87. }  


现在,我们的线程可以在页面退出的时候正常停止了。
 
但是这段代码还是有问题的。我们仔细看看,线程在Activity构造的时候就已经创建了,然后在程序进到前台的时候启动,退到后台的时候停止。但是线程有这么一个特性:
一旦线程的run()函数运行结束了,这个线程就销毁了,不能再启动了。
现在我们的程序,在退出后将不可能再次显示,所以系统会马上回收掉Activity。如果我们的页面增加一个按钮,迁移到另一个页面,那么在那个页面返回的时候,就会有异常出现。我们修改一下代码来试试。
增加一个Activity:

 

 

[java] view plaincopy
 
  1. package com.study;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5.   
  6. /** 
  7.  * 临时页面 
  8.  * @author yihui823 
  9.  */  
  10. public class TempActivity extends Activity {  
  11.   
  12.     /** Called when the activity is first created. */  
  13.     @Override  
  14.     public void onCreate(Bundle savedInstanceState) {  
  15.         super.onCreate(savedInstanceState);  
  16.         setContentView(R.layout.main);  
  17.     }  
  18. }  



 

[java] view plaincopy
 
    1. <pre class="java" name="code"></pre>  
    2. <p>别忘了修改AndroidManifest.xml,增加Activity的说明:</p>  
    3. <p align="left">         <activity android:name=".TempActivity"<br>  
    4.                   android:label="@string/app_name"/><br>  
    5. </p>  
    6. <p align="left">修改BadCodeActivity:</p>  
    7. <p><br>  
    8. </p>  
    9. <pre class="java" name="code">package com.study;  
    10.   
    11. import android.app.Activity;  
    12. import android.content.Intent;  
    13. import android.os.Bundle;  
    14. import android.os.Handler;  
    15. import android.os.Message;  
    16. import android.util.Log;  
    17. import android.view.View;  
    18. import android.view.View.OnClickListener;  
    19. import android.widget.Button;  
    20. import android.widget.Toast;  
    21.   
    22. /** 
    23.  * 一个段不好的代码 
    24.  * @author yihui823 
    25.  */  
    26. public class BadCodeActivity extends Activity {  
    27.   
    28.     private static final String TAG = "BadCodeActivity";  
    29.       
    30.     //登录服务  
    31.     private LoginService lService = LoginService.getInstance();  
    32.       
    33.     //外线程访问UI线程的Handle  
    34.     private Handler mhandle = new Handler(){  
    35.         @Override  
    36.         public void handleMessage(Message msg) {  
    37.             Toast.makeText(BadCodeActivity.this, "登录成功", Toast.LENGTH_LONG).show();  
    38.         }  
    39.     };  
    40.       
    41.     //通知停止线程的标记  
    42.     private boolean stopFlag = false;  
    43.       
    44.     //登录成功标记  
    45.     private boolean loginOk = false;  
    46.       
    47.     /** 
    48.      * 登录用的线程类 
    49.      */  
    50.     private class LoginThread extends Thread {  
    51.           
    52.         @Override  
    53.         public void run() {  
    54.             while(!stopFlag) {  
    55.                 loginOk = lService.isLogin();  
    56.                 if (loginOk) {  
    57.                     break;  
    58.                 }  
    59.                 try {  
    60.                     lService.login();  
    61.                     Thread.sleep(1000);  
    62.                 } catch (InterruptedException e) {  
    63.                     e.printStackTrace();  
    64.                 }  
    65.             }  
    66.             mhandle.sendEmptyMessage(0);  
    67.         }  
    68.           
    69.         /** 
    70.          * 通知线程需要停止 
    71.          */  
    72.         public void stopLogin() {  
    73.             stopFlag = true;  
    74.         }  
    75.     };  
    76.   
    77.     //用来登录的线程  
    78.     private LoginThread loginThread = new LoginThread();  
    79.       
    80.       
    81.     /** Called when the activity is first created. */  
    82.     @Override  
    83.     public void onCreate(Bundle savedInstanceState) {  
    84.         super.onCreate(savedInstanceState);  
    85.         setContentView(R.layout.main);  
    86.           
    87.  <span style="color:#ff0000;">       Button btn = (Button)findViewById(R.id.btn);  
    88.         btn.setOnClickListener(new OnClickListener() {  
    89.             @Override  
    90.             public void onClick(View arg0) {  
    91.                 startActivity(new Intent(BadCodeActivity.this,TempActivity.class));  
    92.             }  
    93.         });</span>  
    94.           
    95.         Log.d(TAG, "BadCodeActivity instance is called onCreate :" + this.hashCode());  
    96.     }  
    97.       
    98.     public void onResume() {  
    99.         super.onResume();  
    100.         Log.d(TAG, "BadCodeActivity instance is called onResume :" + this.hashCode());  
    101.         loginThread.start();  
    102.     }  
    103.       
    104.     public void onPause() {  
    105.         super.onPause();  
    106.         loginThread.stopLogin();  
    107.     }  
    108. }  
    109. </pre>  
    110. <p><br>  
    111. </p>  
    112. <p align="left"> </p>  
    113. <p>其实就是加了一个按钮,做一个页面迁移。别忘了在main.xml里面加上:</p>  
    114. <pre class="html" name="code"><Button  
    115.     android:id="@+id/btn"    
    116.     android:layout_width="fill_parent"   
    117.     android:layout_height="wrap_content"   
    118.     android:text="@string/hello"  
    119.     />  
    120. </pre>  
    121. <p><br>  
    122.  </p>  
    123. <p>现在我们运行程序。运行之后,点击按钮,画面闪动一下说明是切换了页面。我们偷了个懒,两个Activity共用一个layout,所以页面没有任何变化。但是没关系,我们看log,we are login已经停止输出了。这个时候,我们再按返回键,应该是切换回BadCodeActivity。这个时候系统报错:</p>  
    124. <p>java.lang.IllegalThreadStateException: Thread already started.</p>  
    125. <p>显然,就是说线程已经启动过了,不能再次被利用。</p>  
    126. <p>我们对代码需要做一点点修改。当然,我们也顺手改掉一个BUG:在退出的时候还会报告登录成功。</p>  
    127. <p>并且,我们把控制变量都放在内部类里,做到变量最小化生存空间。</p>  
    128. <p>修改后如下:</p>  
    129. <pre class="java" name="code">package com.study;  
    130.   
    131. import android.app.Activity;  
    132. import android.content.Intent;  
    133. import android.os.Bundle;  
    134. import android.os.Handler;  
    135. import android.os.Message;  
    136. import android.util.Log;  
    137. import android.view.View;  
    138. import android.view.View.OnClickListener;  
    139. import android.widget.Button;  
    140. import android.widget.Toast;  
    141.   
    142. /** 
    143.  * 一个段不好的代码 
    144.  * @author yihui823 
    145.  */  
    146. public class BadCodeActivity extends Activity {  
    147.   
    148.     private static final String TAG = "BadCodeActivity";  
    149.       
    150.     //登录服务  
    151.     private LoginService lService = LoginService.getInstance();  
    152.       
    153.     //外线程访问UI线程的Handle  
    154.     private Handler mhandle = new Handler(){  
    155.         @Override  
    156.         public void handleMessage(Message msg) {  
    157.             Toast.makeText(BadCodeActivity.this, "登录成功", Toast.LENGTH_LONG).show();  
    158.         }  
    159.     };  
    160.       
    161.     /** 
    162.      * 登录用的线程类 
    163.      */  
    164.     private class LoginThread extends Thread {  
    165.   
    166.         //通知停止线程的标记  
    167.         private boolean stopFlag = false;  
    168.           
    169.         //登录成功标记  
    170.         private boolean loginOk = false;  
    171.           
    172.         @Override  
    173.         public void run() {  
    174.             while(!stopFlag) {  
    175.                 loginOk = lService.isLogin();  
    176.                 if (loginOk) {  
    177.                     break;  
    178.                 }  
    179.                 try {  
    180.                     lService.login();  
    181.                     Thread.sleep(1000);  
    182.                 } catch (InterruptedException e) {  
    183.                     e.printStackTrace();  
    184.                 }  
    185.             }  
    186.             if (loginOk) {  
    187.                 mhandle.sendEmptyMessage(0);  
    188.             }  
    189.         }  
    190.           
    191.         /** 
    192.          * 通知线程需要停止 
    193.          */  
    194.         public void stopLogin() {  
    195.             stopFlag = true;  
    196.         }  
    197.     };  
    198.   
    199.     //用来登录的线程  
    200.     private LoginThread loginThread;  
    201.       
    202.       
    203.     /** Called when the activity is first created. */  
    204.     @Override  
    205.     public void onCreate(Bundle savedInstanceState) {  
    206.         super.onCreate(savedInstanceState);  
    207.         setContentView(R.layout.main);  
    208.           
    209.         Button btn = (Button)findViewById(R.id.btn);  
    210.         btn.setOnClickListener(new OnClickListener() {  
    211.             @Override  
    212.             public void onClick(View arg0) {  
    213.                 startActivity(new Intent(BadCodeActivity.this,TempActivity.class));  
    214.             }  
    215.         });  
    216.           
    217.         Log.d(TAG, "BadCodeActivity instance is called onCreate :" + this.hashCode());  
    218.     }  
    219.       
    220.     public void onResume() {  
    221.         super.onResume();  
    222.         Log.d(TAG, "BadCodeActivity instance is called onResume :" + this.hashCode());  
    223.         <span style="color:#ff0000;">loginThread = new LoginThread();</span>  
    224.         loginThread.start();  
    225.     }  
    226.       
    227.     public void onPause() {  
    228.         super.onPause();  
    229.         loginThread.stopLogin();  
    230.     }  
    231. }  
    232.   
    233. </pre>  
    234. <p align="left"><br>  
    235.  </p>  
    236. <p>现在,我们点击按钮,进入到TempActivity的时候,登录log停止输出;然后按返回键,回到BadCodeActivity的时候,登录log又继续输出。程序基本完成,没有僵尸线程存在了。红色的那行代码是关键!</p>  
    237. <p>我们总结一下:</p>  
    238. <p><strong>1, 线程对象一定要有变量指向它,以便我们可以控制。</strong></p>  
    239. <p><strong>2, 线程类一定要有停止条件,以便外界通知线程自行停止。</strong></p>  
    240. <p><strong>3, 线程启动之后,不管是不是已经停止了,都是不能再次利用的。</strong></p>  
    241. <p>4,  <strong>在onResume里新建和启动线程,在onPause里停止线程。</strong></p>  
    242. <p> </p>  
    243. <p> </p>  
    244. <pre></pre>  
    245. <pre></pre>  
    246. <pre></pre>  
    247. <pre></pre>  

posted on 2015-05-05 19:51  wwicked  阅读(210)  评论(0编辑  收藏  举报

导航