Android View绘制系统及硬件加速

·  阅读 128

       我们从ActivityThread如何创建Activity实例并逐一调用其生命周期回调函数开始分析View的绘制系统。

Activity的启动步骤

 1. 点击桌面App图标,Launcher进程采用Binder IPC向system_server进程发起startActivity请求; 

 2. system_server进程接收到请求后,向zygote进程发送创建进程的请求; 

 3. Zygote进程fork出新的子进程,即App进程;

 4. App进程,通过Binder IPC向sytem_server进程发起attachApplication请求; 

 5. system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送scheduleLaunchActivity请求;

 6. App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息; 

ActivityThread.scheduleLaunchActivity()

        // we use token to identify this activity without having to send the
        // activity itself back to the activity manager. (matters more with ipc)
        @Override
        public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                int procState, Bundle state, PersistableBundle persistentState,
                List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
            updateProcessState(procState, false);
 
            ActivityClientRecord r = new ActivityClientRecord();
....
            r.token = token;
            r.ident = ident;
            r.intent = intent;
...
            r.overrideConfig = overrideConfig;
            updatePendingConfiguration(curConfig);
 
            sendMessage(H.LAUNCH_ACTIVITY, r);
}
复制代码

sendMessage,最后调用到mH.sendMessage(msg);
mH为H类的一个实例,H就是Handler的一个子类,发送消息之后,查看H类的handleMessage()方法

                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;
复制代码

 继续执行ActivityThread.handleLaunchActivity

    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...
...
       Activity a = performLaunchActivity(r, customIntent);
...
...
       if (a != null) {
            ....
            handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
...
             if (!r.activity.mFinished && r.startsNotResumed) {
 
                performPauseActivityIfNeeded(r, reason);
             }
            .....
       }else{
            // If there was an error, for any reason, tell the activity manager to stop us.
            try {
                ActivityManager.getService()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
 
       }
...
...
}
复制代码

7. 主线程在收到Message后,通过发射机制创建目标Activity,并回调Activity.onCreate()等方法。 

ActivityThread.performLaunchActivity 这里会收集要启动的Activity的相关信息,主要是package和component信息,然后通过ClassLoader将要启动的Activity类加载出来。

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
   ....
....
        ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }
        ComponentName component = r.intent.getComponent();
        if (component == null) {
            component = r.intent.resolveActivity(
                mInitialApplication.getPackageManager());
            r.intent.setComponent(component);
        }
....
        if (r.activityInfo.targetActivity != null) {
            component = new ComponentName(r.activityInfo.packageName,
                    r.activityInfo.targetActivity);
        }
...
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
...
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
...
...
        if (activity != null) {
....
 
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
...
...
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
...
...
                r.activity = activity;
                r.stopped = true;
                if (!r.activity.mFinished) {
                    activity.performStart();
                    r.stopped = false;
                }
...
...
 
 
        }
...
...
    }

    public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
    }
复制代码

 Activity实例从执行attch()函数开始关联到View的绘制系统,先看一下整体框架图:

Activity的attach()方法

   Activity的attach()方法是一个初始化的过程,他在ActivityThread的performLaunchActivity()方法中被调用,主要做了两件事情:

  1. 初始化Window,其中Window的构造方法还做了一些事,比如说,我们比较关心的LayoutInflater的初始化。

  2. 初始化WindowManager,并且set到Window里面去。

    Activity.java private Window mWindow;

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
         ...
         ...
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
          ...
          ...
          ...
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
         ...
    复制代码

    }

Activity的onCreate()方法

Activity的onCreate()中setContentView调用的是getWindow().setContentView()

实际调用的是PhoneWindow.setContentView():

 // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    ViewGroup mContentParent;


    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        //过场动画
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
复制代码

 如果是第一次mContentParent为空为执行installDecor, 其中mDecor如果为空则在generateDecor中创建:

PhoneWindow.java
private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
...
        if (mContentParent == null) {
//返回当前Activity的内容区域视图,即我们的布局文件显示区域mContentParent
//根据窗口的风格修饰,选择对应的修饰布局文件,并且将id为content的FrameLayout赋值给mContentParent
            mContentParent = generateLayout(mDecor);
//初始化一堆属性值
...
...
...
       }
}
复制代码

当 DecorView 创建成功之后,接下来就是通过 generateLayout 创建 mContentParent。(mContentParent 是一个 Viewgroup 对象):

protected ViewGroup generateLayout(DecorView decor) {  
    // Apply data from current theme.  
   //获取window属性
    TypedArray a = getWindowStyle();  
    if (false) {  
        System.out.println("From style:");  
        String s = "Attrs:";  
        for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) {  
            s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "="  
                    + a.getString(i);  
        }  
        System.out.println(s);  
    }  
    //判断窗口是否浮动
    mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);  
    int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)  
            & (~getForcedWindowFlags());  
    if (mIsFloating) {  
        setLayout(WRAP_CONTENT, WRAP_CONTENT);  
        setFlags(0, flagsToUpdate);  
    } else {  
        setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);  
    }  

    //判断窗口是否有标题
    if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {  
        requestFeature(FEATURE_NO_TITLE);  
    }  
    //判断窗口是否满屏
    if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {  
        setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN&(~getForcedWindowFlags()));  
    }  
    //判断窗口是否
    if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {  
        setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));  
    }  
    WindowManager.LayoutParams params = getAttributes();  
//设置输入法模式
    if (!hasSoftInputMode()) {  
        params.softInputMode = a.getInt(  
                com.android.internal.R.styleable.Window_windowSoftInputMode,  
                params.softInputMode);  
    }  
    if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled,  
            mIsFloating)) {  
        /* All dialogs should have the window dimmed */  
        if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {  
            params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;  
        }  
        params.dimAmount = a.getFloat(  
                android.R.styleable.Window_backgroundDimAmount, 0.5f);  
       //动画
    }  
    if (params.windowAnimations == 0) {  
        params.windowAnimations = a.getResourceId(  
                com.android.internal.R.styleable.Window_windowAnimationStyle, 0);  
    }  
    // The rest are only done if this window is not embedded; otherwise,  
    // the values are inherited from our container.  
    if (getContainer() == null) {  
        if (mBackgroundDrawable == null) {  
            if (mBackgroundResource == 0) {  
                mBackgroundResource = a.getResourceId(  
                        com.android.internal.R.styleable.Window_windowBackground, 0);  
            }  
            if (mFrameResource == 0) {  
                mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0);  
            }  
            if (false) {  
                System.out.println("Background: "  
                        + Integer.toHexString(mBackgroundResource) + " Frame: "  
                        + Integer.toHexString(mFrameResource));  
            }  
        }  
        mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000);  
    }  
    **以上都是设置一些window的属性,这里也说明设置window属性要在setContentView()之前设置的原因了。**

    // Inflate the window decor.  

    **//下面是根据各种情况设置layoutResource**

    int layoutResource;  
    int features = getLocalFeatures();  
    // System.out.println("Features: 0x" + Integer.toHexString(features));  
    if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {  
        if (mIsFloating) {  
            layoutResource = com.android.internal.R.layout.dialog_title_icons;  
        } else {  
            layoutResource = com.android.internal.R.layout.screen_title_icons;  
        }  
        // System.out.println("Title Icons!");  
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {  
        // Special case for a window with only a progress bar (and title).  
        // XXX Need to have a no-title version of embedded windows.  
        layoutResource = com.android.internal.R.layout.screen_progress;  
        // System.out.println("Progress!");  
    } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {  
        // Special case for a window with a custom title.  
        // If the window is floating, we need a dialog layout  
        if (mIsFloating) {  
            layoutResource = com.android.internal.R.layout.dialog_custom_title;  
        } else {  
            layoutResource = com.android.internal.R.layout.screen_custom_title;  
        }  
    } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {  
        // If no other features and not embedded, only need a title.  
        // If the window is floating, we need a dialog layout  
        if (mIsFloating) {  
            layoutResource = com.android.internal.R.layout.dialog_title;  
        } else {  
            layoutResource = com.android.internal.R.layout.screen_title;  
        }  
        // System.out.println("Title!");  
    } else {  
        // Embedded, so no decoration is needed.  
        layoutResource = com.android.internal.R.layout.screen_simple;  
        // System.out.println("Simple!");  
    }  
    mDecor.startChanging();  


    View in = mLayoutInflater.inflate(layoutResource, null);  
    //将layoutResource转化成View添加到decor中,
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));  
//然后又通过findViewById找到该view,

    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);  
    if (contentParent == null) {  
        throw new RuntimeException("Window couldn't find content container view");  
    }  
    if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {  
        ProgressBar progress = getCircularProgressBar(false);  
        if (progress != null) {  
            progress.setIndeterminate(true);  
        }  
    }  
    // Remaining setup -- of background and title -- that only applies  
    // to top-level windows.  
    if (getContainer() == null) {  
        Drawable drawable = mBackgroundDrawable;  
        if (mBackgroundResource != 0) {  
            drawable = getContext().getResources().getDrawable(mBackgroundResource);  
        }  
        mDecor.setWindowBackground(drawable);  
        drawable = null;  
        if (mFrameResource != 0) {  
            drawable = getContext().getResources().getDrawable(mFrameResource);  
        }  
        mDecor.setWindowFrame(drawable);  
        // System.out.println("Text=" + Integer.toHexString(mTextColor) +  
        // " Sel=" + Integer.toHexString(mTextSelectedColor) +  
        // " Title=" + Integer.toHexString(mTitleColor));  
        if (mTitleColor == 0) {  
            mTitleColor = mTextColor;  
        }  
        if (mTitle != null) {  
            setTitle(mTitle);  
        }  
        setTitleColor(mTitleColor);  
    }  
    mDecor.finishChanging();  
    //然后返回
    return contentParent;  

} 
复制代码

在 generateLayout 方法里面首先根据应用主题 style 设置一堆值进行设置,我们设置的 android:theme 属性都是在这里的 getWindowStyle 方法中获取的,而我们在代码中通过 requestWindowFeature() 设置的属性是在 getLocalFeature 方法中获取的,这也是为什么 requestWindowFeature() 代码要在 setContentView() 前面执行。 然后根据设定好的 features 值选择不同的窗口修饰布局文件,得到布局文件的 layoutResource 值,LayoutInflater 把布局的资源文件解析成 View 之后,添加到 DecorView 中,这个 View 就是 PhoneWindow 的 mContentRoot 成员变量,而 mContentParent 就是布局文件中 ID 为 @android:id/content 的 FramLayout。 再回到 setContentView 方法中,如果 Window 没有设置 FEATURE_CONTENT_TRANSITIONS 的话,就通过 LayoutInflater 把布局文件加载到 mContentParent 中。

ActivityThread的handleResumeActivity()方法

       Activity 调运 setContentView 方法自身不会显示布局的,需要通过调用 ActivityThread 类的 handleResumeActivity 将它激活。 在 handlerResumeActivity 中调用 Activity 的 makeVisible 方法显示我们上面通过 setContentView 创建的 mDecor 视图族。

    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ActivityClientRecord r = mActivities.get(token);
...
        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason);
...
        if (r != null) {
            final Activity a = r.activity;
...
            // If the window hasn't yet been added to the window manager,
            // and this guy didn't finish itself or start another activity,
            // then go ahead and add the window.
            boolean willBeVisible = !a.mStartedActivity;
            if (!willBeVisible) {
                try {
                    willBeVisible = ActivityManager.getService().willActivityBeVisible(
                            a.getActivityToken());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
//获得ViewManager []
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (r.mPreserveWindow) {
                    a.mWindowAdded = true;
                    r.mPreserveWindow = false;
                    // Normally the ViewRoot sets up callbacks with the Activity
                    // in addView->ViewRootImpl#setView. If we are instead reusing
                    // the decor view we have to notify the view root that the
                    // callbacks may have changed.
                    ViewRootImpl impl = decor.getViewRootImpl();
                    if (impl != null) {
                        impl.notifyChildRebuilt();
                    }
                }
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
 //把decor view对象添加到ViewManager中
                        wm.addView(decor, l);
                    } else {
                        // The activity will get a callback for this {@link LayoutParams} change
                        // earlier. However, at that time the decor will not be set (this is set
                        // in this method), so no action will be taken. This call ensures the
                        // callback occurs with the decor set.
                        a.onWindowAttributesChanged(l);
                    }
                }
...
...
            // The window is now visible if it has been added, we are not
            // simply finishing, and we are not starting another activity.
            if (!r.activity.mFinished && willBeVisible
                    && r.activity.mDecor != null && !r.hideForNow) {
...
                r.activity.mVisibleFromServer = true;
                mNumVisibleActivities++;
                if (r.activity.mVisibleFromClient) {
                    r.activity.makeVisible();
                }
            }
...
...
            // Tell the activity manager we have resumed.
            if (reallyResume) {
                try {
                    ActivityManager.getService().activityResumed(token);
                } catch (RemoteException ex) {
                    throw ex.rethrowFromSystemServer();
                }
            }
...
...
        } else {
            // If an exception was thrown when trying to resume, then
            // just end this activity.
            try {
                ActivityManager.getService()
                    .finishActivity(token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }
复制代码

wm就是windowManagerImpl的实例,通过调用addView()将decor添加到ViewRootImpl中去:

WindowManagerGlobal.java
 
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
//单利模式
private static WindowManagerGlobal sDefaultWindowManager;
    public static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
    }
....
....
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
...
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
....
....
//实例化ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);
 
            view.setLayoutParams(wparams);
//添加view到集合中
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
....
            // do this last because it fires off messages to start doing things
            try {
//将view添加到ViewRootImpl中去
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
....
            }
        }
    }
复制代码

View 的绘制过程是由 ViewRootImpl 来完成的。

Android硬件加速

大概从Android 4.+开始,默认情况下都是支持和开启了硬件加速的,也存在手机支持硬件加速,但是部分API不支持硬件加速的情况,如果使用了这些API,就需要主动关闭硬件加速,或者在View层,或者在Activity层,比如Canvas的clipPath等。硬件加速,直观上说就是依赖GPU实现图形绘制加速,与软件绘制的区别主要是图形的绘制究竟是GPU来处理还是CPU,如果是GPU,就认为是硬件加速绘制,反之,软件绘制。无论是软件绘制还是硬件加速,绘制内存的分配都是类似的,软件绘制需要请求SurfaceFlinger服务分配一块内存,硬件加速有可能从FrameBuffer硬件缓冲区直接分配内存(原理同SurfaceFlinger),两者的绘制都是在APP端,绘制完成之后同样需要通知SurfaceFlinger进行合成。对于Android APP而言,基于GPU的硬件加速绘制可以分为如下几个阶段:

  • 第一阶段:APP在UI线程构建OpenGL渲染需要的命令及数据
  • 第二阶段:CPU将数据上传(共享或者拷贝)给GPU,PC上一般有显存一说,但是ARM这种嵌入式设备内存一般是GPU、CPU共享内存
  • 第三阶段:通知GPU渲染,一般而言,真机不会阻塞等待GPU渲染结束,效率低,CPU通知结束后就返回继续执行其他任务,当然,理论上也可以阻塞执行,glFinish就能满足这样的需求(不同GPU厂商实现不同,Android源码自带的是软件实现的,只具有参考意义)(Fence机制辅助GPU CPU同步)
  • 第四阶段:swapBuffers,并通知SurfaceFlinger图层合成
  • 第五阶段:SurfaceFlinger开始合成图层,如果之前提交的GPU渲染任务没结束,则等待GPU渲染完成,再合成(Fence机制),合成依然是依赖GPU,不过这就是下一个任务了

       

Fence即栅栏,栅栏的角色与它的名字非常类似.一组线程可以使用栅栏来集体进行相互同步;在本质上,每个线程在到达某种周知的状态时调用栅栏的wait()方法,阻塞起来,以等待其它所有参与线程调用wait()方法表明它们也到达了这个状态.一旦所有的线程都到达栅栏,它们就会集体解除阻塞,并一起继续执行;引起程序调用栅栏的wait()方法进行阻塞的那个状态叫做栅栏状态。

       在Android里,GraphicBuffer的同步主要借助Fence同步机制,它最大的特点是能够处理GPU、CPU、HWC间的同步。因为,GPU处理一般是异步的,当我们调用OpenGL API返回后,OpenGL命令并不是即刻被GPU执行的,而是被缓存在本地的GL命令缓冲区中,等缓冲区满的时候,才会真正通知GPU执行,而CPU可能完全不知道执行时机,除非CPU主动使用glFinish()强制刷新,阻塞等待这些命令执行完,但是,毫无疑问,这会使得CPU、GPU并行处理效率降低,至少,渲染线程是被阻塞在那里的;相对而言异步处理的效率要高一些,CPU提交命令后就返回,不等待GPU处理完,这样渲染线程被解放处理下一条消息,不过这个时候图形未被处理完毕的前提的下就被提交给SurfaceFlinger图形合成,那么SurfaceFlinger需要知道什么时候这个GraphicBuffer被GPU处理填充完毕,这个时候就是Fence机制发挥作用的地方。

RenderThread的作用

在Android 5.0之前,Android应用程序的主线程同时也是一个Open GL线程。但是从Android 5.0之后,Android应用程序的Open GL线程就独立出来了,称为Render Thread。

RenderThread 是一个由系统管理的线程较之 UI 线程的延迟, RenderThread 的动画播放更为流畅,当启动硬件加速后, Android 使用 display list 组件进行绘制而非直接使用 CPU 绘制每一帧 ,Display List 是一系列绘制操作的记录, 抽象为 RenderNode 类。

Display List 可以按需多次绘制而无须同业务逻辑交互,特定的绘制操作 (如 translation, scale 等) 可以作用于整个 display list 而无须重新分发绘制操作,当知晓了所有绘制操作后, 可以针对其进行优化: 例如, 所有的文本可以一起进行绘制一次,可以将对 display list 的处理转移至另一个线程(非 UI 线程),这一点恰好是 RenderThread 负责的: 在 UI 线程外执行优化操作与将绘制操作分发给 GPU。

在 Lollipop 之前, 执行重操作的同时对 View 属性执行动画 (例如在 Activity 间执行 transition 动画) 几乎不可能,而 Lollipop 及以上的 Android 版本, 这类动画以及其它一些效果 (例如 ripple ) 却可以流畅的执行这样的黑科技便源自 RenderThread 的帮助。

渲染工作的真正执行者是 GPU, 而 GPU 对于动画一无所知: 执行动画的唯一途径便是将每一帧的不同绘制操作分发给 GPU, 但该逻辑本身不能在 GPU 上执行而如果在 UI 线程执行该操作, 任意的重操作都将阻塞新的绘制指令及时分发, 于是动画便出现了延迟。

如前文所述, RenderThread 可以处理 display list 流程的某些部分, 但要注意到 display list 的创建和修改仍需要在 UI 线程中执行,DrawOp树构建完毕后,UI线程利用RenderProxy向RenderThread线程发送一个DrawFrameTask任务请求,RenderThread被唤醒,开始渲染,大致流程如下:

  • 首先进行DrawOp的合并
  • 接着绘制特殊的Layer
  • 最后绘制其余所有的DrawOpList
  • 调用swapBuffers将前面已经绘制好的图形缓冲区提交给Surface Flinger合成和显示。
分类:
Android
标签: