[016]BootAnimation引发的思考

前言

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

bootanimation在哪里

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

对照应用:

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

bootanimation的启动

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

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的时候都清楚,暂时先不用代码细节,我们后面慢慢分析。

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方法,有点长,有兴趣的可以仔细看看

    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方法的代码不多,但是值得思考的问题有很多,以下是我对这边所有代码注解

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的时候已经完成了。

    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绘制的。

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

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方法的绘制

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也支持触摸事件,应用的触摸事件的本质是什么 ?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容