Binder 与 四大组件工作原理 其一

news2025/1/15 17:37:06

Binder

Binder的组成结构

Binder的架构如图所示
在这里插入图片描述

ServiceManager负责把Binder Server注册到一个容器中。

我们可以这样理解Client、Server 、ServiceManager、Binder Driver之间的关系:

把ServiceManager比喻成电话局,存储着每个住宅的座机电话。张三给李四打电话,拨打电话号码,会转接到电话局,电话局的接线员查询到这个号码的地址,因为李四的电话号码之间在电话局注册过,所以能够拨通;如果没有注册,就会提示该号码不存在。

张三就是Client,李四就是Server,电话局就是ServiceManager,而接线员就是Binder Driver。

Binder的通信过程

Binder的通信过程和交互路线如下图所示
在这里插入图片描述

  1. Server 在 ServiceManager 这个容器中注册

  2. Client 想要调用 Server 的 add 方法,就需要先获取 Server 对象, 但是 ServiceManager 不会把真正的 Server 对象返回给 Client,而是把 Server 的一个代理对象返回给 Client ,也就是 Proxy

  3. Client 调用 Proxy 的 add 方法,Binder驱动会帮他去调用 Server 的 add 方法,并把结果返回给 Client 。

在这里插入图片描述

AIDL

创建 Book 类

在主包下面建一个 aidl 包专门存放进程通讯用到的数据, Book 类继承 Parcelable 接口(原因:AIDL 只能传送继承 Parcelable 接口的类)

package com.example.zs.ipcdemo.aidl;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {
    private String bookId;
    private String bookName;

    public String getBookId() {
        return bookId;
    }

    public void setBookId(String bookId) {
        this.bookId = bookId;
    }

    public String getBookName() {
        return bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }

    public static Creator<Book> getCREATOR() {
        return CREATOR;
    }

    public Book(String bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    protected Book(Parcel in) {
        bookId = in.readString();
        bookName = in.readString();
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(bookId);
        dest.writeString(bookName);
    }

    public void readFromParcel(Parcel in) {
        bookId = in.readString();
        bookName = in.readString();
    }

    @Override
    public int describeContents() {
        return 0;
    }

    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel in) {
            return new Book(in);
        }

        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
}

创建 Book.aidl 、IBookManager.aidl

为什么要有 Book.aidl 类,因为只有将类在 aidl 中声明时候,AIDL 才能调用 Book 类。所以说要让 AIDL 能够传送自定义类需要 1、继承 Parcelable 接口 2、创建同名 .aidl 文件声明自己。

package com.example.zs.ipcdemo.aidl;

parcelable Book;

IBookManager.aidl 设置让客户端允许调用的接口。

package com.example.zs.ipcdemo.aidl;

import com.example.zs.ipcdemo.aidl.Book;

interface IBookManager{
    List<Book> getBookList();
    void addBook(in Book book);
}

这里面有个地方要注意一下 addBook 方法中有 in 这个类型,下面我们简单说一下 AIDL 文件中 in 类型out 类型和 inout 数据的区别。

  • in :客户端的参数输入:是把实参的值赋值给行参 那么对行参的修改,不会影响实参的值 。
  • out :服务端的参数输入:传递以后,行参和实参都是同一个对象,只是他们名字不同而已,对行参的修改将影响实参的值。
  • inout:这个可以叫输入输出参数,客户端可输入、服务端也可输入。客户端输入了参数到服务端后,服务端也可对该参数进行修改等,最后在客户端上得到的是服务端输出的参数。

我们手动 ReBuild 一下我们的项目系统在这个目录下app\build\generated\source\aidl\debug 为我们自动生成 IBookManger.java ,接下来让我们看一下 IBookManger.java 的庐山真面目。

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: E:\\GitHub\\IPCDemo\\app\\src\\main\\aidl\\com\\example\\zs\\ipcdemo\\aidl\\IBookManager.aidl
 */
package com.example.zs.ipcdemo.aidl;

public interface IBookManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.example.zs.ipcdemo.aidl.IBookManager {
        private static final java.lang.String DESCRIPTOR = "com.example.zs.ipcdemo.aidl.IBookManager";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an com.example.zs.ipcdemo.aidl.IBookManager interface,
         * generating a proxy if needed.
         */
        public static com.example.zs.ipcdemo.aidl.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.example.zs.ipcdemo.aidl.IBookManager))) {
                return ((com.example.zs.ipcdemo.aidl.IBookManager) iin);
            }
            return new com.example.zs.ipcdemo.aidl.IBookManager.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    java.util.List<com.example.zs.ipcdemo.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.example.zs.ipcdemo.aidl.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.example.zs.ipcdemo.aidl.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.example.zs.ipcdemo.aidl.IBookManager {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public java.util.List<com.example.zs.ipcdemo.aidl.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.example.zs.ipcdemo.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.example.zs.ipcdemo.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

            @Override
            public void addBook(com.example.zs.ipcdemo.aidl.Book book) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    if ((book != null)) {
                        _data.writeInt(1);
                        book.writeToParcel(_data, 0);
                    } else {
                        _data.writeInt(0);
                    }
                    mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public void registerListener(com.example.zs.ipcdemo.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeStrongBinder((((listener != null)) ? (listener.asBinder()) : (null)));
                    mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

            @Override
            public void unregisterListener(com.example.zs.ipcdemo.aidl.IOnNewBookArrivedListener listener) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeStrongBinder((((listener != null)) ? (listener.asBinder()) : (null)));
                    mRemote.transact(Stub.TRANSACTION_unregisterListener, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);

    }

    public java.util.List<com.example.zs.ipcdemo.aidl.Book> getBookList() throws android.os.RemoteException;

    public void addBook(com.example.zs.ipcdemo.aidl.Book book) throws android.os.RemoteException;
}

刚看有点蒙逼,别急我们一步一步分析,IBookManager 文件中,包括 IBookManager.aidl 的两个接口,以及 Stub 和 Proxy 两个实现了 IBookManager 接口的类,其中 Stub 是定义在 IBookManager 接口中的,而 Proxy 则定义在 Stub 类中。

在这里插入图片描述

Client 执行流程

1、当服务连接建立完成后,客户端执行代码。IBookManager.Stub.asInterface(service) 这句代码的作用是判断传入的参数 IBinder 对象和自己是否在同一个进程如果不是则把这个IBinder 参数包装成一个 Proxy 对象,这时调用 StubgetBookList() 方法,间接调用 ProxygetBookList() 方法,看一下代码。

// 连接服务
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(final ComponentName name, IBinder service) {
            //  IBinder 通过 asInterface 判断
            //  asInterface方法的作用是判断参数——也就是IBinder对象,和自己是否在同一个进程:
            //  是: 则直接转换、直接使用,接下来就跟 Binder 跨进程通信无关啦
            //  否: 则把这个IBinder参数包装成一个 Proxy 对象,这时调用 Stub 的方法,间接调用Proxy的方法
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            try {
                mBookManager = bookManager;
                bookManager.getBookList();
                Log.d(TAG, "get books count:" + bookManager.getBookList().size());
                bookManager.registerListener(arrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBookManager = null;
        }
    };
public static com.example.zs.ipcdemo.aidl.IBookManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            // 作用是判断传入的参数 IBinder 对象和自己是否在同一个进程
            if (((iin != null) && (iin instanceof com.example.zs.ipcdemo.aidl.IBookManager))) {
                return ((com.example.zs.ipcdemo.aidl.IBookManager) iin);
            }
            return new com.example.zs.ipcdemo.aidl.IBookManager.Stub.Proxy(obj);
        }

2、Proxy 在自己的 getBookList() 方法中,会使用 Parcelable 来准备数据,把函数名称、函数参数都写入 _data,让 _reply 接收函数返回值。最后使用 IBindertransact() 方法,把数据就传给 BinderServer 端了。

 @Override
            public java.util.List<com.example.zs.ipcdemo.aidl.Book> getBookList() throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.util.List<com.example.zs.ipcdemo.aidl.Book> _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    // 通知服务器调用该方法
                    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                    
                    /** 调用mRemote.transact()方法后,Binder驱动会挂起当前客户端线程,
                     * 并向服务端发送一个消息,这个消息就包括客户端传递的参数。
                     * 服务端接收到消息,解析数据执行完相关任务后会把执行结果写入reply中,
                    **/ 然后向Binder驱动发送一个notify消息,Binder驱动从挂起处唤醒客户端线程继续执行。
                    
                    _reply.readException();
                    _result = _reply.createTypedArrayList(com.example.zs.ipcdemo.aidl.Book.CREATOR);
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }

mRemote指什么?
当一个Server Binder被创建时其在Binder驱动中同时会创建一个mRemote引用指向该Server。Client端要访问Server时首先要获取Server在Binder中的引用mRemote,获取引用后就可以通过mRemote.transact()方法向服务端发送消息。

transact做了以下几件事

  • 以线程间消息通信的模式向服务端发送客户端传递过来的参数。
  • 挂起当前客户端线程,等待服务端线程执行完毕后的通知(notify)
  • 接收服务端线程通知,继续执行客户端线程,并返回执行结果

Server 执行流程

Server 则是通过 onTransact() 方法接收 Client 进程传过来的数据,包括函数名称、函数参数,找到对应的函数,这里是 getBookList() ,把参数喂进去,得到结果,返回。所以 onTransact() 函数经历了读数据、执行要调用的函数、把执行结果再写数据的过程。

 @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                // 客户端调用指令 getBookList()
                case TRANSACTION_getBookList: {
                    data.enforceInterface(DESCRIPTOR);
                    // 调用本地服务 getBookList() 方法
                    java.util.List<com.example.zs.ipcdemo.aidl.Book> _result = this.getBookList();
                    reply.writeNoException();
                    // 将数据传给客户端
                    reply.writeTypedList(_result);
                    return true;
                }
                case TRANSACTION_addBook: {
                    data.enforceInterface(DESCRIPTOR);
                    com.example.zs.ipcdemo.aidl.Book _arg0;
                    if ((0 != data.readInt())) {
                        _arg0 = com.example.zs.ipcdemo.aidl.Book.CREATOR.createFromParcel(data);
                    } else {
                        _arg0 = null;
                    }
                    this.addBook(_arg0);
                    reply.writeNoException();
                    return true;
                }
            return super.onTransact(code, data, reply, flags);
        }

四大组件的工作原理

Activity的工作原理

App是怎样启动的

在手机屏幕上点击某个App的图标,假设是斗鱼app,这个APP的首页面就会展示在我们面前,看似简单的操作,背后经历了Activity和AMS的反反复复的通信过程。首选要搞清楚手机屏幕是一个Activity,这个Activity所在的app,业界称之为Launcher,Launcher是各大手机厂商提供的。

Launcher为每个app的图标提供了启动这个app所需要的Intent信息,如:包名、首页面地址、category等等,这些信息是app安装的时候,PackageManagerService从各个apk包中的AndroidManifest文件中读取到的。

启动流程

①Launcher通知AMS,要启动斗鱼app,而且指定了启动app的哪个页面

②AMS收到消息后,把要启动的页面记录下来

③Launcher当前页面进入Paused状态,然后通知AMS,“你去找对应的app吧”

④AMS检查斗鱼app是否已经启动了,是,则唤起斗鱼app,否,就要启动一个新的进程,AMS所在的新进程中创建一个ActivityThread对象,启动相对应的main函数

⑤app启动后,通知AMS,“我启动好了”

⑥AMS翻出之前记录下的值(要启动的页面),告诉斗鱼app,启动这个页面

⑦斗鱼app启动首页,创建Context并与首页Activity关联,然后调用启动页面的Activity的onCreate函数

流程至此完成,分为两部分,1~3 阶段:Launcher和AMS相互通信,4~7阶段:斗鱼app与AMS相互通信

第一阶段:Launcher通知AMS

在这里插入图片描述

上图就是点击Launcher上的app之后,会调用Launcher的startActivitySafely方法,其实还是会调用Activity的startActivity方法,intent中带着要启动app所需要的关键信息,如:启动的页面类名等等,所以为什么要在Manifest中,给首页面指定的action和category了,就是在启动的时候Launcher会找到这个关键信息进行启动

启动app时候,调来调去最终会调用startActivityForResult方法,startActivityForResult的代码如下:

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
            options = transferSpringboardActivityOptions(options);
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if (ar != null) {
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
            if (requestCode >= 0) {
              
                mStartedActivity = true;
            }
 
            cancelInputsAndStartExitTransition(options);
            
        } else {
            if (options != null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {
                
                mParent.startActivityFromChild(this, intent, requestCode);
            }
        }
    }

在以上代码中,我们发现了一个mMainThread变量,这是一个ActivityThread类型的变量,就是主线程,也就是UI线程,它代表了整个app,读者不禁要问,ActivityThread代表了App应用程序,那么Application类代表什么?其实,Application对于android系统来说并没有那么重要,它只是个上下文,Application是app的上下文,ContextActivity的上下文。

通过ActivityThreadgetApplicationThread方法取到一个Binder对象,这个对象类型为ApplicationThread,代表了launcher所在的app进程,mToken也是一个Binder对象,代表launcher这个Activity,也通过Instumentation传给AMS,AMS查询记录就知道是谁向AMS发送请求了。

查看mInstrumentation.execStartActivity方法,借助execStartActivity,Activity会把数据传递给ActivityManagerNative
ServiceManager是一个容器,AMN通过getDefault方法,从ServiceManager中取得一个名为activity的对象,然后把它包装成一个ActivityManagerPoxy对象(AMP),AMP就是AMS的代理对象。
AMN的getDefault方法返回类型为IActivityManager,而不是AMP,AMN和AMP都实现了IActivityManager接口,AMS继承了AMN。

在这里插入图片描述

第二阶段:AMS处理Launcher传过来的信息

1:Binder(也就是AMN和AMP)和AMS通信,肯定每次是做不同的事,比如这次Launcher要启动斗鱼app,那么会发送类型为START_ACTIVITY的请求给AMS,同时会告诉AMS要启动哪个Activity

2:AMS说:“好了,我知道了”,然后会检查AndroidManifest文件,检查是否存在要启动的Activity,如果不存在就抛出Activity not found异常信息

3:AMS通知Launcher:“没你什么事了,洗洗睡吧”,那么AMS是通过什么途径告诉Launcher的呢?结论是:AMS通过ApplicationThreadProxy发送消息,而App端则通过ApplicationThread来接受这个消息。

第三阶段:Launcher去休眠,然后通知AMS,“我休眠了

在这里插入图片描述

ApplicationThread(APT)接收到来自AMS的消息后,调用AcctivityThread的sendMessage方法,向Launcher的主线程消息队列发送PAUSE_ACTIVITY消息,发送消息是通过一个名为H的Handler类完成的,AMS给Activity发送的所有消息,以及给其他三大组件发送的消息,都是经过H类,既然都经过这条路,我们就可以从这里做插件化技术。

第四阶段:AMS启动新的进程

AMS接下里要启动斗鱼App的首页,因为此app不在后台进程中,所以要启动一个新的进程,这里调用的事Process.start方法,并且指定了ActivityThread的main函数为入口函数。

第五阶段:新进程启动,以ActivityThread的main函数为入口

启动新进程,就是启动一个新的app,为这个进程创建ActivityThread对象,这就是我们熟悉的UI主线程。创建好UI线程后,立刻进入ActivityThread的main函数,接下来要做两件具有重大意义的事:

  • ①创建一个主线程Looper,也就是MainLooper,MainLooper就是在这里创建的

  • ②创建Applicaition,Application也是在这里创建的

主线程在收到BIND_APPLICATION消息后,根据传递过来的ApplicationInfo创建一个对应的LoadedApk对象(标志当前apk信息),然后创建ContextImpl对象(标志当前进程环境),紧接着通过反射创建目标Application,并调用其attach方法,将ContextImpl对象设置为目标Applcation的上下文环境,最后调用Application的onCreate函数,做一些初始化工作

App的灵魂是ActivityTread,也就是主线程,app开发人员用不到,但使用反射是可以修改这个类的一些行为的。

创建App的最后就是要告诉AMS,“我启动好了”,同时把自己的ActivityThread对象发送给AMS,AMS电话簿中就多了这个新的App登记信息,AMS以后就通过这个ActivityThread对象,向这个App发送消息。

第六阶段:AMS告诉新App要启动哪个Acctivity

AMS把传入的ActivityThread对象转为一个ApplicationThread对象,用于以后和这个App跨进程通信,在第六阶段AMS从过去记录中翻出来要启动哪个Activity,然后通过ATP告诉App

在这里插入图片描述

第七阶段:启动斗鱼首页Activity

在这里插入图片描述

在Binder的另一端,App通过APT接收到AMS的消息,仍然在H的handleMessage方法的swicth语句中处理,每次都是APT执行AcctivityThread的sendMessage方法,在这个方法中,把消息拼装一下,然后扔给H的swicth语句分析,来决定要执行ActivityThread的那个方法。

handleLaunchActivity方法要做的是哪些事?

①通过Instrumentati0on的newActivity方法,创建要启动的Activity实例。

②为这个Acctivity创建一个上下文Context对象,并与Activity进行关联

③通过Instrumentaction的callActivityOnCreate方法,执行Activity的onCreate方法,从而启动Activity,至此App启动完毕,这个流程是经过多次握手,App和AMS频繁的向对方发送消息,而发送消息的机制,是建立在Binder的基础之上的。

App内部的页面跳转

接下来我们看App内部页面的跳转。

从ActivityA跳转到ActivityB,其实可以把ActivityA看作是Launcher,那么这个跳转过程,和App的启动过程就很像了。

有了前面的分析基础,会发现,这个过程不需要重新启动一个新的进程,所以可以省略App启动过程中的一些步骤,流程简化为:

  1. ActivityA向AMS发送一个启动ActivityB的消息。
  2. AMS保存ActivityB的信息,然后通知App,你可以休眠了(onPaused)。
  3. ActivityA进入休眠,然后通知AMS,我休眠了。
  4. AMS发现ActivityB所在的进程就是ActivityA所在的进程,所以不需要重新启动新的进程,所以它就会通知App,启动ActivityB。
  5. App启动ActivityB。

在这里插入图片描述
以上的分析,仅限于ActivityA和ActivityB在相同的进程中,如果在Manifest中指定这两个Activity不在同一个进程中,那么就又是另一套流程了,但是整体流程大同小异。

Context家族史

Activity和Service都有Context,这三个类,还有Application,其实是亲戚一家子。
在这里插入图片描述

Activity因为有了一层Theme,所以中间有个ContextThemeWrapper,相当于它是Service和Application的侄子。

ContextWrapper只是一个包装类,没有任何具体的实现,真正的逻辑都在ContextImpl里面。

一个应用包含的Context个数:Service个数+Activity个数+1(Application类本身对应一个Context对象)。

应用程序中包含多个ContextImpl对象,而其内部变量mPackageInfo指向同一个PackageInfo对象。

Activity 和 Context的区别

我们就拿Activity举例子,看看Activity和Context的联系和区别。

我们知道,跳转到一个新的Activity要这么写:
在这里插入图片描述

我们还知道,也可以在Activity中使用getApplicationContext方法获取Context上下文信息,然后使用Context 的startActivity方法,启动一个新的Activity:
在这里插入图片描述
这二者的区别是什么?

Activity中:

@Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            // Note we want to go through this call for compatibility with
            // applications that may have overridden the method.
            startActivityForResult(intent, -1);
        }
    }

在Activity中无论是使用哪一种startActivity方法都会调用到Activity自身的方法,所以是一样的。

然而在其他的Context子类,例如ContextImpl.java中的实现,会检查有没有设置Flag:FLAG_ACTIVITY_NEW_TASK,否则会报错:

ContextImpl中:

@Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();

        // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
        // generally not allowed, except if the caller specifies the task id the activity should
        // be launched in. A bug was existed between N and O-MR1 which allowed this to work. We
        // maintain this for backwards compatibility.
        final int targetSdkVersion = getApplicationInfo().targetSdkVersion;

        if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                && (targetSdkVersion < Build.VERSION_CODES.N
                        || targetSdkVersion >= Build.VERSION_CODES.P)
                && (options == null
                        || ActivityOptions.fromBundle(options).getLaunchTaskId() == -1)) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                            + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                            + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/467962.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

记录-Vue移动端日历设计与实现

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 工作中遇到一个需求是根据日历查看某一天/某一周/某一月的睡眠报告&#xff0c;但是找了好多日历组件都不是很符合需求&#xff0c;只好自己手写一个日历组件&#xff0c;顺便记录一下。 先看看UI给的…

linux文件编辑与编辑命令

文章目录 一、linux文件编辑与编辑命令总结 一、linux文件编辑与编辑命令 Linux mkdir命令:创建目录 Linux more命令:显示文本文件内容 Linux cat命令:连接文件并打印到标准输出设备上 Linux grep命令:检索文件内容 Linux rm命令:删除文件或目录 Linux touch命令:修改文件的时…

15款时间计划、任务管理APP/软件对比(团队\个人)

15 款不同类型的日程、任务管理应用&#xff1a;1.PingCode&#xff1b;2.Worktile&#xff1b;3.Todoist&#xff1b;4.Trello&#xff1b;5.Microsoft To Do&#xff1b;6.Asana&#xff1b;7.Google 任务&#xff1b;8.Notion&#xff1b;9.Monday.com&#xff1b;10.Teambi…

Matlab对日期变量和时间变量的管理

Matlab2012a内置了三个函数 datanumdatevecdatestr 靠这三个函数&#xff0c;可以基本实现日期变量和时间变量的管理。下面直接来看。 &#xff08;1&#xff09;datanum 这个函数用来将字符串&#xff0c;日期矢量转为通用日&#xff08;数值型&#xff09;。所谓的通用日…

js特殊对象 - RegExp对象(正则表达式)

1、概述 正则表达式用于定义一些字符串的规则&#xff0c;计算机可以根据正则表达式&#xff0c;来检查一个字符串是否符合规则&#xff0c;获取将字符串中符合规则的内容提取出来。 使用typeof检查正则对象&#xff0c;会返回object。 2、创建正则对象 2.1、使用对象创建 语法…

Java spring 注解 @PostConstruct 实战讲解

前言 在最近的学习中&#xff0c;发现了一个非常实用的注解 —— PostConstruct。通过学习了解&#xff0c;逐步发现它能帮助我更轻松的解决不少原本很复杂的问题。 下面&#xff0c;结合实例介绍 PostConstruct 注解的特性&#xff0c;因为PreDestroy基本用不到&#xff0c;所…

C++算法初级11——01背包问题(动态规划2)

C算法初级11——01背包问题&#xff08;动态规划2&#xff09; 文章目录 C算法初级11——01背包问题&#xff08;动态规划2&#xff09;问题引入0-1背包问题分析0-1背包问题的形式化分析优化 问题引入 辰辰采药 辰辰是个天资聪颖的孩子&#xff0c;他的梦想是成为世界上最伟大…

Ubuntu开机自启动一些东西

有三种方式做开机自启动 目录 1.免除sudo密码 2.Startup 2.desktop 3.service 1.免除sudo密码 做完这一步你的所有sudo命令都不会再让你输密码了 如果你的开机自启动的东西需要sudo&#xff0c;那么这一步就是必须的&#xff0c;如果不需要sudo&#xff0c;那么这一步可…

Linux安装kubectl

前言 以下所有命令基于CentOS7.9系统&#xff0c;官方参考文档&#xff1a;> 文章最后附有一键安装的脚本&#xff0c;可以直接运行脚本进行安装 下载安装文件 1. 下载最新发行版 curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/st…

C嘎嘎~~[类 中篇]

类 中篇 6.类的实例化7.类对象模型8.this指针8.1this指针是什么8.2this指针的特性 6.类的实例化 什么叫类的 实例化?? 首先, 我们应该关注这个"实" — 实际存在的, 它的反义词是 “虚” — 不存在的. > 类中的成员变量是虚的(相当于声明), 在类外面创建的对象是…

《程序员面试金典(第6版)》面试题 16.06. 最小差(双指针,pair数据结构)

题目描述 给定两个整数数组a和b&#xff0c;计算具有最小差绝对值的一对数值&#xff08;每个数组中取一个值&#xff09;&#xff0c;并返回该对数值的差 示例&#xff1a; 输入&#xff1a;{1, 3, 15, 11, 2}, {23, 127, 235, 19, 8}输出&#xff1a;3&#xff0c;即数值对(…

Power BI动态日期轴方法总结

趋势&#xff0c;应该是我们做可视化时最熟悉的一个词了&#xff0c;看趋势自然离不开日期&#xff0c;年度趋势&#xff0c;月趋势&#xff0c;周趋势等等。Power BI中我们可以借助于计算表&#xff0c;计算组&#xff0c;字段参数来实现动态实时轴的效果。 计算表实现动态日…

Node.js--基础

一、Node.js是什么 Node.js is a JavaScript runtime built on Chrome’s V8 JavaScript engine. 1、特性 Node.js 可以解析JS代码&#xff08;没有浏览器安全级别的限制&#xff09;提供很多系统级别的API&#xff0c;如&#xff1a; 文件的读写 (File System)进程的管理 …

每日一个小技巧:1分钟告诉你文字转图片的方法有哪些

在数字时代&#xff0c;信息传递快速便捷&#xff0c;但文字在传递中却显得单调乏味&#xff0c;难以吸引人们的眼球。为了解决这个问题&#xff0c;越来越多的人开始寻找方法将文字转化为图片。文字转图片不仅能够让文字更具视觉冲击力&#xff0c;还能够在社交媒体、广告宣传…

Nginx常见应用场景

文章目录 场景一&#xff1a;代理静态文件场景二&#xff1a;代理服务器 本教程讲述 Nginx 的常见应用场景。内容接上文&#xff1a;Nginx基本配置。 前提&#xff1a;假设我们已经安装好了 Nginx&#xff0c;并启动成功。 场景一&#xff1a;代理静态文件 Nginx 一个常用的功…

Hilt 和协程助力启动框架搭建:解决代码混乱和初始化策略问题

关于Hilt的使用&#xff0c;目前已经比较普及了&#xff0c;想必大家已经知道。今天说的是一个如何利用Hilt来做一个启动框架的故事。 是否经历过大型项目的启动优化&#xff0c;一遍过去无任何效果&#xff0c;第二遍过去好几处报错&#xff0c;第三遍过去启动不了&#xff0…

开放耳机有什么优缺点,列举出几款口碑不错的开放式耳机

开放式耳机是通过骨头振动传递声音&#xff0c;而不是通过耳道或鼓膜&#xff0c;因此它具有许多优势&#xff0c;比如可以在运动过程中保持对环境的感知&#xff0c;并避免对听力造成伤害。随着科技的进步和用户需求的增加&#xff0c;开放式耳机也在不断更新。目前市面上的开…

springboot+nodejs+vue众筹项目管理系统平台系统

筹资人&#xff08;企业&#xff09;&#xff1a; 1&#xff0c;可以后台注册并登录&#xff0c;发布项目情况&#xff0c;众筹项目需要管理员审核通过后才能展现在前台&#xff0c;没有审核或者审核不通过不会在前台展示&#xff1b; 众筹项目包括项目名称&#xff0c;项目分类…

盲目自学网络安全只会成为脚本小子?

前言&#xff1a;我们来看看怎么学才不会成为脚本小子 一&#xff0c;怎么入门&#xff1f; 1、Web 安全相关概念&#xff08;2 周&#xff09; 了解网络安全相关法律法规 熟悉基本概念&#xff08;SQL 注入、上传、XSS、CSRF、一句话木马等&#xff09;。 通过关键字&…

springboot整合flowable的简单使用

内容来自网络整理&#xff0c;文章最下有引用地址&#xff0c;可跳转至相关资源页面。若有侵权请联系删除 环境&#xff1a; mysql5.7.2 springboot 2.3.9.RELEASE flowable 6.7.2 采坑&#xff1a; 1.当前flowable sql需要与引用的pom依赖一致&#xff0c;否则会报library…