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

 

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

 

原创文章,如有转载,请注明出处: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 target=_blank 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">

别忘了修改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线程的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();
    }
}


 

其实就是加了一个按钮,做一个页面迁移。别忘了在main.xml里面加上:

<Button
	android: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线程的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();
    }
}


 

现在,我们点击按钮,进入到TempActivity的时候,登录log停止输出;然后按返回键,回到BadCodeActivity的时候,登录log又继续输出。程序基本完成,没有僵尸线程存在了。红色的那行代码是关键!

我们总结一下:

1, 线程对象一定要有变量指向它,以便我们可以控制。

2, 线程类一定要有停止条件,以便外界通知线程自行停止。

3, 线程启动之后,不管是不是已经停止了,都是不能再次利用的。

4,  在onResume里新建和启动线程,在onPause里停止线程。

 

 欢迎添加个人微信公众号:


 
 
 
 

                
  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值