Android 进程与线程解读:面试总是被问到的进程间通信你了解

2021-10-15  本文已影响0人  笨笨11

Android中的进程

进程

当应用程序组件启动并且该应用程序没有任何其他组件在运行时,Android 系统会为该应用程序启动一个新的 Linux 进程,并使用单个执行线程。 默认情况下,同一应用程序的所有组件都在同一进程和线程(称为“主”线程)中运行。

进程的等级(生命周期)

前台进程(Foreground process)

它表明用户正在与该进程进行交互操作优先级是最高的。Android系统-依据下面的条件来将一个进程标记为前台进程:

可见进程(Visible process)

它表明虽然该进程没有持有任何前台组件,但是它还是能够影响到用户看得到的界面。android系统依据下面的条件将一个进程标记为可见进程:

服务进程(Service process)

持有已使用 startService() 方法启动的Service。 虽然这些进程对用户来说并不直接可见,但它们一般都在做用户关心的事情(如后台网络数据上传或下载)

缓存进程(Cached process)

缓存进程是当前不需要的进程,因此当其他地方需要内存等资源时,系统可以根据需要随意终止它。

在决定如何对流程进行分类时,系统将根据在流程中当前活动的所有组件中找到的最重要的级别来做出决定。

进程的优先级也可以基于进程对它的其他依赖性而增加。

多进程

默认情况下,同一应用程序的所有组件都在同一进程中运行,大多数应用程序不应更改这一点。但是,如果你发现需要控制某个组件属于哪个进程,则可以在 < application> 中进行。

每种类型的组件元素 (< activity >、< service>、< receiver> 和 < provider>) 的清单条目都支持 android:process 属性,该属性可以指定该组件应在其中运行的进程。

< application> 元素还支持 android:process 属性。

Android 的一个不同寻常的基本特性是应用程序进程的生命周期不受应用程序本身直接控制。相反,它是由系统通过系统知道正在运行的应用程序部分的组合、这些东西对用户的重要性以及系统中可用的总内存量来确定的。

默认进程就是主进程。其他进程一般来说都是子进程。

如:咱们用到的微信,他那么多功能肯定不是在一个默认进程里面操作的。使用多进程,即使某个功能因为线程问题导致进程崩溃也崩溃一个进程,其他非该进程的功能可以正常使用。

多进程产生多个Application

如果注册的四大组件中的任意一个组件时用到了多进程,运行该组件时,都会创建一个新的Application对象。对于多进程重复创建Application这种情况,只需要在该类中对当前进程加以判断即可。

com.scc.demo(12095):com.scc.demo进程名;12095进程id

代码实现:AndroidMainfest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.scc.demo">
    <application
        android:name=".SccApp"
        ...
        android:theme="@style/Theme.Demo">
        <activity android:name=".actvitiy.MainActivity"
            >
            ...
        </activity>

        <activity android:name=".actvitiy.TouchActivity"
            android:process="com.scc.touch.wudi"/>
        <activity android:name=".actvitiy.ViewActivity"
            android:process=":view"/>
        ...
    </application>

</manifest>
复制代码

根据默认进程名和当前进程名比较是否进行初始化。

public class SccApp extends Application {
    @RequiresApi(api = Build.VERSION_CODES.P)
    @Override
    public void onCreate() {
        super.onCreate();
        getProcessName(BuildConfig.APPLICATION_ID);
    }
    public void getProcessName(String processName){
        ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> processInfos = activityManager.getRunningAppProcesses();
        if(processInfos!=null)
        {
            for(ActivityManager.RunningAppProcessInfo processInfo:processInfos){
                MLog.e(processInfo.processName);
                if(processName.equals(processInfo.processName)){
                    init();
                }
            }
        }
    }
    //初始化
    private void init(){
        //
        CrashReport.initCrashReport(getApplicationContext(), "注册时申请的APPID", false);
    }
}
复制代码

进程间通信

这个内容太多,咱在后面的文章再详细描述。

Android中的线程

线程分为两种:

一个线程总是由另一个线程启动,所以总有一个特殊的线程,叫做主线程。它是应用启动并执行的第一个线程。每次启动一个新工作线程,都会从主线程分出一条独立的线。

UI/Main Thread (主线程)

启动应用程序时,系统会为应用程序创建一个执行线程,称为 "main"。该线程非常重要,因为它负责将事件发送到适当的用户界面小部件,包括绘图事件。与Android UI toolkit (来自Android.widget和Android.view包的组件)交互的线程。

因此,主线程有时也称为UI线程。但是,在特殊情况下,应用程序的主线程可能不是它的UI线程。

使用线程注解时,注意:构建工具将@MainThread和 @UiThread注释视为可互换的,因此你可以@UiThread 从@MainThread方法中调用方法,反之亦然。但是,在系统应用程序在不同线程上具有多个视图的情况下,UI 线程可能与主线程不同。因此,你应该 @UiThread 使用 @MainThread.

在同一进程中运行的所有组件都在UI线程中实例化。

此外,Android UI toolkit不是线程安全的。因此,你不能从工作线程操作UI—你必须从UI线程对用户界面执行所有操作。因此,Android的单线程模型只有两条规则:

阻塞UI线程

如果所有事情都发生在UI线程中,那么执行长时间操作(如网络访问或数据库查询)将阻塞整个UI。

发生ANR的原因:

Worker Thread操作UI

@Override
protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_thread);
    //Worker Thread(工作线程)
    new Thread(new Runnable() {
        @Override
        public void run() {
            //操作UI线程
            Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
        }
    }).start();
}
复制代码

运行后直接报错:

2021-10-12 14:47:47.495 4122-4247/com.scc.demo E/AndroidRuntime: FATAL EXCEPTION: Thread-7
    Process: com.scc.demo, PID: 4122
    java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()
        at android.widget.Toast$TN.<init>(Toast.java:895)
        at android.widget.Toast.<init>(Toast.java:205)
        at android.widget.Toast.makeText(Toast.java:597)
        at android.widget.Toast.makeText(Toast.java:566)
        at com.scc.demo.actvitiy.ThreadActivity$1.run(ThreadActivity.java:18)
        at java.lang.Thread.run(Thread.java:919)
复制代码

Worker Thread(工作线程)

因不能阻塞主线程,但是有些耗时操作(如加载图片、网络请求等)非即时相应的则可以通过工作线程来执行

注意,你不能从UI线程或"主"线程以外的任何线程更新UI。

为了解决这个问题,Android提供了几种从其他线程访问UI线程的方法:

样例:子线程访问UI线程

public class ThreadActivity extends ActivityBase{
    TextView tvName;
    @Override
    protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_thread);
        tvName = findViewById(R.id.tv_name);
        tvName.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                csThread();
                startThread();
            }
        });
    }
    private void csThread(){
        //Worker Thread(工作线程)
        new Thread(new Runnable() {
            @Override
            public void run() {
                //这样写直接报错
                tvName.setText("我是Worker Thread---行路难!行路难!");
//                ------强大的分割线------
//                下面几种方式都没问题
                //第一种:Activity.runOnUiThread(Runnable)
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tvName.setText("我是Worker Thread---行路难!行路难!");
                        Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
                    }
                });

                //第二种:View.post(Runnable)
                tvName.post(new Runnable() {
                    @Override
                    public void run() {
                        tvName.setText("我是Worker Thread---行路难!行路难!");
                        Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
                    }
                });

                //第三种:View.postDelayed(Runnable, long)
                tvName.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        tvName.setText("我是Worker Thread---行路难!行路难!");
                        Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
                    }
                },1000);

                //第四种:Handler(下面有源码,都基于Handler来做的)
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        tvName.setText("我是Worker Thread---行路难!行路难!");
                        Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }).start();
    }
}
复制代码

子线程直接操作主线程报错信息:

理论上应该拿 3.1.2 Worker Thread 操作UI 时的报错信息。既然都能通过这种方式解决,就多举一个。

2021-10-12 16:02:51.754 8635-8676/com.scc.demo E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.scc.demo, PID: 8635
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8798)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1606)
        at android.view.View.requestLayout(View.java:25390)
        ...
        at android.widget.TextView.checkForRelayout(TextView.java:9719)
        at android.widget.TextView.setText(TextView.java:6311)
        ...
        at com.scc.demo.actvitiy.ThreadActivity$2.run(ThreadActivity.java:31)
        at java.lang.Thread.run(Thread.java:923)
复制代码

几种方法源码

    //Activity.runOnUiThread(Runnable)
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

    //View.post(Runnable)
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        getRunQueue().post(action);
        return true;
    }

    //View.postDelayed(Runnable, long)
    public boolean postDelayed(Runnable action, long delayMillis) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.postDelayed(action, delayMillis);
        }

        getRunQueue().postDelayed(action, delayMillis);
        return true;
    } 
复制代码

你会发现他们都是使用 Handler 来完成的。所以在 子线程访问UI线程 的样例中咱,可以使用 new Handler() 来完成更新 UI。

线程的状态

开启线程的三种方式

    private void startThread(){
        //第一种:继承Thread重写run方法
        new MyThread().start();
        //第二种:实现Runnable重写run方法
        new Thread(new MyRunanble()).start();
        //第三种:实现Callable重写call方法
        FutureTask<Integer> ft = new FutureTask<Integer>(new MyCallable());
        new Thread(ft).start();

    }
    class MyThread extends Thread{
        @Override
        public void run() {
            MLog.e(this.getClass().getName());
        }
    }
    class MyRunanble implements Runnable{

        @Override
        public void run() {
            MLog.e(this.getClass().getName());
        }
    }
    class MyCallable implements Callable {
        @Override
        public Object call() throws Exception {
            MLog.e(this.getClass().getName());
            return null;
        }
    }
复制代码

小结

Callable 和 Runnable 类似,但是功能更强大,具体表现在:

常见面试题

run()和start()方法区别

wait、notify、notifyAll

注意:当要调用wait()或notify()/notifyAll()方法时,一定要放到synchronized(obj)代码中,否则会报错java.lang.IllegalMonitorStateException。当调用obj.notify/notifyAll后,调用线程依旧持有obj锁,因此等待线程虽被唤醒,但仍无法获得obj锁,直到调用线程退出synchronized块,释放obj锁后,其他等待线程才有机会获得锁继续执行。

join、sleep、wait

线程阻塞

线程中断

使用 interrupt()中断,但调用 interrupt()方法只是传递中断请求消息,并不代表要立马停止目标线程。然后通过抛出InterruptedException来唤醒它

public class Thread {
    // 中断当前线程
    public void interrupt();
    // 判断当前线程是否被中断
    public boolen isInterrupt();
    // 清除当前线程的中断状态,并返回之前的值
    public static boolen interrupted();
}
复制代码

线程池ThreadPoolExecutor

线程池的工作原理:线程池可以减少创建和销毁线程的次数,从而减少系统资源的消耗。

当一个任务提交到线程池时:

线程池的种类

FixedThreadPool:可重用固定线程数的线程池,只有核心线程,没有非核心线程,核心线程不会被回收,有任务时,有空闲的核心线程就用核心线程执行,没有则加入队列排队。

SingleThreadExecutor:单线程线程池,只有一个核心线程,没有非核心线程,当任务到达时,如果没有运行线程,则创建一个线程执行,如果正在运行则加入队列等待,可以保证所有任务在一个线程中按照顺序执行,和FixedThreadPool的区别只有数量。

CachedThreadPool:按需创建的线程池,没有核心线程,非核心线程有Integer.MAX_VALUE个,每次提交任务如果有空闲线程则由空闲线程执行,没有空闲线程则创建新的线程执行,适用于大量的需要立即处理的并且耗时较短的任务。

ScheduledThreadPoolExecutor:继承自ThreadPoolExecutor,用于延时执行任务或定期执行任务,核心线程数固定,线程总数为Integer.MAX_VALUE。

如何保证线程安全

线程安全性体现在:

volatile、synchronized、Lock、ReentrantLock 区别

Thread为什么不能用stop方法停止线程

从官方文档可以得知,调用Thread.stop()方法是不安全的,这是因为当调用Thread.stop()方法时,会发生下面两件事:

java中的同步的方法

为啥需要同步呢?因为在多线程并发控制,当多个线程同时操作一个可共享的资源时,如果没有采取同步机制,将会导致数据不准确,因此需要加入同步锁,确保在该线程没有完成操作前被其他线程调用,从而保证该变量的唯一性和准确性。

Android 进程与线程视频解读→视频地址

作者:Android帅次
链接:https://juejin.cn/post/7019132459694424077
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

上一篇 下一篇

猜你喜欢

热点阅读