进程与消息处理

Android应用程序是通过消息来驱动的,系统为每一个应用程序维护一个消息队列,应用程序的主线程首先不断地从这个队列中获取消息,然后对这些消息进行处理,这样就实现了通过消息来驱动应用程序的执行。在这一处理机制中,涉及进程、线程、信息处理等。

1.进程与线程

1.1什么是进程

进程是一个具有一定独立功能的程序关于某个数据数据集合的一次运行活动。它是操作系统动态执行的基本单元。在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。基本单元是指操作系统在并发执行的任务中的某个任务,也就是说多个进程一起执行等同于同时执行多个任务。应用程序在运行的时候,操作系统会单独给这个应用程序所属的进程分配堆内存。该内存不被其他进程共享,是独立的,用于应用程序运行时数据集合的存放、变量的内存分配、软件的资源文件的存放等。

通俗地讲,一个进程代表一个应用程序,该应用程序运行在自己的进程当中,使用系统为其分配的堆内存,不受其他应用程序或者是其他进程的影响,是独立运行的。当然,一个进程可以同时运行多个应用程序,这时堆内存是共享的。

在Android系统中,一个进程会对应一个虚拟机,大部分情况下,虚拟机的运行内存为16MB。虚拟机的运行内存在"/system/build.prop"文件中配置,配置语句"dalvik.vm.heapsize=24m",表示该进程拥有最大的运行内存,"dalvik.vm.heapgrothlimit=16m"表示应用程序实际能操作的内存大小。该文件在一般情况下是不可修改的,所以Android在应用程序配置文件AndroidManifest.xml中的Application节点下可设置属性largeheap="true"来使用最大的内存。在应用程序需要大的堆内存时设置该属性,能在一定程度上避免"out of memory"的出现。

1.2进程模型

在安装Android应用程序的时候,Android系统会为每个程序分配一个Linux用户ID,并设置相应的权限,这样其他应用程序就不能访问此应用程序所拥有的数据和资源了。在Android系统中,一个用户ID识别一个应用程序。应用程序通常位于设备上的ROM中,其在安装时被分配一个用户ID,并且保持不变。默认情况下,每个Android应用程序运行在它自己的进程中。当需要执行应用程序时,Android会启动一个虚拟机,即一个新的进程来执行,因此不同的Android应用程序运行在相互隔离的环境中。

图1.进程中运行单个应用程序

假如两个应用程序的用户ID是一样的,那么这两个应用程序将运行在同一个进程中。这就是一个进程中存在多个应用程序的情况,此时这两个使用同一个堆内存,使用同一个虚拟机。要实现这个功能,首先必须使用相同的私钥签署这些应用程序,然后必须使用AndroidManifest.xml文件给它们分配相同的Linux用户ID,这通过在Manifest节点下的android:sharedUserId属性来实现。

图2.进程中运行两个应用程序

1.3Android中进程的生命周期

为了让应用程序快速地进入运行状态,Android系统会尽量让每个开启过的应用程序的进程一直运行着,以便用户在需要切换到应用程序时,能够很快地开启并进入运行状态。但是随着开启运用程序的增多、系统资源的减少,系统会删除一些旧的进程以回收内存给新开启的应用程序或者更需要内存的应用程序。哪些进程称为旧进程呢?

Android系统为每个应用程序分配了一个进程。应用程序中组件(Activity、Service、BroadCast)的状态决定一个进程的“重要性层次”,层次最低属于旧进程。这个“重要性层次”有五个等级,也就是进程的生命周期,按最高层次到最低层次排列如下。

(1)前台进程

  • 所谓的前台进程就是用户正在使用该应用程序,并且正处于当前应用程序的界面中。当某进程正在前台被用户使用或Activity组件的OnResume ()方法被调用时,该进程属于前台进程。
  • 当某进程中与用户交互的Activity相绑定的一个Service正在执行任务时,该进程也属于前台进程。
  • 当与用户交互的服务Service调用startForeground()时,那么该进程属于前台进程。
  • 当它承载的服务正在执行其生命周期回调函数时( OnCreate ()中,OnStart()中,或OnDestroy()),该进程属于前台进程
  • 它承载的BroadcastReceiver正在执行其onReceive()方法时,该进程属于前台进程。

任一时间内,仅有少数进程会处于前台,仅当内存无法维持它们同时运行时才会被回收。一般来说在这种情况下,设备已经处于使用虚拟内存的状态,必须要杀死某些前台进程来保证用户界面的持续响应。

(2)可视进程

  • 没有前台组件(Activity),但仍可被用户在屏幕上看见的进程,称为可视进程。
  • 当某进程包含一个不在前台,但仍然为用户可见的Activity(它的onPause()方法被调用)时,该进程属于可视进程。比较典型的就是前台Activity有一个对话框,Activity位于其下并可以看到到时,该进程称为可视进程。
  • 当某进程包含了一个绑定至一个可视的Activity的服务时,该进程也属于可视进程。

可视进程依然被Android系统视为是很重要的,直到不回收它们便无法维持前台进程运行时,才会被回收。

(3)服务进程

服务进程是由startService()方法启动的服务,它不会变成上述两类进程。尽管服务进程不会直接为用户所见,但是它们一般都在做着用户所关心的事情(比如在后台播放MP3或者从网上下载东西)。所以系统会尽量维持它们的运行,只有在系统的内存不足以维持前台进程和可视进程时,才会回收它们。

(4)背景进程

背景进程包含目前不为用户所见的Activity(Activity对象的onStop()方法被调用)。这些进程与用户体验没有直接的联系,可以在任意时刻被杀死回收内存供前台进程、可视进程以及服务进程使用。一般来说,背景进程会有很多,它们存放于一个LRU(最近最少使用)列表中以确保最后被用户使用的Activity杀死。如果一个Activity正确地实现了生命周期方法,并捕获了正确的状态,那么它的进程被杀死对用户不会有任何不良影响。

(5)空进程

空进程不包含任何活动应用程序组件。这种进程存在的唯一原因是作为缓存以缩短组件再次运行时的启动时间。系统经常会杀死这种进程以保持进程缓存和系统内核缓存之间的平衡。

Android会依据进程中当前活跃组件的重要成都来尽可能来尽可能高地估量一个进程的级别,比如一个进程中同时有一个服务和一个可视的Activity,则进程会被判定为可视进程,而不是服务进程。

此外,一个进程的级别可能会由于其他进程依赖于它而升高。一个为其他进程提供服务的进程级别永远高于使用它服务的进程。比如A进程中的内容提供者为B中的客户提供服务,或者进程A中的服务为进程B中的组件所绑定,则A进程最低也会被视为与进程B拥有同样的重要性。

1.4Android进程间的通信

Android系统并没有采用Linux系统中那么复杂的进程通信机制,而是采用了基于Binder的机制,可能是考虑到移动终端的硬件设备或者是其设备的内存。Android的Binder是基于OpenBinder实现的,有兴趣的读者可以进行相关的了解。Binder可以参考Service来了解Android进程的通信。

1.5什么是线程

线程是进程中的一个实体,它的基本思想是将程序的执行和资源分开,只拥有一点必不可少的资源。一个进程可拥有多个线程,它可以和属于同一进程的其他线程共享进程所拥有的所有资源。同一进程中的线程之间可以并发执行。这样的话,并发程度可以显著的提高。线程有许多进程所具有的特征,因此被称为轻型进程。

结合Android系统来说,当一个程序第一次启动时,Android会同时启动一个对应的主线程(Main Thread)。主线程主要负责处理与UI相关的事件,比如用户的按键事件、用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理。所以,主线程又被叫作UI线程。在开发Android应用时,必须遵守单线程模型的原则:Android UI操作并不是线程安全的(Thread-safe),并且这些操作必须在UI线程中执行。

1.6Android的线程模型

上面讲到Android系统的线程模型是单线程模型,并且不是线程安全的。

1.什么单线程模型

单线程模型就是在一个进程中只能有一个线程在运行,剩下的线程必须等待当前的线程执行完了才能运行。也可以说,一个线程来操作和管理所有的运行,无论是启动新的线程还是其他操作,都由该线程触发和管理。这种模型的缺点在于系统完成一个很小的任务都必须占用很长的时间。在Android系统中,单线程模型就是UI线程控制,即在多个线程运行的状态下,其他线程要执行,必须等待UI线程的操作处理完成后才能进行。

2.什么是线程安全

(1)可以同时被多个线程调用,而调用者不需要任何操作(同步)来确保线程的安全性,这称为线程安全。所以,如果在其他线程中调用UI线程中的组件,可能使UI主线程受到伤害。

(2)当多个线程访问一个类时,如果不用考虑这些线程在运行环境下的调用和交替执行,并且不需要额外的同步及在调用方代码不必做其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。

因为Android是单线程的,又是线程非安全的,所以在开发的时候必须考虑以下两点。

①不要阻塞UI线程,就是说耗时的操作不要放在UI线程中处理,比如网络连接、下载文件、读取数据库等。UI线程中处理界面的呈现和视图(View)事件响应,所以Google官方推荐使用MVC(Model View Contoller)模式来开发Android应用程序。

②不要在非UI线程中调用和刷新UI相关的组件(android.widget和android.view包中的组件)这会涉及Android线程的不安全性。如果在非UI线程中调用了UI相关组件,那就等着报错吧。

1.7进程和线程

一个Android应用只能存在一个进程,但可以存在多个线程。也就是说,当应用启动后,系统分配了内存,这个进程的内存不被其他进程使用,但被进程中一个或多个线程共享。宏观地讲,所所有的进程都是并发执行的,但进程中的多个线程同时执行并不是并发的,系统的CPU会根据应用的线程数触发每个线程执行的时刻。当CPU时间轮到分配某个线程执行时刻时该线程开始执行,执行到下一个线程执行的时刻,依此轮询,直到该线程执行结束。

1.8进程和线程的重要性

Android应用的开发着对进程属性的修改是有限的,仅仅操作一些属性就可以了。一个应用最少具有一个线程,但可以有多个线程。合理地应用线程可以提高系统资源的利用率,提高应用的质量,给用户更好的体验。所以每个开发者都应该理解并熟练掌握线程知识。

2.Handler和AsynTask

Android系统的线程模型属于单线程模型,假如在非UI线程中去访问或者更新只能在UI线程中更新的视图(View,Widget)类,就会报异常。但很多耗时的工作又不能放在UI线程中运行,因为这样容易造成UI线程的阻塞,而非UI线程又不能去更新UI的组件视图。比如在后台非UI线程中下载文件时,又想在UI线程中更新进度条让用户有更直观的感受,应该怎么办呢?Android系统提供了两个更新UI的辅助类:Handler与AsynTask。

2.1Handler的基本概念

Handler类是Android操作系统为开发者封装的一个能异步处理信息的一个辅助类。通过Handler能够很容易地处理信息的发送和接受处理。Handler运作的过程中包含了Android消息机制。

2.2Android消息机制

在了解消息机制之前,首先要明白Handler是怎么使用的。我们在Activity中可以直接使用Handler,代码如下:

  //1.重写Handler类中的handleMessage方法来处理相应值的消息
  //2.通过handler对象的obtainMessage()方法来获取对应handler实例的Message对象
  //3.设置Message对象的相关属性,最要的是值(.what),然后再用hanlder实例的sendMessage(msg)方法将消息发送出去
        Handler handler=new Handler(){
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
                switch (msg.what){
                    case TEST_MSG:
                        //执行刷新操作
                        break;
                        default:break;
                }
            }
        };
        //获得Message对象
        Message msg = handler.obtainMessage();
        msg.arg1 = 0x01;
        msg.arg2 = 0x02;
        msg.obj = "ObjectType";
        //用于在HandleMessage方法中识别消息,也就是用来标识自己
        msg.what = TEST_MSG;
        //讲消息发送到消息队列中,由handlerMessage来处理
        handler.sendMessage(msg);

在其他Thread中使用Handler的代码如下:

   class LooperThread extends Thread{
        public Handler mHandler;

        @Override
        public void run() {
            super.run();
            //通过该方法创建Looper对象,同时初始化消息队列
            Looper.prepare();
            mHandler = new Handler(){
                @Override
                public void handleMessage(@NonNull Message msg) {
                    //在这里接受处理信息
                    super.handleMessage(msg);
                }
            };
            //Loop中通过死循环不断从消息队列中取消息,并回调。
            //HandleMessage(msg)方法处理该消息
            Looper.loop();
        }
    }

上述两种情况中的后者是在单独的线程中创建Handler实例的,创建Handler的步骤如下。

(1)得到Looper对象,得到MessageQueue对象(Looper.prepare()方法内部已完成这两件事)。

(2)实例化Handler并重写handleMessage() 方法,用来接收传递过来的消息,并做相应的处理。

(3)调用Loop()方法死循环获得消息队列的变化,并分发消息,最终回调handleMessage()。

在Activity中可以直接实例化Handler对象的原因是,在Activity启动的过程中,Android的框架在主线程也就是Activity的主线程已经调用Looper.prepare()和Looper.loop()方法了,也就是说,Activity所在的主线程启动的时候就已经有了Looper和MessageQueue的实例对象,而开发者要做的就是实例化Handler。

前面总是说到消息处理、消息机制,那到底什么是消息机制呢?要了解消息机制,首先必须先了解消息处理过程中的相关类。 

  • Handler:主要是开发者用来处理消息类型的类。
  • Message:消息实体类,其中包含了几个重要的成员变量:int arg0,int arg1,int  what,Runnable target。
  • MessageQueue:消息队列,用于存放Message实体,遵循队列数据结构的规则。
  • Looper:Handler与MessageQueue间的桥梁。

Handler的消息机制模型如图3。

图3.Handler的消息机制模型

Android的消息机制就是通过Handler、Looper和消息队列(MessageQueue)来异步处理线程在执行的过程中所引起线程阻塞或线程不安全任务的机制。其中MessageQueue是消息能够异步执行的数据结构;Looper对象则取消息和分发消息以及回调消息处理函数;Handler是发送要执行的消息到消息队列,以及在回调函数中具体处理这个消息。

2.3Handler的具体使用场合

使用默认的构造方法“Handler h = new Handler()”实例化的时候,系统默认将该Handler的Looper对象和MessageQueue关联到Handler所在的线程。若加入到非UI线程,并且没有调用Looper的prepare()方法,那么就会出现“throw new RuntimeException("can't create hanler inside thread that has not called Looper.prepare()")”的异常,意思是没有调用Looper.prepare()的线程不能实例化Handler对象。这是因为不调用Looper.prepare(),Handler就不能实现消息机制的整个过程。

Android系统是单线程模型的,在非UI线程中更新UI组件会伤害UI线程,这是一种线程不安全的情况。使用Handler就可以避免这种情况,把刷新UI的工作留给Handler去做,让耗时的任务在非UI线程中执行,只要用Handler发送消息去更新UI即可。怎么做到呢?就是通过在UI线程中实例化Handler,使得Handler处理消息的过程也在UI线程中执行,这样就不会危害到UI线程了。

2.4AsyncTask的介绍

前面讲到用Handler来进行异步消息的处理和耗时任务的操作,但是使用Handler的过程较为复杂。若其服务的对象不是频繁使用Handler对象来处理任务和消息,那么使用Handler就有点复杂了。所以,Android系统中还为开发者提供了一个异步处理消息的任务类——AsyncTask,它是一个轻量级的异步任务处理类,下面介绍如何使用它。

2.5AsyncTask的使用

使用AsyncTask来处理任务很简单,分为以下三个步骤。

(1)创建一个继承自AsyncTask的类。

(2)重写doInBackground()方法,在该方法中添加后台执行任务的代码

(3)在UI Thread中创建实例并调用execute()方法,传入执行任务过程中要使用的参数,就是doInBackground(Param ... param)的参数。

注意:在onPreExecute()、onPostExecute()中可以访问UI组件,在doInBackground()中不能访问UI组件。


public class ReadTask extends AsyncTask {
    ProgressDialog dialog;
    //任何执行前执行该方法,可以访问UI组件
    @Override
    protected void onPreExecute() {
        dialog = ProgressDialog.show(Main2Activity.this,"","xxx");
        super.onPreExecute();
    }

    //后台执行任务,不可以执行任何与UI相关的操作
    @Override
    protected Object doInBackground(Object[] objects) {
        //省略
        return null;
    }
    //任务执行完成后调用,可以使用UI组件
    @Override
    protected void onPostExecute(Object o) {
        dialog.dismiss();
        super.onPostExecute(o);
    }
}

2.6为什么AsyncTask要在UI Thread中创建才能使用

只有在UI Thread中才能访问UI组件。在Android源代码中,AsyncTask维护了一个Handler的子类和一个线程池对象。线程池对象用来执行耗时操作,Handler用来处理消息。因为Handler关联的Looper对象与Handler所在的线程是一样的,若AsyncTask不在UIThread中创建,就没有Looper对象与Handler关联,也就不能执行消息,在onPreExecute()、onPostExecute()就不能调用UI组件。

2.7Handler和AsyncTask

AsyncTask是使用Handler的消息处理机制来异步执行任务的,也就是说,AsyncTask是Handler使用的一种封装,因此AsyncTask的灵活程度就会受到限制,但较Handler,其使用更为简单、安全、轻巧。Handler则比AsyncTask灵活,没有太多限制,一般在频繁执行任务和刷新操作中使用,但具有一定的线程不安全性。

3.Application

1.什么是Application

一般情况下,初学者会误以为配置在AndroidManifest.xml文件中的“<action android:name="android.intent.action.MAIN" />”所在的Activity在程序启动时最先被调用。其实,Android程序启动时除去后台内核资源分配以及开启进程的操作外,最先被调用的是Application类,随后才是Activity或者其他组件。一个应用中可以不包含任何组件,但是不能不包含Application,也就是AndroidManifest.xml下节点为<Application>和</Application>中配置的文件。

我们可以将Application看作应用程序的唯一实例,Activity等组件可以有多个,但是Application只有一个。既然它是唯一的,那么在应用开发过程中所需的全局变量及其操作就可存放在Application类中。同时,Application是进程自Context的,说明它具备了上下文属性。

2.如何使用Application

使用Application要遵循以下两个步骤。

(1)创建一个类xxx继承自Application

(2)在<Application android:name=".xxx"></Application>配置Application。

这样程序在启动的时候就会先调用Application,然后调用其他组件、注意:onCreate()方法是其开始创建时就被调用的,onTerminate()方法是其停止时被调用的,onConfigurationChanged()方法则是当其配置发生变化时被调用的,onLowMemory()是当其在低内存时被调用的。需要注意,重写这些方法的时候,必须调用其父类的方法super.onCreate()。

既然Application是唯一的应用实例,那么在它里面设置的属性或变量就具有全局性,比如设置<Application android:Theme="@android:style/Theme.NoTitleBar">,在Activity没有设置Theme的时候,所有的Activity都将遵循Application的主题,其他的属性也是如此。

3.单例模式与Application

有些读者可能存在疑问,单例模式照样能实现这种全局变量的功能,为什么还要使用Application呢?实质上Application也使用了单例模式,但在Application中保存了Context上下文变量,而普通的单例模式是无法获得Context对象的,这就是Application和其他单例模式的区别。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值