安卓的进程与线程及其相关代码实现

快速预览

*组件指的是:安卓4大组件:Activity,Service,BroadCastReceiver,ContentProvider

· 任何应用程序运行于其所拥有的进程中,默认情况下,该程序的组件也运行在那个进程中。


· 任何缓慢的,阻塞的操作都应该新起一个线程,从而避免使用户觉得慢。


当一个应用程序组件启动,且应用程序无其他组件运行,安卓系统将开启一个新的Linux进程,而此进程中仅有单一的执行线程。默认情况下,同一应用程序的所有组件都运行于同一个进程和线程中(也叫主线程)。如果组件启动时,已经存在应用程序的组件进程了,那么组件将以那个进程启动,并使用相同的执行线程。然而,你可以安排不同的组件运行于独立的进程中,同时,你可以为任意进程创建额外的线程。


本文讨论了在一个安卓程序中,线程和进程如何工作。


进程


默认情况下,一个程序的所有组件将在一个进程下运行,并且大部分应用程序不应该更改它。然而,如果你发现你需要控制特定组件所属的进程,你可以在manifest 文件中这么做。


manifest条目对于每一个组件元素,如<activity><service><receiver>,<provider>都支持 android:process属性,你可以制定组件应该执行的进程。这样,你可以决定哪些在不同进程中,哪些在同一进程中。同样,你可以让不同的应用程序的组件运行在同一进程中,只要拥有相同Linux 用户ID和认证。


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


安卓可能会在某些情况下关闭一些进程,当内存较少而另外的进程需要立即为用户提供服务时。运行在进程中的应用程序组件被杀掉将会导致销毁(destroyed)。当他们再次工作时,进程将会再次启动这些组件。


当决定杀掉哪个进程时,安卓系统会衡量他们对于相关用户的重要性。举个例子,更有可能关闭不可见的activities 所对应的进程,而不是可见activities  所对应的进程。因此,决定关闭进程取决于组件运行的状态。用于决定进程被终结的规则可作如下讨论。


进程生命周期


安卓系统试图维持应用程序进程尽可能长,但最终还是要移除旧进程来为新的或者更重要的进程开辟内存空间。为了决定进程的留存或者销毁,系统将进程放置在“重要性分级”中,这基于运行在进程中的组件和他们的状态。拥有最低重要性的进程将会先被消灭,然后是次低的,以此类推,用于回收系统资源。

 

重要性分级存在5级。接下来的清单呈现了不同类型的进程,以重要性进行排序(第一个是最重要的,也是最后被杀死的)。


前台进程


用于相应用户当前操作的进程。一个进程被视为前台进程如果下面条件的任何一个为真。


·  它所持有的Activity 正与用户交互。(ActivityonResume()方法被调用)。

·  它所持有的Service绑定到正与用户交互的activity

·  它所持有的Service在前台运行——服务以调用startForeground().

·  它所持有的Service调用了以下三个回调函数之一:(onCreate()onStart(), or onDestroy()).

·  它所持有的BroadcastReceiver执行onReceive() 方法。


通常而言,在给定的时间下只有少数的前台进程得以保留。只有内存非常低时,他们才会被杀死。


通常,那也是因为设备到达内存换页状态,所以杀死前台进程也是为了用户交互请求的需要。


可见进程


一个进程没有任何前台组件,但他任然可以影响到用户在屏幕上所见。一个被视为“可见”,如果他满足如下几个之一。

 

·  其所持有的 Activity不在前台,但对于用户而言是可见的(onPause()方法被调用时)。这可能在这种情况下发生:如果前台activity 开启了一个对话框,之前的 activity在对话框后面,虽然它不再属于前台,但仍然可见。

·  它所持有的服务(Service )绑定了一个可见或者前台活动(activity)

 

可见进程被视为非常重要,除非为了前台进程运行,不然不会被杀死。


服务进程


服务进程运行一个以 startService()方法启动的服务,并且其不会进入之前提到的两个类别中的任意一个。尽管服务进程不会与用户所见的东西之间关联,但他们通常还是在做一些用户关心的事情(例如播放音乐已经下载数据),所以系统保证他们运行,除非这儿没有足够多的内存来维持前台进程或者可见进程的运作。


后台进程


后台进程持有用户当前不可见activity (也就是方法被调用)。这些进程对于用户体验没有任何直接影响,所以可在杀死他们来回收内存以供 前台、可见、服务进程使用。通常有很多后台程序运行,所以将他们放入LRU(最少最近使用)列表来保证最近用户所见activity 的进程最后被杀死。如果activity实现它自身生命周期的方法正确,并保持其当前状态,那么杀死进程将不会对用户体验产生任何影响,这是因为一旦用户导航到此activity ,其可见信息被恢复。查看Activities 文档以获得更多关于存储与恢复状态的信息。


空进程


进程未持有任何活动的应用程序组件。保留这种进程只有一个目的:缓存,从而在下次组件需要运行它的时候提高启动速度。系统经常杀死这些进程以在进程缓存和潜在的内核缓存中平衡全局系统资源。


安卓是基于当前活动进程中组件的重要性的,使用其中的最高级别来对进程进行排名。举个例子,如果一个进程拥有服务(service)和可见活动(activity),它将被以可见进程进行排名,而不是服务进程


额外说明的是,一个进程的排名可能上升,倘若其他进程依赖它的话——一个服务于其他进程的进程绝不会排名低于其服务的对象。举个例子,如果A内容提供者(content provider)将为进程B提供服务,或者A中服务绑定到B中组件,那么A的重要性至少等于B


由于运行服务的进程高于一个持有后台活动的进程,所以初始化长耗时操作的活动最好开启一个服务来进行那些操作,而不是简单的创建工作线程——尤其是那些可能比活动本身存在的时间更长的操作。举个例子,一个活动向网页上传一张图片应该开启一个服务来执行这个上传,这样即使用户离开了当前的activity,上传操作能够在后台继续执行。使用服务将使得进程至少具有“服务进程”优先级,而无论activity发生了什么。这对于广播接收者也是一样的道理,使用一个服务而不是简单将费时操作置于一个线程中。


线程

        当一个应用程序启动后,系统会创建一个主线程来执行应用程序。这个线程非常重要,因为他能够管理分发线程给合适的用户接口组件,这包括了绘制事件。也是这个线程与安卓UI工具包中的组件进行交互。(来自包android.widget 和包android.view中的组件)。因为这个原因,主线程有时也被叫做UI线程。   

       系统不会为每一个组件实例创建UI线程。运行在同一进程中的所有组件都是由UI线程进行初始化的,并且,系统对于这些组件的调用也是由UI线程进行分发。因此,相应系统回调的方法也常常运行在进程中的UI线程中。(例如onKeyDown()报告用户操作或者生命周期回调方法)。

        举个例子,当用户触摸屏幕上的一个按键时,你的应用程序的UI线程分发触摸事件给小组件(widget),小组件会设置自己成为“按下状态,并向事件队列发送宣布自身无效(invalidate:也就是请求重绘)的请求。UI线程将请求出列,然后通知组件重新绘制自己。

       当你的应用程序紧张的处理用户交互的响应时,这种单一的线程模型可能会表现得很差,除非你的应用程序实现的非常合理。特别的,如果任何事情都发生在UI线程中,进行长时间的操作,例如访问网络或者数据库查询都将阻塞整个UI线程。当线程被阻塞时,没有事件能够被分发,包括了绘制事件。从用户的观点来看,应用程序就表现为挂起的状态。更糟的是,如果UI线程阻塞超过了一定的秒数(例如现在设为5秒)那么臭名昭著的ANR(应用无法响应)对话框就会出现在用户眼前。用户可能会决定退出,甚至如果他们不高兴的话,会卸载你的程序。

        额外说一句,安卓的UI工具包不是线程安全的,这就意味着你不要在工作线程中进行UI操作——你必须在UI线程中完成所有用户接口的操作。因此,对于安卓简单线程模型而言两个简单的规则:

1 不要阻塞UI线程

2 不要在UI线程外访问安卓UI工具包


工作线程

由于上文提到的 单一线程模型至关重要的一点是,它必须响应UI线程,同时还不能阻塞它。如果你执行的操作不是瞬间完成的,你应该在独立的线程中去进行(如工作线程,或者后台线程)

举个例子,下面的例子就是点击事件在额外的线程中下载图片,然后在ImageView中显示它。

                                                                                                         

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap = loadImageFromNetwork("http://example.com/image.png");
            mImageView.setImageBitmap(b);
        }
    }).start();
} 

                                                                                                                                                             

乍一眼看去似乎能够很好的工作,因为它创建一个线程来处理网络操作。然而,它违反了单线程模型中的第二条准则:不准在UI线程外访问安卓UI工具包—这个例子在工作线程而不是UI线程中修改了图片视图(ImageView)。

为了解决这个问题,安卓提供了几种途径从其他线程中访问UI线程:

· Activity.runOnUiThread(Runnable)

· View.post(Runnable)

· View.postDelayed(Runnable, long)


举个例子,你可以使用 View.post(Runnable) 方法修改上面的代码:

*将mImageView.setImageBitmap(b);放到UI线程中做

                                                                                                                      

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

                                                                                                                      


现在这种实现是线程安全的了:网络操作在额外的线程中进行,而对于的ImageView 操作则放在了UI线程中。

然而,随着操作复杂性的增加,这种类型的代码可能变得非常复杂难以维护。为了处理更加复杂的交互过程,你应该考虑在工作线程中使用Handler ,它能够处理发送自UI线程的报文。或许最佳的解决方法是扩展AsyncTask 类,它简化了需要与UI进行交互的工作线程的执行。

使用 异步任务(AsyncTask

AsyncTask 允许你在用户接口中进行异步工作。它在工作线程中进行阻塞操作,然后将结果发布到UI线程中,而不需要你自己提供处理者或者处理线程。


为了使用它,你必须创建继承自AsyncTask的子类 ,然后实现它的回调方法doInBackground(),它在后台进程池中运行。为了更新你的UI,你必须实现onPostExecute()方法,它将doInBackground() 得到的结果在UI线程中运行,这样你便能够安全的更新UI了。你可以在UI线程中调用 execute()来执行这个任务。


举个例子,你可以在之前的例子中这样来使用异步任务(AsyncTask ):

                                                                                                           

public void onClick(View v) {
    new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
    /** 系统调用这个来在工作线程中执行工作,

         同时传递AsyncTask.execute()给予的参数。

         The system calls this to perform work in a worker thread and
      * delivers it the parameters given to AsyncTask.execute() */
    protected Bitmap doInBackground(String... urls) {
        return loadImageFromNetwork(urls[0]);
    }
    
    /** 系统调用这个来执行UI线程中的工作,同时处理doInBackground()的结果。 

        The system calls this to perform work in the UI thread and delivers
      * the result from doInBackground() */
    protected void onPostExecute(Bitmap result) {
        mImageView.setImageBitmap(result);
    }
}

                                                                                                          

现在,你的UI是安全的,同时你的代码又简化了。这是因为你将工作分割到了工作线程中和UI线程中(分别对应doInBackground和onPostExecute)。

你应该阅读参阅 AsyncTask 类来获得完整的理解如何使用它,但是你可以阅读下文来获得一个快速的预览:

· 你可以使用泛型来指定包括参数,进度值,任务的最终值。

· 方法doInBackground() 自动在工作线程中执行。

· 方法onPreExecute()onPostExecute() onProgressUpdate()都在UI线程中被调用

· 方法doInBackground()返回的值将送往onPostExecute

· 你可以在doInBackground任何时候调用 publishProgress()来在UI线程上执行onProgressUpdate()

· 你可以在任何时候,任何线程中取消任务。

注意:另一个可能遇到的问题是当你使用工作线程时,由于实时配置改变(例如用户改变屏幕方向)可能导致中的工作线程超乎预期的重启,这可能会摧毁你额工作线程。你可以参阅源码Shelves 来找到”当其中存在重启,如何持续你的任务,或者合适的取消你任务”的办法。


线程安全 方法

在某些情景下,你实现的方法可能被超过一个线程调用,因此它的写入必须是线程安全的。

这点是正确的首先是由于这样的方法可能被远程调用:例如处于绑定服务(bound service)中的方法。当实现自的IBinder 方法调用与IBinder 运行处于同一进程中,方法将在调用者线程中执行。然而,当调用源来自其他进程,调用将从与IBinder处于同一进程的线程池中挑选线程来执行方法。举个例子,当服务的onBind() 方法将被服务进程中的UI线程调用,方法实现onBind()返回的对象将被线程池的线程所调用(举个例子,实现RPC方法的子类) 。因为服务能够拥有超过一个客户,也不止一个线程池能够参与同一个IBinder 方法。因此IBinder方法必须以线程安全的方式实现。

相似的,一个内容提供者能够接受来自其他进程的数据请求。尽管the ContentResolver  和 ContentProvider 类隐藏了关于进程间通信如何管理的细节。ContentProvider 的方法:query(), insert(), delete(),update(), and getType()——将由内容提供者进程中的线程池调用,而不是进程中的UI线程。由于这些方法可能被任意多的线程同时调用,所以他们需要是线程安全的。

进程间通信


        安卓提供了一种进程间通信机制(IPC),使用的是远程过程调用(RPC),由activity 或者其他应用程序组件来调用,但是在远端执行(其他进程内),然后结果返回给调用者。这意味着需要将方法调用和它的数据分解到操作系统能够理解的水平,然后从本地进程和地址空间传输到远端进程和地址空间,然后再在那那重组和重演它。然后再返回来重新经历一遍,这次是返回值。安卓提供了全部的代码来进行IPC事务,所以你能够专注于定义和实现RPC过程接口。

为了进行IPC,你的应用程序必须绑定服务,使用bindService().。想知道更多的信息,查阅开发手册 Services





安卓主线程(UI线程)是线程不安全的:对UI控件的操作都应在主线程中完成;UI线程不应执行耗时操作,以免程序不响应(即ANR异常)


实现新线程的常用方法(注意要调用start方法启动新线程而不是run方法):

一、定义类,实现Runnable接口

    class MyRunnable implements Runnable {
        /**
         * 实现接口的run方法
         */
        @Override
        public void run() {
            // run方法的中程序体将在新线程中执行
        }
    }
    new Thread(new MyRunnable()).start();


二、简洁写法

        new Thread(new Runnable() {
            @Override
            public void run() {
                // 新线程操作
            }
        }).start();


三、定义类,继承Thread(不推荐):

    class MyThread extends Thread {
        /**
         * 覆写run方法
         */
        @Override
        public void run() {
            // 新线程操作
        }
    }
    new MyThread().start();


Thread和Runnable关系

    public class Thread extends Object implements Runnable {
    }


创建UI线程/主线程的Handler

一、(推荐写法,用于不处理消息,只是接收Runnable的情况下)

    // 无参数实例化时,会创建当前正在运行线程(即主线程)的Handler
    Handler handler  = new Handler();

如果handler需要处理消息,按照以下写法将会产生警告This Handler class should be static or leaks might occur。此时可使用方法二)

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            }
        }
    };

二、(缺点在于需要定义一个新的类,写起来比较麻烦)

    // 无参数实例化时,会创建当前正在运行线程(即主线程)的Handler
    private Handler handler = new MyHandler() {
        // 处理其他线程发送的消息
        @Override
        public void handleMessage(Message msg) {
            Log.d("msg""msg what = " + msg.what);
            switch (msg.what) {
            }
        }
    };

    protected static class MyHandler extends Handler {
    }

三、(推荐写法,可用于接收Runnable和处理消息的情况下)

    boolean handleMessage(Message msg) {
        switch (msg.what) {
        }
        return true;
    }

    private final Handler mainHandler = new Handler(Looper.getMainLooper(),
            new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {
                    return ActivityMain.this.handleMessage(msg);
                }
            });

在UI线程中执行程序

        handler.post(new Runnable(){
            @Override
            public void run() {
                // 操作UI控件等...
            }
        });


创建新线程的Handler

    private HandlerThread handlerThread;
    private Handler handler;

    this.handlerThread = new HandlerThread(getClass().getSimpleName());
    this.handlerThread.start();
    this.handler = new Handler(this.handlerThread.getLooper(),
            new Handler.Callback() {
                public boolean handleMessage(Message msg) {
                    return NewThread.this.handleMessage(msg);
                }
            });

    /**
     * 处理消息
     * 
     * @param msg
     * @return
     */
    private boolean handleMessage(Message msg) {
        switch (msg.what) {
        }
        return true;
    }

通过Message进行线程间通信:在任意线程中向其他线程Handler发送消息

    /**
     * 向线程Handler发送消息
     * 
     * @param id
     * @param o
     */
    protected void sendMsg(int id, Object o) {
        if (handler != null) {
            Message msg = Message.obtain();
            msg.obj = o;
            msg.what = id;
            handler.sendMessage(msg);
        }
    }

    /**
     * 向线程Handler发送消息
     * 
     * @param what
     */
    protected void sendMsg(int what) {
        if (handler != null) {
            handler.sendEmptyMessage(what);
        }
    }


    handler.sendMessageDelayed(msgmilliseconds);

    Message.obtain(handlerwhatobj).sendToTarget();


转载网址是http://purplesword.info
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值