不良代碼展示-Activity中使用線程的例子

來源:互聯網
上載者:User

 

一段壞代碼如何變成優雅代碼

 

原創文章,如有轉載,請註明出處: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() {@Overridepublic 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();    }}

這個例子呢,顯然啟動並執行時候會報錯的。請見我的另一篇文章:Android畫面UI中的線程約束。我們在非UI線程裡去控制UI介面,就必須使用Handler來發送訊息。修改代碼如下:

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();//外線程訪問UI線程的Handleprivate Handler mhandle = new Handler(){@Overridepublic void handleMessage(Message msg) {Toast.makeText(BadCodeActivity.this, "登入成功", Toast.LENGTH_LONG).show();}};    /** Called when the activity is first created. */    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);new Thread(new Runnable() {@Overridepublic void run() {while(!lService.isLogin() ) {try {lService.login();Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}mhandle.sendEmptyMessage(0);}}).start();    }}

紅色部分代碼,就是修改的地方。現在,這段代碼可以運行了,而且還貌似不錯,是吧。

但是,一個好的程式,不能只是應付正常情況,還要應付錯誤情況,是吧。如果登入總是出錯怎麼樣呢?我們把LoginService類略微改動,如下:

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線程的Handleprivate Handler mhandle = new Handler(){@Overridepublic 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 {@Overridepublic 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);    }}

別忘了修改AndroidManifest.xml,增加Activity的說明:

         <activity android:name=".TempActivity"
                  android:label="@string/app_name"/>

修改BadCodeActivity:

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線程的Handleprivate Handler mhandle = new Handler(){@Overridepublic 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 {@Overridepublic 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);                Button btn = (Button)findViewById(R.id.btn);        btn.setOnClickListener(new OnClickListener() {@Overridepublic 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());        loginThread.start();    }        public void onPause() {    super.onPause();    loginThread.stopLogin();    }}

 

其實就是加了一個按鈕,做一個頁面遷移。別忘了在main.xml裡面加上:

<Buttonandroid:id="@+id/btn"      android:layout_width="fill_parent"     android:layout_height="wrap_content"     android:text="@string/hello"    />

 

現在我們運行程式。運行之後,點擊按鈕,畫面閃動一下說明是切換了頁面。我們偷了個懶,兩個Activity共用一個layout,所以頁面沒有任何變化。但是沒關係,我們看log,we are login已經停止輸出了。這個時候,我們再按返回鍵,應該是切換回BadCodeActivity。這個時候系統報錯:

java.lang.IllegalThreadStateException: Thread already started.

顯然,就是說線程已經啟動過了,不能再次被利用。

我們對代碼需要做一點點修改。當然,我們也順手改掉一個BUG:在退出的時候還會報告登入成功。

並且,我們把控制變數都放在內部類裡,做到變數最小化生存空間。

修改後如下:

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線程的Handleprivate Handler mhandle = new Handler(){@Overridepublic 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;@Overridepublic 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() {@Overridepublic 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());        loginThread = new LoginThread();        loginThread.start();    }        public void onPause() {    super.onPause();    loginThread.stopLogin();    }}

 

現在,我們點擊按鈕,進入到TempActivity的時候,登入log停止輸出;然後按返回鍵,回到BadCodeActivity的時候,登入log又繼續輸出。程式基本完成,沒有殭屍線程存在了。紅色的那行代碼是關鍵!

我們總結一下:

1, 線程對象一定要有變數指向它,以便我們可以控制。

2, 線程類一定要有停止條件,以便外界通知線程自行停止。

3, 線程啟動之後,不管是不是已經停止了,都是不能再次利用的。

4,  在onResume裡建立和啟動線程,在onPause裡停止線程。

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.