关闭

Android(线程一) 线程

1111人阅读 评论(0) 收藏 举报
分类:

    在Android开发中,我们总是会遇到线程!最近有时间整理了有关Android线程的东西,和大家分享!

一.线程描述。

    在Android中,有两种性质的线程,主线程和子线程。

    1.主线程。主线程是指进程所拥有的线程,一个进程中只有一个主线程。主线程也叫UI线程,主要作用是处理界面相关的业务。

    2.子线程。Android中的耗时操作必须在非UI线程中,否则,可能会引起界面卡顿,甚至,可能应用崩溃。所以,耗时操作例如访问网络、I/O操作,要在子线程中执行。

    由于Android 特性,如果在主线程中执行耗时操作那么就会导致程序无法及时响应,因此耗时操作必须放到子线程中去执行。从Android 3.0开始系统要求网络访问必须在子线程中执行,否则网络访问将会失败并抛出android.os.NetworkOnMainThreadException异常,这样做是为了避免在主线程由于耗时操作所阻塞从而出现ANR现象。

二.线程状态。

    Android的线程其实是来自于Java线程,那么就简单描述Java线程的概念。

   2.1 进程和线程的区别。

    最初在Unix等多用户、多任务操作系统环境下,为了提高操作系统的并行性和资源利用率,提出了进程的的概念。进程首先是个动态的过程,是程序的一次执行。进程用于表示应用程序在内存环境中执行的基本单元,通常有如下一些特点。

    (1). 进程是操作系统环境中的基本成分,是系统资源分配的基本单位。

    (2). 进程在执行过程中有内存单元的初始入口,并且进程存活过程中始终拥有独立的内存地址空间。

    (3). 进程的生存周期状态包括创建、就绪、运行、阻塞和死亡等状态。

    从用户的角度来看,进程是应用程序的一个执行过程。从操作系统核心角度来看,进程代表的是操作系统分配内存、CPU时间片等资源的基本单位,是为正在运行的程序提供的运行环境。

   随着对计算机能力需求的不断增长,计算机系统的资源也显得越发宝贵。频繁的进程状态切换会消耗大量的系统资源,为解决此问题,提出了线程的概念。进程仍然是处理器调度的基本单位,但不再是资源分配的基本单位。线程的状态切换比进程切换的负担要小得多。Java语言从开始就被设计成为多线程环境,从语言级别上支持多线程。

  在计算机操作系统中,进程是进行资源分配和调度的基本单位,这对于基于Linux的Android系统也不例外。在Android的设计中,一个应用默认有有个(主)进程。但是我们可以通过配置实现一个应用对应多个进程。

    2.2.接下来,我们就分析一下Java线程的状态。Java线程有五种状态。

    (1).新建状态(NEW) ,这种状态是刚刚创建了新的线程。在线程类使用了new 关键字实例化后且在调用start() 方法之前,线程处于创建状态。处于创建状态的线程仅仅分配了内存空间,属于生命周期的初始状态。

    (2).就绪状态(RUNNABLE),这种状态是指调用了该线程的start()方法,变得可运行,等待获取CPU的使用权。在线程调用了start()方法后即处于就绪状态。处于就绪状态的线程具备了除CPU之外运行所需的所有资源。就绪状态 线程排队等待CPU,有系统调度为其分配。

    (3).运行状态(Running),这种状态是指线程获取CPU,执行程序片段。处于就绪状态的线程获得CPU之后即处于运行状态。处于运行状态的线程才开始真正执行线程run()方法的内容。

    (4).阻塞状态(Blocked),这种状态是指处于Running状态的线程因为某种原因,比如调用了sleep()方法,而让出了CPU的使用权。处于运行状态的线程如果因为某种原因不能继续执行,则进入阻塞状态。阻塞状态与就绪状态不同:就绪状态只是因为缺少CPU而不能执行,而阻塞状态是由于各种原因引起线程不能执行,不仅仅是缺少CPU。引起阻塞的原因解除后,线程再次转为就绪状态,等等分配CPU运行。

    (5).死亡状态(Dead),这种状态是指线程执行完成或者出现某种异常退出了run()方法。当线程执行完run()方法的内容或被强制终止时,线程处于死亡状态,线程的整个生命周期结束。

     线程生命周期,如下图所示:


三.线程使用。

   Android中实现子线程用两种方式,一种是继承Thread类,另一种是实现Runnable接口。

   搭建一个web项目,Android客户端可以访问,返回json数据,解析后显示在TextView上!请求网络部分使用这两种去实现,

  1.继承Thread类

findViewById(R.id.btn_getdata).setOnClickListener(
				new OnClickListener() {

					@Override
					public void onClick(View v) {
						// TODO Auto-generated method stub
						new MyThread().start();
					}
				});

class MyThread extends Thread {
		@Override
		public void run() {
			String jsonStr = NetWorkUtils.getData1(url);
			if (!TextUtils.isEmpty(jsonStr)) {
				Log.e("jsonStr", jsonStr);
				tv.setText(jsonStr);
			}
		}
	}

如果你和我一样,都是直接在子线程中设置TextView的文字,那么程序肯定会报错,错误日志如下:


显而易见,是说,我们不能在非UI线程中更新UI。那么接下来,我们修改程序:

  String url = "http://192.168.1.114:8080/json/get_data.json";
	private TextView tv;
	StringBuffer buffer = new StringBuffer();
	private Handler MyHandle = new Handler() {
		public void handleMessage(android.os.Message msg) {
			String json = (String) msg.obj;
			List<Person> list = parseJsonData(json);
			for (Person person : list) {
				buffer.append(person.toString());
			}
			tv.setText(buffer.toString());
		};
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		tv = (TextView) findViewById(R.id.tv_msg);
		findViewById(R.id.btn_getdata).setOnClickListener(
				new OnClickListener() {

					@Override
					public void onClick(View v) {
						// TODO Auto-generated method stub
						new MyThread().start();
					}
				});
	}

	class MyThread extends Thread {
		@Override
		public void run() {
			String jsonStr = NetWorkUtils.getData1(url);
			if (!TextUtils.isEmpty(jsonStr)) {
				Log.e("jsonStr", jsonStr);
				Message message = new Message();
				message.obj = jsonStr;
				MyHandle.sendMessage(message);
			}
		}
	}


/**
	 * 使用gson解析
	 * @param jsonStr
	 */
	private List<Person> parseJsonData(String jsonStr) {
		Gson gson = new Gson();
		List<Person> list = gson.fromJson(jsonStr,
				new TypeToken<List<Person>>() {
				}.getType());
		return list;
	}

在子线程中,请求完接口后,获取到json数据后,然后通过Handler通知主线程去更新UI(设置TextView的文字)。

2.实现Runnable接口。你真的了解Runnable接口,详情请看你不知道的Runnable接口

 String url = "http://192.168.1.114:8080/json/get_data.json";
	private TextView tv;
	StringBuffer buffer = new StringBuffer();
	private Handler MyHandle = new Handler() {
		public void handleMessage(android.os.Message msg) {
			String json = (String) msg.obj;
			List<Person> list = parseJsonData(json);
			for (Person person : list) {
				buffer.append(person.toString());
			}
			tv.setText(buffer.toString());
		};
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		tv = (TextView) findViewById(R.id.tv_msg);
		findViewById(R.id.btn_getdata).setOnClickListener(
				new OnClickListener() {

					@Override
					public void onClick(View v) {
						// TODO Auto-generated method stub
						new Thread(new MyRunnable()).start();
					}
				});
	}

	class MyRunnable implements Runnable {

		@Override
		public void run() {
			 String jsonStr = NetWorkUtils.getData1(url);
			if (!TextUtils.isEmpty(jsonStr)) {
				Log.e("jsonStr", jsonStr);
				Message message = new Message();
				message.obj = jsonStr;
				MyHandle.sendMessage(message);
			}
		}
	}


/**
	 * 使用gson解析
	 * @param jsonStr
	 */
	private List<Person> parseJsonData(String jsonStr) {
		Gson gson = new Gson();
		List<Person> list = gson.fromJson(jsonStr,
				new TypeToken<List<Person>>() {
				}.getType());
		return list;
	}

PS:1.详细实现,代码下载链接地址 Android 线程 解析

     2.也许有的朋友会说,耗时操作的话,开启一个服务就可以了,不用开启一个线程,因为好多资料上都写着,访问网络或者其他耗时操作应该在一个服务中去执行。这样的说法其实不严谨,因为即使你开启了服务,但是还是依旧在主线程中运行(服务中没有开启子线程),你可以在服务中打印线程的名称或者线程id,那么肯定显示的是主线程的信息。所以,即使开启服务,那么有关耗时操作的话,还需要在服务中开启子线程去执行耗时操作!

     3.实现多线程有两种方式,但是你真的了解Runnable接口吗,详情请看,你不知道的Runnable接口

四.总结。

   本篇只是简单的描述了,线程的概念、状态以及如何使用,希望读完后,你对于线程有新的认识!如果你还要了解多线程有关的知识,可以参考这篇文章,Android(线程二) 线程池详解!

PS: Android进程知识补充  (摘录自 技术小黑屋的博文)

1. android:process

  应用实现多进程需要依赖android:process这个属性;

  适用元素:Application,Activity,Service,ContentProvider,BroadCastReceive;

  通常情况下,这个属性的值是以‘:’开头的,表示这个进程是应用私有的。无法在跨应用之间共用;

  如果该属性值以小写字母开头,表示这个进程为全局进程。可以被多个应用共用。

  一个 应用android:process简单示例:

<activity android:name=".MusicPlayerActivity" android:process=":music"/>

<activity android:name=".AnotherActivity" android:process="droidyue.com"/>
2.应用多进程的好处

(1).增加App可用内存;

在Android中,默认情况下系统会为每个App分配一定大小的内存。比如从最早的16M到后面的32M以及48M等。具体内存大写取决于硬件和系统版本。

这些有限的内存对于普通的App是够用的,但是对于展示大量图片的应用来说,显得实在是捉襟见肘。

仔细研究一下,你会发现系统这个限制是作用域进程的(毕竟进程是作为资源分配的基本单位)。意思是说,如果一个应用实现了多个进程,那么这个应用可以获得更多的内存。

(2).独立于主进程;
可以独立于主进程,确保某些任务的执行和完成。

举一个简单的例子,之前的一个项目存在推出的功能,其具体实现为杀掉进程。为了保证某些统计数据上报正常,不受当前进程推出的影响,我们可以使用独立的进程来完成。

3.多进程的不足与缺点

(1).数据共享问题

由于处于不同的进程导致了数据无法共享内容,无论是static变量还是单例模式的实现;

SharedPreferences还没有增加多进程的支持;

跨进程共享数据可以通过Intert,Messager,AIDL等。

(2).SQLite容易被锁

由于每个进程可能会使用各自的SQLOpenHelper实例,如果两个进程同时对数据库操作,则会发生SQLiteDatabaseLockedException等异常;

解决方法,可以使用ContentProvider来实现或者使用其他存储方式。

(3).不必要的初始化

多进程之后,每个进程在创建的时候,都会执行自己的Application.onCrete方法;

通常情况下,onCreate中包含了我们很多业务相关的初始化,更重要的这其中的没有做按照进程;

按需初始化,即每个进程都会执行全部的初始化;

按需初始化,需要根据当前进程名称,进行最小需要的业务初始化;

按需初始化可以选择简单的 if else 判断,也可以结合工厂模式···;



  推荐文章,JSON 解析

                 JSON 使用讲解


1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:404574次
    • 积分:4733
    • 等级:
    • 排名:第6915名
    • 原创:123篇
    • 转载:27篇
    • 译文:0篇
    • 评论:139条
    博客专栏
    最新评论