android线程管理一(进程与线程)

来源:互联网 发布:淘宝店怎么加入天猫 编辑:程序博客网 时间:2024/06/05 04:39

前言

     如果某个应用程序组件是第一次被启动,且这时应用程序也没有其他组件在运行,则Android系统会为应用程序创建一个包含单个线程的linux进程。默认情况下,同一个应用程序的所有组件都运行在同一个进程和线程里(这个线程叫做“main”主线程)。如果组件启动时,已经存在应用程序的进程了(因为应用程序的其它组件已经在运行了),则此组件会在已有的进程和线程中启动运行。不过,可以指定组件运行在其他进程里,也可以为任何进程创建额外的线程。  转载请注明出处:小石头的博客  :http://blog.csdn.net/lu1024188315/article/details/74518599

一 进程

1 UI组件与进程  

     默认情况下,同一个应用程序内的所有组件都是运行在同一个进程中的,大部分应用程序也不会去改变它。不过,如果需要指定某个特定组件所属的进程,则可以利用manifest 文件来达到目的。

manifest文件中的每种组件元素——<activity>、<service>、<receiver>和<provider>都支持定义android:process属性,用于指定组件运行的进程。设置此属性即可实现每个组件在各自的进程中运行,或者某几个组件共享一个进程而其它组件运行于独立的进程。设置此属性也可以让不同应用程序的组件运行在同一个进程中——实现多个应用程序共享同一个Linux用户ID、赋予同样的权限。

   <application>元素也支持android:process属性,用于指定所有组件的默认进程。

    如果内存不足,可又有其它为用户提供更紧急服务的进程需要更多内存,Android可能会决定关闭一个进程。在此进程中运行着的应用程序组件也会因此被销毁。当需要再次工作时,会为这些组件重新创建一个进程。

在决定关闭哪个进程的时候,Android系统会权衡它们相对用户的重要程度。比如,相对于一个拥有可见activity的进程,更有可能去关闭一个activity已经在屏幕上看不见的进程。也就是说,是否终止一个进程,取决于运行在此进程中组件的状态。

2 进程的生命周期

    Android系统试图尽可能长时间地保持应用程序进程,但为了新建或者运行更加重要的进程,总是需要清除过时进程来回收内存。为了决定保留或终止哪个进程,根据进程内运行的组件及这些组件的状态,系统把每个进程都划入一个重要性层次结构中。重要性最低的进程首先会被清除,然后是下一个最低的,依此类推,这都是回收系统资源所必需的。

重要性层次结构共有5级,以下列表按照重要程度列出了各类进程(第一类进程是最重要的,将最后一个被终止):

(1)前台进程 

用户当前操作所必须的进程。满足以下任一条件时,进程被视作处于前台:

a:其中运行着正与用户交互的Activity(Activity对象的onResume()方法已被调用)。

b:其中运行着被正与用户交互的activity绑定的服务Service(这句话可以理解为一个Service,这个Service被绑定在当前正在与用户交互的Activity上,这个Service已经被启动并且正在运行)。

c:其中运行着“前台”服务Service——服务以startForeground()方式被调用。

d:其中运行着正在执行生命周期回调方法(onCreate()、onStart()或onDestroy())的服务Service。

e:其中运行着正在执行onReceiver()方法的BroadcastReceiver

      一般而言,任何时刻前台进程都是为数不多的,只有作为最后的策略——当内存不足以维持它们同时运行时——才会被终止。通常,设备这时候已经到了内存分页状态(memory paging state)的地步,终止一些前台进程是为了保证用户界面的及时响应.

(2) 可见进程

     没有前台组件、但仍会影响用户在屏幕上所见内容的进程。满足以下任一条件时,进程被认为是可见的:

a:其中运行着不在前台的activity,但用户仍然可见到此activityonPause()方法被调用了)。比如以下场合就可能发生这种情况:前台activity打开了一个Dialog(对话框,而之前的activity还允许显示在后面。

b:其中运行着被可见(或前台)activity绑定的服Service

可见进程被认为是非常重要的进程,除非无法维持所有前台进程同时运行了,它们是不会被终止的。

(3) 服务进程

      此进程运行着由startService()方法启动的服务,它不会升级为上述两级别。尽管服务进程不直接和用户所见内容关联,但他们通常在执行一些用户关心的操作(比如在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台、可见进程同时运行,系统会保持服务进程的运行。

(4) 后台进程

    包含目前用户不可见activityActivity对象的onStop()方法已被调用)的进程。这些进程对用户体验没有直接的影响,系统可能在任意时间终止它们,以回收内存供前台进程、可见进程及服务进程使用。通常会有很多后台进程在运行,所以它们被保存在一个LRU(最近最少使用)列表中,以确保最近被用户使用的activity最后一个被终止。如果一个activity正确实现了生命周期方法,并保存了当前的状态,则终止此类进程不会对用户体验产生可见的影响。因为在用户返回时,activity会恢复所有可见的状态.

(5)空进程

    不含任何活动应用程序组件的进程。保留这种进程的唯一目的就是用作缓存,以改善下次在此进程中运行组件的启动时间。为了在进程缓存和内核缓存间平衡系统整体资源,系统经常会终止这种进程。

3 进程的级别规则

         依据进程中目前活跃组件的重要程度,Android会给进程评估一个尽可能高的级别。例如:如果一个进程中运行着一个服务和一个用户可见的activity,则此进程会被评定为可见进程,而不是服务进程。

此外,一个进程的级别可能会由于其它进程的依赖而被提高——为其它进程提供服务的进程级别永远不会低于使用此服务的进程。比如:如果A进程中的content provider为进程B中的客户端提供服务,或进程A中的服务被进程B中的组件所调用,则A进程至少被视为与进程B同样重要。

因为运行服务的进程级别是高于后台activity进程的,所以,如果activity需要启动一个长时间运行的操作,则为其启动一个服务service会比简单地创建一个工作线程更好些——尤其是在此操作时间比activity本身存在时间还要长久的情况下。比如,一个activity要把图片上传至Web网站,就应该创建一个服务来执行之,即使用户离开了此activity,上传还是会在后台继续运行。不论activity发生什么情况,使用服务可以保证操作至少拥有服务进程的优先级。同理,上一篇中的广播接收器broadcast receiver也是使用服务而非线程来处理耗时任务的。

二 线程

1 android中的UI线程      

      应用程序启动时,系统会为它创建一个名为“main”主线程。主线程非常重要,因为它主要负责处理与UI相关的事件,例如用户的按键事件用户的触摸事件屏幕绘图事件,并且把相关的事件分发给相应的组件进行处理。它也是应用程序与Android UI组件包(来自android.widgetandroid.view包)进行交互的线程。因此,主线程有时也被叫做UI线程。

      系统并不会为每个组件的实例都创建单独的线程。运行于同一个进程中的所有组件都是在UI线程中实例化的,对每个组件的系统调用也都是由UI线程分发的。因此,对系统回调进行响应的方法(比如报告用户操作的onKeyDown()或生命周期回调方法)总是运行在UI线程中。举个例子,当用户触摸屏幕上的按钮时,应用程序的UI线程把触摸事件分发给widgetwidget先把自己置为按下状态,再发送一个显示区域已失效(invalidate的请求到事件队列中。UI线程从队列中取出此请求,并通知widget重绘自己。如果应用程序在与用户交互的同时需要执行繁重的任务,单线程模式可能会导致运行性能很低下,除非应用程序的执行时机刚好很合适。如果UI线程需要处理每一件事情,那些耗时很长的操作——诸如访问网络或查询数据库等——将会阻塞整个UI(线程)。一旦线程被阻塞,所有事件都不能被分发,包括屏幕绘图事件。从用户的角度看来,应用程序看上去像是挂起了。更糟糕的是,如果UI线程被阻塞超过一定时间(目前大约是5秒钟),用户就会被提示那个可恶的ANR异常。如果引起用户不满,他可能就会决定退出并删除这个应用程序。

     此外,Andoid的UI组件包并不是线程安全的,这意味着如果有多个线程并发操作UI组件,可能会出现线程安全问题。因此不允许从工作线程中操作UI——只能从UI线程中操作用户界面。于是,Andoid的单线程模式必须遵守两个规则:

a:不要阻塞UI线程(也就是说不能在UI线程中不能进行耗时操作)。

b:不要在UI线程之外访问AndoidUI组件包(也就是说Android平台只允许在UI线程中修改UI组件)。

2 线程分类

      线程根据作用可以分为用户线程守护线程,一般我们平时用于执行具体业务逻辑的线程都是用户线程,而守护线程则一般是为正在运行的用户线程提供便利服务的。比方说JVM中的GC(垃圾回收器)线程,它就是一个守护线程,它的作用就是回收其它普通用户线程执行完成后遗留下的内存资源。那我们就会想,若在JVM中的普通用户线程执行完各自的业务逻辑后消亡,守护线程依然在运行,那JVM实例还有存在的必要吗?答案当然是 -- NO。JVM实例不会因为守护线程没有终止而继续存在,它会在所有普通用户线程终止后退出。
       那既然JVM实例并不会因为守护线程正在运行而持续保持,那为了避免操作和对象的完整性,我们不应该将一些较为重要的业务逻辑放在守护线程中执行,如文件、数据库的操作,因为它有可能在任何时候甚至在一个操作的中间发生中断。守护线程并不仅仅存在于系统级别,我们自己也可以创建守护线程,我们只需要Thread对象在调用start()方法之前调用setDaemon(boolean on)方法,将参数设置为true,那我们所创建的线程便是守护线程。

3 线程优先级

       谈到线程的优先级,可能许多读者认为这部分知识并没有单独拿出来学习的必要,因为在java中,线程的优先级无非就是为线程设置从1-10之间的线程等级,在程序运行时,线程调度器会首先选择具有较高优先等级的线程。其实上面读者对线程优先级的理解并没有错,但理解得并不是很深刻和全面,因为线程的优先级是高度依赖系统的,不同的操作系统平台的线程实现机制是不同的。就拿Windows 和Linux操作系统来讲,Windows系统有7个线程优先级别,而Linux系统拥有2的31次方线程优先级别。当Java虚拟机依赖于宿主主机平台(Linux或Windows)的线程实现机制时,java线程的优先级会被映射到宿主主机平台的优先级上,对于拥有2的31次方优先等级的Linux系统来说,支持映射java 10个线程优先等级当然没有问题,但对于只有7个线程优先等级的Windows系统来说,要映射到Java的10个线程优先等级上,势必会造成很多问题,例如可能Java里面的优先级1、2会等同于Windows系统里的1等级,或是线程优先级8、9、10等同于Windows系统里的7等级。那此种情况下,Java的线程优先级的设置就没什么作用了。
       另外Java程序是运行在Java虚拟机中的,而由于虚拟机版本的差异化,也会导致线程优先级的映射产生不同的结果,例如Sun为Linux提供的Java虚拟机,线程的优先级被忽略--即所有线程具有相同的优先级。
      一般我们可以通过调用setPriority(int newPriority)方法为线程设置优先级,方法中传递1-10之间的常量值,用于设定线程优先等级,1为最低优先级(在Java中可以用Thread.MIN_PRIORITY),10为最高优先级(在程序中可以用Thread.MAX_PRIORITY),如何不设置的话,默认线程等级为5(在程序中可以用Thread.NORM_PRIORITY)。

二 线程通信

runOnUiThread

    既然UI线程不能够进行耗时操作,那么想要通过网络请求数据怎么办?当然我们可以使用Thread开启一个子线程,如下(这是部分代码,完整代码点击参考):
new  Thread(new Runnable() {            @Override            public void run() {              //从网络下载图片资源              final Bitmap bitmap  = loadImage();              ThreadTestActivity.this.runOnUiThread(new Runnable() {                  @Override                  public void run() {                      if (bitmap != null){                          imgThread.setImageBitmap(bitmap);                      }                  }              });            }}).start();
      细心的人会发现这里直接调用runOnUiThread方法修改UI组件了,不是说在除UI线程外是不能修改UI组件的吗?是,没有错,那么我们看下runOnUiThread源码:
Activity#runOnUiThread:
//声明消息机制并初始化它final Handler mHandler = new Handler();public final void runOnUiThread(Runnable action) {       //如果当前线程不是UI线程的话,就通过mHandler把action发送到UI线程进程处理        if (Thread.currentThread() != mUiThread) {            mHandler.post(action);        } else {            action.run();        }}
    看吧,从上面来看修改UI组件的工作最终还是在UI线程中进行的,并没有违反第二条规则。
当然这样修改UI组件的方式还有:
Activity.runOnUiThread(Runnable)View.post(Runnable)View.postDelayed(Runnable, long)

2 Handle机制

    android中Handle就是为了实现线程通信,首先在UI线程中初始化一个Handle,子线程请求的数据通过Handle发送过来,然后在handleMessage的方法中进行处理:
网络请求数据(这是部分代码,完整代码点击参考
new  Thread(new Runnable() {            @Override            public void run() {                final Bitmap bitmap  = loadImage(IMG_URL);                //处理方式二                //发送请求结果                Message message =  handler.obtainMessage();                message.what = 21;                message.obj = bitmap;                handler.sendMessage(message);            }}).start();
结果处理:
private Handler handler = new Handler(){        @Override        public void handleMessage(Message msg) {            super.handleMessage(msg);            switch (msg.what){                case 21://处理请求结果                    img_handle.setImageBitmap((Bitmap) msg.obj);                    break;            }        }};

异步任务

      异步任务AsyncTask允许以异步的方式对用户界面进行操作。它先阻塞工作线程,再在UI线程中呈现结果,在此过程中不需要对线程和handler进行人工干预。在doInBackground()方法中处理耗时操作,在onPostExecute方法中对请求结果进行处理。

实现源码(这是部分代码,完整代码点击参考

 //处理方法三private class DownloadImageTask extends AsyncTask<String, Void, Bitmap>{        //在这个方法中处理耗时任务        @Override        protected Bitmap doInBackground(String... params) {            String imgUrl = params[0];            if (TextUtils.isEmpty(imgUrl)){//如果imgUrl 为null 则解析默认图片                return BitmapFactory.decodeResource(ThreadTestActivity.this.getResources(),                 R.mipmap.ic_launcher);            }            return loadImage(imgUrl);        }        //在这个方法中对请求结果进行处理        @Override        protected void onPostExecute(Bitmap bitmap) {            super.onPostExecute(bitmap);            if (bitmap != null){                img_async.setImageBitmap(bitmap);            }        }}
发起请求:
//处理方式三 不要和上面的同时使用new DownloadImageTask().execute(IMG_URL);

三 进程间通讯

     Android利用远程过程调用(remote procedure call,RPC)提供了一种进程间通信(IPC)机制,通过这种机制,被activity或其他应用程序组件调用的方法将(在其他进程中)被远程执行,而所有的结果将被返回给调用者。这就要求把方法调用及其数据分解到操作系统可以理解的程度,并将其从本地的进程和地址空间传输至远程的进程和地址空间,然后在远程进程中重新组装并执行这个调用。执行后的返回值将被反向传输回来。Android提供了执行IPC事务所需的全部代码,因此只要把注意力放在定义和实现RPC编程接口上即可。进程通信方式有:Binder机制、管道、Socket。

 多线程

1多线程理论    

     在Java里有很多地方都在用多线程实现,尽管有时你没有发现,如android开发,当我们点击一个按钮时,我们总不能等待后台的数据处理完在给我们跳转吧,这样用户体验实在是太差了,我们可以先完成跳转,再加一些可以让用户等待的业务(如缓冲条等),然后再呈现结果。还有如我们开发Web时必用的Servlet,天生具有多线程性质,因为对于网站来说,同时又多个访问是最起码的需求,我们绝对不可能一个接一个的处理请求,那样根本不符合现实。这些地方就用到了多线程,并发地处理多个请求,提高程序的响应速度。

2 多线程利弊

       多线程总会让程序跑的更快,所以我们应该多多使用。其实任何事物具有两面性,多线程也会带来一些负面的东西,如完美的解决并发带来的线程安全(这个问题很不好解决,也是处理多线程问题最棘手的事情,而且解决现程安全就会用到同步,同步的话又会带来性能的下降),所以从开发难度、硬件需求、维护成本等方面综合考虑,我们还是得根据实际情况来决定,到底需不需要多线程,或者哪部分需要,都是值得商榷的事儿。

3 进程与线程的关系

         这个问题很经典,无数面试官想从这个问题得到你对线程和进程的理解程度,想完全讲清楚有点儿难度,同时也需要篇幅。从程序开发角度来讲,进程是资源分配的基本单位,是一个程序或者服务的基本单位。我们可以说进程就是程序的执行过程,这个过程包括很多东西,如CPU执行时间、运行内存、数据等,而且是一个动态的过程。线程是轻量级的进程,它们是共享在父进程拥有的资源下,每个线程在父进程的环境中顺序的独立的执行一个活动,每个CPU核心在同一时刻只能执行一个线程,尽管我们有时感觉自己的计算机同时开着多个任务,其实他们每个的执行都是走走停停的,CPU轮流给每个进程及线程分配时间。总结一下二者关系:

a.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位
b.资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
c.处理机分给线程,即真正在处理机上运行的是线程。
d.线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

4 Java多线程实现基本方式

(1)实现Runnable

public class MyThread implements Runnable{@Overridepublic void run() {for(int i=0; i<20; i++){System.out.println(Thread.currentThread().getName()+"-"+i);}}public static void main(String[] args) {Thread t1 = new Thread(new MyThread());Thread t2 = new Thread(new MyThread());t1.start();t2.start();}}

(2)继承Thread

public class MyThread extends Thread{@Overridepublic void run() {for(int i=0; i<20; i++){System.out.println(Thread.currentThread().getName()+"-"+i);}}public static void main(String[] args) {new MyThread().start();new MyThread().start();}}

文献参考:
1《Processes and Threads》
2《线程的状态、分类及优先级
3《JAVA多线程理论


原创粉丝点击