前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[016]BootAnimation引发的思考

[016]BootAnimation引发的思考

作者头像
王小二
发布2020-06-08 10:47:33
8341
发布2020-06-08 10:47:33
举报
文章被收录于专栏:王小二的Android站

前言

BootAnimation就是安卓系统的开机动画,估计网上面对BootAnimation的源码解读已经一大堆了,但是我想借BootAnimation分析以及和应用的对比来让读者好好理解一个应用的本质。

bootanimation在哪里

bootanimation的源码在frameworks/base/cmd/bootanimation目录下,是c++写的,最后编译成一个可执行程序bootanimation是在手机system/bin目录下。

对照应用:

我们编译出来的是一个APK的压缩包,一般是dex加一些资源,放在我们手机system目录或者data目录。

bootanimation的启动

bootanimation会在android开机启动的时候执行init.rc然后执行以下指令,然后就会找到bootanimation的可执行程序并运行。

代码语言:javascript
复制
service bootanim /system/bin/bootanimation
    class core animation
    user graphics
    group graphics audio
    disabled
    oneshot
    writepid /dev/stune/top-app/tasks

然后就会运行bootanimation_main.cpp中main方法,这是应该大家在刚开始学c语言或者java语言的hello world的时候都清楚,暂时先不用代码细节,我们后面慢慢分析。

代码语言:javascript
复制
int main()
{
    setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);

    bool noBootAnimation = bootAnimationDisabled();
    ALOGI_IF(noBootAnimation,  "boot animation disabled");
    if (!noBootAnimation) {

        sp<ProcessState> proc(ProcessState::self());
        ProcessState::self()->startThreadPool();

        waitForSurfaceFlinger();

        // create the boot animation object
        sp<BootAnimation> boot = new BootAnimation(new AudioAnimationCallbacks());
        ALOGV("Boot animation set up. Joining pool.");

        IPCThreadState::self()->joinThreadPool();
    }
    ALOGV("Boot animation exit");
    return 0;
}
对照应用:

其实一个应用的启动过程和上述有点类似,说白了就是Zygote进程加载应用的dex文件,然后执行ActivityThread.java的中main方法,有点长,有兴趣的可以仔细看看

代码语言:javascript
复制
    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

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

        Looper.prepareMainLooper();

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

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

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

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

bootanimation的main方法

虽然main方法的代码不多,但是值得思考的问题有很多,以下是我对这边所有代码注解

代码语言:javascript
复制
int main()
{
    //设置进程的优先级
    setpriority(PRIO_PROCESS, 0, ANDROID_PRIORITY_DISPLAY);
    //判断是否disable BootAnimation
    bool noBootAnimation = bootAnimationDisabled();
    ALOGI_IF(noBootAnimation,  "boot animation disabled");
    if (!noBootAnimation) {
        //初始化Binder服务
        sp<ProcessState> proc(ProcessState::self());
        //启动Binder线程池
        ProcessState::self()->startThreadPool();
        //等待SurfaceFlinger服务结束
        waitForSurfaceFlinger();
        //创建开机动画的对象,其实BootAnimation这个对象会另外运行一个线程
        // create the boot animation object
        sp<BootAnimation> boot = new BootAnimation(new AudioAnimationCallbacks());
        ALOGV("Boot animation set up. Joining pool.");
        //将当前线程加入Binder线程池避免退出。
        IPCThreadState::self()->joinThreadPool();
    }
    ALOGV("Boot animation exit");
    return 0;
}
细节1:

sp<ProcessState> proc(ProcessState::self()); ProcessState::self()->startThreadPool(); 这两行代码,会初始化bootanimation的Binder服务,为什么要Binder服务,因为开机动画需要SurfaceFlinger的支持,SurfaceFlinger就是一个Binder服务。

对照应用:

应用除了需要SurfaceFlinger,还需要AMS,PMS,WMS等大量的Binder服务,所以也需要初始化Binder服务,但是我们发现ActivityThread.java的main方法中并没有Binder服务的初始化,其实应用的Binder服务的初始化在onZygoteInit的时候已经完成了。

代码语言:javascript
复制
    virtual void onZygoteInit()
    {
        sp<ProcessState> proc = ProcessState::self();
        ALOGV("App process: starting thread pool.\n");
        proc->startThreadPool();
    }
细节2:

大家会发现bootanimation的最后一步是IPCThreadState::self()->joinThreadPool(),这个的意思是将当前的线程也就是main方法所在的线程加入Binder的线程池,并block住。为什么要这样子做,这样子是保证当前进程不退出。

对照应用:

其实在ActivityThread.java的main方法中也有类似的操作就是Looper.loop(),也是为了避免主线程也就是UI线程退出。

小结:

其实开机动画也好,我们自己写的应用也好,本质上主线程就是一个永远没有返回或者结束的main方法。

bootanimation的界面绘制

先看如下代码,简单总结一下就是通过Binder调用从SurfaceFlinger获取了一块Surface,并将Surface和OpenGL绘制引擎进行绑定,因为开机动画是通过OpenGL绘制的。

代码语言:javascript
复制
status_t BootAnimation::readyToRun() {
    mAssets.addDefaultAssets();

   //获得屏幕
    sp<IBinder> dtoken(SurfaceComposerClient::getBuiltInDisplay(
            ISurfaceComposer::eDisplayIdMain));
    //获得屏幕信息
    DisplayInfo dinfo;
    status_t status = SurfaceComposerClient::getDisplayInfo(dtoken, &dinfo);
    if (status)
        return -1;
    //创建SurfaceControl
    // create the native surface
    sp<SurfaceControl> control = session()->createSurface(String8("BootAnimation"),
            dinfo.w, dinfo.h, PIXEL_FORMAT_RGB_565);
    //设置Layer
    SurfaceComposerClient::Transaction t;
    t.setLayer(control, 0x40000000)
        .apply();
    //获得对应的Surface
    sp<Surface> s = control->getSurface();

    //初始化opengl egl
    // initialize opengl and egl
    const EGLint attribs[] = {
            EGL_RED_SIZE,   8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE,  8,
            EGL_DEPTH_SIZE, 0,
            EGL_NONE
    };
    EGLint w, h;
    EGLint numConfigs;
    EGLConfig config;
    EGLSurface surface;
    EGLContext context;
   //获得一块屏幕
    EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
    //0,0就是代表NULL,正常情况下这个两个是返回,最大,最小的版本
    eglInitialize(display, 0, 0);
    //选择我们自己需要的配置
    eglChooseConfig(display, attribs, &config, 1, &numConfigs);
    //创建一个Window,注意这个时间创建一个传入了一个EGLNativeWindowType,建立起OPENGL和Surface之间的关系
    surface = eglCreateWindowSurface(display, config, s.get(), NULL);
    //创建统一的上下文
    context = eglCreateContext(display, config, NULL, NULL);
    //获取surface的宽度
    eglQuerySurface(display, surface, EGL_WIDTH, &w);
    //获取surface的高度
    eglQuerySurface(display, surface, EGL_HEIGHT, &h);
   //将这个上下文和当前创建的屏幕和Surface绑定
    if (eglMakeCurrent(display, surface, surface, context) == EGL_FALSE)
        return NO_INIT;
    ......省略部分代码
    return NO_ERROR;
}
对照应用:

一般应用都是基于Activity开发的,其实Activity就是帮你隐藏了太多像SurfaceFlinger申请Surface,并在Surface使用Canvas进行绘制。

申请Surface /frameworks/base/core/java/android/view/ViewRootImpl.java

代码语言:javascript
复制
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {

        ...省略部分代码
       //mSurface需要通过relayout方法,才能变成可用的Surface
        int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
                mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
                mPendingMergedConfiguration, mSurface);

        return relayoutResult;
    }

通过Surface获得Canvas对象然后传递给view进行ondraw方法的绘制

代码语言:javascript
复制
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {

        // Draw with software renderer.
        final Canvas canvas;
        ....省略代码
        // 通过前面申请的Surface锁定以后,获得Canvas对象
        canvas = mSurface.lockCanvas(dirty);
        ....省略代码
       // 将Canvas对象传递给View进行draw的方法的回调,也就是当前的activity开始界面绘制
        mView.draw(canvas);
        ....省略代码
        return true;
    }

总结

对BootAnimation和应用进行比较,我更多的希望读者可以透过现象看本质,其实开机动画和应用非常类似。只不过应用的启动需要Zygote进程,AMS,PMS,WMS的支持,分装了好多对Binder初始化,Surface的申请,绘制,而开机动画启动比较快,这个时候Zygote进程,AMS,PMS,WMS都还没有准备好,只能依靠SurfaceFlinger和Binder的最原始的接口来显示UI。

进一步思考

BootAnimation不支持触摸事件,应用支持触摸事件,我们能否让BootAnimation也支持触摸事件,应用的触摸事件的本质是什么 ?

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • bootanimation在哪里
  • bootanimation的启动
  • bootanimation的main方法
  • bootanimation的界面绘制
  • 总结
  • 进一步思考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档