APP 启动原理

作者: gogoingmonkey | 来源:发表于2018-05-07 14:50 被阅读46次

    Android是基于Linux内核的,当手机启动,加载完Linux内核后,会由Linux系统的init祖先进程fork出Zygote进程,所有的Android应用程序进程以及系统服务进程都是这个Zygote的子进程(由它fork出来的)最重要的一个就是SystemServer,在ZygoteInit类的main方法中,会调用startSystemServer方法开启系统里面重要的服务,包括ActivityManagerService(Activity管理器,简称AMS,可以理解为一个服务进程,负责Activity的生命周期管理)、PackageManagerService(包管理器)、WindowManagerService(窗口管理器)、PowerManagerService(电量管理器)等等,这个过程中还会创建系统上下文

    public final class SystemServer {  
      
        //zygote的主入口  
        public static void main(String[] args) {  
            new SystemServer().run();  
        }  
      
        public SystemServer() {  
            // Check for factory test mode.  
            mFactoryTestMode = FactoryTest.getMode();  
        }  
          
        private void run() {  
              
            ...ignore some code...  
              
            //加载本地系统服务库,并进行初始化   
            System.loadLibrary("android_servers");  
            nativeInit();  
              
            // 创建系统上下文  
            createSystemContext();  
              
            //初始化SystemServiceManager对象,下面的系统服务开启都需要调用SystemServiceManager.startService(Class<T>),这个方法通过反射来启动对应的服务  
            mSystemServiceManager = new SystemServiceManager(mSystemContext);  
              
            //开启服务  
            try {  
                startBootstrapServices();  
                startCoreServices();  
                startOtherServices();  
            } catch (Throwable ex) {  
                Slog.e("System", "******************************************");  
                Slog.e("System", "************ Failure starting system services", ex);  
                throw ex;  
            }  
             
            ...ignore some code...  
          
        }  
      
        //初始化系统上下文对象mSystemContext,并设置默认的主题,mSystemContext实际上是一个ContextImpl对象。调用ActivityThread.systemMain()的时候,会调用ActivityThread.attach(true),而在attach()里面,则创建了Application对象,并调用了Application.onCreate()。  
        private void createSystemContext() {  
            ActivityThread activityThread = ActivityThread.systemMain();  
            mSystemContext = activityThread.getSystemContext();  
            mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);  
        }  
      
        //在这里开启了几个核心的服务,因为这些服务之间相互依赖,所以都放在了这个方法里面。  
        private void startBootstrapServices() {  
              
            ...ignore some code...  
              
            //初始化ActivityManagerService  
            mActivityManagerService = mSystemServiceManager.startService(  
                    ActivityManagerService.Lifecycle.class).getService();  
            mActivityManagerService.setSystemServiceManager(mSystemServiceManager);  
              
            //初始化PowerManagerService,因为其他服务需要依赖这个Service,因此需要尽快的初始化  
            mPowerManagerService = mSystemServiceManager.startService(PowerManagerService.class);  
      
            // 现在电源管理已经开启,ActivityManagerService负责电源管理功能  
            mActivityManagerService.initPowerManagement();  
      
            // 初始化DisplayManagerService  
            mDisplayManagerService = mSystemServiceManager.startService(DisplayManagerService.class);  
          
        //初始化PackageManagerService  
        mPackageManagerService = PackageManagerService.main(mSystemContext, mInstaller,  
           mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);  
          
        ...ignore some code...  
          
        }  
      
    }  
    

    注意下:

    private void createSystemContext() {  
           ActivityThread activityThread = ActivityThread.systemMain();  
           mSystemContext = activityThread.getSystemContext();  
                     mSystemContext.setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);  
       } 
    

    这里干了三件事,一个是创建ActivityThread,这个ActivityThread就是我们常说的“主线程”,也就是所谓的UI线程,APP的主入口。ActivityThread随后会创建一个mainLooper来开启消息循环,这也就是为什么在"主线程"中我们使用Handler不需要手动创建Looper的原因。第二件事是获得了系统的上下文,第三件事是设置了默认的主题。如果想要启动新的应用,ActivityManagerService会通过socket进程间通信(IPC)机制来通知Zygote进程fork出新的进程

    点击了一个APP图标

    系统第一个启动的APP是Lancher,也就是我们手机的主界面,继承自Activity,实现了点击事件、触摸、长按等接口,在android源码Lancher.java中,我们可以看到onclick方法

    public void onClick(View v) {  
          
              ...ignore some code...  
                  
             Object tag = v.getTag();  
            if (tag instanceof ShortcutInfo) {  
                // Open shortcut  
                final Intent intent = ((ShortcutInfo) tag).intent;  
                int[] pos = new int[2];  
                v.getLocationOnScreen(pos);  
                intent.setSourceBounds(new Rect(pos[0], pos[1],  
                        pos[0] + v.getWidth(), pos[1] + v.getHeight()));  
            //开始开启Activity  
                boolean success = startActivitySafely(v, intent, tag);  
      
                if (success && v instanceof BubbleTextView) {  
                    mWaitingForResume = (BubbleTextView) v;  
                    mWaitingForResume.setStayPressed(true);  
                }  
            } else if (tag instanceof FolderInfo) {  
                //如果点击的是图标文件夹,就打开文件夹  
                if (v instanceof FolderIcon) {  
                    FolderIcon fi = (FolderIcon) v;  
                    handleFolderClick(fi);  
                }  
            } else if (v == mAllAppsButton) {  
            ...ignore some code...  
            }  
        }  
    

    可以看出我们点击了主界面上的应用图标后调用的就是startActivitySafely这个方法,继续深入进去,然后再往下走,我们发现调用的是startActivity方法,最后会调用Instrumentation.execStartActivity(),Instrumentation这个类就是完成对Application和Activity初始化和生命周期的工具类,然后Instrumentation会通过ActivityManagerService的远程接口向AMS发消息,让他启动一个Activity。 也就是说调用startActivity(Intent)以后, 会通过Binder IPC机制, 最终调用到ActivityManagerService。AMS会通过socket通道传递参数给Zygote进程。Zygote孵化自身, 并调用ZygoteInit.main()方法来实例化ActivityThread对象并最终返回新进程的pid。ActivityThread随后依次调用Looper.prepareLoop()和Looper.loop()来开启消息循环。在ActivityThread会创建并绑定Application,这个时候才会realStartActivity(),并且AMS会将生成的Activity加到ActivityTask的栈顶,并通知ActivityThread暂停当前Activity(暂停Lancher,进入我们自己的APP)。

    一.Launcher接收到Click Event,获取应用信息之后,会通过Binder IPC机制向ActivityManagerService(简称AMS)发起启动应用(startActivity(intent))的请求。此时AMS会做以下三个内部操作:
    1.通过PackageManager的resolveIntent方法收集这个intent对象的指向信息,并将指向信息存储在一个intent对象中;
    2.通过grantUriPermissionLocked方法来验证用户是否有足够的权限去调用该intent对象指向的Activity
    3.如果有权限,AMS会检查并在新的task中启动目标Activity
    二、待AMS做完上述三个内部操作外,它会检查这个进程的ProcessRecord是否存在。如果ProcessRecord是null,启动进程会调用AMS的startProcessLocked方法,内部调用Process的start方法,通过socket通道传递参数给Zygote进程
    三、之后Zygote进程会通过孵化自身的方式去fork一个新的Linux进程(此处特指UI进程),并调用ZygoteInit.main()方法来实例化ActivityThread对象并最终返回新进程的pid
    四、进程创建之后会加载ActivityThread,执行ActivityThread的main方法。然后再main方法中会实例化ActivityThread,同时创建ApplicationThread,Looper,Hander对象,调用attach方法进行Binder通信。随后依次调用Looper.prepareLoop()和Looper.loop()来开启消息循环
    五、接下来要做的就是将进程和指定的Application绑定起来。 这个是通过上一步的ActivityThread对象中调用bindApplication()方法完成的,该方法发送一个BIND_APPLICATION的消息到消息队列中,最终通过handleBindApplication()方法处理该消息,然后调用makeApplication()方法来加载App的classes到内存中
    六、统已经拥有了该application的进程. 后面的调用顺序就是普通的从一个已经存在的进程中启动一个新进程的activity了。实际调用方法是realStartActivity(),它会调用application线程对象中的sheduleLaunchActivity()方法发送一个LAUNCH_ACTIVITY消息到消息队列中,通过 handleLaunchActivity()来处理该消息
    

    Application在何处初始化

    在ActivityThread.main()中,有一句话是thread.attach(false),在这个attach方法中,有一句比较重要的地方

    mgr.attachApplication(mAppThread)  
    

    这个就会通过Binder调用到AMS里面对应的方法,继续研究源码,在handleBindApplication中,完成了Application的创建
    在makeApplication方法中,最后调用的是instrumentation.callApplicationOnCreate(app);
    这个方法里面的onCreate就是调用了我们Application的OnCreate方法

    public Application makeApplication(boolean forceDefaultAppClass,  
                Instrumentation instrumentation) {  
            if (mApplication != null) {  
                return mApplication;  
            }  
      
            Application app = null;  
      
            String appClass = mApplicationInfo.className;  
            if (forceDefaultAppClass || (appClass == null)) {  
                appClass = "android.app.Application";  
            }  
      
            try {  
                java.lang.ClassLoader cl = getClassLoader();  
                if (!mPackageName.equals("android")) {  
                    initializeJavaContextClassLoader();  
                }  
                ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);  
                app = mActivityThread.mInstrumentation.newApplication(  
                        cl, appClass, appContext);  
                appContext.setOuterContext(app);  
            } catch (Exception e) {        }  
            mActivityThread.mAllApplications.add(app);  
            mApplication = app;  
      
        //传进来的是null,所以这里不会执行,onCreate在上一层执行  
            if (instrumentation != null) {  
                try {  
                    instrumentation.callApplicationOnCreate(app);  
                } catch (Exception e) {  
                     
                }  
            }  
            ...ignore some code...   
                    
           }  
      
            return app;  
        }  
    
    
    image.png

    什么地方我们可以进行优化

    首先需要明确的是,在Application的onCreate之前,这个时间都是无法进行优化的,因为这部分是系统替我们完成的,开发者无能为力。所以我们能做的,就是从Application的onCreate方法开始,到lauyout_main.xml第一次布局绘制完成之前,来进行启动时间的优化。

    reportFullyDrawn

    系统日志中的Display Time只是布局的显示时间,并不包括数据的加载,因为很多App在加载时会使用懒加载模式,即数据拉取后,再刷新默认的UI。所以,系统给我们定义了一个类似的『自定义上报时间』——reportFullyDrawn


    image.png

    其中Displayed Time代表的是UI显示的时机,reportFullyDrawn()代表的是数据加载完之后的时机.显示UI之前必定要执行Application的onCreate操作和第一个Activity的onCreate操作,reportFullyDrawn是由我们自己调用的,一般在数据全部加载完毕后,手动调用 ,(API 19+)等这两个方法执行完之后才能够看到UI。而想要看到有网络数据的UI还需要一些时间(网络请求、UI绘制等都需要时间)。所以,在应用自定义的Application类和 第一个Activity类中,onCreate()方法做的事情越多,启动消耗的时间越长.

    Application的attachBaseContext方法,它执行于onCreate方法之前,如果方法数超过65536的话,我们一般会在该方法中进行dex分包操作,但其实这种操作会消耗很多的时间

    为什么启动时会出现短暂黑屏或白屏的现象

    系统进程在创建Application的过程中会产生一个BackgroudWindow,等到App完成了第一次绘制,系统进程才会用MainActivity的界面替换掉原来的BackgroudWindow

    也就是说当用户点击你的app那一刻到系统调用Activity.onCreate()之间的这个时间段内,WindowManager会先加载app主题样式中的windowBackground做为app的预览元素,然后再真正去加载activity的layout布局。
    很显然,如果你的application或activity启动的过程太慢,导致系统的BackgroundWindow没有及时被替换,就会出现启动时白屏或黑屏的情况(取决于你的主题是Dark还是Light)

    延迟初始化

    延迟初始化并不是减少了启动时间,而是让耗时操作让位、让资源给UI绘制,将耗时的操作延迟到UI加载完毕后,所以,这里建议通过mDecoView.post方法,来进行延迟加载,代码如下:

    getWindow().getDecorView().post(new Runnable() {
        @Override public void run() {
            // ......
        }
    });
    

    我们的ContentView就是通过mDecoView.addView加入到根布局的,所以,通过这种方式,可以让延迟加载的内容,在ContentView初始化完毕后,再进行执行,保证了UI绘制的流畅性

    异步初始化

    这个很简单,就是让App在onCreate里面尽可能的少做事情,而利用手机的多核特性,尽可能的利用多线程,例如一些第三方框架的初始化,如果能放线程,就尽量的放入线程中,最简单的,你可以直接new Thread(),当然,你也可以通过公共的线程池来进行异步的初始化工作,这个是最能够压缩启动时间的方式

    后台任务

    这里主要介绍如何通过IntentService类处理异步请求,在IntentService的内部,有一个工作线程来处理耗时操作,启动IntentService的方式和启动传统Service一样,同时,当任务执行完后,IntentService会自动停止,而不需要去手动控制。

    public class InitIntentService extends IntentService {
    
        private static final String ACTION = "com.xys.startperformancedemo.action";
    
        public InitIntentService() {
            super("InitIntentService");
        }
    
        public static void start(Context context) {
            Intent intent = new Intent(context, InitIntentService.class);
            intent.setAction(ACTION);
            context.startService(intent);
        }
    
        @Override
        protected void onHandleIntent(Intent intent) {
            SystemClock.sleep(2000);
            Log.d(TAG, "onHandleIntent: ");
        }
    }
    

    我们将耗时任务丢到IntentService中去处理,系统会自动开启线程去处理,同时,在任务结束后,还能自己结束Service,多么的人性化!OK,只需要在Application或者Activity的onCreate中去启动这个IntentService即可:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        InitIntentService.start(this);
    }
    

    最后不要忘记在AndroidManifest.xml中注册Service。

    相关文章

      网友评论

        本文标题:APP 启动原理

        本文链接:https://www.haomeiwen.com/subject/qjpmrftx.html