Android中线程那些事

简介: 如何理解线程在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制的产生,并且线程的创建和销毁都会有相应的开销,当系统中存在大量的线程时,系统会通过时间陪轮转的方式调度每个线程,在这么多线程中有一个被称为主线程,主线程是指进程所拥有的线程,在JAVA中默认情况下一个进程只有一个线程,这个线程就是主线程。

如何理解线程

在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制的产生,并且线程的创建和销毁都会有相应的开销,当系统中存在大量的线程时,系统会通过时间陪轮转的方式调度每个线程,在这么多线程中有一个被称为主线程,主线程是指进程所拥有的线程,在JAVA中默认情况下一个进程只有一个线程,这个线程就是主线程。主线程主要处理界面交互相关的逻辑,因为用户随时会和界面发生交互,因此主线程在任何时候都必须有比较高的响应速度,否则就会产生一种界面卡顿的感觉。为了保持较高的响应速度,这就要求主线程中不能执行耗时的任务,这个时候子线程就派上用场了。子线程也叫工作线程,除了主线程以外的线程都是子线程。

Android中的线程

Android沿用了JAVA的线程模型,其中的线程也分为主线程和子线程,其中主线程又叫UI线程。在Android系统中,在默认情况下,一个应用程序内的各个组件(如Activity、BroadcastReceiver、Service)都会在同一个进程(Process)里执行,且由此进程的主线程负责执行。如果有特别指定(通过android:process),也可以让特定组件在不同的进程中运行。无论组件在哪一个进程中运行,默认情况下,他们都由此进程的主线程负责执行。主线程既要处理Activity组件的UI事件,又要处理Service后台服务工作,通常会忙不过来。为了解决此问题,主线程可以创建多个子线程来处理后台服务工作,而本身专心处理UI画面的事件。子线程的任务则是执行耗时任务,比如网络请求,I/O操作等。从Android4.0开始系统要求网络访问必须在子线程中进行,否则网络访问将会失败并抛出NetWorkOnMainThreadException这个异常,这样做是为了避免主线程由于被耗时操作阻塞从而出现ANR现象。

为什么会出现ANR

Android希望UI线程能根据用户的要求做出快速响应,如果UI线程花太多时间处理后台的工作,当UI事件发生时,让用户等待时间超过5秒而未处理,Android系统就会给用户显示ANR提示信息。主线程除了处理UI事件之外,还要处理Broadcast消息。所以在BroadcastReceiver的onReceive()函数中,不宜占用太长的时间,否则导致主线程无法处理其它的Broadcast消息或UI事件。如果占用时间超过10秒, Android系统就会给用户显示ANR提示信息。解决办法自然还是解放UI主线程,将耗时操作交给子线程,避免阻塞。

Android中也有main()方法

刚接触Android的开发者可能会因为找不到Java程序的执行入口main()方法而觉得疑惑,其实Android中当然是也有main()方法的(如下),它被包装在源码中的ActivityThread类里。ActivityThread为应用程序的主线程类,所有的Apk程序都有且仅有一个ActivityThread类,程序的入口为该类中的static main()方法,ActivityThread所在的线程即为UI线程或主线程。Activity从main()方法开始执行,调用prepareMain()为UI线程创建一个消息队列(MessageQueue)。然后创建一个ActivityThread对象,在ActivityThread的初始化代码中会创建一个H(Handler)对象和一个ApplicationThread(Binder)对象。其中Binder负责接收远程AmS的IPC调用,接收到调用后,则通过Hander把消息发送到消息队列,UI主线程会异步地从消息队列中取出消息并执行相应操作,比如start,pause,stop等。接着UI主线程调用Looper.loop()方法进入消息循环体,进入后就会不断地从消息队列中读取并处理消息。

    public static final void main(String[] args) {
        SamplingProfilerIntegration.start();

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();

        if (Process.supportsProcesses()) {
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }

        thread.detach();
        String name = (thread.mInitialApplication != null)
            ? thread.mInitialApplication.getPackageName()
            : "<unknown>";
        Slog.i(TAG, "Main thread of " + name + " is now exiting");
    }
}

Android中的子线程

Android中开启一个子线程无非还是这两种方法

1:继承Thread类

public class MyThread extends Thread {  

    public void run(){  

    }  
}

new MyThread().start();  

2:实现Runnable接口

public class MyRunnable implements Runnable{  

    @Override  
    public void run() {  
        // TODO Auto-generated method stub  
    }  
} 

new MyThread().start(); 

Android APK程序中都有哪些线程?

通过debug,我们可以捕获当前应用程序中的线程(如下图),其中蓝色选中部分即为当前应用程序的主线程,当前程序中还运行了三个Binder,每个Binder对象都对应一个线程,这些Binder线程主要负责接收Linux Binder驱动发送的IPC调用。除此以外还有Java中的守护线程和垃圾回收线程堆裁剪守护进程等在运行。

这里写图片描述

程序中自定义Thread和UI线程的区别是什么?

自定义Thread和UI线程的区别在于,UI线程是从ActivityThread运行的,在该类中的main()方法中,已经使用Looper.prepareMainLooper()为该线程添加了Looper对象,即已经为该线程创建了消息队列(MessageQueue),因此,程序员才可以在Activity中定义Hander对象(因为声明Hander对象时,所在的线程必须已经创建了MessageQueue)。而普通的自定义Thread是一个裸线程,因此,不能直接在Thread中定义Hander对象,从使用场景的角度讲,即不能直接给Thread对象发消息,但却可以给UI线程发消息。

子线程为什么不能更新UI

因为UI访问是没有加锁的,在多个线程中访问UI是不安全的,如果有多个子线程都去更新UI,会导致界面不断改变而混乱不堪。所以最好的解决办法就是只有一个线程有更新UI的权限,所以这个时候就只能有一个线程振臂高呼:放开那女孩,让我来!那么最合适的人选只能是主线程。

子线程也可以更新UI

SurfaceView是 android 里唯一一个可以在子线程更新的控件。SurfaceView可以在主线程之外的线程中向屏幕绘图。这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。当需要快速,主动地更新View的UI,或者当前渲染代码阻塞GUI线程的时间过长的时候,SurfaceView就是解决上述问题的最佳选择。

子线程可以更新除SurfaceView以外的UI

子线程更新UI?没错,不信下面的代码跑一遍试试,并不会报错,而且正确显示。

public class MainActivity extends AppCompatActivity {

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView=(TextView)findViewById(R.id.textView);

        new Thread(new Runnable() {
            @Override
            public void run() {
                mTextView.setText("Child Thread");
            }
        }).start();
    }
}

这是为什么呢?一个应用程序中有一个主线程和若干个子线程,而线程的检查工作是由ViewRoot完成的。ViewRoot是什么呢?可以简单的理解为Window和View之前的桥梁或者纽带。而ViewRoot的创建是在onResume()之后才完成的,也就是说在onResume()之前,系统本身是无法区分当前线程到底是主线程还是子线程,而上面的代码中UI的更新操作在onCreate()中完成,先于onResume(),所以上述的子线程才有机会越俎代庖。

子线程如何与主线程通信

1、Activity.runOnUiThread(Runnable)

mHandle.setOnClickListener(new OnClickListener() { 

            @Override 
            public void onClick(View v) { 
                new Thread(new Runnable() { 

                    @Override 
                    public void run() { 
                        MainActivity.this.runOnUiThread(new Runnable() { 
                                     // 耗时操作            
                                     loadNetWork();   
                        @Override 
                            public void run() { 
                                mTextView.setText(来自网络的文字);             
                            } 
                        }); 

                    } 
                }); 

            } 
        }); 

2、 View.post(Runnable)

mHandle.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {

                    @Override
                    public void run() {
                              // 耗时操作           
                              loadNetWork();      
                        mTextView.post(new Runnable() {

                            @Override
                            public void run() {
                                mTextView.setText(来自网络的文字);    
                            }
                        });

                    }
                });



            }
        });

3、View.postDelayed(Runnable,long)

mHandle.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {

                    @Override
                    public void run() {
                                // 耗时操作           
                                loadNetWork();  
                        mTextView.postDelayed(new Runnable() {

                            @Override
                            public void run() {
                                mTextView.setText(来自网络的文字);                                
                            }
                        }, 10);

                    }
                });


            }
        });

4、Handler(子线程调用Handler的
handle.sendMessage(msg);

Handler handle = new Handler() {

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            mTextView.setText(来自网络的文字);
        }

    };

class MyThread extends Thread {

        @Override
        public void run() {
             // 耗时操作           
            loadNetWork();  

            Message msg = new Message();
            handle.sendMessage(msg);
            super.run();
        }


    }

5、AsyncTask

主线程调用:

aTask ak = new aTask();
ak.execute();

AsyncTask

private class aTask extends AsyncTask { 

    //后台线程执行时 
    @Override 
    protected Object doInBackground(Object... params) { 
        // 耗时操作             
        return loadNetWork(); 
    } 
    //后台线程执行结束后的操作,其中参数result为doInBackground返回的结果 
    @Override 
    protected void onPostExecute(Object result) { 
        super.onPostExecute(result); 
        mTextView.setText(result); 
    } 
    } 

总结

最后来个总结,Android中的线程延续了JAVA的设计模型,默认一个应用程序只有一个主线程,主线程的开启是在Activity的main()方法。主线程实际上是一个死循环,不断的循环处理系统以及其他子线程发来的消息。主线程的绑定是在DecorView初始化的时候,也就是生命周期的onResume()之后。主线程主要处理UI操作,和Broadcast相关消息,主线程如果长时间无法响应,将出现ANR,为了避免ANR,耗时操作一般都开启子线程处理。子线程处理完再发消息通知主线程来改变UI。

欢迎加群:183899857
我们一起讨论技术问题

相关文章
|
4月前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
【7月更文挑战第28天】在Android开发中,确保UI流畅性至关重要。多线程与异步编程技术可将耗时操作移至后台,避免阻塞主线程。我们通常采用`Thread`类、`Handler`与`Looper`、`AsyncTask`及`ExecutorService`等进行多线程编程。
57 2
|
4月前
|
Java Android开发
Android面试题经典之Glide取消加载以及线程池优化
Glide通过生命周期管理在`onStop`时暂停请求,`onDestroy`时取消请求,减少资源浪费。在`EngineJob`和`DecodeJob`中使用`cancel`方法标记任务并中断数据获取。当网络请求被取消时,`HttpUrlFetcher`的`cancel`方法设置标志,之后的数据获取会返回`null`,中断加载流程。Glide还使用定制的线程池,如AnimationExecutor、diskCacheExecutor、sourceExecutor和newUnlimitedSourceExecutor,其中某些禁止网络访问,并根据CPU核心数动态调整线程数。
129 2
|
23天前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
40 4
|
2月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
109 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
2月前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
在Android开发中,为应对复杂应用场景和繁重计算任务,多线程与异步编程成为保证UI流畅性的关键。本文将介绍Android中的多线程基础,包括Thread、Handler、Looper、AsyncTask及ExecutorService等,并通过示例代码展示其实用性。AsyncTask适用于简单后台操作,而ExecutorService则能更好地管理复杂并发任务。合理运用这些技术,可显著提升应用性能和用户体验,避免内存泄漏和线程安全问题,确保UI更新顺畅。
80 5
|
2月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
67 10
|
2月前
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。
|
2月前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
|
3月前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
57 4
|
3月前
|
安全 网络安全 数据安全/隐私保护
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享安卓与iOS开发中的线程管理比较
【8月更文挑战第30天】本文将探讨网络安全与信息安全的重要性,并分享关于网络安全漏洞、加密技术和安全意识的知识。我们将了解常见的网络攻击类型和防御策略,以及如何通过加密技术和提高安全意识来保护个人和组织的信息安全。