微信号:jszj2014215

介绍:主要分享移动互联网的相关产品和资讯,关注你将学习到更多,在互联网的当下你会赚更多的钱...

Android从启动到程序运行发生的事情(三)

2016-04-15 10:31 Android技术之家

IPC通信

上面总结完了Android的消息处理机制,那么就顺带总结一下IPC通信吧,毕竟上面提到过那么多次Binder和Socket。

资料:为什么Android要采用Binder作为IPC机制?

知乎上面的回答相当的好,这个博主对系统底层也是颇有钻研,学习。

这里就结合上面的知乎回答以及加上《Linux程序设计》还有一本Linux内核剖析(书名忘了但是讲得真的非常好),掺杂一些个人的理解。

进程的定义

UNIX标准把进程定义为:“一个其中运行着一个或多个进程的地址控件和这些线程所需要的系统资源”。目前,可以简单的把进程看做正在运行的程序。

进程都会被分配一个唯一的数字编号,我们成为PID(也就是进程标识符),它通常是一个取值范围从2到32768的正整数。当进程被启动时,系统将按顺序选择下一个未被使用的数字作为PID,当数字已经回绕一圈时,新的PID重新从2开始,数字1一般是为init保留的。在进程中,存在一个自己的栈空间,用于保存函数中的局部变量和控制函数的调用与返回。进程还有自己的环境空间,包含专门为这个进程建立的环境变量,同时还必须要维护自己的程序计数器,这个计数器用来记录它执行到的位置,即在执行线程中的位置。

在Linux中可以通过system函数来启动一个进程

守护进程

这里就需要提到一个守护进程了,这个在所有的底层中经常都会被提到。

在linux或者unix操作系统中在系统引导的时候会开启很多服务,这些服务就叫做守护进程。为了增加灵活性,root可以选择系统开启的模式,这些模式叫做运行级别,每一种运行级别以一定的方式配置系统。 守护进程是脱离于终端并且在后台运行的进程。守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。

守护进程常常在系统引导装入时启动,在系统关闭时终止。如果想要某个进程不因为用户或终端或其他的变化而受到影响,那么就必须把这个进程变成一个守护进程

防止手机服务后台被杀死

是不是在手机的设置界面看当前正在运行的服务时会发现有的APP不止存在一个服务?有的APP后台存在两个,有的存在三个?有的流氓软件也会这么设置,这样的话就可以一直运行在后台,用户你关也关不了(倒不是说所有这么设置的都是流氓软件,因为有的软件需要保持一个长期的后台在线,这是由功能决定的)。

这里有两种方法(可能还有更多,这里只总结我了解的):

  • 第一种方法就是利用android中service的特性来设置,防止手机服务后台被杀死。通过更改onStartCommand方法的返回值,将service设置为粘性service,那么当service被kill的时候就会将服务的状态返回到最开始启动的状态,也就是运行的状态,所以这个时候也就会再次重新运行。但是需要注意一点,这个时候的intent值就为空了,获取的话需要注意一下这一点。

  • 第二种就是fork出一个C的进程,因为在Linux中,子类进程在父类被杀死销毁的时候不会随之杀死,它会被init进程领养。所以也就可以使用这一个方法,利用主进程fork出一个C进程在后台运行,一旦检测到服务被杀死(检测的方式多种,可使用观察者模式,广播,轮询等等),就重启服务即可

IPC通信

上面总结了进程的相关基础,这里就开始总结一下进程间通信(IPC )的问题了。

现在Linux现有的所有IPC方式:

  1. 管道:在创建时分配一个page大小的内存,缓存区大小有限

  2. 消息队列:信息复制两次,额外的cpu消耗,不适合频繁或信息量大的通信

  3. 共享内存:无需复制,共享缓冲区直接附加到进程虚拟地址控件,速度是在所有IPC通信中最快的。但是进程间的同步问题操作系统无法实现,必须由各进程利用同步工具解决。

  4. Socket:作为更通用的接口,传输效率低,主要用于不通机器或跨网络的通信

  5. 信号量:常作为一种锁机制。

  6. 信号:不适用于信息交换,更适用于进程件中断控制,例如kill process

到了这里,就有了问题,为什么在Linux已经存在这么多优良的IPC方案时,Android还要采取一种新的Binder机制呢?

猜测:我觉得Android采用这种新的方式(当然也大面积的同时使用Linux的IPC通信方式),最多两个原因:

  1. 推广时手机厂商自定义ROM底层的保密性或者公司之间的关系。

  2. 在某些情况下更适合手机这种低配置,对效率要求极高,用户体验极其重要的设备

资料

对于Binder来说,存在着以下的优势:

  • 性能角度:Binder的数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,而共享内存是一次都不需要拷贝,因此Binder的性能仅次于共享内存

  • 稳定性来说:Binder是基于C/S架构的,也就是Client和Server组成的架构,Client端有什么需求,直接发送给Server端去完成,架构清晰,分工明确。而共享内存的实现方式复杂,需要充分考虑访问临界资源的并发同步问题,否则可能会出现死锁等问题。从稳定性来说,Binder的架构优于共享内存。

  • 从安全的角度:Linux的传统IPC方式的接收方无法获得对方进程可靠的UID(用户身份证明)/PID(进程身份证明),从而无法鉴别对方身份,而Android是一个对安全性能要求特别高的操作系统,在系统层面需要对每一个APP的权限进行管控或者监视,对于普通用户来说,绝对不希望从App商店下载偷窥隐射数据、后台造成手机耗电等问题。传统的Linux IPC无任何保护措施,完全由上层协议来确保。而在Android中,操作系统为每个安装好的应用程序分配了自己的UID,通过这个UID可以鉴别进程身份。同时Android系统对外只暴露Client端,Client端将任务发送给Server端,Server端会根据权限控制策略判断UID/PID是否满足访问权限。也就是说Binder机制对于通信双方的身份是内核进行校验支持的。例如Socket方式只需要指导地址就可以连接,他们的安全机制需要上层协议来假设

  • 从语言角度:Linux是基于C的,而Android是基于Java的,而Binder是符合面向对象思想的。它的实体位于一个进程中,而它的引用遍布与系统的各个进程之中,它是一个跨进程引用的对象,模糊了进程边界,淡化了进程通信的过程,整个系统仿佛运行于同一个面向对象的程序之中。

  • 从公司角度:Linux内核是开源的,GPL协议保护,受它保护的Linux Kernel是运行在内核控件,对于上层的任何类库、服务等只要进行系统调用,调用到底层Kernel,那么也必须遵循GPL协议。而对于Android来说,Google巧妙地将GPL协议控制在内核控件,将用户控件的协议采用Apache-2.0协议(允许基于Android的开发商不向社区反馈源码)。

反射

刚才谈到Binder的时候提了一下效率的问题,那这里就不得不讲到反射了。

反射它允许一个类在运行过程中获得任意类的任意方法,这个是Java语言的一个很重要的特性。它方便了程序员的编写,但是降低了效率。

实际上,对于只要不是特别大的项目(非Android),反射对于效率的影响微乎其微,而与之对比的开发成本来说就更划算了。
但是,Android是一个用于手机的,它的硬件设施有限,我们必须要考虑到它的这个因素,用户体验是最重要的。以前看到过国外的一项统计。在一个APP中的Splash中使用了反射,结果运行时间增加了一秒,这个已经算是很严重的效率影响了。

为什么反射影响效率呢

这里就需要提到一个东西,JIT编译器。JIT编译器它可以把字节码文件转换为机器码,这个是可以直接让处理器使用的,经过它处理的字节码效率提升非常大,但是它有一个缺点,就是把字节码转换成机器码的过程很慢,有的时候甚至还超过了不转换的代码效率(转换之后存在一个复用的问题,对于转换了的机器码,使用的次数越多就越值的)。因此,在JVM虚拟机中,也就产生了一个机制,把常用的、使用频率高的字节码通过JIT编译器转换,而频率低的就不管它。而反射的话则是直接越过了JIT编译器,不管是常用的还是非常用的字节码一律没有经过JIT编译器的转化,所以效率就会低。

而在Android里面,5.0之前使用的是Davlik虚拟机,它就是上面的机制,而在Android5.0之后Google使用了一个全新的ART虚拟机全面代替Davlik虚拟机。

ART虚拟机会在程序安装时直接把所有的字节码全部转化为机器码,虽然这样会导致安装时间边长,但是程序运行的效率提升非常大。
【疑问:那在Android5.0之后的系统上,反射会不会没影响了?由于现在做项目的时候更多考虑的是向下兼容,单独考虑5.0的情况还没有,等以后有需求或者是有机会的时候再深入了解一下,以后更新】

继续断点4

刚才总结了Android的消息处理机制和IPC通信,那么我们主线程的消息处理机制是什么时候开始的呢?因为我们知道在主线程中我们是不需要手动调用Looper.prepare()和Looper.loop()的。

Android的主线程就是ActivityThread,主线程的入口方法是main方法,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop来开启消息循环,所以这一步实际上是系统已经为我们做了,我们就不再需要自己来做。

ActivityThread通过AppplicationThread和AMS进行进程件通信,AMS以进程间通信的方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向Handler发送消息,Handler收到消息后会将ApplicationThread中的逻辑切换到主线程中去执行,这个过程就是主线程的消息循环模型。

上面总结到了APP开始运行,依次调用onCreate/onStart/onResume等方法,那么在onCreate方法中我们经常使用的setContentView和findViewById做了什么事呢?

Activity界面显示

首先,就考虑到第一个问题,也就是setContentView这个东西做了什么事,这里就要对你当前继承的Activity分类了,如果是继承的Activity,那么setContentView源码是这样的:

    /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

    /**
     * Set the activity content to an explicit view.  This view is placed
     * directly into the activity's view hierarchy.  It can itself be a complex
     * view hierarchy.  When calling this method, the layout parameters of the
     * specified view are ignored.  Both the width and the height of the view are
     * set by default to {@link ViewGroup.LayoutParams#MATCH_PARENT}. To use
     * your own layout parameters, invoke
     * {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}
     * instead.
     *
     * @param view The desired content to display.
     *
     * @see #setContentView(int)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(View view) {
        getWindow().setContentView(view);
        initWindowDecorActionBar();
    }

    /**
     * Set the activity content to an explicit view.  This view is placed
     * directly into the activity's view hierarchy.  It can itself be a complex
     * view hierarchy.
     *
     * @param view The desired content to display.
     * @param params Layout parameters for the view.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(int)
     */
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().setContentView(view, params);
        initWindowDecorActionBar();
    }

这里面存在着3个重载函数,而不管你调用哪一个,最后都会调用到initWindowDecorActionBar()这个方法。

而对于新的一个AppcompatActivity,这个Activity里面包含了一些新特性,现在我做的项目里基本都是使用AppcompatActivity代替掉原来的Activity,当然也并不是一定的,还是要根据项目的实际情况来选择。

在AppcompatActivity中,setContentView是这样的:

 @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }

    @Override
    public void setContentView(View view) {
        getDelegate().setContentView(view);
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().setContentView(view, params);
    }

一样的3个重载函数,只是里面没有了上面的那个init方法,取而代之的是一个getDelegate().setContentView,这个delegate从字面上可以了解到它是一个委托的对象,源码是这样的:

 /**
     * @return The {@link AppCompatDelegate} being used by this Activity.
     */
    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }

而在AppCompatDelegate.Create方法中,则会返回一个很有意思的东西:

/**
     * Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code activity}.
     *
     * @param callback An optional callback for AppCompat specific events
     */
    public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return create(activity, activity.getWindow(), callback);
    }

private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        final int sdk = Build.VERSION.SDK_INT;
        if (sdk >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (sdk >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (sdk >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV7(context, window, callback);
        }
    }

这里会根据SDK的等级来返回不同的东西,这样的话就不深究了,底层的话我撇了一下,应该原理和Activity是一样的,可能存在一些区别。这里就用Activity来谈谈它的setContentView方法做了什么事。

在setContentView上面有段注释:

Set the activity content from a layout resource. The resource will be inflated, adding all top-level views to the activity.

这里就介绍了它的功能,它会按照一个布局资源去设置Activity的内容,而这个布局资源将会被引入然后添加所有顶级的Views到这个Activity当中。

这是个啥意思勒。

下面从网上扒了一张图:

这里是整个Activity的层级,最外面一层是我们的Activity,它包含里面的所有东西。

再上一层是一个PhoneWindow,这个PhoneWindow是由Window类派生出来的,每一个PhoneWindow中都含有一个DecorView对象,Window是一个抽象类。

再上面一层就是一个DecorView,我理解这个DecorView就是一个ViewGroup,就是装View的。

而在DecoreView中,最上面的View就是我们的TitleActionBar,下面就是我们要设置的content。所以在上面的initWindowDecorActionBar就能猜到是什么意思了吧。

而在initWindowDecorActionBar方法中,有一段代码:

 /**
     * Creates a new ActionBar, locates the inflated ActionBarView,
     * initializes the ActionBar with the view, and sets mActionBar.
     */
    private void initWindowDecorActionBar() {
        Window window = getWindow();

        // Initializing the window decor can change window feature flags.
        // Make sure that we have the correct set before performing the test below.
        window.getDecorView();

        if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
            return;
        }

        mActionBar = new WindowDecorActionBar(this);
        mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);

        mWindow.setDefaultIcon(mActivityInfo.getIconResource());
        mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
    }

注意上面的window.getDecoreView()方法的注释,该方法会设置一些window的标志位,而当这个方法执行完之后,就再也不能更改了,这也就是为什么很多第三方SDK设置window的标志位时一定要求要在setContentView方法前调用。

findViewById

我们通过一个findViewById方法可以实现对象的绑定,那它底层究竟是怎么实现的呢?

findViewById根据继承的Activity类型的不同也存在着区别,老规矩,还是以Activity的来。

/**
     * Finds a view that was identified by the id attribute from the XML that
     * was processed in {@link #onCreate}.
     *
     * @return The view if found or null otherwise.
     */
    @Nullable
    public View findViewById(@IdRes int id) {
        return getWindow().findViewById(id);
    }

从源码来看,findViewById也是经过了一层层的调用,它的功能如同它上面的注释一样,通过一个view的id属性查找view,这里也可以看到一个熟悉的getWindow方法,说明findViewById()实际上Activity把它也是交给了自己的window来做

/**
     * Finds a view that was identified by the id attribute from the XML that
     * was processed in {@link android.app.Activity#onCreate}.  This will
     * implicitly call {@link #getDecorView} for you, with all of the
     * associated side-effects.
     *
     * @return The view if found or null otherwise.
     */
    @Nullable
    public View findViewById(@IdRes int id) {
        return getDecorView().findViewById(id);
    }

而在这里面,又调用了getDecorView的findViewById()方法,这也相当于是一个层层传递的过程,因为DecorView我理解为就是一个ViewGroup,而当运行getDecorView().findViewById()方法时,就会运行View里面的findViewById方法。它会使用这个被给予的id匹配子View的Id,如果匹配,就返回这个View,完成View的绑定

/**
     * Look for a child view with the given id.  If this view has the given
     * id, return this view.
     *
     * @param id The id to search for.
     * @return The view that has the given id in the hierarchy or null
     */
    @Nullable
    public final View findViewById(@IdRes int id) {
        if (id < 0) {
            return null;
        }
        return findViewTraversal(id);
    }

    /**
     * {@hide}
     * @param id the id of the view to be found
     * @return the view of the specified id, null if cannot be found
     */
    protected View findViewTraversal(@IdRes int id) {
        if (id == mID) {
            return this;
        }
        return null;
    }

最后总结一下(Activity中),findViewById的过程是这样的:

Activity -> Window -> DecorView -> View


 
Android技术之家 更多文章 自动展开标题通知栏,兼容各个版本,欢饮大家关注,并参与讨论。 minSdkVersion、targetSdkVersion、targetApiLevel的区别 ndk调用实战 GreenDao数据库操作 大家来找茬 看看下面这个单例模式有什么不妥的
猜您喜欢 树莓派3以35刀开售 成功的秘诀在于长久的坚持和耐心 每周阅读清单:Gon,EventBus,官方MVP 架构,一大波活动 Python基础教程15:列表(Lists) 【重要通知】联盟已于2月25日暂停“邀请技术员”功能