翻译 Processes and Threads

本文深入探讨了Android系统中的进程与线程管理机制,详细介绍了Android如何为应用分配进程和线程,以及如何通过不同的组件配置来控制进程的生命周期。此外,还介绍了如何避免UI线程阻塞,确保良好的用户体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近翻译了android的Processes and Threads一篇(2.1),希望大家批评指正,谢谢各位。

进程与线程

当应用中的任何一个组件都没有在运行的时候,你去启动这个应用,安卓操作系统就会为这个应用程序执行一个单独的线程,这样也就启动一个新的linux进程。

默认情况下,所有同一应用中的所有组件运行在相同的线程和进程(主进程 main thread)中。如果应用中的某个组件开始启动,并且这个应用已经生成一个进程(因为这个应用中的另外一个组件已经存在),这时候这个组件就会被同一线程中的这个进程启动执行。

但是,你可以让同一应用中不同的组件,运行在不同的进程中,并且你可以为任意进程添加额外的线程。

--------------------------------------------------------------------
进程

默认情况下,同一应用中所有的组件都运行在相同的进程中,并且大多数的应用也应遵守这个规定。但是如果你发现,你需要控制组件属于哪个进程,那就在manifest文件中修改。

manifest中组件元素<activity>、<service>、<receiver>和<privider>都支持 android:process的属性,android:process属性可以指定一个进程,让这个组件通过指定的这个进程运。通过设置这个属性,可以让每个组件运行在它自己的进程中,或者一些组件运行在一个进程中,另一些组件运行在另外的进程中。你也可以通过设置andorid:process 让不同应用中的组件运行中相同的进程中,条件是需要提供相同的linux userId和相同的数字签名。

<application>这个元素也支持 andorid:process这个属性,设置的值,会对所有的组件有效。

在当内存不足并且andorid系统需要立即提供给用户另外的进程时候,andriod有可能决定关闭某个进程,运行在这个进程上的组件随着被杀死后也就销毁了。一个进程又启动了继续为那些运行在他上面的组件服务。

当决定杀死哪个进程的时候,android系统会衡量他们与用户之间的关系是否重要。例如,不再显示在屏幕上的进程与运行在前台屏幕上的进程相比,他一般都会关闭前者。决定是否要关闭一个进程,与这个进程上运行的组件的状态有关。

接下来讨论进程关闭的规则

进程的生命周期
android系统会尽可能长的维护一个应用进程,但是最终会通过移除老的进程以便清空内存或创建更重要的进程。那么哪个进程要保存,哪个要杀死呢,系统根据线程上组件的状态指定了个优先级,优先级最低的先被淘汰,然后是下个最低优先级的,直到恢复系统资源。

有五个优先级别,下面按优先级由高到低的顺序列出目前不同级别的进程。
1、Foreground process(前台进程)
用户要求正在做的进程。一个进程当满足下面的任何一种条件时被认为是一个前台进程
一个正在和用户交互的activity上跑的进程(activity中的onResume方法被调用)
一个service为载体的进程,这个service绑定在正在和用户交互的activity上。
一个service为载体的进程,这个service启动的时候是调用startForeground()方法启动的。
一个service为载体的进程,这个service正在执行生命周期中的回调函数(onCreate()、onStart()、或者onDestory)
一个broadcastReceiver为载体的进程,这个BroadcastReceiver正在执行onReceive()方法
通常情况下,在一个时间点只有少数的前台进程存在。前台进程只有在这种情况下被杀掉,那就是内存小的不足以继续运行了。通常在这个时间点,设备到达一种内存分页状态,所以kill掉一些前台进程,让人际交互。

2、Visible Process (可视进程)
进程没有任何前台组件,但是仍然影响用户所看到屏幕上的东西。如果进程满足以下任何一个条件那就是可视进程。

一个运行在不再前端的activity上的进程,但是对用户来说仍然是可见的,(activity的onPause()方法被调用)。这种情况一般在什么情况下发生呢?例如,前端的Activity启动了个dialog,并且这个activity在dailog后面显示。
一个通过service组件运行的进程,这个service绑定在一个可视的(或者是前端)的activity上。
可视化的进程是十分重要的,除非是保证所有的前台进程运行,一般状态下是不会被杀死的。
3、Service Process (服务进程)

这个进程是一个运行的service,并且这个service已经通过调用startService()方法启动了,并且不属于较高的两个类别之一。虽然service进程没有与用户看到的任何东西所绑定,但是他做的事情用户还是能关注到(比如在后台播放音乐或通过网络下载数据),所以系统会一直让他们运行的,除非没有足够的内存来共同保存前台进程,可视进程以及服务进程的时候。
4、Background process(后台进程)
这种以activity为载体的进程对用户来说当前并没有显示出来(activity中的onStop()方法被调用)。对用户体验来说并没有直接的影响,系统可以在为前台进程、可视进程、服务进程回收内存的任何时候结束掉这种后台进程。通常有很多的后台金车再运行,他们被保存在LRU(least recently used)列表中,保证这种以activity为载体的进程最近被看到的最后被杀死。如果一个activity正确的实现了他的生命周期,并且保存了他的当前状态,杀死他这个线程对用户体验来说不会造成视觉上的影响,因为当用户导航回到这个activity的时候,这个activity会恢复他的可见状态。可以通过查看activity的文档来看如果保存和恢复他的状态。
5、Empty process(空进程)
进程中没有任何应用组件。保存这种进程存活的唯一原因是缓存的目的,下次组件用它跑的时候改善开始时间。系统往往为平衡进程缓存与底层核心缓存的系统的资源时候杀死这些进程。

android根据当前组件在进程中的重要性来排列进程的优先级(就是)。例如,如果一个进程托管了一个servie和可视的Activity,这个进程被定义为可视进程,而不是服务进程。

另外,由于其他进程依赖于某个进程,这个进程的等级可能会增加,--一个服务于其他进程的等级不肯能比其他进程中的任何一个的等级低。例如,一个内容提供者在进程A中服务在进程B中的client,或者如果一个service在进程A中,并且A绑定的组件在进程程B中,那么进程A的优先级至少要和进程B的一致。

因为进程上运行一个service的等级要高于后台活动的进程,一个activity要启动一个长时间运行的操作最好启动一个service,不是简单的启动一个工作线程,特别是如果这个操作要长于这个activity的情况下。例如,一个activity正在从网络上加载一个图片,应当启动个service来完成加载工作,这样即便是用户离开了这个activity,加载工作也会继续进行。使用service保证操作至少是服务进程基本的,这样就不用在乎acitivy怎么样了(后台运行,还是销毁什么的)。这也是broadcast receiver应该使用service而不是写个定时的线程的原因了。


--------------------------
Thread(线程)

当一个应用程序启动后,系统就为这个应用创建了一个线程,这个线程就是被叫做“main”的线程。这个线程非常重要,因为他负责用户界面窗体展现的事件,包括画图事件。在这个线程中他负责与在与Android UI toolkit(这些组件一般都是android.widget 和 android.view 包中的) 中的组件进行交互。综上,main线程也称作是UI线程。

系统不会为每个组件实例创造个单独的线程。所有的组件运行在相同的进程中,这个进程被实例化在UI线程中,系统从线程角度调用每个组件。因此,系统的回调函数(例如与用户交互的onKeyDown() 或 生命周期的回调函数)始终会运行在UI线程中的进程中。

例如,当用户点击屏幕上的一个按钮的时候,你的应用的UI进程会分发给窗体(widget)点击事件,包括依次出现的点击过程中的状态,以及发送给事件队列的invalidate请求。UI线程从队列中取出请求并唤醒这个窗体让他redraw itself。


当你的应用频繁的做用户体验的工作的时候的时候,单个的线程模式的性能就不好了,除非你恰当的执行了你的应用程序。具体的说就是,如果一切都发生在UI线程里面,执行长期的操作,如网络访问或数据库查找,会阻碍整个UI。当线程被阻塞的时候,其他事件就不能被分发执行了,包括绘图事件。从用户体验来说,这个应用就挂在那里了。更糟糕的情况是,如果UI线程被阻塞了几秒钟(大约5秒)这时候展现给用户的就是“程序没响应”(application not responding ANR)的对话框了。这时候用户如果不高兴也许就退出你的应用后直接就把它卸载了。

另外,android的UI工具包不是线程安全的。所以千万不要用 worker thread 来操作你的UI界面,你必须通过UI thread 来做你所有的操作。因此android的单线程模式有两个规定:
1、禁止阻塞UI线程
2、不能通过UI线程以外的线程来操作android UI 工具包。

worker threads
综上所述,由于UI thread对应用的界面起着非常重要的作用,并且他还是个单线程,所以千万不要使它阻塞啊。如果你有很多不是立即要展现的操作,你应该确保让他们运行在不同的线程里面("background"或者"worker"线程中)。

例如下面的代码展示了在一个点击的监听事件,是怎么通过一个单独的线程下载图片的并在ImageView展示的。

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


首先,看上去好像能正常的工作,因为创建了一个新的线程来处理网络的操作。但是他违反了我们单个线程的第二个原则,那就是禁止UI线程以外的线程来操作andorid ui工具类,因为他通过内部UI 线程的内部线程改变了 imageView。这种不确定的意外行为很难排查的,并且找错的时候也很费时。

为了解决这个问题,android提供了几种方法来通过其他线程来访问主线程,下面举了些例子
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable,long)
例如上面的例子我们用View.post(Runnable)方法可以这么写
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线程来操作的。
但是随着操作的复杂性的增加,这种代码就不能很好的解决问题了,并且也不好维护。为了用work thread的方式来解决复杂的问题,也许我们考虑在 worker 线程中使用Handler,通过它来与UI线程来进行信息的交互。也许最好的解决方式是集成AsyncTask类,这样就大大简化了worker thread 与UI thread 直接的交互。

Using AsyncTask(使用AsyncTask类)

AsyncTask线程在用户界面上允许你进行异步的工作。worker thread 运行完阻塞的操作,并把结果发送给UI线程,并不需要你来处理这些线程或(和)处理程序自己。

怎么使用它呢?AsyncTask的子类必须要实现doInBackground()回调方法,这个回调方法运行在后台的线程池里面。通过实现onPostExecute()方法来更新你的UI界面,这个方法是从doInBackground()方法中获取的结果,并且运行在UI线程中,这样就可以安全的更新你的UI界面了。可以在UI线程中,通过调用execte()方法来运行这个task。

例如,你可以使用这个以前的例子来使用AsyncTask

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

private class DownloadImageTask extends AsynTask<String,Void,Bitmap>{
/**
The system calls this to perform work in a worker
thread and delivers it the parameters given to AsyncTask.execte();
**/
protected Bitmap doInBackground(String... urls){
return loadImageFromNetwork(urls[0]);
}
/**
The system call this to perform work in the UI thread and delivers
the result from doInBackground();
*/
protected void onPostExecute(Bitmap result){
mImageView.setImageBitmap(result);
}

}

这样UI就安全了,并且代码也很简单,因为他实现了一部分在worker thread中运行,另一部分在UI Thread中运行。

AsyncTask中详细描述了他是怎么工作的,这里大体阐述一下。
1、可以使用泛型定义阐述的类型,程序的值,已经任务的最终值
2、doInBackground()方法执行的时候最终会运行在一个worker thread中。
3、onPreExecute() onPostExecute()和onProgressUpdate()方法 都可以在UI thread 中执行
4、值由doInBackground()方法传递给onPostExecute()方法
5、UI thread上 可以在任何时候通过doInBackground()方法调用publishProgress()方法,来执行onProgressUpdate()
You can call publishProgress() at anytime in doInBackground() to execute onProgressUpdate() on the UI thread
也就是 在UI中调用 doInBackground() doInBackground又调用了 publishProgress(),publishProgress()调用了onProgressUpdate()
6、可以在任何时候从任何一个线程中取消这个任务。

注意:你可能遇到的另外一个问题是由于运行配置的改变(比如用户改变了屏幕的方向),总之是导致销毁worker thread的配置变化,会导致wroker thread 的意外重启。当其中一个任务重启的时候如何让你的任务不受影响,当activity 被销毁的时候,如何取消你的任务,看看 shelves 这个工程中的源码吧。

Thread-safe methods
在一些环境下,你继承的方法可能不止被一个线程调用,所以必须要写成线程安全的。
首先这些方法是真正能被远程的调用的,比如在bound service中的方法。当调用在IBinder中的一个实现方法的调用动作起源与IBinder 正在运行的运行动作来在于同一个进程,这个方法在调用者的线程中执行。但是,当调用来源于另一个进程的时候,这个方法是在线程池中的某个线程中被执行,系统与IBinder在相同的进程中(他没有被UI 线程中的进程执行)。例如,尽管service的onBid()方法从UI thread的service线程中被执行,实现这个类中的onBind()方法返回(例如实现RPC方法的子类)会被线程池中的线程调用。因为一个service 可能有多个client,多个线程池在同一时间能执行同一个IBinder方法。所以IBinder方法,实现的时候必须是线程安全的。
类似的,一个内容提供者(content provider)能够在其他线程中接受数据请求。尽管,ContentResolver和contentPrivider类隐藏了进程间的通信是如何进行管理的细节,ContentPriovider方法响应那些请求的方法被从线程池中的这个content provider的进程调用,不是UI 进程中的线程。因为这些方法在同一时间可能被任意数量的线程调用,所以我们在实现的时候必须保证是线程安全的。

Interprocess Communication(线程间的通信)

andorid 为线程之间的通信(IPC)提供了一个机制,那就是使用远程调用(RPC),通过activity或者其他组件调用方法,但是在另一个进程中被远程执行,不会给调用者返回任何结果。需要一个方法调用和数据分解到能理解一个级别的操作系统上,从本地线程和地址空间传递给远程线程和地址空间,然后,在这里重组并扮演这个调用。返回的结果这时候在反方向上传输。andorid提供实现IPC上传输的代码,所以你可以把重点放在定义或实现RPC程序的接口上。

为了实现IPC你的程序必须绑定一个service,通过bindService()方法。你可以查看Services的文章获取更多的信息。
Chapter 4: Processor Architecture. This chapter covers basic combinational and sequential logic elements, and then shows how these elements can be combined in a datapath that executes a simplified subset of the x86-64 instruction set called “Y86-64.” We begin with the design of a single-cycle datapath. This design is conceptually very simple, but it would not be very fast. We then introduce pipelining, where the different steps required to process an instruction are implemented as separate stages. At any given time, each stage can work on a different instruction. Our five-stage processor pipeline is much more realistic. The control logic for the processor designs is described using a simple hardware description language called HCL. Hardware designs written in HCL can be compiled and linked into simulators provided with the textbook, and they can be used to generate Verilog descriptions suitable for synthesis into working hardware. Chapter 5: Optimizing Program Performance. This chapter introduces a number of techniques for improving code performance, with the idea being that programmers learn to write their C code in such a way that a compiler can then generate efficient machine code. We start with transformations that reduce the work to be done by a program and hence should be standard practice when writing any program for any machine. We then progress to transformations that enhance the degree of instruction-level parallelism in the generated machine code, thereby improving their performance on modern “superscalar” processors. To motivate these transformations, we introduce a simple operational model of how modern out-of-order processors work, and show how to measure the potential performance of a program in terms of the critical paths through a graphical representation of a program. You will be surprised how much you can speed up a program by simple transformations of the C code. Bryant & O’Hallaron fourth pages 2015/1/28 12:22 p. xxiii (front) Windfall Software, PCA ZzTEX 16.2 xxiv Preface Chapter 6: The Memory Hierarchy. The memory system is one of the most visible parts of a computer system to application programmers. To this point, you have relied on a conceptual model of the memory system as a linear array with uniform access times. In practice, a memory system is a hierarchy of storage devices with different capacities, costs, and access times. We cover the different types of RAM and ROM memories and the geometry and organization of magnetic-disk and solid state drives. We describe how these storage devices are arranged in a hierarchy. We show how this hierarchy is made possible by locality of reference. We make these ideas concrete by introducing a unique view of a memory system as a “memory mountain” with ridges of temporal locality and slopes of spatial locality. Finally, we show you how to improve the performance of application programs by improving their temporal and spatial locality. Chapter 7: Linking. This chapter covers both static and dynamic linking, including the ideas of relocatable and executable object files, symbol resolution, relocation, static libraries, shared object libraries, position-independent code, and library interpositioning. Linking is not covered in most systems texts, but we cover it for two reasons. First, some of the most confusing errors that programmers can encounter are related to glitches during linking, especially for large software packages. Second, the object files produced by linkers are tied to concepts such as loading, virtual memory, and memory mapping. Chapter 8: Exceptional Control Flow. In this part of the presentation, we step beyond the single-program model by introducing the general concept of exceptional control flow (i.e., changes in control flow that are outside the normal branches and procedure calls). We cover examples of exceptional control flow that exist at all levels of the system, from low-level hardware exceptions and interrupts, to context switches between concurrent processes, to abrupt changes in control flow caused by the receipt of Linux signals, to the nonlocal jumps in C that break the stack discipline. This is the part of the book where we introduce the fundamental idea of a process, an abstraction of an executing program. You will learn how processes work and how they can be created and manipulated from application programs. We show how application programmers can make use of multiple processes via Linux system calls. When you finish this chapter, you will be able to write a simple Linux shell with job control. It is also your first introduction to the nondeterministic behavior that arises with concurrent program execution. Chapter 9: Virtual Memory. Our presentation of the virtual memory system seeks to give some understanding of how it works and its characteristics. We want you to know how it is that the different simultaneous processes can each use an identical range of addresses, sharing some pages but having individual copies of others. We also cover issues involved in managing and manipulating virtual memory. In particular, we cover the operation of storage allocators such as the standard-library malloc and free operations. CovBryant & O’Hallaron fourth pages 2015/1/28 12:22 p. xxiv (front) Windfall Software, PCA ZzTEX 16.2 Preface xxv ering this material serves several purposes. It reinforces the concept that the virtual memory space is just an array of bytes that the program can subdivide into different storage units. It helps you understand the effects of programs containing memory referencing errors such as storage leaks and invalid pointer references. Finally, many application programmers write their own storage allocators optimized toward the needs and characteristics of the application. This chapter, more than any other, demonstrates the benefit of covering both the hardware and the software aspects of computer systems in a unified way. Traditional computer architecture and operating systems texts present only part of the virtual memory story. Chapter 10: System-Level I/O. We cover the basic concepts of Unix I/O such as files and descriptors. We describe how files are shared, how I/O redirection works, and how to access file metadata. We also develop a robust buffered I/O package that deals correctly with a curious behavior known as short counts, where the library function reads only part of the input data. We cover the C standard I/O library and its relationship to Linux I/O, focusing on limitations of standard I/O that make it unsuitable for network programming. In general, the topics covered in this chapter are building blocks for the next two chapters on network and concurrent programming. Chapter 11: Network Programming. Networks are interesting I/O devices to program, tying together many of the ideas that we study earlier in the text, such as processes, signals, byte ordering, memory mapping, and dynamic storage allocation. Network programs also provide a compelling context for concurrency, which is the topic of the next chapter. This chapter is a thin slice through network programming that gets you to the point where you can write a simple Web server. We cover the client-server model that underlies all network applications. We present a programmer’s view of the Internet and show how to write Internet clients and servers using the sockets interface. Finally, we introduce HTTP and develop a simple iterative Web server. Chapter 12: Concurrent Programming. This chapter introduces concurrent programming using Internet server design as the running motivational example. We compare and contrast the three basic mechanisms for writing concurrent programs—processes, I/O multiplexing, and threadsand show how to use them to build concurrent Internet servers. We cover basic principles of synchronization using P and V semaphore operations, thread safety and reentrancy, race conditions, and deadlocks. Writing concurrent code is essential for most server applications. We also describe the use of thread-level programming to express parallelism in an application program, enabling faster execution on multi-core processors. Getting all of the cores working on a single computational problem requires a careful coordination of the concurrent threads, both for correctness and to achieve high performance翻译以上英文为中文
08-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值