深入解析Android 虚拟机
上QQ阅读APP看书,第一时间看更新

6.3 进程与线程

当某个组件第一次运行的时候,Android启动了一个进程。在默认情形下,所有的组件和程序运行在这个进程和线程中。另外,也可以安排组件在其他的进程或者线程中运行。在本节的内容中,将简要介绍Android进程与线程的基本知识。

6.3.1 进程

组件运行的进程由Manifest File控制,组件中的节点<activity>、<service>、<receiver>和<provider>都包含了一个Process属性。通过Process属性,可以设置组件运行的进程,既可以配置组件在一个独立进程中运行,也可以配置多个组件在同一个进程运行,甚至可以配置多个程序在同一个进程中运行(前提是这些程序共享一个User ID并给定同样的权限)。另外,在<application>节点中也包含了Process属性,可以用来设置程序中所有组件的默认进程。

所有的组件在此进程的主线程中实例化,系统对这些组件的调用从主线程中分离。并非每个对象都会从主线程中分离。一般来说,响应例如View.onKeyDown()用户操作的方法和通知的方法也在主线程中运行。这就表示,组件被系统调用的时候不应该长时间运行或者阻塞操作(如网络操作或者计算大量数据),因为这样会阻塞进程中的其他组件。可以把这类操作从主线程中分离。

当更加常用的进程无法获取足够内存,Android可能会关闭不常用的进程。下次启动程序的时候会重新启动进程。

当决定哪个进程需要被关闭的时候,Android会考虑哪个对用户更加有用。如Android会倾向于关闭一个长期不显示在界面的进程来支持一个经常显示在界面的进程,是否关闭一个进程决定于组件在进程中的状态。

6.3.2 线程

即使为组件分配了不同的进程,有时候也需要再分配线程。比如用户界面需要很快对用户进行响应,因此某些费时的操作,如网络连接、下载或者非常占用服务器时间的操作应该放到其他线程。

线程通过Java的标准对象Thread创建,在Android中提供了如下管理线程的方法。

Looper:在线程中运行一个消息循环。

Handler:传递一个消息。

HandlerThread:创建一个带有消息循环的线程。

Android会让一个应用程序在单独的线程中,指导它创建自己的线程。除了上述方法外,通过使用应用程序组件,例如Activity、service、broadcast receiver,可以在主线程中实现实例化操作。

6.3.3 线程安全的方法

了解了进程和线程的基本知识后,很有必要了解线程安全方面的知识。在某些情况下,方法可能调用不止一个线程,因此需要注意方法的线程安全。例如当一个调用在IBinder(是一个接口,是对跨进程的对象的抽象)对象中的方法的程序启动了和IBinder对象相同的进程,方法就在IBinder的进程中执行。但是,如果调用者发起另外一个进程,方法在另外一个线程中运行,这个线程与IBinder对象在一个线程池中,它不会在进程的主线程中运行。如果一个Service从主线程被调用onBind()方法,onBind()返回的对象中的方法会被从线程池中调用。因为一个服务可能有多个客户端请求,不止一个线程池会在同一时间调用IBinder的方法,所以此时IBinder必须保证线程安全。

6.3.4 Android的线程模型

Android包括一个应用程序框架、几个应用程序库和一个基于Dalvik虚拟机的运行时,所有这些都运行在Linux内核之上。通过利用Linux内核的优势,Android得到了大量操作系统服务,包括进程和内存管理、网络堆栈、驱动程序、硬件抽象层、安全性等相关的服务。

在安装Android应用程序的时候,Android会为每个程序分配一个Linux用户ID,并设置相应的权限,这样其他应用程序就不能访问此应用程序所拥有的数据和资源了。

在Linux中,一个用户ID识别一个给定用户。在Android上,一个用户ID识别一个应用程序。应用程序在安装时被分配用户ID,应用程序在设备上的存续期间内,用户ID保持不变。

在默认情况下,每个APK运行在它自己的Linux进程中。当需要执行应用程序中的代码时,Android会启动一个JVM,即一个新的进程来执行,因此不同的apk运行在相互隔离的环境中。

不同的Android应用程序可以运行在相同的进程中,要想实现这个功能,首先必须使用相同的私钥签署这些应用程序,然后使用manifest文件给它们分配相同的Linux用户ID,这通过用相同的“值/名”定义manifest属性android:sharedUserId来实现。

(1)Android的单线程模型。

当第一次启动一个程序时,Android会同时启动一个对应的主线程(Main Thread),主线程主要负责处理与UI相关的事件,如用户的按键事件、用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理,因此主线程通常又被称为UI线程。

在开发Android应用时必须遵守单线程模型的原则:Android UI操作并不是线程安全的,并且这些操作必须在UI线程中执行。

如果在非UI线程中直接操作UI线程,则会抛出如下异常:

        android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view
        hierarchy can touch its views

这与普通的Java程序是不同的。

由于UI线程负责事件的监听和绘图处理,因此必须保证UI线程能够随时响应用户的需求,UI线程里的操作应该向中断事件那样短小,费时的操作(如网络连接)需要另开线程;否则,如果UI线程超过5 s没有响应用户请求,会弹出对话框提醒用户终止应用程序。

如果在新开的线程中需要对UI进行设定,就可能违反单线程模型,因此Android采用一种复杂的Message Queue机制保证线程间通信。

(2)Message Queue。

Message Queue是一个消息队列,用来存放通过Handler发布的消息。Android在第一次启动程序时默认会为UI thread创建一个关联的消息队列,可以通过Looper.myQueue()得到当前线程的消息队列,用来管理程序的一些上层组件,例如activities和broadcast receivers等。可以在自己的子线程中创建Handler与UI thread通信。

通过Handler可以发布或者处理一个消息或者是一个Runnable的实例,每个Handler都会与唯一的一个线程以及该线程的消息队列管理。Looper扮演着一个Handler和消息队列之间通信桥梁的角色。程序组件首先通过Handler把消息传递给Looper, Looper把消息放入队列。Looper也把消息队列里的消息广播给所有的Handler, Handler接收到消息后调用handleMessage进行处理。例如下面的演示代码:

        public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.main);
          editText = (EditText) findViewById(R.id.weather_city_edit);
          Button button = (Button) findViewById(R.id.goQuery);
          button.setOnClickListener(this);
          Looper looper = Looper.myLooper();  //得到当前线程的Looper实例,由于当前线程是UI线程也可以通过
        Looper.getMainLooper()//得到
            messageHandler = new MessageHandler(looper);  //此处甚至可以不需要设置Looper,因为Handler默
        //认就使用当前线程的Looper
        }
        public void onClick(View v) {
          new Thread() {
            public void run() {
                Message message = Message.obtain();
                message.obj = "abc";
                messageHandler.sendMessage(message);  //发送消息
              }
          }.start();
        }
        Handler messageHandler = new Handler {
          public MessageHandler(Looper looper) {
            super(looper);
          }
          public void handleMessage(Message msg) {
            setTitle((String) msg.obj);
          }
        }

对于上述演示代码,当这个activity执行完oncreate、onstart和onresume后,就监听UI的各种事件和消息。当点击一个按钮后,启动一个线程,线程执行结束后,通过handler发送一个消息,由于这个handler属于UI线程,因此这个消息也发送给UI线程,然后UI线程又把这个消息给handler处理,而这个handler是UI线程创造的,它可以访问UI组件,因此就更新了页面。

由于通过handler需要自己管理线程类,如果业务稍微复杂,代码看起来就比较混乱,因此Android提供了类AsyncTask来解决这个问题。

(3)AsyncTask。

首先继承类publishProgress,实现如下所示的方法。

onPreExecute():该方法将在执行实际的后台操作前被UI thread调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条。

doInBackground(Params...):在方法onPreExecute执行后马上执行,该方法运行在后台线程中。这里将主要负责执行那些很耗时的后台计算工作。

publishProgress():更新实时的任务进度。该方法是抽象方法,必须实现子类。

onProgressUpdate(Progress...):在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。

onPostExecute(Result):在doInBackground执行完成后,onPostExecute方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread。

使用publishProgress类时需要遵循以下规则。

Task的实例必须在UI thread中创建。

execute方法必须在UI thread中调用。

不要手动调用这些方法,只调用execute即可。

该task只能被执行一次,否则多次调用时将会出现异常。

例如下面的演示代码:

        public void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.main);
              editText = (EditText) findViewById(R.id.weather_city_edit);
              Button button = (Button) findViewById(R.id.goQuery);
              button.setOnClickListener(this);
        }
        public void onClick(View v) {
              new GetWeatherTask().execute(“aaa”);
        }
        class GetWeatherTask extends AsyncTask<String, Integer, String> {
            protected String doInBackground(String... params) {
                return getWetherByCity(params[0]);
            }
            protected void onPostExecute(String result) {
                setTitle(result);
            }