Android API 指南, 线程和进程

概述:

在android 系统中,当应用启动的时候, android 系统会使用单个执行线程为应用启动新的 Linux 进程。默认情况下,同一应用

的所有组件在同一个的进程和线程(称为“主”线程)中运行。如果某个应用组件启动且该应用已存在进程(因为存在该应用的其

他组件),则该组件会在此进程内启动并使用相同的执行线程。 但是,您可以安排应用中的其他组件在单独的进程中运行,并为任何

进程创建额外的线程。

进程:

默认情况下,一个应用里面的所有组件都是运行在同一个进程中的。但是,如果您发现需要控制某个组件所属的进程,则可在清单文

件中执行此操作。如果你想将某个组件放到单独的进程中去执行。可以在清单文件中,配置组件的 android:process 属性。支持该属性的组件有:

<Activity>、<Service>、<Receiver>、<Provider>,即android app 里面的四大组件。另外<Application>元素下也支持该属性,这是整个应用的默认

进程。

进程生命周期:

Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要清除旧进程来回收内存。 为了确定保留或终止哪些

进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 必要时,系统会首先消除重要性最低的进程,然

后是重要性略逊的进程,依此类推,以回收系统资源。重要性层次结构一共有 5 级。

1. 前台进程

用户当前操作所必需的进程。如果一个进程满足以下任一条件,即视为前台进程:

a. 用户正在交互的 Activity(已调用 Activity 的 onResume() 方法)

b.      某个 Service,后者绑定到用户正在交互的 Activity

c. 正在“前台”运行的 Service(服务已调用 startForeground()

d. 正执行一个生命周期回调的 ServiceonCreate()onStart() 或 onDestroy()

e. 正执行其 onReceive() 方法的 BroadcastReceiver

2. 可见进程

没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 如果一个进程满足以下任一条件,即视为可见进程:

不在前台、但仍对用户可见的 Activity(已调用其 onPause() 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一

Activity,则有可能会发生这种情况。

绑定到可见(或前台)Activity 的 Service

可见进程被视为是极其重要的进程,除非为了维持所有前台进程同时运行而必须终止,否则系统不会终止这些进程。

3. 服务进程

正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们

通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)。因此,除非内存不足以维持所有前台进程和可见进程同时运行,否则系统

会让服务进程保持运行状态。

4. 后台进程

包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,

以回收内存供前台进程、可见进程或服务进程使用。 通常会有很多后台进程在运行,因此它们会保存在 LRU (最近最少使用)列表中,以确保包含用户

最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明

显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。

5. 空进程

不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 为使总体系统资源在进程

缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。

Android 系统在内存紧张的情况下, 会执行内存释放以满足当前使用。上面优先级是android系统保留内存使用的顺序。空进程会在内存紧张的时候

马上被释放, 前台进程正在和用户交互, 这个进程Android是不会去回收的。就是说基本不可能主动去回收一个前台进程

线程:

应用启动后, 系统会分配进程。同时会为应用创建一个 主线程/UI线程

如果 UI 线程需要处理所有任务,则执行耗时很长的操作(例如,网络访问或数据库查询)将会阻塞整个 UI。如果 UI 线程被阻塞超过几秒钟时间

(目前大约是 5 秒钟),用户就会看到一个让人厌烦的“应用无响应”(ANR) 对话框。

线程的注意事项有两点: 1. 不能通过 额外的工作线程(也就是除了 主线程 之外的线程)操作UI。 2. 不能阻塞UI线程。

例如: 点击事件下载文件, 下载完成之后,更新textView:

public void onClick(View v) {
    new Thread(
            new Runnable() {
                @Override
                public void run() {
                    try {
                        // 让线程睡眠2s, 模拟耗时
                        Thread.sleep(2000);
                        // 下载完成
                        textView.setText("下载完成");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
    ).start();
}

代码感觉没什么问题,可是一运行的时候就报错了:

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

这个错误就是说, 只有创建这个view的线程才可以操作这个view。意思就是别的线程不能操作主线程的UI。 上面的代码中, textView.setText("下载完成“);这行代码就是在操作主线程UI了。报错就是因为这行代码。


Android 提供了几种途径来从其他线程访问 UI 线程。

1. Activity.runOnUiThread(Runnable)

2. View.post(Runnable)

3. View.postDelay(Runnable, long)

代码改造:

public void onClick(View v) {
        /*new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        try {

                            // 让线程睡眠2s, 模拟耗时
                            Thread.sleep(2000);

                            // 下载完成
//                            textView.setText("下载完成");

                            runOnUiThread();

                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
        ).start();*/

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                try {

                    // 让线程睡眠2s, 模拟耗时
                    Thread.sleep(2000);

                    // 下载完成
                    textView.setText("下载完成");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }

这样就完成改造了。 在运行后就没问题了。 下载完成后, View 显示”下载完成“字样。 其实这个方法也可以很直观的看出他的功能, runOnUiThread(Runnable), 运行在UI

线程中, 参数是一个Runnable对象。 可猜测这个方法将Runnable里面的执行方法放到UI线程中,使用这个方法的时候, 在这个方法里面的操作就相当于在UI线程中操作了。所以我们就可以理所当然的可以更新UI组件了。。。, 其他两个方法用法都差不多。


使用上面提供出来的三种方法, 当然可以实现和UI线程的交互, 但是,随着操作日趋复杂,这类代码也会变得复杂且难以维护。 要通过工作线程处

理更复杂的交互,可以考虑在工作线程中使用 Handler 处理来自 UI 线程的消息。官方认为最好的解决方案或许是扩展 AsyncTask 类,此类简化了与 UI

进行交互所需执行的工作线程任务。


AsyncTask使用:

AsyncTask 允许对用户界面执行异步操作。它会先阻塞工作线程中的操作,然后在 UI 线程中发布结果,而无需您亲自处理线程和/或处理程序。

要使用它,必须创建 AsyncTask 子类并实现 doInBackground() 回调方法,该方法将在后台线程池中运行。要更新 UI,必须实现 onPostExecute() 

以传递doInBackground() 返回的结果并在 UI 线程中运行,这样,您即可安全更新 UI。稍后,您可以通过从 UI 线程调用 execute() 来运行任务。

使用AsyncTask类来改造上面的代码:

public class MyAsyncTask extends AsyncTask<String, Void, String> {
    @Override
    protected String doInBackground(String... params) {
        try {

            // 让线程睡眠2s, 模拟耗时
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "下载完成";
    }

    @Override
    protected void onPostExecute(String s) {
        super.onPostExecute(s);
        textView.setText(s);
    }
}


AsyncTask 实现类, 重写doInBackgroud方法,和onPostExecute方法。同样也实现了耗时操作的功能。AsyncTask类中还有其他几个方法,可以

很好的与UI线程交互。需要详细了解的,可以去看看这个api接口。


Android 还有一个进程间的通信: IPC机制。是本地进程和远程进程间的通信。这个东西, 后面在研究。


感觉进程和线程这两个名词很容易搞混淆。所以这两者之间的关系必须要有比较深刻的认识!!!!







猜你喜欢

转载自blog.csdn.net/u011326269/article/details/51758533
今日推荐