Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all 5930 articles
Browse latest View live

React Native组件篇(三) — TextInput组件

$
0
0


    TextInput是什么

          文本输入框,相当于iOS中我们熟悉的UITextField,通过键盘输入并显示内容。
          两者属性有很大相同之处,下面大家一起看一下。

    TextInput常见属性

 下面是TextInput常用的属性,大家对于 UITextField都很熟悉了,常用属性就不一一写代码发效果图,自己可以试试。

    

  • value 字符串型
    文本输入的默认值
  • onChangeText 函数
    监听用户输入的值
看下效果:





代码:(生命周期现在还没有说我也是偏面的了解,以后会系统的学习,现在先不介绍)

constructor(props) {
        super(props);
        //设置当前状态是text  初始值为空
        this.state = {text: ''};
    }

  render() {  
      return(  
        <View style={styles.container}> 
          <TextInput style={styles.TextInputStyles} 
              onChangeText={(Text)=>{
                this.setState({text:Text});
              }}
          /> 
          <Text style={{padding:10, fontSize:42}}>
                {this.state.text}
          </Text>
        </View>  
      );  
   }


  • keyboardType 键盘类型决定打开哪种键盘,例如,数字键盘。

    enum('default', "ascii-capable", 'numbers-and-punctuation', 'url', 'number-pad', 'phone-pad', 'name-phone-pad', 'email-address', 'decimal-pad', 'twitter', 'web-search', "numeric")


  • multiline 布尔型
    如果值为真,文本输入可以输入多行。默认值为假。


  • password 布尔型
    如果值为真,文本输入框就成为一个密码区域。默认值为假。


  • placeholder 字符串型
    在文本输入之前字符串将被呈现出来,通常被称为占位文字

  • placeholderTextColor 字符串型
    占位符字符串的文本颜色


  • autoCapitalize enum('none', 'sentences', 'words', 'characters')
    可以通知文本输入自动利用某些字符。

    characters:所有字符,
    words:每一个单词的首字母
    sentences:每个句子的首字母(默认情况下)
    none:不会自动使用任何东西


  • autoCorrect 布尔型
    如果值为假,禁用自动校正。默认值为真。


  • autoFocus 布尔型
    如果值为真,聚焦 componentDidMount 上的文本。默认值为假。


  • bufferDelay 数值型
    这个会帮助避免由于 JS 和原生文本输入之间的竞态条件而丢失字符。默认值应该是没问题的,但是如果你每一个按键都操作的非常缓慢,那么你可能想尝试增加这个。


  • clearButtonMode enum('never', 'while-editing', 'unless-editing', 'always')
    清除按钮出现在文本视图右侧的时机


  • controlled 布尔型
    如果你真想要它表现成一个控制组件,你可以将它的值设置为真,但是按下按键,并且/或者缓慢打字,你可能会看到它闪烁,这取决于你如何处理 onChange 事件。


  • editable 布尔型
    如果值为假,文本是不可编辑的。默认值为真。

  • enablesReturnKeyAutomatically 布尔型
    如果值为真,当没有文本的时候键盘是不能返回键值的,当有文本的时候会自动返回。默认值为假。

  • onBlur 函数
    当文本输入是模糊的,调用回调函数

  • onChange 函数
    当文本输入的文本发生变化时,调用回调函数

  • onFocus 函数
    当输入的文本是聚焦状态时,调用回调函数

  • returnKeyType enum('default', 'go', 'google', 'join', 'next', 'route', 'search', 'send', 'yahoo', 'done', 'emergency-call')
    决定返回键的样式

  • secureTextEntry 布尔型
    如果值为真,文本输入框就会使输入的文本变得模糊,以便于像密码这样敏感的文本保持安全。默认值为假。



    • 授之以鱼不如授之以渔

        组件篇的文章也写了三篇了,大家也知道了学习控件基本上就是学习他的属性及应用,那么我们去哪找控件的属性呢?

       比如今天的TextInput ,我罗列的只是其中一部分,那么我怎么去翻  TextInput的API呢?

       

    import {
       AppRegistry,
      StyleSheet,
      View,
      Text,
      TextInput,
    } from 'react-native';

    大家从这里可以看出来,TextInput在react-native 里面,那我们去找一下,看看可以找到不。


    然后找到了这个文件夹:




    里面那么多文件夹,应该在哪呢,接着往下找,

    最后在这个文件夹里找到了这个文件:





    那么属性在哪呢?我们点开看看,最后我们看到propTypes里有我们罗列的那些属性还有一些我们没有接触过的属性。这时候我们可以测试一下这些属性都是干什么的了。


    不只这一个控件,我们学过的和没有学习的控件都可以在这里找到,大家慢慢的试试新组件吧。




    作者:ZY_FlyWay 发表于2017/8/16 17:20:40 原文链接
    阅读:26 评论:0 查看评论

    【Android View源码分析(一)】setContentView加载视图机制深度分析

    $
    0
    0

    【大圣代的技术专栏 http://blog.csdn.net/qq_23191031 转载烦请注明出处,尊重他人劳动成功就是对您自己的尊重】

    Ps:不喜欢看文字的可以直接到文字尾,看图说话。

    1, 前言

    1. 在前面《【Android 控件架构】详解Android控件架构与常用坐标系》的文章中我们提到了setContentView()方法,当时只是匆匆带过,并没有阐明具体流程。而这篇文章就是从Activity中的setContentView()方法出发结合上篇的视图框架,详细分析setContentView()的工作原理。还是贴一张图复习一下吧。

    1. 从上面的文章中我们知道setContentView()方法是用来设置ContentView布局地,当系统调用了setContentView()方法所有的控件就得到了显示,但是你有想过Android系统是如何让xml文件加载到界面并显示出来的呢?setContentView()中具体是如何实现的呢?就让我们在这些疑问来进入下面的探讨吧。

    2 从setContentView说起(基于Api 25 Android 7.1.1)

    本来是想基于Api 26来看的,可是后来才想起来 Android 8.0的源码还没发布。。。

    # 2-1 Activity源码中的setContentView
    经过阅读Android的源码发现,系统为我们提供了三个setContentView()的重载方法,他们都调用了getWindow()中的setContentView()方法。

    
        public void setContentView(@LayoutRes int layoutResID) {
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
        }
    
        public void setContentView(View view) {
            getWindow().setContentView(view);
            initWindowDecorActionBar();
        }
    
        public void setContentView(View view, ViewGroup.LayoutParams params) {
            getWindow().setContentView(view, params);
            initWindowDecorActionBar();
        }
    

    那么 getWindow()方法有事做什么的呢,咱们继续往下看。

    2-2 关于窗口Window类的一些关系

    getWindow()的作用

        /**
         * Retrieve the current {@link android.view.Window} for the activity.
         * This can be used to directly access parts of the Window API that
         * are not available through Activity/Screen.
         *
         * @return Window The current window, or null if the activity is not
         *         visual.
         */
       // 如果返回为null表示,则表示当前Activity不在窗口上
        public Window getWindow() {
            return mWindow;
        }
    
         ...
    
         mWindow = new PhoneWindow(this, window);

    通过源码我们可以看到getWindow()方法返回的就是PhoneWindow的实例对象(PhoneWindow是抽象类Window的唯一实现类 PhoneWindow在线源码地址

    public class PhoneWindow extends Window implements MenuBuilder.Callback {
    
        private final static String TAG = "PhoneWindow";
    
        ...
    
        // This is the top-level view of the window, containing the window decor.
        private DecorView mDecor;
    
        // 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.
        private ViewGroup mContentParent;
    
        private ViewGroup mContentRoot;
        ...
    }

    而在PhoneWindow中我们看到了作为成员变量的 mDecor,(在Android 7.1.1中DecorView已经不再是PhoneWindow的内部类了,而且包都换了,有图有真相)。

    Android 5.1.1

    Android 7.1.1

    查看DecorView之后发现public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks,看见没有,DecorView才是Activity的根布局(root view),他继承了 FrameLayout负责Activity视图的加载,而DecorView本身则是由PhoneWindow加载的。PhoneWindow是如何加载DecorView的呢,咱们带着问题继续往下看

        public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
        private static final String TAG = "DecorView";
    
        private static final boolean DEBUG_MEASURE = false;
    
        private static final boolean SWEEP_OPEN_MENU = false;
    
        // The height of a window which has focus in DIP.
        private final static int DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP = 20;
        // The height of a window which has not in DIP.
        private final static int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
            ....
     }

    一言不可就上图:

    Android 5.xx时期PhoneWindow与DecorView的关系

    Android 7.1.1时期PhoneWindow与DecorView的关系

    2-3 PhoneWindow中的setContentView方法

    Window类中setContentView方法是抽象的,所以我们直接去看PhonWindow类中关于 setContentView方法的实现过程

    @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) {
                //创建DecorView,并添加到mContentParent上
                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 {
                //将要加载的资源添加到mContentParent上
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                //回调通知表示完成界面加载
                cb.onContentChanged();
            }
        }

    源码中的第一步就是验证mContentParent是否为 null,如果为null则表示程序是第一次运行,执行installDecor。如果不为null则会判断当前是否设置了FEATURE_CONTENT_TRANSITIONS(这个属性表示内容加载时需不需要过场动画,默认为false)。如果没有使用过场动画则移除mContentParent中的所有view(所以说 setContentView方法可以多次调用,因为他会移除掉所有的控件);

    如果在初始化mContentParent之后,用户设置了启用转场动画则使用Scene开启过度,否则mLayoutInflater.inflate(layoutResID, mContentParent);将我们的资源文件通过LayoutInflater对象转化为控件树添加到mContentParent中。

    再来看下PhoneWindow类的setContentView(View view)方法和setContentView(View view, ViewGroup.LayoutParams params)方法源码,如下:

    422    @Override
    423    public void setContentView(View view) {
    424        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    425    }
     @Override
    428    public void setContentView(View view, ViewGroup.LayoutParams params) {
    429        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    430        // decor, when theme attributes and the like are crystalized. Do not check the feature
    431        // before this happens.
    432        if (mContentParent == null) {
    433            installDecor();
    434        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    435            mContentParent.removeAllViews();
    436        }
    437
    438        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    439            view.setLayoutParams(params);
    440            final Scene newScene = new Scene(mContentParent, view);
    441            transitionTo(newScene);
    442        } else {
    443            mContentParent.addView(view, params);
    444        }
    445        mContentParent.requestApplyInsets();
    446        final Callback cb = getCallback();
    447        if (cb != null && !isDestroyed()) {
    448            cb.onContentChanged();
    449        }
    450        mContentParentExplicitlySet = true;
    451    }

    看见没有,我们其实只用分析setContentView(View view, ViewGroup.LayoutParams params)方法即可,如果你在Activity中调运setContentView(View view)方法,实质也是调运setContentView(View view, ViewGroup.LayoutParams params),只是LayoutParams设置为了MATCH_PARENT而已。

    所以直接分析setContentView(View view, ViewGroup.LayoutParams params)方法就行,可以看见该方法与setContentView(int layoutResID)类似,只是少了LayoutInflater将xml文件解析装换为View而已,这里直接使用View的addView方法追加道了当前mContentParent而已。

    2-4 installDecor()方法 源码分析

    2614    private void installDecor() {
    2615        mForceDecorInstall = false;
    2616        if (mDecor == null) {
    2617            mDecor = generateDecor(-1);
    2618            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
    2619            mDecor.setIsRootNamespace(true);
    2620            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
    2621                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
    2622            }
    2623        } else {
    2624            mDecor.setWindow(this);
    2625        }
    2626        if (mContentParent == null) {
                         //根据窗口的风格修饰,选择对应的修饰布局文件,并且将id为content的FrameLayout赋值给mContentParent
    2627            mContentParent = generateLayout(mDecor);
    
                          //......
    
    2674            } else {
    2675                mTitleView = (TextView) findViewById(R.id.title);
    2676                if (mTitleView != null) {
                               //根据FEATURE_NO_TITLE隐藏,或者设置mTitleView的值  
    2677                    if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
    2678                        final View titleContainer = findViewById(R.id.title_container);
    2679                        if (titleContainer != null) {
    2680                            titleContainer.setVisibility(View.GONE);
    2681                        } else {
    2682                            mTitleView.setVisibility(View.GONE);
    2683                        }
    2684                        mContentParent.setForeground(null);
    2685                    } else {
    2686                        mTitleView.setText(mTitle);
    2687                    }
    2688                }
    2689            }
    

    我在源码中发现了一个很重要的东西,请看第2677行!!!,这就在最根本上解释了:为什么要在setContentView()方法之前设置requestWindowFeature(Window.FEATURE_NO_TITLE)才能不显示TitleActionBar部分,达到全屏的效果。

    言归正传,installDecor()方法一进来就判断mDcor是否为空,为空怎么办创建一个喽,咦generateDecor(-1)传一个 -1 是什么鬼???代码规范呢!Google也可以这么写代码么??……咳咳。

    2263    protected DecorView generateDecor(int featureId) {
          //......
    2281        return new DecorView(context, featureId, this, getAttributes());
    2282    }

    ps:怎么又一大堆,看来7.1.1的源码和5.1.1的差异真是不小啊。啥,Androdi5.1.1里面的长啥样?

    protected DecorView generateDecor() {  
            return new DecorView(getContext(), -1);  
        }  

    不看不知道,一看吓一跳。看见没有,一共两行。这里就不展开讨论了…..

    2-5 generateLayout()方法 源码分析

    在源码 2626行,我们看到当 mContentParent == null的时候使用generateLayout(mDecor)方法创建一个mContentParent出来。generateLayout(mDecor)看名字好像倒是像用来设置layout的。

    2284  protected ViewGroup generateLayout(DecorView decor) {
    2285        // Apply data from current theme.
                 //首先通过WindowStyle中设置的各种属性,对Window进行requestFeature或者setFlags  
    2287        TypedArray a = getWindowStyle();
    2288       
                //...
    2299        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
    2300        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
    2301                & (~getForcedWindowFlags());
    2302        if (mIsFloating) {
    2303            setLayout(WRAP_CONTENT, WRAP_CONTENT);
    2304            setFlags(0, flagsToUpdate);
    2305        } else {
    2306            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
    2307        }
    2309        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
    2310            requestFeature(FEATURE_NO_TITLE);
    2311        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
    2312            // Don't allow an action bar if there is no title.
    2313            requestFeature(FEATURE_ACTION_BAR);
    2314        }
    
                //....
    
                //...根据当前sdk的版本确定是否需要menukey  
    2413        WindowManager.LayoutParams params = getAttributes();
    
    2491        // Inflate the window decor.
    2492
    2493        int layoutResource;
    2494        int features = getLocalFeatures();
    
                //......
                //根据设定好的features值选择不同的窗口修饰布局文件,得到layoutResource值
    
                //把选中的窗口修饰布局文件添加到DecorView对象里,并且指定contentParent值 
    2495        // System.out.println("Features: 0x" + Integer.toHexString(features));
    2496        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
    2497            layoutResource = R.layout.screen_swipe_dismiss;
    2498        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
    2499            if (mIsFloating) {
    2500                TypedValue res = new TypedValue();
    2501                getContext().getTheme().resolveAttribute(
    2502                        R.attr.dialogTitleIconsDecorLayout, res, true);
    2503                layoutResource = res.resourceId;
    2504            } else {
    2505                layoutResource = R.layout.screen_title_icons;
    2506            }
    2507            // XXX Remove this once action bar supports these features.
    2508            removeFeature(FEATURE_ACTION_BAR);
    2509            // System.out.println("Title Icons!");
    2510        } else if {
                    //......
    2552
    2553        mDecor.startChanging(); //通知 开始改变
    2554        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    2555
    2556        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    
                //......
    
    2604        mDecor.finishChanging();//通知 改变完成
    2605
    2606        return contentParent;
    2607    }
        }

    从整体角度来讲这个方法就是根据用户设置的风格、标签为窗口选择不同的主布局文件,DecorView做为根视图将该窗口根布局添加进去,然后获取id为content的FrameLayout返回给mContentParent对象。所以installDecor方法实质就是产生mDecor和mContentParent对象。 哎!我怎么没看见DecorView添加布局的代码呢?别急下边就告诉你怎么回事。

    在进入这个方法时,系统就会调用getWindowStyle() 在当前的Window的theme中获取我们的Window属性,对我们的Window设置各种requestFeature,setFlags等等。

    getWindowStyle()为抽象类Window提供的方法,具体源码如下:

    665    public final TypedArray getWindowStyle() {
    666        synchronized (this) {
    667            if (mWindowStyle == null) {
    668                mWindowStyle = mContext.obtainStyledAttributes(
    669                        com.android.internal.R.styleable.Window);
    670            }
    671            return mWindowStyle;
    672        }
    673    }

    我们顺藤摸瓜找到属性位置 源码地址

    <!-- The set of attributes that describe a Windows's theme. -->  
       <declare-styleable name="Window">  
           <attr name="windowBackground" />  
           <attr name="windowContentOverlay" />  
           <attr name="windowFrame" />  
           <attr name="windowNoTitle" />  
           <attr name="windowFullscreen" />  
           <attr name="windowOverscan" />  
           <attr name="windowIsFloating" />  
           <attr name="windowIsTranslucent" />  
           <attr name="windowShowWallpaper" />  
           <attr name="windowAnimationStyle" />  
           <attr name="windowSoftInputMode" />  
           <attr name="windowDisablePreview" />  
           <attr name="windowNoDisplay" />  
           <attr name="textColor" />  
           <attr name="backgroundDimEnabled" />  
           <attr name="backgroundDimAmount" />  

    所以这里就是解析我们为Activit设置theme的地方,至于theme一般可以在AndroidManifest.xml文件中设置。

    设置theme的位置

    而AppTheme则在 res/value/style.xml文件里

    接下来就到关键的部分了,2494-2510行:通过对features和mIsFloating的判断,获取不同的主布局文件为layoutResource进行赋值,值可以为R.layout.screen_custom_title;R.layout.screen_action_bar;等等。

    经过上面的源码我们可以看到设置features,除了theme中设置的,我们还可以在代码中进行:

    //通过java文件设置:
    
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    
    //通过xml文件设置:
    
    android:theme="@android:style/Theme.NoTitleBar"

    其实我们平时requestWindowFeature()设置的features值就是在这里通过getLocalFeature()获取的;而android:theme属性也是通过这里的getWindowStyle()获取的。两方式具体流程不同,但是效果是一样的。

    所以这下你应该就明白在java文件设置Activity的属性时必须在setContentView方法之前调用requestFeature()方法的原因了吧。

    我靠,我还是没看见DecorView添加布局的代码啊 ,这就来:

    源码 2554行,进行了如下操作:

    2554        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    看名字是在进行资源文件的加载,具体是怎么操作的呢:

    1801    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
             //......
    1813        final View root = inflater.inflate(layoutResource, null);
            //......
    1824            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            //......
    1826        mContentRoot = (ViewGroup) root;
            //......
    1828    }

    在源码1824行,系统将 layoutResource 所代表的主布局文件。添加到 DecorView 中,而在源码中第 2556行我们可以看到,系统又在DecorView中需找一个ID_ANDROID_CONTENT布局赋值给contentParent

     ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

    ID_ANDROID_CONTENT又是个什么东西呢?我在Windows抽象类中找到了它的源码。注释说的很明确,每一个主布局都拥有id为content的控件。通过mContentRoot = (ViewGroup) root;我们可以清楚的知道,layoutResource既为整个窗口的根布局。

    /**
     * The ID that the main layout in the XML layout file should have.
     */
      public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

    随手贴几个布局文件加以证明:
    R.layout.screen_simple:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true"
        android:orientation="vertical">
    
        <ViewStub android:id="@+id/action_mode_bar_stub"
                  android:inflatedId="@+id/action_mode_bar"
                  android:layout="@layout/action_mode_bar"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:theme="?attr/actionBarTheme" />
    
         <FrameLayout
             android:id="@android:id/content"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:foregroundInsidePadding="false"
             android:foregroundGravity="fill_horizontal|top"
             android:foreground="?android:attr/windowContentOverlay" />
    </LinearLayout>

    R.layout.screen_simple_overlay_action_mode

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fitsSystemWindows="true">
    
        <FrameLayout
             android:id="@android:id/content"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:foregroundInsidePadding="false"
             android:foregroundGravity="fill_horizontal|top"
             android:foreground="?android:attr/windowContentOverlay" />
    
        <ViewStub android:id="@+id/action_mode_bar_stub"
                  android:inflatedId="@+id/action_mode_bar"
                  android:layout="@layout/action_mode_bar"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:theme="?attr/actionBarTheme" />
    </FrameLayout>

    同样在Windows抽象类中找到了findViewByID方法的源码,findViewByID的作用就是将在DecoreView中需找 idcontentFragmentLayout赋值给 contentParent

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

    最后generateLayout()的最后系统还会调用Callback接口的成员函数onContentChanged来通知对应的Activity组件视图内容发生了变化。至此Android setContentView()方法分析完成。

    3,总结

    图片被缩小了不清楚,不要紧。请右键 - 在新标签中打开图片。

    一张图片解决问题

    由此就组成了我们在《【Android 控件架构】详解Android控件架构与常用坐标系》一篇中提到的视图框架(图中contentView就是源码中的contentParent)

    4,参考:

    如果说我比别人看得更远些,那是因为我站在了巨人的肩上
    1. 在线源码地址
    1. Android应用setContentView与LayoutInflater加载解析机制源码分析
    2. Android 源码解析 之 setContentView
    3. Android UI 窗口体系 —— 源码阅读

    作者:qq_23191031 发表于2017/8/14 23:42:47 原文链接
    阅读:297 评论:0 查看评论

    如果不从事编程,我可以做什么?

    $
    0
    0

    加油

    在校期间

    踏入工作的不到五年时间里,曾多次问自己,如果有一天我不做开发了,我能做什么?相信这个问题,一直困扰着很多人。

    一入码海深似海,从此天涯是路人。想起在校期间,一次和学委出去吃饭,她问了我一句,如果毕业后你不做软件,你会去做什么?当时不假思索的回答了“写作呀”!她好像在怀疑自己的耳朵,“你是说写作吗”?“对呀”!在中学期间就喜欢写东西,那时候最喜欢的作家就是沈从文,最爱的小说是他的《边城》,大学期间,总想写一部《边城》一样的世外桃源,每次写出来的却是巴金的口吻(巴金也是我很喜欢的作家之一),描述另一个吃人的礼教,人与人的冷漠和趋炎附势。在那几年里,我留下了四十万左右的悲情文字,现在依然躺在老家的一个抽屉,有时候想想,还是觉得挺好玩的。

    如果简书能早两年出现,可能真的会改变我的一生,或许我真的不会走coding的道路,在去年开始用简书后,越用越喜欢,这么纯净的平台,不就是我在校期间一直寻找的嘛!为何这么晚才出现在我的世界里?最初的两年里,我并不是很喜欢编程,尤其在被代码虐的遍体鳞伤之后,每每听着水木年华的歌,随心敲着笔记本的键盘,拼凑出自己喜欢的语句,才能从中找到一点点快乐。这几年的时间,更多的花费在了代码提高上,很少再去写一些自己喜欢的东西了。

    学如逆水行舟,不进则退,如今组织语言,有时候都觉得很乏力,到底是我们真的老了,还是江郎才尽?

    病急乱投医

    在离校后出来找工作的前夕,妈妈还在找我谈话,她想我留在老家,让一个阿姨带我做生意,我能明白妈妈的良苦用心,她想把子女留在身边,不想在外受苦受累,可那一刻我想到了一件事,在我去上学的时候,老家就有人说闲话,说我学习不好,还一直想上学,拖累家里,后来妈妈和我说,只要我想上学,上到什么时候都可以,家里供得起,自己的儿女上学不要别人管。

    从小到大,我什么都好就是学习不好,学习不好的我却一直喜欢上学,直到毕业这么多年了,依然喜欢学校的氛围,如果当时选择留在家里了,我不知道有些人会怎么在背后评论我,更不知道会怎么说妈妈当初的判断,最后的最后,我还是决定出来,就算饿的只剩一口气,也坚决不回去。

    出来很简单,出来又能做什么呢?在病急乱投医期间,我被黑中介坑过,四年多了,我也没跟别人提到过,一直认为自己是天才的我居然被黑中介坑了,内心难以接受,只怪当初自己太年轻,在交了800元中介费后,我被忽悠到了一个吴江的工厂,进去的时候,看到一大群人排着队等着体检,然后入职,天哪!难道这就是我的未来吗?这就是我将来要走的路吗?

    交了800元的学费,学会了一些基本的常识和自我保护,庆幸的是自己没有被骗进传销,天无绝人之路,可是我的路又在哪里?我想不到什么路适合自己,既然没有太多选择,那不能荒废大学几年学的东西呀(虽然那几年什么都没学会,~~(>_<)~~),在我决定从事软件的时候,我非常没有底气,什么都不会的我能找到工作吗?

    踏入编程

    从离校开始算起到我第一份工作,这中间有半年的时间差,这半年算是被一家培训机构骗去了,我从不承认我培训过,最直接的原因是我从那边走了之后,我们班一大半的人都不能解释封装、继承、多态,更不说那些类与对象了,结业的时候,也好像只有少数几个人交了学费,在我入职第一家公司后,经历了比被黑中介骗还要难受的一件事,我还没入职,一个处了很多年的哥们就和我说,java都不会,还去做Android,一句话说的我泪水在眼中打转,最怕身边的人质疑我,我也曾和他们说过,不管以后我做什么,只要不是错的太明显,支持就好。可那一刻我等到的却是一句嫌弃,那一刻,我也相信万箭穿心的存在。。。

    那一天,我千万次的问自己,我真的适合做编程吗?不做编程,我又能做什么?离校半年了,到现在还不就是为了编程这份工作吗?就这么放弃了,真的不会后悔吗?中学时代的自信哪去了?

    编程,说白了,不就是26个字母,算上大小写也不就是52个字母嘛,再加上十个阿拉伯数字,再加上几个特殊符号,不就这么点东西嘛,这么天才的我怎么可能会放弃。。。

    虽然这么想,但这是在安慰自己,心里一点底都没有,没办法,慢慢学喽!在最初工作的两年里,那期间经常想,如果不做软件,我到底能做什么?这期间有人喊我一起做淘宝,有人喊我一起搞养殖,总觉得这些不太适合我,代码写多了,偶尔做出点自己满意的东西,那种由内向外散发出的喜悦,让我渐渐的喜欢上了编程,对于最近这几年,没太多的想法,除了好好工作外,主要想做好一款属于自己的APP,最好能是一个纯洁的平台。

    近期和未来

    自从来上海后,一方面是为了锻炼自己,另一方面是想吃些干净的食物,便开始学着下厨,最开心的是上次回家教姐姐和嫂子做油焖大虾,她们也都吃的很开心,但我知道我不会去做厨师,爱好和职业是两码事,我不喜欢天天围着厨房转的日子,还不如写代码来的开心。

    如果有一天不做编程了,我能做什么?我真不知道,至少现在我没想到一个比较适合我的,或许,我会去开一家餐饮店,把自己喜欢做的菜做好,自己只做一、两道菜,其它的菜由厨师搞定,不定期跟着学几道菜,就像刚刚说的那样,我才不愿意天天围着厨房转,但我喜欢各种美味,喜欢各种小吃,喜欢做饭给身边的人吃。虽然是个胖子,但是对于吃,一直都是来者不拒,我也始终相信,体重,不会影响我飞的高度。

    如果有一天不做编程了,我能做什么?或许,我会去开一家类似猫的天空之城一样的书店,天天在书香中度过,谈笑有鸿儒,往来无白丁,一定是很美好的生活,在浩瀚的书海中寻找最美的风景,用笔勾勒出最美的篇章,抛开世人的目光,在文字的天空尽情的翱翔,此处有神仙眷侣,此处有美酒佳肴,此处有镇守边疆的战士,此处还有诗和远方,想想都开心,哈哈^_^。

    一个喜欢看书的吃货,一名喜欢打游戏的开发,如鱼饮水,冷暖自知,对于现在的生活,慢慢的习惯了,说不上来好还是不好,但是短期内不想打破现状,最近只想多学点东西,做点自己觉得有意义的事情,一点点完成人生的四个目标。

    一天的最后,祝自己生日快乐,感谢爸爸妈妈赐予我生命,亲爱的自己,你也辛苦了,(^__^) 嘻嘻……

    微信扫我^_^

    这里写图片描述

    作者:pangpang123654 发表于2017/8/17 0:16:33 原文链接
    阅读:116 评论:5 查看评论

    内核源码阅读(五)进程ID

    $
    0
    0

    接着上一节我们继续学习进程ID。
    在上一节中我们提到了node是一个散列表元素,对于这个散列表并未做过多解释,在这里我们给出更加详细的描述。

    这个散列表是为了在给定的命名空间中查找对应与指定PID数值的pid数组的pid结构实例。
    static struct hlist_head *pid_hash;
    上面的hlist_head是一个内核的标准数据结构,用于建立双向散列表。
    pid_hash是一个hlist_head数组,全局pid哈希表,桶数目介于16-4096之间,由系统可用内存决定 ,pidhash_init()用于计算并分配合适的所需内存。

    假如我们已经分配了一个新pid实例,并设置ID类型,可使用下面的函数将其和进程关联起来。

    int fastcall attach_pid(struct task_struct *task, enum pid_type type,
            struct pid *pid)
    {
        struct pid_link *link;
    
        /* 建立task_struct与pid的关联 */
        link = &task->pids[type];
        link->pid = pid;
        /* 建立pid与task_struct的关联 */
        hlist_add_head_rcu(&link->node, &pid->tasks[type]);
    
        return 0;
    }

    下面我们将关注如何通过上节的数据结构来获取局部的id,如task_pid,task_tgid等,以及命名空间局部数字ID和task_struct的相互转换过程。

    struct task_struct *find_task_by_pid_type_ns(int type, int nr,
            struct pid_namespace *ns)
    {
        return pid_task(find_pid_ns(nr, ns), type);
    }
    
    EXPORT_SYMBOL(find_task_by_pid_type_ns);
    
    /**
     * 通过全局pid查找任务
     */
    struct task_struct *find_task_by_pid(pid_t nr)
    {
        return find_task_by_pid_type_ns(PIDTYPE_PID, nr, &init_pid_ns);
    }
    EXPORT_SYMBOL(find_task_by_pid);
    
    /**
     * 在当前进程的命名空间中,查找特定进程号的进程
     */
    struct task_struct *find_task_by_vpid(pid_t vnr)
    {
        return find_task_by_pid_type_ns(PIDTYPE_PID, vnr,
                current->nsproxy->pid_ns);
    }
    EXPORT_SYMBOL(find_task_by_vpid);
    
    /**
     * 根据id在命名空间中查找进程
     */
    struct task_struct *find_task_by_pid_ns(pid_t nr, struct pid_namespace *ns)
    {
        return find_task_by_pid_type_ns(PIDTYPE_PID, nr, ns);
    }
    EXPORT_SYMBOL(find_task_by_pid_ns);
    
    struct pid *get_task_pid(struct task_struct *task, enum pid_type type)
    {
        struct pid *pid;
        rcu_read_lock();
        pid = get_pid(task->pids[type].pid);
        rcu_read_unlock();
        return pid;
    }
    pid_t task_pid_nr_ns(struct task_struct *tsk, struct pid_namespace *ns)
    {
        return pid_nr_ns(task_pid(tsk), ns);
    }
    EXPORT_SYMBOL(task_pid_nr_ns);
    
    pid_t task_tgid_nr_ns(struct task_struct *tsk, struct pid_namespace *ns)
    {
        return pid_nr_ns(task_tgid(tsk), ns);
    }
    EXPORT_SYMBOL(task_tgid_nr_ns);
    
    pid_t task_pgrp_nr_ns(struct task_struct *tsk, struct pid_namespace *ns)
    {
        return pid_nr_ns(task_pgrp(tsk), ns);
    }
    EXPORT_SYMBOL(task_pgrp_nr_ns);
    
    pid_t task_session_nr_ns(struct task_struct *tsk, struct pid_namespace *ns)
    {
        return pid_nr_ns(task_session(tsk), ns);
    }
    EXPORT_SYMBOL(task_session_nr_ns);
    struct pid *get_task_pid(struct task_struct *task, enum pid_type type)
    {
        struct pid *pid;
        rcu_read_lock();
        pid = get_pid(task->pids[type].pid);
        rcu_read_unlock();
        return pid;
    }
    
    struct task_struct *fastcall get_pid_task(struct pid *pid, enum pid_type type)
    {
        struct task_struct *result;
        rcu_read_lock();
        result = pid_task(pid, type);
        if (result)
            get_task_struct(result);
        rcu_read_unlock();
        return result;
    }

    下面将介绍如何生成唯一的PID。
    内核采用了一个大的位图来对PID进行管理和跟踪,每个PID用一个比特来标识,空闲置0,反之置1即可。

    在这里需要注意在进行pid分配建立一个新进程时,由于进程可能在多明敏空间中可见,则必须生成局部PID,这个需先在alloc_pid()中处理,然后才能调用alloc_pidmap()去分配pid,释放的时候同样。

    struct pid *alloc_pid(struct pid_namespace *ns)
    {
        struct pid *pid;
        enum pid_type type;
        int i, nr;
        struct pid_namespace *tmp;
        struct upid *upid;
    
        pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
        if (!pid)
            goto out;
    
        tmp = ns;
        for (i = ns->level; i >= 0; i--) {
            nr = alloc_pidmap(tmp);
            if (nr < 0)
                goto out_free;
    
            pid->numbers[i].nr = nr;
            pid->numbers[i].ns = tmp;
            tmp = tmp->parent;
        }
    
        get_pid_ns(ns);
        pid->level = ns->level;
        atomic_set(&pid->count, 1);
        for (type = 0; type < PIDTYPE_MAX; ++type)
            INIT_HLIST_HEAD(&pid->tasks[type]);
    
        spin_lock_irq(&pidmap_lock);
        for (i = ns->level; i >= 0; i--) {
            upid = &pid->numbers[i];
            hlist_add_head_rcu(&upid->pid_chain,
                    &pid_hash[pid_hashfn(upid->nr, upid->ns)]);
        }
        spin_unlock_irq(&pidmap_lock);
    
    out:
        return pid;
    
    out_free:
        for (i++; i <= ns->level; i++)
            free_pidmap(pid->numbers[i].ns, pid->numbers[i].nr);
    
        kmem_cache_free(ns->pid_cachep, pid);
        pid = NULL;
        goto out;
    }
    /**
     * 在命名空间中,查找并分配一个可用的pid号
     */
    static int alloc_pidmap(struct pid_namespace *pid_ns)
    {
        int i, offset, max_scan, pid, last = pid_ns->last_pid;
        struct pidmap *map;
    
        pid = last + 1;
        if (pid >= pid_max)
            pid = RESERVED_PIDS;
        offset = pid & BITS_PER_PAGE_MASK;
        map = &pid_ns->pidmap[pid/BITS_PER_PAGE];
        max_scan = (pid_max + BITS_PER_PAGE - 1)/BITS_PER_PAGE - !offset;
        for (i = 0; i <= max_scan; ++i) {
            if (unlikely(!map->page)) {
                void *page = kzalloc(PAGE_SIZE, GFP_KERNEL);
                /*
                 * Free the page if someone raced with us
                 * installing it:
                 */
                spin_lock_irq(&pidmap_lock);
                if (map->page)
                    kfree(page);
                else
                    map->page = page;
                spin_unlock_irq(&pidmap_lock);
                if (unlikely(!map->page))
                    break;
            }
            if (likely(atomic_read(&map->nr_free))) {
                do {
                    if (!test_and_set_bit(offset, map->page)) {
                        atomic_dec(&map->nr_free);
                        pid_ns->last_pid = pid;
                        return pid;
                    }
                    offset = find_next_offset(map, offset);
                    pid = mk_pid(pid_ns, map, offset);
                /*
                 * find_next_offset() found a bit, the pid from it
                 * is in-bounds, and if we fell back to the last
                 * bitmap block and the final block was the same
                 * as the starting point, pid is before last_pid.
                 */
                } while (offset < BITS_PER_PAGE && pid < pid_max &&
                        (i != max_scan || pid < last ||
                            !((last+1) & BITS_PER_PAGE_MASK)));
            }
            if (map < &pid_ns->pidmap[(pid_max-1)/BITS_PER_PAGE]) {
                ++map;
                offset = 0;
            } else {
                map = &pid_ns->pidmap[0];
                offset = RESERVED_PIDS;
                if (unlikely(last == offset))
                    break;
            }
            pid = mk_pid(pid_ns, map, offset);
        }
        return -1;
    }
    
    fastcall void free_pid(struct pid *pid)
    {
        /* We can be called with write_lock_irq(&tasklist_lock) held */
        int i;
        unsigned long flags;
    
        spin_lock_irqsave(&pidmap_lock, flags);
        for (i = 0; i <= pid->level; i++)
            hlist_del_rcu(&pid->numbers[i].pid_chain);
        spin_unlock_irqrestore(&pidmap_lock, flags);
    
        for (i = 0; i <= pid->level; i++)
            free_pidmap(pid->numbers[i].ns, pid->numbers[i].nr);
    
        call_rcu(&pid->rcu, delayed_put_pid);
    }
    /**
     * 在命名空间中,释放一个可用的pid号
     */
    static fastcall void free_pidmap(struct pid_namespace *pid_ns, int pid)
    {
        struct pidmap *map = pid_ns->pidmap + pid / BITS_PER_PAGE;
        int offset = pid & BITS_PER_PAGE_MASK;
    
        clear_bit(offset, map->page);
        atomic_inc(&map->nr_free);
    }
    
    作者:qq_21127151 发表于2017/8/17 0:45:37 原文链接
    阅读:136 评论:0 查看评论

    Kotlin入门(14)继承的那些事儿

    $
    0
    0
    上一篇文章介绍了类对成员的声明方式与使用过程,从而初步了解了类的成员及其运用。不过早在《Kotlin入门(12)类的概貌与构造》中,提到MainActivity继承自AppCompatActivity,而Kotlin对于类继承的写法是“class MainActivity : AppCompatActivity() {}”,这跟Java对比有明显差异,那么Kotlin究竟是如何定义基类并由基类派生出子类呢?为廓清这些迷雾,本篇文章就对类继承的相关用法进行深入探讨。

    博文《Kotlin入门(13)类成员的众生相》在演示类成员时多次重写了WildAnimal类,这下你兴冲冲地准备按照MainActivity的继承方式,从WildAnimal派生出一个子类Tiger,写好构造函数的两个输入参数,补上基类的完整声明,敲了以下代码不禁窃喜这么快就大功告成了:
    class Tiger(name:String="老虎", sex:Int = 0) : WildAnimal(name, sex) {
    }
    谁料编译器无情地蹦出错误提示“The type is final, so it cannot be inherited from”,意思是WildAnimal类是final类型,所以它不允许被继承。原来Java默认每个类都能被继承,除非加了关键字final表示终态,才不能被其它类继承。Kotlin恰恰相反,它默认每个类都不能被继承(相当于Java类被final修饰了),如果要让某个类成为基类,则需把该类开放出来,也就是添加关键字open作为修饰。因此,接下来还是按照Kotlin的规矩办事,重新写个采取open修饰的基类,下面即以鸟类Bird进行演示,改写后的基类代码框架如下:
    open class Bird (var name:String, val sex:Int = 0) {
        //此处暂时省略基类内部的成员属性和方法
    }
    现在有了基类框架,还得往里面补充成员属性和成员方法,然后给这些成员添加开放性修饰符。就像大家在Java和C++世界中熟知的几个关键字,包括public、protected、private,分别表示公开、只对子类开放、私有。那么Kotlin体系参照Java世界也给出了四个开放性修饰符,按开放程度从高到低分别是:
    public : 对所有人开放。Kotlin的类、函数、变量不加开放性修饰符的话,默认就是public类型。
    internal : 只对本模块内部开放,这是Kotlin新增的关键字。对于App开发来说,本模块便是指App自身。
    protected : 只对自己和子类开放。
    private : 只对自己开放,即私有。
    注意到这几个修饰符与open一样都加在类和函数前面,并且都包含“开放”的意思,乍看过去还真有点扑朔迷离,到底open跟四个开放性修饰符是什么关系?其实也不复杂,open不控制某个对象的访问权限,只决定该对象能否繁衍开来,说白了,就是公告这个家伙有没有资格生儿育女。只有头戴open帽子的类,才允许作为基类派生出子类来;而头戴open帽子的函数,表示它允许在子类中进行重写。
    至于那四个开放性修饰符,则是用来限定允许访问某对象的外部范围,通俗地说,就是哪里的男人可以娶这个美女。头戴public的,表示全世界的男人都能娶她;头戴internal的,表示本国的男人可以娶她;头戴protected的,表示本单位以及下属单位的男人可以娶她;头戴private的,表示肥水不流外人田,只有本单位的帅哥才能娶这个美女噢。
    因为private的限制太严厉了,只对自己开放,甚至都不允许子类染指,所以它跟关键字open势同水火。open表示这个对象可以被继承,或者可以被重载,然而private却坚决斩断该对象与其子类的任何关系,因此二者不能并存。倘若在代码中强行给某个方法同时加上open和private,编译器只能无奈地报错“Modifier 'open' is incompatible with 'private'”,意思是open与private不兼容。
    按照以上的开放性相关说明,接下来分别给Bird类的类名、函数名、变量名加上修饰符,改写之后的基类代码是下面这样:
    //Kotlin的类默认是不能继承的(即final类型),如果需要继承某类,则该父类应当声明为open类型。
    //否则编译器会报错“The type is final, so it cannot be inherited from”。
    open class Bird (var name:String, val sex:Int = MALE) {
        //变量、方法、类默认都是public,所以一般都把public省略掉了
        //public var sexName:String
        var sexName:String
        init {
            sexName = getSexName(sex)
        }
    
        //私有的方法既不能被外部访问,也不能被子类继承,因此open与private不能共存
        //否则编译器会报错:Modifier 'open' is incompatible with 'private'
        //open private fun getSexName(sex:Int):String {
        open protected fun getSexName(sex:Int):String {
            return if(sex==MALE) "公" else "母"
        }
    
        fun getDesc(tag:String):String {
            return "欢迎来到$tag:这只${name}是${sexName}的。"
        }
    
        companion object BirdStatic{
            val MALE = 0
            val FEMALE = 1
            val UNKNOWN = -1
            fun judgeSex(sexName:String):Int {
                var sex:Int = when (sexName) {
                    "公","雄" -> MALE
                    "母","雌" -> FEMALE
                    else -> UNKNOWN
                }
                return sex
            }
        }
    }
    好不容易鼓捣出来一个正儿八经的鸟儿基类,再来声明一个它的子类试试,例如鸭子是鸟类的一种,于是下面有了鸭子的类定义代码:
    //注意父类Bird已经在构造函数声明了属性,故而子类Duck无需重复声明属性
    //也就是说,子类的构造函数,在输入参数前面不要再加val和var
    class Duck(name:String="鸭子", sex:Int = Bird.MALE) : Bird(name, sex) {
    }
    子类也可以定义新的成员属性和成员方法,或者重写被声明为open的父类方法。比方说性别名称“公”和“母”一般用于家禽,像公鸡、母鸡、公鸭、母鸭等等,指代野生鸟类的性别则通常使用“雄”和“雌”,所以定义野生鸟类的时候,就得重写获取性别名称的getSexName方法,把“公”和“母”的返回值改为“雄”和“雌”。方法重写之后,定义了鸵鸟的类代码如下所示:
    class Ostrich(name:String="鸵鸟", sex:Int = Bird.MALE) : Bird(name, sex) {
        //继承protected的方法,标准写法是“override protected”
        //override protected fun getSexName(sex:Int):String {
        //不过protected的方法继承过来默认就是protected,所以也可直接省略protected
        //override fun getSexName(sex:Int):String {
        //protected的方法继承之后允许将可见性升级为public,但不能降级为private
        override public fun getSexName(sex:Int):String {
            return if(sex==MALE) "雄" else "雌"
        }
    }

    除了上面讲的普通类继承,Kotlin也存在与Java类似的抽象类,抽象类之所以存在,是因为其内部拥有被abstract修饰的抽象方法。抽象方法没有具体的函数体,故而外部无法直接声明抽象类的实例;只有在子类继承之时重写抽象方法,该子类方可正常声明对象实例。举个例子,鸡属于鸟类,可公鸡和母鸡的叫声是不一样的,公鸡是“喔喔喔”地叫,而母鸡是“咯咯咯”地叫;所以鸡这个类的叫唤方法callOut,发出什么声音并不确定,只能先声明为抽象方法,连带着鸡类Chicken也变成抽象类了。根据上述的抽象类方案,定义好的Chicken类代码示例如下:
    //子类的构造函数,原来的输入参数不用加var和val,新增的输入参数必须加var或者val。
    //因为抽象类不能直接使用,所以构造函数不必给默认参数赋值。
    abstract class Chicken(name:String, sex:Int, var voice:String) : Bird(name, sex) {
        val numberArray:Array<String> = arrayOf("一","二","三","四","五","六","七","八","九","十");
        //抽象方法必须在子类进行重写,所以可以省略关键字open,因为abstract方法默认就是open类型
        //open abstract fun callOut(times:Int):String
        abstract fun callOut(times:Int):String
    }
    接着从Chicken类派生出公鸡类Cock,指定公鸡的声音为“喔喔喔”,同时还要重写callOut方法,明确公鸡的叫唤行为。具体的Cock类代码如下所示:
    class Cock(name:String="鸡", sex:Int = Bird.MALE, voice:String="喔喔喔") : Chicken(name, sex, voice) {
        override fun callOut(times: Int): String {
            var count = when {
                //when语句判断大于和小于时,要把完整的判断条件写到每个分支中
                times<=0 -> 0
                times>=10 -> 9
                else -> times
            }
            return "$sexName$name${voice}叫了${numberArray[count]}声,原来它在报晓呀。"
        }
    }
    同样派生而来的母鸡类Hen,也需指定母鸡的声音“咯咯咯”,并重写callOut叫唤方法,具体的Hen类代码如下所示:
    class Hen(name:String="鸡", sex:Int = Bird.FEMALE, voice:String="咯咯咯") : Chicken(name, sex, voice) {
        override fun callOut(times: Int): String {
            var count = when {
                times<=0 -> 0
                times>=10 -> 9
                else -> times
            }
            return "$sexName$name${voice}叫了${numberArray[count]}声,原来它下蛋了呀。"
        }
    }
    定义好了callOut方法,外部即可调用Cock类和Hen类的该方法了,调用代码示例如下:
        //调用公鸡类的叫唤方法
        tv_class_inherit.text = Cock().callOut(count++%10)
        //调用母鸡类的叫唤方法
        tv_class_inherit.text = Hen().callOut(count++%10)

    既然提到了抽象类,就不得不提接口interface。Kotlin的接口与Java一样是为了间接实现多重继承,由于直接继承多个类可能存在方法冲突等问题,因此Kotlin在编译阶段就不允许某个类同时继承多个基类,否则会报错“Only one class may appear in a supertype list”。于是乎,通过接口定义几个抽象方法,然后在实现该接口的具体类中重写这几个方法,从而间接实现C++多重继承的功能。
    在Kotlin中定义接口需要注意以下几点:
    1、接口不能定义构造函数,否则编译器会报错“An interface may not have a constructor”;
    2、接口的内部方法通常要被实现它的类进行重写,所以这些方法默认为抽象类型;
    3、与Java不同的是,Kotlin允许在接口内部实现某个方法,而Java接口的所有内部方法都必须是抽象方法;
    Android开发最常见的接口是控件的点击监听器View.OnClickListener,其内部定义了控件的点击动作onClick,类似的还有长按监听器View.OnLongClickListener、选择监听器CompoundButton.OnCheckedChangeListener等等,它们无一例外都定义了某种行为的事件处理过程。对于本文的鸟类例子而言,也可通过一个接口定义鸟儿的常见动作行为,譬如鸟儿除了叫唤动作,还有飞翔、游泳、奔跑等等动作,有的鸟类擅长飞翔(如大雁、老鹰),有的鸟类擅长游泳(如鸳鸯、鸬鹚),有的鸟类擅长奔跑(如鸵鸟、鸸鹋)。因此针对鸟类的飞翔、游泳、奔跑等动作,即可声明Behavior接口,在该接口中定义几个行为方法如fly、swim、run,下面是一个定义好的行为接口代码例子:
    //Kotlin与Java一样不允许多重继承,即不能同时继承两个类(及以上类),
    //否则编译器报错“Only one class may appear in a supertype list”,
    //所以仍然需要接口interface来间接实现多重继承的功能。
    //接口不能带构造函数(那样就变成一个类了),否则编译器报错“An interface may not have a constructor”
    //interface Behavior(val action:String) {
    interface Behavior {
        //接口内部的方法默认就是抽象的,所以不加abstract也可以,当然open也可以不加
        open abstract fun fly():String
        //比如下面这个swim方法就没加关键字abstract,也无需在此处实现方法
        fun swim():String
        //Kotlin的接口与Java的区别在于,Kotlin接口内部允许实现方法,
        //此时该方法不是抽象方法,就不能加上abstract,
        //不过该方法依然是open类型,接口内部的所有方法都默认是open类型
        fun run():String {
            return "大多数鸟儿跑得并不像样,只有鸵鸟、鸸鹋等少数鸟类才擅长奔跑。"
        }
        //Kotlin的接口允许声明抽象属性,实现该接口的类必须重载该属性,
        //与接口内部方法一样,抽象属性前面的open和abstract也可省略掉
        //open abstract var skilledSports:String
        var skilledSports:String
    }
    那么其他类实现Behavior接口时,跟类继承一样把接口名称放在冒号后面,也就是说,Java的extends和implement这两个关键字在Kotlin中都被冒号取代了。然后就像重写抽象类的抽象方法一样,重写该接口的抽象方法,以鹅的Goose类为例,重写接口方法之后的代码如下所示:
    class Goose(name:String="鹅", sex:Int = Bird.MALE) : Bird(name, sex), Behavior {
        override fun fly():String {
            return "鹅能飞一点点,但飞不高,也飞不远。"
        }
    
        override fun swim():String {
            return "鹅,鹅,鹅,曲项向天歌。白毛浮绿水,红掌拨清波。"
        }
    
        //因为接口已经实现了run方法,所以此处可以不用实现该方法,当然你要实现它也行。
        override fun run():String {
            //super用来调用父类的属性或方法,由于Kotlin的接口允许实现方法,因此super所指的对象也可以是interface
            return super.run()
        }
    
        //重载了来自接口的抽象属性
        override var skilledSports:String = "游泳"
    }
    这下大功告成,Goose类声明的群鹅不但具备鸟类的基本功能,而且能飞、能游、能跑,活脱脱一只栩栩如生的大白鹅呀:
        btn_interface_behavior.setOnClickListener {
            tv_class_inherit.text = when (count++%3) {
                0 -> Goose().fly()
                1 -> Goose().swim()
                else -> Goose().run()
            }
        }

    总结一下,Kotlin的类继承与Java相比有所不同,首先Kotlin的类默认不可被继承,如需继承则要添加open声明;而Java的类默认是允许被继承的,只有添加final声明才表示不能被继承。其次,Kotlin除了常规的三个开放性修饰符public、protected、private,另外增加了修饰符internal表示只对本模块开放。再次,Java的类继承关键字extends,以及接口实现关键字implement,在Kotlin中都被冒号所取代。最后,Kotlin允许在接口内部实现某个方法,而Java接口的内部方法只能是抽象方法。


    点此查看Kotlin入门教程的完整目录


    __________________________________________________________________________
    本文现已同步发布到微信公众号“老欧说安卓”,打开微信扫一扫下面的二维码,或者直接搜索公众号“老欧说安卓”添加关注,更快更方便地阅读技术干货。
    作者:aqi00 发表于2017/8/17 9:36:38 原文链接
    阅读:24 评论:0 查看评论

    iOS探索--页面控制和跳转

    $
    0
    0

    一、页面控制
    在ios项目中关联UI和代码界面有俩种方法Main.storyboard和xib。当然有些人说代码内也可以定义UI,再此不细说。Main.storyboard重量级,适合管理多个ViewController。xib的比较轻量级。

    storyboard和xib区别:一个工程中可以有多个xib文件,一个xib文件对应着一个视图控制器和多个视图。而使用storyboard时,一个工程只需要一个主storyboard文件就可以了。因此,在包含多个视图控制器的情况下,采用storyboard管理比较方便,而且storyboard还可以描述界面之间的导航关系。

    1.ViewController关联xib的
    新建一个类勾选Also create XIB file选项。
    这里写图片描述
    勾选Also create XIB file后,会自动创建一个和ViewController类配置好的xib的文件。
    这里写图片描述
    然后在拖控件关联到File’s Owner。
    这里写图片描述
    2.关联NetViewController.h,监听button,并找到这个控件
    这里写图片描述
    鼠标点击button按着conforl键有一条线拉到@interface方法里。
    会弹出一条弹框如图。Connection是选择需要的方法类型。可以选择Action。会自动添加一条监听方法- (IBAction)myclea:(id)sender;。
    NextViewController.h查看监听方法。
    这里写图片描述
    在监听方法中可以实现点击事件。
    在弹出弹框时默认Outlet类型,在代码中可以通过self关键字找到这个控件。
    这里写图片描述
    拖拽label控件到NextViewController.h文件中
    这里写图片描述
    在NextViewController.m文件中使用
    这里写图片描述
    2.通过tag属性值找到这个控件
    这里写图片描述
    代码中:

    UILabel* label =(UILabel*)[self.view viewWithTag:10];

    3.页面跳转

    如果使用导航
    第一个页面按钮方法:
    [self.navigationController pushViewController:secondVC animated:YES];
    第二个页面按钮方法(返回):
    [self.navigationController popViewControllerAnimated:YES];

    如果使用模态
    第一个页面按钮方法:
    [self presentViewController:secondVC animated:YES completion:nil];
    第二个页面按钮方法(返回):
    [self dismissViewControllerAnimated:YES completion:nil];

    页面跳转方法直接在监听的方法中使用就行,例如:

    //跳转到下一个页面
    
    - (IBAction)myOk:(id)sender {
        NextViewController* next = [[NextViewController alloc] init];
        [self presentViewController:next animated:YES completion:nil];
    }
    //返回
    - (IBAction)twoButton:(id)sender {
        [self dismissViewControllerAnimated:YES completion:nil];
    }

    当然在实际开发中不需要这么麻烦。
    直接可以在NextViewController.m中定义好Button的指针变量和IBAction方法。然后去xib文件中关联就好了。
    1.定义变量

    @interface NextViewController ()
    {
        __weak IBOutlet UIButton *loginButton;
    }

    2.定义IBAction方法

    - (IBAction)login:(id)sender{
       //此处实现点击需要的方法
    }

    3.关联xib
    这里写图片描述
    鼠标点击File’s Owner按着confrol键移动到LoginButton
    这里写图片描述
    主要关联:Received Action方法和Outlets方法里的loginButton。

    至此,关联以及监听方法讲解完毕。

    作者:haoaoo 发表于2017/8/16 15:49:07 原文链接
    阅读:39 评论:0 查看评论

    Android开发学习(12)Jersey构建RESTful后台服务

    $
    0
    0

    先来简单了解下概念,方便我们搭建相关服务:

    概念

    RESTful

    REST(英文:Representational State Transfer,简称REST)描述了一个架构样式的网络系统,比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,他是 HTTP 规范的主要编写者之一。在目前主流的三种Web服务交互方案中,REST相比于SOAP(Simple Object Access protocol,简单对象访问协议)以及XML-RPC更加简单明了,无论是对URL的处理还是对Payload的编码,REST都倾向于用更加简单轻量的方法设计和实现。值得注意的是REST并没有一个明确的标准,而更像是一种设计的风格。

    Jersey

    Jersey RESTful 框架是开源的RESTful框架, 实现了JAX-RS (JSR 311 & JSR 339) 规范。它扩展了JAX-RS 参考实现, 提供了更多的特性和工具, 可以进一步地简化 RESTful service 和 client 开发。尽管相对年轻,它已经是一个产品级的 RESTful service 和 client 框架。

    接下来我们开始搭建我们自己的RESTful后台服务;

    搭建步骤

        <dependency>
          <groupId>org.glassfish.jersey.containers</groupId>
          <artifactId>jersey-container-servlet</artifactId>
          <version>2.17</version>
        </dependency>
        <dependency>
          <groupId>org.glassfish.jersey.core</groupId>
          <artifactId>jersey-client</artifactId>
          <version>2.17</version>
        </dependency>
        <dependency>
          <groupId>org.glassfish.jersey.media</groupId>
          <artifactId>jersey-media-json-jackson</artifactId>
          <version>2.17</version>
        </dependency>
    • 修改web.xml
    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>jerseyDemo</display-name>
    
      <servlet>
        <servlet-name>jersey-restful</servlet-name>
        <servlet-class>
          org.glassfish.jersey.servlet.ServletContainer
        </servlet-class>
        <init-param>
          <param-name>jersey.config.server.provider.packages</param-name>
          <param-value>com.xvshu.cn</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>jersey-restful</servlet-name>
        <url-pattern>/rest/*</url-pattern>
      </servlet-mapping>
    
    </web-app>
    • 新增rest服务
    package com.xvshu.cn.rest;
    
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.PathParam;
    import javax.ws.rs.Produces;
    
    /**
     * Created by xvshu on 2017/8/16.
     */
    @Path("/user")
    public class UserRestServide {
    
        @GET
        @Path("/{from}/{userName}/{passWord}")
        @Produces("text/plain;charset=UTF-8")
        public String sayHello2UserByText(@PathParam("userName") String username,@PathParam("passWord") String passWord,@PathParam("from") String from) {
            String result = "false";
    
            System.out.println("==>rest/from:"+String.valueOf(from)+" user:"+String.valueOf(username)+" pass:"+String.valueOf(passWord));
            username=username.trim();
            passWord=passWord.trim();
            if(username.equals("admin") && passWord.equals("admin")){
                result="true";
            }
            return result;
        }
    }
    
    • 发布
      项目正常打包发布至tomcat,完成部署

    结果展示

    前台请求
    rest_web

    后台日志
    rest_service

    总结

    Jersey框架的原理非常类似与spring mvc,这个框架的学习非常轻松,和以前的spring mvc相似成都几乎100%,相信有同样学习经验的同学应该有所感受,如果对spring mvc感兴趣,请移步我的相关博客:http://blog.csdn.net/xvshu/article/details/42435167

    作者:xvshu 发表于2017/8/16 18:05:26 原文链接
    阅读:25 评论:0 查看评论

    React Native 生命周期

    $
    0
    0

    前言:

             在面向对象编程中,任何对象的存在都会存在生命周期。类似我们iOS 的View,就会有LoadView,ViewWillAppear,ViewDidLoad等等生命周期。RN也不例外,这篇主要学习RN的生命周期,在开发中如果掌握了并熟练的运用生命周期函数的话,往往开发能事半功倍。

      React Native生命周期简介





    如图,可以把组件生命周期大致分为三个阶段:

    • 第一阶段:是组件第一次绘制阶段,如图中的上面虚线框内,在这里完成了组件的加载和初始化;
    • 第二阶段:是组件在运行和交互阶段,如图中左下角虚线框,这个阶段组件可以处理用户交互,或者接收事件更新界面;
    • 第三阶段:是组件卸载消亡的阶段,如图中右下角的虚线框中,这里做一些组件的清理工作。

    生命周期回调函数(ES5写法)

    下面来详细介绍生命周期中的各回调函数,先说下和上图对应的ES5写法。

    getDefaultProps

    在组件创建之前,会先调用 getDefaultProps(),这是全局调用一次,严格地来说,这不是组件的生命周期的一部分。在组件被创建并加载候,首先调用 getInitialState(),来初始化组件的状态。

    componentWillMount

    然后,准备加载组件,会调用 componentWillMount(),其原型如下:

    void componentWillMount()  
    

    这个函数调用时机是在组件创建,并初始化了状态之后,在第一次绘制 render() 之前。可以在这里做一些业务初始化操作,也可以设置组件状态。这个函数在整个生命周期中只被调用一次。

    componentDidMount

    在组件第一次绘制之后,会调用 componentDidMount(),通知组件已经加载完成。函数原型如下:

    void componentDidMount()  
    

    这个函数调用的时候,其虚拟 DOM 已经构建完成,你可以在这个函数开始获取其中的元素或者子组件了。需要注意的是,RN 框架是先调用子组件的 componentDidMount(),然后调用父组件的函数。从这个函数开始,就可以和 JS 其他框架交互了,例如设置计时 setTimeout 或者 setInterval,或者发起网络请求。这个函数也是只被调用一次。这个函数之后,就进入了稳定运行状态,等待事件触发。

    componentWillReceiveProps

    如果组件收到新的属性(props),就会调用 componentWillReceiveProps(),其原型如下:

    void componentWillReceiveProps(  
      object nextProps
    )
    

    输入参数 nextProps 是即将被设置的属性,旧的属性还是可以通过 this.props 来获取。在这个回调函数里面,你可以根据属性的变化,通过调用 this.setState() 来更新你的组件状态,这里调用更新状态是安全的,并不会触发额外的 render() 调用。如下:

    componentWillReceiveProps: function(nextProps) {  
      this.setState({
        likesIncreasing: nextProps.likeCount > this.props.likeCount
      });
    }
    

    shouldComponentUpdate

    当组件接收到新的属性和状态改变的话,都会触发调用 shouldComponentUpdate(...),函数原型如下:

    boolean shouldComponentUpdate(  
      object nextProps, object nextState
    )
    

    输入参数 nextProps 和上面的 componentWillReceiveProps 函数一样,nextState 表示组件即将更新的状态值。这个函数的返回值决定是否需要更新组件,如果 true 表示需要更新,继续走后面的更新流程。否者,则不更新,直接进入等待状态。

    默认情况下,这个函数永远返回 true 用来保证数据变化的时候 UI 能够同步更新。在大型项目中,你可以自己重载这个函数,通过检查变化前后属性和状态,来决定 UI 是否需要更新,能有效提高应用性能。

    componentWillUpdate

    如果组件状态或者属性改变,并且上面的 shouldComponentUpdate(...) 返回为 true,就会开始准更新组件,并调用 componentWillUpdate(),其函数原型如下:

    void componentWillUpdate(  
      object nextProps, object nextState
    )
    

    输入参数与 shouldComponentUpdate 一样,在这个回调中,可以做一些在更新界面之前要做的事情。需要特别注意的是,在这个函数里面,你就不能使用 this.setState 来修改状态。这个函数调用之后,就会把 nextProps 和 nextState 分别设置到 this.props和 this.state 中。紧接着这个函数,就会调用 render() 来更新界面了。

    componentDidUpdate

    调用了 render() 更新完成界面之后,会调用 componentDidUpdate() 来得到通知,其函数原型如下:

    void componentDidUpdate(  
      object prevProps, object prevState
    )
    

    因为到这里已经完成了属性和状态的更新了,此函数的输入参数变成了 prevProps 和 prevState

    componentWillUnmount

    当组件要被从界面上移除的时候,就会调用 componentWillUnmount(),其函数原型如下:

    void componentWillUnmount()  
    

    在这个函数中,可以做一些组件相关的清理工作,例如取消计时器、网络请求等。


    生命周期回调函数学习笔记小例(ES6)


    学习就要与时俱进,试着接受和学习新东西,下面的例子都是用ES6写的。


    1、设置默认属性


    代码:

     class RNHybrid extends Component {
      
      render() {  
          return(  
            <View style={styles.container}> 
              <Text style={{padding:10, fontSize:42}}>
                    {this.props.name}
              </Text>
            </View>  
          );  
       }
    }
    
    
    RNHybrid.defaultProps = {
      name: 'Mary',
    };


    效果:





    ES5和ES6写法对比:

    ES6

    class Greeting extends React.Component {
      // ...
    }
    
    Greeting.defaultProps = {
      name: 'Mary'
    };
    


    ES5

    var Greeting = createReactClass({
      getDefaultProps: function() {
        return {
          name: 'Mary'
        };
      },
    
      // ...
    
    });


    总结:
              props相当于iOS 里的属性,但是这个属性只是readonly。我们可以通过this.props来读取属性。


    2、设置状态


       由图片我们知道,当我们修改状态的时候,会从新调用render函数重新渲染页面,所以一些和界面有关的动态变量需要设置成状态。

       如上一篇的例子,我在从新copy一遍:

    看下效果:





    代码:(生命周期现在还没有说我也是偏面的了解,以后会系统的学习,现在先不介绍)

    [javascript] view plain copy
    1. constructor(props) {  
    2.         super(props);  
    3.         //设置当前状态是text  初始值为空  
    4.         this.state = {text: ''};  
    5.     }  
    6.   
    7.   render() {    
    8.       return(    
    9.         <View style={styles.container}>   
    10.           <TextInput style={styles.TextInputStyles}   
    11.               onChangeText={(Text)=>{  
    12.                 this.setState({text:Text});  
    13.               }}  
    14.           />   
    15.           <Text style={{padding:10, fontSize:42}}>  
    16.                 {this.state.text}  
    17.           </Text>  
    18.         </View>    
    19.       );    
    20.    }  

    ES5和ES6写法对比:

    ES6 

    class myClass extends React.Component {
      constructor(props) {
        super(props);    this.state = {text:''};
      }
      // ...
    }
    


    ES5

    var myClass = createReactClass({
      getInitialState: function() {
        return {text: ''};
      },
      // ...
    });
    


    ES5和ES6还有一个不同的地方,如果调用事件函数需要bind绑定

    例如:

    class RNHybrid extends Component {
      
    
      constructor(props) {
            super(props);
            this.state = {age:this.props.age};
        }
    
     handleClick() {
        alert(this.state.age);
      }
    
      render() {  
          return(  
            <View style={styles.container}> 
              <Text style={{padding:10, fontSize:42}} onPress={this.handleClick}>
                    {this.props.name}
              </Text>
            </View>  
          );  
       }
    }

    这样写你点击的时候将会报错:



    你需要将函数绑定:

    1.可以在构造函数里绑定

     constructor(props) {
            super(props);
            this.state = {age:this.props.age};
            this.handleClick = this.handleClick.bind(this);
        }

    2.还可以在调用的时候绑定

     <Text style={{padding:10, fontSize:42}} onPress={this.handleClick.bind(this)}>
                    {this.props.name}
     </Text>


    3、其他生命周期函数验证


    代码:

    import React, { Component } from 'react';
    import {
       AppRegistry,
      StyleSheet,
      View,
      Text,
      TextInput,
    } from 'react-native';
    
    var  nowTime = new Date();
    var  showText;
    
    class RNHybrid extends Component {
      
      constructor(props) {  
            super(props);  
            console.log('state:'+nowTime);
            showText = 'state:'+nowTime+'\r\r';   
            //设置当前状态是text  初始值为空  
            this.state = {text: ''};  
      }
    
    
      componentWillMount(){
        console.log('componentWillMount:'+nowTime);
        showText = showText+'componentWillMount:'+nowTime+'\r\r';   
      } 
    
      componentDidMount(){
        console.log('componentDidMount:'+nowTime);
        showText = showText+'componentDidMount:'+nowTime+'\r\r';   
        alert(showText);
      } 
    
      shouldComponentUpdate(){
        console.log('shouldComponentUpdate:'+nowTime);
        showText = showText+'shouldComponentUpdate:'+nowTime+'\r\r';   
        return true;
      } 
       
      componentWillUpdate(){
        console.log('componentWillUpdate:'+nowTime);
        showText = showText+'componentWillUpdate:'+nowTime+'\r\r';       
      } 
    
      componentDidUpdate(){
        console.log('componentDidUpdate:'+nowTime);
        showText = showText+'componentDidUpdate:'+nowTime+'\r\r';       
      } 
    
      componentWillUnmount(){
        console.log('componentWillUnmount:'+nowTime);
        showText = showText+'componentWillUnmount:'+nowTime+'\r\r';       
      }
    
      render() {    
          return(    
            <View style={styles.container}>   
              <TextInput style={styles.TextInputStyles}   
                  onChangeText={(Text)=>{  
                    this.setState({text:Text});  
                  }}  
              />   
              <Text style={{marginTop:10,padding:10, fontSize:15,borderColor:'gray',borderWidth:1}}>  
                    {showText}  
              </Text>  
            </View>    
          );    
       }  
    }
    
    RNHybrid.defaultProps = {
      name: 'Mary',
      age:'18',
    };
    
    
    const styles = StyleSheet.create({
       container:{
       		marginTop:100,
       		flexDirection:'row',
          flexWrap:'wrap',
          justifyContent:'space-around',
       },
       TextInputStyles:{
          width:200,
          height:60,
          borderWidth:2,
          borderColor:'red',
       },
    });
    
    AppRegistry.registerComponent('RNHybrid', () => RNHybrid);


    效果:




    分析:

        当加载时候,按照 构造函数-> componentWillMount -> render->componentDidMount 这个顺序来的。
         细心的人可能会发现,界面上并没有显示componentDidMount,是因为执行了这个函数并没有重新render。
        当你输入的时候改变state就按照图上左下角部分进行。重新render的时候,就会看到componentDidMount出现。

        验证图上的分析是合理的,我们也放心了。

    作者:ZY_FlyWay 发表于2017/8/17 16:01:13 原文链接
    阅读:46 评论:0 查看评论

    xamarin android 打造ListView万能适配器

    $
    0
    0

    早些时候接触xamarin android 的列表,写了很多ListView的Adapter,建一个ListView就写一个Adapter,每一个Adapter里面还有去写一个ViewHolder的类来优化,自从看了hongyang博客的listview万能适配器的文章,学习良多,所以就写篇关于xamarin android ListView通用适配器的文章。

    本章主要分为以下三点:

    1. 打造通用的ViewHolder优化ListView性能
    2. 使用泛型Adapter适应不同布局的ListView
    3. C#委托和Java匿名内部类的比较

    打造通用的ViewHolder优化ListView性能

      public  class ViewHolder:Java.Lang.Object
        {
            private  SparseArray<View>  Views;
             View ConvertView;
            private Context context ;
             int mPosition;
            private ViewHolder(Context _context,ViewGroup  parent ,int itemLayoutId,int position)
            {
                this.mPosition = position;
                Views = new SparseArray<View>();
                ConvertView =  LayoutInflater.From(_context).Inflate(itemLayoutId,null);
                ConvertView.Tag = this;
            }
            public static ViewHolder Get(Context context , View convertView,ViewGroup  parent ,int itemLayoutId,int position)
            {
                if (convertView == null)
                {
                    return new ViewHolder(context, parent, itemLayoutId, position);
                }
                else
                {
                    ViewHolder holder = (ViewHolder)convertView.Tag;
                    holder.mPosition = position;
                    return holder;
                }
            }
            public T GetView<T>(int viewId) where T :View
            {
                View view = Views.Get(viewId);
                if (view == null)
                {
                    view = ConvertView.FindViewById<T>(viewId);
                    Views.Put(viewId,view);
                }
                return (T)view;
            }
            public View GetConvertView()
            {
                return ConvertView;
            }
            /// <summary>
            /// 给TextView 设置文本
            /// </summary>
            /// <param name="viewId"></param>
            /// <param name="text"></param>
            /// <returns></returns>
            public ViewHolder SetText(int viewId ,string  text)
            {
                TextView view = GetView<TextView>(viewId);
                view.Text = text;
                return this;
            }
    
            /// <summary>
            /// 给ImageView 设置图片
            /// </summary>
            public ViewHolder SetImageBitMap(int viewId , Bitmap bm)
            {
                ImageView view = GetView<ImageView>(viewId);
                view.SetImageBitmap(bm);
                return this;
            }
        }

    稍微解释一下:
    ViewHolder的构造方法中初始化变量,并将ListView的布局view的Tag设置成viewholder的实例
    Get方法使用单例模式初始化ViewHolder
    GetView泛型方法获取ListView布局中的空间
    SetText、SetImageBitMap给一些常用的控件复值

    使用泛型Adapter适应不同布局的ListView

    CommonAdapter是一个泛型的基类,一些重复的重写方法都在这个基类,然后我们写一个子类继承这个CommonAdapter,就简化了很多代码

     public abstract class CommonAdapter<T> :BaseAdapter
        {
            Context mContext;
             List<T> mData;
             int mItemLayoutId;
            public CommonAdapter(Context context, List<T> data, int itemLayoutId):base()
            {
                this.mContext = context;
                mData = data;
                mItemLayoutId = itemLayoutId;
            }
            public override int Count
            {
                get
                {
                    return mData.Count;
                }
            }
            public override  Java.Lang.Object GetItem(int position)
            {
                return null;
    
            }
            public override long GetItemId(int position)
            {
                return position;
            }
            public override View GetView(int position, View convertView, ViewGroup parent)
            {
                var item = mData[position];
                ViewHolder viewHolder = ViewHolder.Get(mContext, convertView, parent, mItemLayoutId, position);
                convert(viewHolder,mData[position]);
    
                System.Diagnostics.Debug.Write(position);
                return viewHolder.GetConvertView();
            }
            public abstract void convert(ViewHolder helper, T item);
            public ViewHolder GetViewHolder(int position, View convertView, ViewGroup parent)
            {
                return ViewHolder.Get(mContext, convertView, parent, mItemLayoutId, position);
            }
        }

    写一个newsAdapter继承CommonAdapter,只需要将赋值的代码写在重写方法convert里面极客,这样就简化了很多的代码

        public class NewsAdapter<T> : CommonAdapter<T>
        {
            public NewsAdapter(Context context, List<T> data, int resId) : base(context, data, resId)
            {
    
            }
    
            public override void convert(ViewHolder helper, T item)
            {
                NewsViewModel model = (NewsViewModel)Convert.ChangeType(item, typeof(NewsViewModel));
                helper.SetText(Resource.Id.tv_news_title, model.Title);
                helper.SetText(Resource.Id.tv_news_id, model.NewsID.ToString());
                helper.SetText(Resource.Id.tv_news_desc, model.Desc);
            }
        }

    C#委托和Java匿名内部类的比较

    看来上面的例子,虽然简化了很多代码,但是每个ListView还是得新建一个Adapter,看来hongyang大神的博客在java中用匿名内部类实现一个Adapter万能通用,但是c#中没有匿名内部内的概念,这就尴尬了。。。。。
    c#中虽然没有这种概念,但语言是相通的,但是委托可以实现。万能通用的adapter代码如下:

        public class Common1Adapter<T> : BaseAdapter
        {
            Context mContext;
            List<T> mData;
            int mItemLayoutId;
            public delegate View GetViewEvent(int position, View convertView, ViewGroup parent, T item, ViewHolder viewHolder);
            public event GetViewEvent OnGetView;
            public Common1Adapter(Context context, List<T> data, int itemLayoutId) : base()
            {
                this.mContext = context;
                mData = data;
                mItemLayoutId = itemLayoutId;
            }
            public override int Count
            {
                get
                {
                    return mData.Count;
                }
            }
            public override Java.Lang.Object GetItem(int position)
            {
                return null;
    
            }
            public override long GetItemId(int position)
            {
                return position;
            }
            public override View GetView(int position, View convertView, ViewGroup parent)
            {
                var item = mData[position];
                ViewHolder viewHolder = ViewHolder.Get(mContext, convertView, parent, mItemLayoutId, position);
                if (OnGetView != null)
                    return this.OnGetView(position,convertView,parent,item,viewHolder);
                return convertView;
            }
        }
    }

    在使用时,只需要在Activity中注册OnGetView 事件,这样多个ListView的Adapter就只需要一个通用的Adapter就行了

       View OnGetView(int position,View  convertView,ViewGroup parent,NewsViewModel model,ViewHolder viewHolder)
            {
                viewHolder.SetText(Resource.Id.tv_news_title, model.Title);
                viewHolder.SetText(Resource.Id.tv_news_id, model.NewsID.ToString());
                viewHolder.SetText(Resource.Id.tv_news_desc, model.Desc);
                return viewHolder.GetConvertView();
            }
       List<NewsViewModel> list_news = new List<Adaptes.NewsViewModel>() {
                    new Adaptes.NewsViewModel () { NewsID =1,Title="测试标题124578cdascdas",Desc ="测acdsdas试内内容10c2da4s内容10c2da4s内容10c2da4s容10c2da4sc5das4cdas"},
                    new Adaptes.NewsViewModel () { NewsID =2,Title="cascascda24578cdascdas",Desc ="测dasc试内容10c2da4s内容10c2da4s内容10c2da4s内容10c2da4sc5das4cdas"},
                };
                Common1Adapter<NewsViewModel> adapter = new Adaptes.Common1Adapter<Adaptes.NewsViewModel>(this, list_news, Resource.Layout.item_listview_news);
                adapter.OnGetView += OnGetView;
                lv_news.Adapter = adapter;

    作者:张林
    标题:xamarin android 打造ListView万能适配器
    原文地址:http://blog.csdn.net/kebi007/article/details/77101947
    转载随意注明出处

    作者:kebi007 发表于2017/8/17 17:55:11 原文链接
    阅读:66 评论:0 查看评论

    Android开发学习(13)Retrofit访问REST服务

    $
    0
    0

    前段时间,有个博友在讨论区提了一个web封装的框架Retrofit,正好接上篇博客,我们搭建了一个简单的rest服务。

    简介

    准确来说,Retrofit 是一个 RESTful 的 HTTP 网络请求框架的封装,网络请求的工作本质上是 OkHttp 完成,而 Retrofit 仅负责 网络请求接口的封装,App应用程序通过 Retrofit 请求网络,实际上是使用 Retrofit 接口层封装请求参数、Header、Url 等信息,之后由 OkHttp 完成后续的请求操作,在服务端返回数据之后,OkHttp 将原始的结果交给 Retrofit,Retrofit根据用户的需求对结果进行解析.

    流程如下:
    flow

    步骤1:添加Retrofit库的依赖
    步骤2:创建 接收服务器返回数据 的类
    步骤3:创建 用于描述网络请求 的接口
    步骤4:创建 Retrofit 实例
    步骤5:创建 网络请求接口实例 并 配置网络请求参数
    步骤6:发送网络请求(异步 / 同步)
    封装了 数据转换、线程切换的操作
    步骤7: 处理服务器返回的数据

    引入步骤

    maven

    <dependency>
                <groupId>com.squareup.retrofit2</groupId>
                <artifactId>retrofit</artifactId>
                <version>2.2.0</version>
            </dependency>
    
            <dependency>
                <groupId>com.squareup.retrofit2</groupId>
                <artifactId>converter-wire</artifactId>
                <version>2.2.0</version>
            </dependency>
    
            <dependency>
                <groupId>com.squareup.retrofit2</groupId>
                <artifactId>converter-gson</artifactId>
                <version>2.2.0</version>
            </dependency>
    
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>2.8.1</version>
            </dependency>

    封装Retrofit相关

    package com.xvshu.android.web.common;
    
    import retrofit2.Retrofit;
    import retrofit2.converter.gson.GsonConverterFactory;
    import retrofit2.converter.wire.WireConverterFactory;
    
    /**
     * Created by xvshu on 2017/8/16.
     */
    public class RetrofitUtils {
    
        private static String baseUrl = "http://172.30.53.58:8080/rest/";
        private static Retrofit  retrofit = null;
    
        public static Retrofit getRetrofit(){
            if(retrofit==null){
                retrofit = new Retrofit.Builder()
                        .addConverterFactory(GsonConverterFactory.create())
                        .addConverterFactory(WireConverterFactory.create())
                        .baseUrl(baseUrl)
                        .build();
            }
            return retrofit;
        }
    
        public static <T> T genService(Class<T> tClass)throws InstantiationException {
            return getRetrofit().create(tClass);
        }
    }
    

    定义service

    package com.xvshu.android.web;
    
    import retrofit2.Call;
    import retrofit2.http.GET;
    import retrofit2.http.Path;
    
    /**
     * Created by xvshu on 2017/8/16.
     */
    public interface UseService {
        @GET("user/android/{userName}/{passWord}")
        Call<String> login(@Path("userName") String userName,@Path("passWord") String passWord);
    }
    

    具体访问

    重写登录代码:

    @Override
        public void onClick(View v) {
            final String userid = useridEt.getText().toString().trim();
            final String pass = passEt.getText().toString().trim();
            if(userid.equals("")){
                promptText.setText("用户名为空");
                return ;
            }
            if(pass.equals("")){
                promptText.setText("密码为空");
                return ;
            }
    
            Boolean isLoginSucess = false;
            try {
                UseService useService = RetrofitUtils.genService(UseService.class);
                Call<String> repos  =useService.login(userid,pass);
                repos.enqueue(new Callback<String>() {
                                  @Override
                                  public void onResponse(Call<String> call, Response<String> response) {
                                      Log.i("loginOnFailure", response.body().toString());
                                      if (response.body().equals("true")) {
                                          Toast.makeText(RetrofitActivity.this, "Rest登录成功!", Toast.LENGTH_LONG).show();
                                          if(cbRememberPass.isChecked()) {
                                              //记住用户名、密码、
                                              SharedPreferences.Editor editor = spUser.edit();
                                              editor.putString(spKeyUser, userid);
                                              editor.putString(spKeyPass,pass);
                                              editor.commit();
                                          }else{
                                              //不记住用户名、密码
                                              SharedPreferences.Editor editor = spUser.edit();
                                              editor.putString(spKeyUser, "");
                                              editor.putString(spKeyPass,"");
                                              editor.commit();
                                          }
    
                                          Intent intent_hello = new Intent(RetrofitActivity.this, HelloActivity.class);
                                          startActivity(intent_hello);
                                      } else {
                                          Toast.makeText(RetrofitActivity.this, "登录失败", Toast.LENGTH_LONG).show();
                                      }
    
                                  }
    
                                  @Override
                                  public void onFailure(Call<String> call, Throwable throwable) {
                                      Log.e("loginOnFailure", throwable.getMessage(),throwable);
                                  }
                              }
                );
    
            }catch (Exception ex){
                Log.e("=RetrofitUtils=>","RetrofitUtils.genService(UseService.class)",ex);
                isLoginSucess=false;
            }
    
    
        }

    效果:
    restlogin

    后台日志:
    log

    作者:xvshu 发表于2017/8/17 18:09:32 原文链接
    阅读:30 评论:0 查看评论

    阿里路由框架--ARouter 源码解析之启动Activity

    geopackage-android 开源的地理空间信息数据库存储

    $
    0
    0

    GeoPackage一个开放的地理空间信息的格式,基于标准的、平台独立的,可移植的、自描述、紧凑格式将地理空间信息。遵循OGC标准,数据库内核使用ormlite数据库。GeoPackage 数据库表可以转换成title进行地图绘制,有自已的表的边界范围等等。

    github网址:https://github.com/ngageoint/geopackage-android

    仓库地址:http://repo1.maven.org/maven2/mil/nga/geopackage/

    这篇博客示例代码下载:http://download.csdn.net/download/qq_16064871/9937001

    1、实现效果





    2、简单的介绍

    geopackage的数据库内核使用ormlite数据库

    FeatureDao 带有Geometry的表,就是说这张表可以存储点,线,面,多边形等等的存储,当然还有其它属性。

    AttributesDao 一般的数据库表。跟一般数据库字段基本是一样的。

    数据库存储格式是gpkg。这种格式也是地图格式之一。不过一般存储的数据相当于数据库数据,不能直接加载与地图显示(osmdroid 加载gpkg格式的离线底图)。需要转换成title。或者自已拿出数据,进行屏幕上地图分辨率的转换,进行绘制也是可以的。在这里就先不做过多介绍。

    下面主要介绍数据库的创建,增删查改。

    3、数据库操作

    创建数据库

            mGeoManager = GeoPackageFactory.getManager(mContext);
    
            File file = new File(FilePathManage.GetInstance().getMapDirectoryPath(), GeopackageConstants.CREATE_ELEVATION_TILES_DB_FILE_NAME); //有数据库,直接打开
    
            if (!file.exists()) {
                try {
                    if (!mGeoManager.createAtPath(GeopackageConstants.CREATE_ELEVATION_TILES_DB_NAME, FilePathManage.GetInstance().getMapDirectory())) {
                        Toast.makeText(mContext, "数据库创建失败", Toast.LENGTH_SHORT).show();
                        return;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    Toast.makeText(mContext, "open storage permission", Toast.LENGTH_SHORT).show();
                    return;
                }
            }


    创建FeatureDao 表,以及增删查改等示例代码

    package com.custom.geopackage;
    
    
    import android.content.ContentValues;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Locale;
    
    import mil.nga.geopackage.BoundingBox;
    import mil.nga.geopackage.GeoPackage;
    import mil.nga.geopackage.attributes.AttributesCursor;
    import mil.nga.geopackage.db.GeoPackageDataType;
    import mil.nga.geopackage.features.columns.GeometryColumns;
    import mil.nga.geopackage.features.user.FeatureColumn;
    import mil.nga.geopackage.features.user.FeatureCursor;
    import mil.nga.geopackage.features.user.FeatureDao;
    import mil.nga.geopackage.features.user.FeatureRow;
    import mil.nga.geopackage.geom.GeoPackageGeometryData;
    import mil.nga.geopackage.projection.ProjectionConstants;
    import mil.nga.geopackage.schema.TableColumnKey;
    import mil.nga.wkb.geom.Geometry;
    import mil.nga.wkb.geom.GeometryType;
    import mil.nga.wkb.geom.Point;
    
    /**
     * 点的操作类
     */
    public class PointGeoOperation extends GeoOperationBase {
    
        private FeatureDao mPointTableDao;
    
        public PointGeoOperation(GeoPackage geoPackage) {
            super(geoPackage);
        }
    
        public void init() {
            if (mGeoPackage != null)
            mPointTableDao = mGeoPackage.getFeatureDao(GeopackageConstants.POINT_TABLE);
        }
    
        public FeatureDao getSurveyDao() {
            return mPointTableDao;
        }
    
        public boolean createTable() {
            //不存在表才创建表
            if (mGeoPackage.isFeatureOrTileTable(GeopackageConstants.POINT_TABLE)){
                return true;
            }
    
            try {
                //创建表
                double minLat=-90+1; double maxLat=90-1;
                double minLon=-180+1;double maxLon= 180-1;
    
                BoundingBox boundingBox = new BoundingBox(minLon,
                        maxLon, minLat, maxLat);
    
                GeometryColumns geometryColumns = new GeometryColumns();
                //设置id
                geometryColumns.setId(new TableColumnKey(GeopackageConstants.POINT_TABLE,
                        "geom"));
                //todo GeometryType 可以选择 点 线 面 多边形等等
                geometryColumns.setGeometryType(GeometryType.POINT);
                geometryColumns.setZ((byte) 1);
    
                /**
                 * 首先构建表的列,然后再构建表对象
                 */
                int index = 0;
                List<FeatureColumn> columns = new ArrayList<FeatureColumn>();
                columns.add(FeatureColumn.createPrimaryKeyColumn(index++,
                        GeopackageConstants.FID));
                columns.add(FeatureColumn.createGeometryColumn(index++,
                        geometryColumns.getColumnName(), geometryColumns.getGeometryType(), false, null));
                columns.add(FeatureColumn.createColumn(index++, GeopackageConstants.POINT_NAME, GeoPackageDataType.TEXT, true, ""));
                columns.add(FeatureColumn.createColumn(index++, GeopackageConstants.CODE, GeoPackageDataType.TEXT, true, ""));
    
                if (mGeoPackage != null)
                    mGeoPackage.createFeatureTableWithMetadata(geometryColumns, boundingBox, ProjectionConstants.EPSG_WORLD_GEODETIC_SYSTEM, columns);
            }  catch (Exception e) {
                e.printStackTrace();
                return false;
            }
            return true;
        }
    
        public boolean deleteTable() {
            if (mGeoPackage != null)
            mGeoPackage.deleteTable(GeopackageConstants.POINT_TABLE);
            return true;
        }
    
        public boolean  insertData(Geometry geometry,ContentValues values) {
            try {
                GeoPackageGeometryData geomData = new GeoPackageGeometryData(
                        mPointTableDao.getGeometryColumns().getSrsId());
                geomData.setGeometry(geometry);
                FeatureRow row = mPointTableDao.newRow();
                row.setGeometry(geomData);
                row.setValue(GeopackageConstants.POINT_NAME,values.getAsString(GeopackageConstants.POINT_NAME));
                row.setValue(GeopackageConstants.CODE,values.getAsString(GeopackageConstants.CODE));
                mPointTableDao.insert(row);
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return true;
        }
    
        public boolean deleteData(int ID) {
            String queSQL = String.format(Locale.ENGLISH, GeopackageConstants.FID + "= %d",ID);
            return mPointTableDao.delete(queSQL,null) > 0;
        }
    
        private List<ContentValues> getAllSurveyPoint() {
            List<ContentValues> arrayslist = new ArrayList<ContentValues>();
            if (mPointTableDao != null) {
                FeatureCursor cursor = mPointTableDao.queryForAll();
                while (cursor.moveToNext()) {
                    ContentValues contentValues = cursor.getRow().toContentValues();
                    arrayslist.add(0, contentValues);
                }
            }
            return arrayslist;
        }
    
        public List<FeatureRow> getAllPoint() {
            List<FeatureRow> arrayslist = new ArrayList<FeatureRow>();
            if (mPointTableDao != null) {
                FeatureCursor cursor = mPointTableDao.queryForAll();
                while (cursor.moveToNext()) {
                    FeatureRow row = cursor.getRow();
                    Point point = (Point) row.getGeometry().getGeometry();
                    arrayslist.add(row);
                }
            }
            return arrayslist;
        }
    
        public void updateWithID(int ID,ContentValues values) {
            String queSQL = String.format(Locale.ENGLISH, GeopackageConstants.FID + "= %d",ID);
            if (mPointTableDao != null)
            mPointTableDao.update(values,queSQL,null);
        }
    
        public boolean updateGeomtry(FeatureRow row)
        {
            return  mPointTableDao.update(row) > 0;
        }
    }
    


    创建AttributesDao 表,增删查改的示例代码

    package com.custom.geopackage;
    
    import android.content.ContentValues;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Locale;
    import mil.nga.geopackage.GeoPackage;
    import mil.nga.geopackage.attributes.AttributesColumn;
    import mil.nga.geopackage.attributes.AttributesCursor;
    import mil.nga.geopackage.attributes.AttributesDao;
    import mil.nga.geopackage.attributes.AttributesRow;
    import mil.nga.geopackage.db.GeoPackageDataType;
    
    /**
     * 线操作类
     */
    public class LineGeoOperation extends GeoOperationBase{
    
        //普通表
        private AttributesDao commonDao;
    
        public LineGeoOperation(GeoPackage geoPackage) {
            super(geoPackage);
        }
    
        public void init() {
            commonDao = mGeoPackage.getAttributesDao(GeopackageConstants.LINE_TABLE);
        }
    
        public AttributesDao getSurveyDao() {
            return commonDao;
        }
    
        public boolean createTable() {
            //不存在表才创建表
            if (mGeoPackage.isTable(GeopackageConstants.LINE_TABLE)){
                return true;
            }
    
            int index = 1;
            List<AttributesColumn> additionalColumns = new ArrayList<>();
            additionalColumns.add(AttributesColumn.createColumn(index++, GeopackageConstants.POINT_NAME,
                    GeoPackageDataType.TEXT, false, ""));
            additionalColumns.add(AttributesColumn.createColumn(index++, GeopackageConstants.CODE,
                    GeoPackageDataType.TEXT, false, ""));
            //根据需要在继续添加,等等
            mGeoPackage.createAttributesTable(GeopackageConstants.LINE_TABLE, GeopackageConstants.FID, additionalColumns);
            return true;
        }
    
        public boolean deleteTable() {
            mGeoPackage.deleteTable(GeopackageConstants.POINT_TABLE);
            return true;
        }
    
        public boolean  insertData(ContentValues values) {
            try {
                AttributesRow row = commonDao.newRow();
                row.setValue(GeopackageConstants.POINT_NAME,values.getAsString(GeopackageConstants.POINT_NAME));
                row.setValue(GeopackageConstants.CODE,values.getAsString(GeopackageConstants.CODE));
                long fid = commonDao.insert(row);
    
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
    
            return true;
        }
    
        public boolean deleteData(int ID) {
            String queSQL = String.format(Locale.ENGLISH, GeopackageConstants.FID + "= %d",ID);
            return commonDao.delete(queSQL,null) > 0;
        }
    
        private List<ContentValues> getAllSurveyPoint() {
            AttributesCursor cursor = commonDao.queryForAll();
            List<ContentValues> arrayslist = new ArrayList<ContentValues>();
            while (cursor.moveToNext()) {
                ContentValues contentValues = cursor.getRow().toContentValues();
                arrayslist.add(0, contentValues);
            }
            return arrayslist;
        }
    
        public List<AttributesRow> getAllPoint() {
            AttributesCursor cursor = commonDao.queryForAll();
            List<AttributesRow> arrayslist = new ArrayList<AttributesRow>();
            while (cursor.moveToNext())
            {
                AttributesRow row = cursor.getRow();
                arrayslist.add(row);
            }
            return arrayslist;
        }
    
        public void updateWithID(int ID,ContentValues values) {
            String queSQL = String.format(Locale.ENGLISH, GeopackageConstants.FID + "= %d",ID);
            commonDao.update(values,queSQL,null);
        }
    
        public boolean updateAttributesRow(AttributesRow row)
        {
            return  commonDao.update(row) > 0;
        }
    }
    

    这两个类的操作管理示例代码,就是一些初始化,打开数据库等

    package com.custom.geopackage;
    
    import android.app.Activity;
    import android.content.ContentValues;
    import android.content.Context;
    import android.database.Cursor;
    import android.database.sqlite.SQLiteDatabase;
    import android.widget.Toast;
    
    import java.io.File;
    
    import mil.nga.geopackage.GeoPackage;
    import mil.nga.geopackage.GeoPackageManager;
    import mil.nga.geopackage.db.metadata.GeoPackageMetadata;
    import mil.nga.geopackage.db.metadata.GeoPackageMetadataDb;
    import mil.nga.geopackage.factory.GeoPackageFactory;
    
    public class CustomGeoPackageManager {
    
        private static GeoPackageManager mGeoManager;
        private Context mContext;
        private static CustomGeoPackageManager myGeoPackageManager = null;
        GeoPackage mGeoPackage;
        PointGeoOperation mPointGeoOperation = null;
        LineGeoOperation mLineGeoOperation = null;
        PolygonGeoOperation mPolygonGeoOperation = null;
    
        private CustomGeoPackageManager(Activity context) {
            this.mContext = context;
    
            mGeoManager = GeoPackageFactory.getManager(mContext);
    
            File file = new File(FilePathManage.GetInstance().getMapDirectoryPath(), GeopackageConstants.CREATE_ELEVATION_TILES_DB_FILE_NAME); //有数据库,直接打开
    
            if (!file.exists()) {
                try {
                    if (!mGeoManager.createAtPath(GeopackageConstants.CREATE_ELEVATION_TILES_DB_NAME, FilePathManage.GetInstance().getMapDirectory())) {
                        Toast.makeText(mContext, "数据库创建失败", Toast.LENGTH_SHORT).show();
                        return;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    Toast.makeText(mContext, "open storage permission", Toast.LENGTH_SHORT).show();
                    return;
                }
            }
    
            initGeopackageMetadata();
    
            mGeoPackage = mGeoManager.open(GeopackageConstants.CREATE_ELEVATION_TILES_DB_NAME);
    
            if (mGeoPackage == null) return;
            getPointGeoOperation().createTable();
            getPointGeoOperation().init();
    
            getLineGeoOperation().createTable();
            getLineGeoOperation().init();
        }
    
        public static CustomGeoPackageManager getInstance(Activity context) {
            if (myGeoPackageManager == null && context != null)
                synchronized (CustomGeoPackageManager.class) {
                    if (myGeoPackageManager == null)
                        myGeoPackageManager = new CustomGeoPackageManager(context);
                }
    
            return myGeoPackageManager;
        }
    
        public GeoPackageManager getGeoPackageManager() {
            if (mGeoManager == null) {
                GeoPackageFactory.getManager(mContext);
            }
            return mGeoManager;
        }
    
        public PointGeoOperation getPointGeoOperation() {
            if (mPointGeoOperation == null) {
                mPointGeoOperation = new PointGeoOperation(mGeoPackage);
            }
            return mPointGeoOperation;
        }
    
        public LineGeoOperation getLineGeoOperation() {
            if (mLineGeoOperation == null) {
                mLineGeoOperation = new LineGeoOperation(mGeoPackage);
            }
            return mLineGeoOperation;
        }
    
        public PolygonGeoOperation getPolygonGeoOperation() {
            if (mPolygonGeoOperation == null) {
                mPolygonGeoOperation = new PolygonGeoOperation(mGeoPackage);
            }
            return mPolygonGeoOperation;
        }
    
        private void initGeopackageMetadata() {
            //TODO 卸载软件时候会把geopackage数据库内部存储的数据路径卸载掉,这里为了查找加上去
            File file = new File(FilePathManage.GetInstance().getMapDirectoryPath());
            File[] projectList = file.listFiles();
    
            GeoPackageMetadataDb dbHelper = new GeoPackageMetadataDb(mContext);
            dbHelper.open();
            SQLiteDatabase db = dbHelper.getWritableDatabase();
            if (projectList == null ) return;
            for (File file1 : projectList) {
                String projectName = file1.getName();
                Cursor query = db.query(GeoPackageMetadata.TABLE_NAME, null, "name=?", new String[]{projectName}, null, null, null);
                if (query.getCount() <= 0) {
                    ContentValues values = new ContentValues();
                    values.put(GeoPackageMetadata.COLUMN_NAME, projectName);
                    values.put(GeoPackageMetadata.COLUMN_EXTERNAL_PATH, FilePathManage.GetInstance().getMapDirectoryPath() + "/" + projectName + ".gpkg");
                    db.insert(GeoPackageMetadata.TABLE_NAME, null, values);
                }
                query.close();
    
            }
        }
    }
    

    看看activity怎么进行数据示例调用

       @Override
        public void onClick(View v) {
            switch (v.getId()){
                case R.id.button: //删除
                    String temp = mIDeditText.getText().toString().trim();
                    if(temp.isEmpty()) return;
                    int id = Integer.parseInt(temp);
                    if(id>0){
                        if(CustomGeoPackageManager.getInstance(this).getPointGeoOperation().deleteData(id)){
                            Toast.makeText(this,"delete succeed",Toast.LENGTH_SHORT).show();
                        }else
                            Toast.makeText(this,"not exit id",Toast.LENGTH_SHORT).show();
                    }
                    break;
                case R.id.button1: //增加
                    Geometry geometry = new Point(23,113);
                    ContentValues values = new ContentValues();
                    values.put(GeopackageConstants.POINT_NAME,"point_name");
                    values.put(GeopackageConstants.CODE,"test_code");
                    if(CustomGeoPackageManager.getInstance(this).getPointGeoOperation().insertData(geometry,values)){
                        Toast.makeText(this,"add succeed",Toast.LENGTH_SHORT).show();
                    }
                    break;
                case R.id.button2: //修改
                    //编辑点返回参数
                    if(CustomGeoPackageManager.getInstance(this).getPointGeoOperation().getAllPoint().size()>3){
                        FeatureRow row = CustomGeoPackageManager.getInstance(this).getPointGeoOperation().getAllPoint().get(2);
                        GeoPackageGeometryData geomData = new GeoPackageGeometryData(
                                CustomGeoPackageManager.getInstance(this).getPointGeoOperation().getSurveyDao().getGeometryColumns().getSrsId());
                        Point point = new Point(true,false,24, 114);
                        point.setZ(new Double(114));
                        geomData.setGeometry(point);
                        row.setGeometry(geomData);
                        row.setValue(GeopackageConstants.POINT_NAME,"point_name2");
                        row.setValue(GeopackageConstants.CODE,"test_code");
                        if(CustomGeoPackageManager.getInstance(this).getPointGeoOperation().updateGeomtry(row)){
                            Toast.makeText(this,"update succeed",Toast.LENGTH_SHORT).show();
                        }
                    }
                    break;
                case R.id.button3: //查询
                    String strResult = "the table have "
                            + String.valueOf(CustomGeoPackageManager.getInstance(this).getPointGeoOperation().getAllPoint().size());
    
                    Toast.makeText(this,strResult,Toast.LENGTH_SHORT).show();
                    break;
                /////////////////////////////////////////////////////////////////////////////
                //普通表操作
    
                case R.id.button5: //删除
                    String temp1 = mIDeditText1.getText().toString().trim();
                    if(temp1.isEmpty()) return;
                    int id1 = Integer.parseInt(temp1);
                    if(id1>0){
                        if(CustomGeoPackageManager.getInstance(this).getLineGeoOperation().deleteData(id1)){
                            Toast.makeText(this,"delete succeed",Toast.LENGTH_SHORT).show();
                        }else
                            Toast.makeText(this,"not exit id",Toast.LENGTH_SHORT).show();
                    }
                    break;
                case R.id.button4: //增加
                    ContentValues values1 = new ContentValues();
                    values1.put(GeopackageConstants.POINT_NAME,"line_name");
                    values1.put(GeopackageConstants.CODE,"test_code");
                    if(CustomGeoPackageManager.getInstance(this).getLineGeoOperation().insertData(values1)){
                        Toast.makeText(this,"add succeed",Toast.LENGTH_SHORT).show();
                    }
                    break;
                case R.id.button6: //修改
                    if(CustomGeoPackageManager.getInstance(this).getLineGeoOperation().getAllPoint().size()>3){
                        AttributesRow row = CustomGeoPackageManager.getInstance(this).getLineGeoOperation().getAllPoint().get(2);
                        row.setValue(GeopackageConstants.POINT_NAME,"point_name2");
                        row.setValue(GeopackageConstants.CODE,"test_code");
                        if(CustomGeoPackageManager.getInstance(this).getLineGeoOperation().updateAttributesRow(row)){
                            Toast.makeText(this,"update succeed",Toast.LENGTH_SHORT).show();
                        }
                    }
                    break;
                case R.id.button7: //查询
                    String strResult1 = "the table have "
                            + String.valueOf(CustomGeoPackageManager.getInstance(this).getLineGeoOperation().getAllPoint().size());
    
                    Toast.makeText(this,strResult1,Toast.LENGTH_SHORT).show();
                    break;
            }
        }

    数据库存储涉及到在机器上存储读写的权限

        <uses-permission android:name="android.permission.INTERNET" />
        <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
        <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <uses-permission android:name="android.permission.VIBRATE" />
        <uses-permission android:name="android.permission.WAKE_LOCK" />

    就先介绍到这里








    作者:qq_16064871 发表于2017/8/18 8:59:12 原文链接
    阅读:0 评论:0 查看评论

    Unity_往复运动_057

    $
    0
    0

    题目:创建脚本实现沿着z轴方向移动,当到达10后,反向移动10,如此不断重复

    using UnityEngine;
    using System.Collections;
    
    public class Move : MonoBehaviour {
        //设置一个标志位 当intFlag 为1的时候表示向Z轴的正方向移动 当intFlag 为-1的时候表示向Z轴的负方向移动 
        int intFlag = 1;
        // Update is called once per frame
        void Update ()
        {
            //当Z轴位置大于10的时候就向负方向移动
            if (transform.position.z>=10)
            {
                intFlag = -1;
            }
            //当Z轴位置小于0的时候就向正方向移动
            if (transform.position.z<0)
            {
                intFlag = 1;
            }
    
            transform.Translate(new Vector3(0, 0, intFlag) * Time.deltaTime * 2);
        }
    }
    

    第二种方法

    using UnityEngine;
    using System.Collections;
    
    public class Move : MonoBehaviour {
        // Update is called once per frame
        void Update ()
        {
            //利用Mathf的pingpong方法达到往复的运动
            transform.position = new Vector3(0,0,Mathf.PingPong(Time.time,10));
        }
    }

    效果如下:
    这里写图片描述

    作者:yy763496668 发表于2017/8/16 21:32:57 原文链接
    阅读:26 评论:0 查看评论

    Unity_宠物跟随效果_058

    $
    0
    0

    下面的案例是实现宠物跟随的效果
    下面的代码是角色移动的脚本

    using UnityEngine;
    using System.Collections;
    
    public class PlayerMove : MonoBehaviour 
    {
        // Update is called once per frame
        void Update () {
            //按W键 前进
            if (Input.GetKey(KeyCode.W))
            {
                transform.position += transform.forward * 1 * Time.deltaTime;
            }
            //按S键 后退
            if (Input.GetKey(KeyCode.S))
            {
                transform.position += transform.forward * -1 * Time.deltaTime;
            }
            //按A键 向左旋转
            if (Input.GetKey(KeyCode.A))
            {
                transform.Rotate(transform.up, -30 * Time.deltaTime);
            }
            //按D键 向右旋转
            if (Input.GetKey(KeyCode.D))
            {
                transform.Rotate(transform.up, -30 * Time.deltaTime);
            }
        }
    }

    以下是宠物绑定的脚本

    using UnityEngine;
    using System.Collections;
    
    public class Follow : MonoBehaviour {
    
        public Transform target;
        public Vector3 offset;
        //向后的距离
        public float backDistance = 2;
    
        //高度
        public float topDistance = 2;
    
        //在LateUpdate中进行物理
        void LateUpdate()
        {
            //设置偏移量
            offset = -target.forward * backDistance + target.up * topDistance;
            //使用插值,让宠物有一个平滑的移动
            transform.position = Vector3.Lerp(transform.position, target.position + offset,Time.deltaTime);
            //宠物的旋转和玩家的旋转保持一致
            transform.rotation = target.rotation;
        }
    }
    

    宠物跟随效果:
    宠物跟随效果

    作者:yy763496668 发表于2017/8/16 23:03:20 原文链接
    阅读:24 评论:0 查看评论

    多线程中断机制

    $
    0
    0

    友情推荐:

    1. 线程池原理
    2. 深入Thread.sleep

    在 java中启动线程非常容易,大多数情况下是让一个线程执行完自己的任务然后自己停掉。一个线程在未正常结束之前, 被强制终止是很危险的事情. 因为它可能带来完全预料不到的严重后果,比如会带着自己所持有的锁而永远的休眠,迟迟不归还锁等。在当前的api中,Thread.suspend、Thread.stop等方法都被Deprecated了,线程只能用interrupt中断,而且不是立刻中断,只是发了一个类似于信号量的东西,通过修改了被调用线程的中断状态来告知那个线程, 说它被中断了,至于什么时候中断,这个有系统判断,会在一个合适的时候进行中断处理。

    /**
     * Created by Zero on 2017/8/17.
     */
    public class ThreadTest1 {
        public static void main(String[] args) {
            NThread nThread = new NThread();
            System.out.println("interrupt执行前");
            nThread.start();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            nThread.interrupt();
            System.out.println("interrupt执行后");
        }
    
        /**
         * 测试多线程的中断机制
         */
        static class NThread extends Thread{
            @Override
            public void run() {
                super.run();
                while(true){
                    System.out.println("依然存活...");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    这里写图片描述

    在上面代码中,线程睡醒之后,调用thread线程的interrupt方法,catch到InteruptedException,设置标志位。interrupt()方法相当于线程睡着的时候一盆凉水来吵醒它,线程表示不开心,并向你抛出一个大大的异常以示不满。

    这里写图片描述

    • 首先:线程中断是一种协作机制,调用线程对象的interrupt方法并不一定就中断了正在运行的线程,它只是要求线程自己在合适的时间中断自己。
    • 其次:任务的方法必须是可中断的,即方法必须抛出InterruptedException。

    由此可见,interrupt() 方法并不能立即中断线程,该方法仅仅告诉线程外部已经有中断请求,至于是否中断还取决于线程自己。在Thread类中除了interrupt() 方法还有另外两个非常相似的方法:interrupted 和 isInterrupted 方法,下面来对这几个方法进行说明:

    • interrupt 此方法是实例方法,用于告诉此线程外部有中断请求,并且将线程中的中断标记设置为true,而不是立即中断。
    • interrupted 此方法是类方法,用来判断当前线程是否已经中断。线程的中断状态由该方法清除。
    • isInterrupted 此方法是实例方法,用来判断线程是否已经中断。线程的中断状态不受该方法的影响。

    总结:java线程中断机制通过调用Thread.interrupt() 方法来做的,这个方法通过修改了被调用线程的中断状态来告知那个线程说它被中断了。对于非阻塞中的线程,只是改变了中断状态,即Thread.isInterrupted() 将返回true;对于可取消的阻塞状态中的线程,比如等待在这些函数上的线程,Thread.sleep()、Object.wait()、Thread.join(), 这个线程收到中断信号后,会抛出InterruptedException,同时会把中断状态置回为true。但调用Thread.interrupted()会对中断状态进行复位。

    /**
     * Created by Zero on 2017/8/17.
     */
    public class ThreadTest1 {
        public static void main(String[] args) {
            NThread nThread = new NThread();
            nThread.start();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("cancel执行前" + System.currentTimeMillis());
            nThread.cancel();
        }
    
        /**
         * 测试多线程的中断机制
         */
        static class NThread extends Thread {
    
            private boolean isCancel;
    
            @Override
            public void run() {
                while (!isCancel) {
                    System.out.println("依然存活...");
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("while结束" + System.currentTimeMillis());
            }
    
            public void cancel() {
                this.isCancel = true;
            }
        }
    }

    执行结果:

    依然存活...
    cancel执行前1502964413042
    while结束1502964415042

    机智的你,此刻一定发现执行前后相差2000毫秒,难道cancel()方法执行了2000毫秒?这纯属扯淡,里面没有任何耗时操作,就是一个赋值而已,其实是子线程的退出,前提条件是while循环结束,当且仅当cancel标示设置为true的瞬间立马执行while的判断,此时的时间差才可以忽略不计(1毫秒内),但是当我们调用cancel方法将isCancel 标记设置为true 时,while循环里面有一个耗时操作(休眠5000毫秒),只有等待耗时操作执行完毕后才会去检查这个标记,所以cancel方法和线程退出中间有时间间隔。

    接下来,我们通过interrupt 和 isinterrupt 方法来中断线程,代码如下:

    /**
     * Created by Zero on 2017/8/17.
     */
    public class ThreadTest1 {
        public static void main(String[] args) {
            NThread nThread = new NThread();
            nThread.start();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("interrupt执行前"+System.currentTimeMillis());
            nThread.interrupt();
        }
    
        /**
         * 测试多线程的中断机制
         */
        static class NThread extends Thread{
    
            @Override
            public void run() {
                while(!interrupted()){
                    System.out.println("依然存活...");
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        System.out.println("InterruptedException");
                        Thread.currentThread().interrupt();
                    }
                }
                System.out.println("interrupt执行后"+System.currentTimeMillis());
            }
        }
    }
    
    依然存活...
    interrupt执行前1502965915683
    InterruptedException
    interrupt执行后1502965915683

    这次立马中断了,是因为在开启子线程后,经过执行3000毫秒的休眠,线程执行了interrupt 方法,此时子线程的5000毫秒休眠还没结束,就像上述所说的睡眠中被一盆冷水吵醒,很不开心的抛了异常,Thread.currentThread().interrupt() 改变了线程的标记状态,在抛出InterruptedException 的同时,线程的中断标志被清除了,再次执行while循环语句的时候,!interrupted() 此时是false,便不再执行while语句。

    此处如果去掉Thread.currentThread().interrupt() ,线程便会无休止的执行下去,此处就不上代码了,就注释掉这一行,运行就可以看到效果,经常看到一些代码在catch中不作任何处理,其实有时候这样是很危险的,此处已经证明。

    两点建议:
    1. 除非你知道线程的中断策略,否则不应该中断它。
    2. 任务代码不该猜测中断对执行线程的含义。遇到InterruptedException异常时,不应该将其捕获后“吞掉”,而应该继续向上层代码抛出。

    微信扫我,^_^

    作者:pangpang123654 发表于2017/8/18 12:02:55 原文链接
    阅读:255 评论:2 查看评论

    Qt之在运行时加载共享库

    $
    0
    0

    简述

    在 Windows 上,共享库由 *.dll 表示;在 Linux 上,由 *.so 表示。一个共享库中的符号被设计为导出的,以便客户端可以从中导入符号。

    要使用共享库,除了 Qt之创建与使用共享库 中介绍的方式之外,Qt 还提供了一种机制,可以在运行时加载共享库,通过 QLibrary 来实现。

    版权所有:一去丶二三里,转载请注明出处:http://blog.csdn.net/liang19890820

    认识 QLibrary

    QLibrary 用于在运行时加载共享库,一个完整的加载流程大概分为以下几步:

    • 构造 QLibrary 实例
    • setFileName():指定共享库的文件名(也可以通过 QLibrary 的构造函数来设置)
    • load():动态加载共享库(isload() 检查加载是否成功)
    • resolve():解析共享库中的符号(如果库还没有加载,那么 resolve() 将隐式地尝试加载)
    • unload():卸载共享库

    QLibrary 有一个很强大的特性 - 在运行时对共享库提供了平台独立的访问。对于库路径,QLibrary 在内部会做以下处理:

    • 如果文件名是绝对路径,则首先尝试加载该路径。若无法找到文件,则尝试使用不同平台的特定文件前缀(例如:Unix 和 Mac 上的“lib”)和后缀(例如:Unix 上的“.so”、Mac 上的“.dylib”、或 Windows 中的“dll“)的名称。
    • 如果文件路径不是绝对的,那么 QLibrary 会修改搜索顺序,首先尝试系统特定的前缀和后缀,然后再指定文件路径。

    这使得可以指定仅由其 basename(即:没有 .dll.so 后缀)标识的共享库,因此相同的代码可以在不同的操作系统上工作。尽管如此,但仍建议尽量减少查找库的次数。

    注意: 一个共享库可以被 QLibrary 的多个实例访问,加载完成后,库将一直保存在内存中,直到应用程序终止。在使用 unload() 卸载库时,如果 QLibrary 的其他实例使用了相同的库,那么调用将失败,并且只有当每个实例都调用 unload() 时才会卸载。

    使用 QLibrary 的优点

    在运行时使用 QLibrary 加载共享库,有很多优点:

    • 无需使用头文件和 lib 文件,就可以编译应用程序。
    • 只需将 dll 文件和可执行程序放在一起
    • 可以在没有 dll 的情况下启动可执行程序,因为 dll 将在运行时(按需)加载。
    • 有助于生成一个较小的可执行程序

    创建共享库

    和前面一样,在 Qt Creator 中创建两个项目:

    • SharedLib:是一个 C++ 共享库项目,其中有一个导出符号。
    • SharedLibClient:是一个 Qt 控制台应用程序,在运行时调用 SharedLib。

    创建共享库的步骤可参考:Qt之创建与使用共享库

    sharedlib_global.h 可以确保正确的宏能够被调用:

    #ifndef SHAREDLIB_GLOBAL_H
    #define SHAREDLIB_GLOBAL_H
    
    #include <QtCore/qglobal.h>
    
    #if defined(SHAREDLIB_LIBRARY)
    #  define SHAREDLIBSHARED_EXPORT Q_DECL_EXPORT
    #else
    #  define SHAREDLIBSHARED_EXPORT Q_DECL_IMPORT
    #endif
    
    #endif // SHAREDLIB_GLOBAL_H

    sharedlib.h 包含了导出的符号:

    #ifndef SHAREDLIB_H
    #define SHAREDLIB_H
    
    #include "sharedlib_global.h"
    
    extern "C" {
        SHAREDLIBSHARED_EXPORT int subtract(int x, int y);
    }
    
    #endif // SHAREDLIB_H

    sharedlib.cpp 包含了具体的实现:

    #include "sharedlib.h"
    
    int subtract(int x, int y)
    {
        return x - y;
    }

    subtract() 符号从 SharedLib 库中导出为 C 函数,函数被包裹在一 个 extern "C" 块中,并使用 Q_DECL_EXPORT 从 DLL 中显式导出。

    在运行时加载共享库

    创建一个简单的客户端(SharedLibClient) - Qt Console Application,它将使用 SharedLib 库中的 subtract() 函数。效果如下:

    这里写图片描述

    在 main.cpp 文件中,使用 QLibrary 在运行时加载共享库:

    #include <QCoreApplication>
    #include <QLibrary>
    #include <qDebug>
    
    int main(int argc, char *argv[])
    {
        QCoreApplication a(argc, argv);
    
        // SharedLibd.dll 与可执行程序位于同一目录
        QLibrary lib("SharedLibd");
    
        // 加载共享库
        if (lib.load()) {
            typedef int (*Fun)(int, int);
            // 解析符号
            Fun sub = (Fun) lib.resolve("subtract");
            if (sub) {
                int result = sub(5, 2);
                qDebug() << result;
            } else {
                qDebug() << "Can not resolve subtract";
            }
            // 卸载共享库
            lib.unload();
        } else {
            qDebug() << lib.errorString();
            return 0;
        }
        return a.exec();
    }

    在 Debug 模式下运行项目,结果会显示在控制台输出上。

    作者:u011012932 发表于2017/8/18 16:56:56 原文链接
    阅读:0 评论:0 查看评论

    一个界面管理多种状态方案

    $
    0
    0

    前言

    很多项目中进行会出现,一个状态对应一种现实样式。是统一在一个界面处理,还是分开n多页面处理呢,
    这篇给出了一点个人建议,及是实现方案。

    效果图

    这里写图片描述

    方案一: 一个activity 全部进行控制。所有的状态。完全靠堆代码。(不推荐)
    方案二 一个activity 中包含BaseFragment 然后 subfragment中实现不同状态。(推荐)
    方案3

    方案一:

    优点:

     代码量少,处理简单ui问题。完全可以写到一个activity中。
    

    缺点:

     后期维护,绝对是噩梦,7,8种状态,来回变换,各种逻辑穿插。导致类膨胀。
    

    方案二:

    优点:

     结构简单,单个类代码量少,清晰。便于维护,扩展性高,符合单一职责
    

    缺点:

     前期工作量相对大于方案一
    

    2.方案二 uml类图:

    这里写图片描述

    抽取基类就是为了复用。 能复用的尽量放入基类,统一入口。

    基类职责:

      1.N多subFragment 公用一套 xml。(如果使用N个布局对应N个subfragment,重复工作量太大。)初始化root布局
    

    2.特殊布局,分发给subFragment去处理,公共区域更新由基类完成。

    3.统一处理一些逻辑。这里是处理状态对应文案,以及文案颜色等

    4.作为工厂类,生成对应样式的subFragment

    subFragmnet 职责:

    1.更新ui
    2.隐藏不需要的xml 组件。默认展示所有xml组件。

    Activity :

    1.作为BaseFragment容器,当然这是常识,fragment无法单独用来展示ui,必须依附于Activity。
    2. 作为Mvp模式中的view,网络请求成功会先走到Activity中,然后通过baseFramgnet,分发数据到具体subFragment中。(fragmen同样可以作为View,这里为了统一处理,较小难度。用Activity做为View)

    Eg:mvp模式不在这里介绍。有需要了解的自行前往google 官方sample地址查看。

    说道这里觉得少了点:

    贴代码:

    subFragment

    public class subFragment1 extends BaseBorrowFragment {
    
        private static BorrowCancelFragment instance;
    
        public static BorrowCancelFragment getInstance() {
            if (instance == null) {
                instance = new BorrowCancelFragment();
            }
            return instance;
        }
    
        @Override
        public View initLayout(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            return super.initLayout(inflater, container, savedInstanceState);
        }
    
        @Override
        void updateUi(LoanDetailResponse loanDetailResponse) {
            LoanApplyItemEntity loanApplyItem = loanDetailResponse.loanApplyItem;
            nyBorrowMoney.setRightString(getUnitString(loanApplyItem.applyAmount));
            nyBorrowDeadline.setRightString(getDataStyle(loanApplyItem));
            nyBorrowDate.setRightString(loanApplyItem.lendTime);
    
            nyPayWay.setRightString(loanApplyItem.repaymentTypeStr);
            nyPayCancelReason.setRightString(loanApplyItem.cancelReason);
        }
    
        @Override
        public void init() {
    
        }
    
        @Override
        public void initView() {
            super.initView();
        }
    
        @Override
        public void initValue() {
            nyBorrowProtocol.setVisibility(View.GONE);
            nyNeedPayDate.setVisibility(View.GONE);
            nyAlreadyMoney.setVisibility(View.GONE);
            nyWaitMoney.setVisibility(View.GONE);
            nyRealPayDate.setVisibility(View.GONE);
            nyRealPayMoney.setVisibility(View.GONE);
        }
    
        @Override
        public void initListener() {
    
        }
    }
    

    baseFragment

    public abstract class BasexFragment extends BaseFragment {
    
        protected Activity baseActivity;
        protected OnContactSelectedListener mListener; //回调监听
        protected LinearLayout lineLayer1, lineLayer2, lineLayer3;
        protected NyCompatView nyBorrowMoney, nyBorrowDeadline, nyBorrowDate,
                nyBorrowProtocol, nyPayWay, nyNeedPayDate, nyAlreadyMoney,
                nyWaitMoney, nyRealPayDate, nyRealPayMoney, nyPayCancelReason;
    
        private TextView txtCommAccount;
        private TextView txtCommDeadline;
        private TextView txtCommBorrowState;
        private TextView txtChangeValue;
    
    
        @Override
        public View initLayout(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_common_borrow_detail, container, false);
        }
    
        @Override
        public void onAttach(Activity activity) {
            super.onAttach(activity);
            this.baseActivity = activity;
            try {
                mListener = (OnContactSelectedListener) activity;
            } catch (ClassCastException e) {
                throw new ClassCastException(activity.toString() + "must implement OnContactSelectedListener");
            }
        }
    
        @Override
        public void initView() {
    
            txtChangeValue = findView(R.id.txt_change_value);
            txtCommAccount = findView(R.id.txt_comm_account);
            txtCommDeadline = findView(R.id.txt_comm_deadline);
            txtCommBorrowState = findView(R.id.txt_comm_borrow_state);
    
    
            lineLayer1 = findView(R.id.line_layer1);
            lineLayer2 = findView(R.id.line_layer2);
            lineLayer3 = findView(R.id.line_layer3);
            nyBorrowMoney = findView(R.id.ny_borrow_money);
            nyBorrowDeadline = findView(R.id.ny_borrow_deadline);
            nyBorrowDate = findView(R.id.ny_borrow_date);
            nyBorrowProtocol = findView(R.id.ny_borrow_protocol);
            nyPayWay = findView(R.id.ny_pay_way);
            nyNeedPayDate = findView(R.id.ny_need_pay_date);
            nyAlreadyMoney = findView(R.id.ny_already_money);
            nyWaitMoney = findView(R.id.ny_wait_money);
            nyRealPayDate = findView(R.id.ny_real_pay_date);
            nyRealPayMoney = findView(R.id.ny_real_pay_money);
            nyPayCancelReason = findView(R.id.ny_pay_cancel_reason);
        }
    
        /**
         * 更新ui
         *
         * @param loanDetailResponse
         */
        protected void updateUi(LoanDetailResponse loanDetailResponse) {
            refreshHeaderUi(loanDetailResponse);
        }
    
        public void refreshHeaderUi(LoanDetailResponse response) {
            String[] strings = getShowTextValue(response.loanApplyItem);
            txtCommAccount.setText(strings[1]);
            txtChangeValue.setText(strings[0]);
    
            txtCommDeadline.setText(response.loanApplyItem.repaymentTime);
            txtCommBorrowState.setText(response.loanApplyItem.sItemStatus);
            txtCommBorrowState.setTextColor(getStateColor(response.loanApplyItem.itemStatus));
        }
    
    
        protected String getFormatData(LoanApplyItemEntity loanApplyItemEntity) {
            int period = loanApplyItemEntity.period;
            int period_unit = loanApplyItemEntity.periodUnit;
            return FormatUtil.getProTimeStr(period, period_unit);
        }
    
    
    
        protected String getUnitString(String applyAmount) {
            return "¥" + applyAmount;
        }
    
    
        public static BaseBorrowFragment borrowFactory(int state) {
            BaseBorrowFragment baseBorrowFragment = null;
            switch (state) {
                case 1:
                    baseBorrowFragment = subFragment1.getInstance();
                    break;
                case 2:
                    baseBorrowFragment = subFragment2.getInstance();
                    break;
                case 3:
                    baseBorrowFragment = subFragment3.getInstance();
                    break;
                case 4:
                    baseBorrowFragment = subFragment4.getInstance();
                    break;
                case 5:
                    baseBorrowFragment = subFragment5.getInstance();
                    break;
                case 6:
                    baseBorrowFragment = subFragment6.getInstance();
                    break;
                case 7:
                    baseBorrowFragment = subFragment7.getInstance();
                    break;
            }
            return baseBorrowFragment;
        }
    
    
        private int getStateColor(int currentStateValue) {
            int color = 0;
            switch (currentStateValue) {
                case 1:
                case 2:
                    color = getResources().getColor(R.color.color_00B0FF);
                    break;
                case 3:
                case 4:
                    color = getResources().getColor(R.color.color_ff6f00);
                    break;
                case 5:
                case 6:
                case 7:
                    color = getResources().getColor(R.color.txt_color_333333);
                    break;
    
                default:
            }
            return color;
    
        }
    
    
    }
    
    作者:o279642707 发表于2017/8/18 17:05:11 原文链接
    阅读:0 评论:0 查看评论

    React Native入门(八)之网络请求Fetch

    $
    0
    0

    前言

    最近有些事情要忙,RN相关的内容没有更新,现在就来了解一下RN中进行网络请求相关的内容!

    介绍

    React Native提供了和web标准一致的Fetch API,用于满足开发者访问网络的需求。
    关于Fetch API的相关内容,可以到下边网站了解:
    https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
    我们这先大致说一下在RN中使用Fetch API 进行网络请求的例子,然后再去了解Fecth的相关内容!

    使用Fetch

    Fetch API 提供一个fetch()方法,要从任意地址获取内容的话,只需简单地将网址作为参数传递给fetch方法即可。

    fetch('https://facebook.github.io/react-native/movies.json')

    Fetch API 是基于 Promise 设计,那么关于 Promise的内容可以参考下面的教程:
    https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
    这里简单提一下:
    我们知道网络请求是一个天然的异步操作,Fetch 方法会返回一个Promise,这种模式可以简化异步风格的代码。Promise 对象是一个返回值的代理,它允许你为异步操作的成功或失败指定处理方法。 这使得异步方法可以像同步方法那样返回值:异步方法会返回一个包含了原返回值的promise 对象来替代原返回值。

    Promise有两个重要的方法,分别是resolve方法和reject方法。如果异步操作成功,则用resolve方法将Promise对象的状态变为“成功”(即从pending变为resolved);如果异步操作失败,则用reject方法将状态变为“失败”(即从pending变为rejected)。

    好了,知道了这些之后,我们来说一下如果处理网络请求返回的结果!

    Fetch 方法会返回一个Promise,一个 Promiseresolve时会回传 Response 对象,也就是说请求成功,会回传一个Response 对象,这个Response 对象就包含了所有返回结果的信息!

    好,下面看一个例子,也是RN中文网的例子,这里做了一些改动!
    首先,我们在浏览器看一下网络请求到的内容:

    {
      "title": "The Basics - Networking",
      "description": "Your app fetched this from a remote endpoint!",
      "movies": [
        { "title": "Star Wars", "releaseYear": "1977"},
        { "title": "Back to the Future", "releaseYear": "1985"},
        { "title": "The Matrix", "releaseYear": "1999"},
        { "title": "Inception", "releaseYear": "2010"},
        { "title": "Interstellar", "releaseYear": "2014"}
      ]
    }

    这个一个请求到一些电影信息的json数据。
    这里我们使用fetch发送网络请求,然后将得到的json数据使用列表展示出来,下面看一下代码:

    class NetText extends Component {
      constructor(props) { //构造器
        super(props);
        this.state = {  //定义的三个状态
          title: '',
          description: '',
          movies: ''
        };
      }
    
      //列表点击事件
      itemClick(item, index) {
        alert('点击了第' + index + '项,电影名称:' + item.title + '上映年份:' + item.releaseYear);
      }
    
      //FlatList的key
      _keyExtractor = (item, index) => index;
    
      //子item渲染
      _renderItem = ({item, index}) => {
        return (
          <TouchableOpacity
            activeOpacity={0.5}
            onPress={this.itemClick.bind(this, item, index)}>
            //这里index为奇偶数给不同的背景
            <View style={[WebTestStyles.item, {backgroundColor: index % 2 == 0 ? 'lightgray' : 'whitesmoke'}]}>
              <Text style={WebTestStyles.itemTitle}>{item.title}</Text>
              <Text style={WebTestStyles.itemYear}>上映年份:{item.releaseYear}</Text>
            </View>
    
          </TouchableOpacity>
        );
      }
    
      //列表分割线
      _itemDivide = () => {
        return (
          <View style={{height: 1, backgroundColor: 'dimgray'}}/>
        );
      }
    
      //getMoviesFromApiAsync() {
      _fetchData = () => {
        fetch('https://facebook.github.io/react-native/movies.json')
          .then((response) => response.json())//把response转为json格式
          .then((jsondata) => {    //上面的转好的json
            //alert(jsondata.movies[0].title);
            this.setState({ //将获取到的数据更新到state中
              title: jsondata.title,
              description: jsondata.description,
              movies: jsondata.movies,
            })
          })
          .catch((error) => {
            console.error(error);
          });
      };
    
      render() {
        return (
          <View style={WebTestStyles.container}>
            <Text style={WebTestStyles.title}>{this.state.title}</Text>
            <Text style={WebTestStyles.description}>{this.state.description}</Text>
            <FlatList   //列表组件
              style={{marginTop: 20}}
              data={this.state.movies}
              keyExtractor={this._keyExtractor}
              renderItem={this._renderItem}
              ItemSeparatorComponent={this._itemDivide}/>
            <Button title="请求网络" onPress={this._fetchData}/>
          </View>
        );
      }
    }
    ;
    
    const WebTestStyles = StyleSheet.create({
      container: {
        flex: 1,
        flexDirection: 'column'
      },
    
      title: {
        fontSize: 20,
        fontWeight: 'bold',
        alignSelf: 'center'
    
      },
    
      description: {
        fontSize: 12,
        alignSelf: 'center'
      },
    
      item: {
        padding: 10
      },
      itemTitle: {
        fontSize: 18,
        fontWeight: 'bold'
      },
      itemYear: {
        fontSize: 16,
      }
    })

    然后看一下效果:
    这里写图片描述

    代码的话,基本上没有什么好说的,就是点击一个按钮去请求网络,然后拿到json格式的数据,两个文本字段,一个数组,然后把数组设置为FlatList的data,将电影以列表形式展示!

    Fetch相关API

    请求参数的设置

    上面我们在请求网络的时候,只是用fetch(url)这样的形式,我们也可以设置其他的参数来请求网络,如下所示:

    • method: 请求使用的方法,如 GETPOST
    • headers: 请求的头信息,形式为 Headers 对象或 ByteString。
    • body: 请求的 body 信息:可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息
    • mode: 请求的模式,如 corsno-cors 或者 same-origin
    • credentials: 请求的 credentials,如 omitsame-origin 或者 include
    • cache: 请求的 cache 模式: default, no-store, reload, no-cache, force-cache, 或者 only-if-cached

    类似这样:

    fetch('https://mywebsite.com/endpoint/', {
      method: 'POST',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        firstParam: 'yourValue',
        secondParam: 'yourOtherValue',
      })
    })

    如果你的服务器无法识别上面POST的数据格式,那么可以尝试传统的form格式,示例如下:

    fetch('https://mywebsite.com/endpoint/', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: 'key1=value1&key2=value2'
    })

    这个得话,就需要和做后台的同事提前沟通好就行了!

    自定义Header

    关于请求头Header的话,还可以通过 Headers() 构造函数来创建一个你自己的 headers 对象。

    var myHeaders = new Headers();
    myHeaders.append("Content-Type", "text/plain");
    myHeaders.append("Content-Length", content.length.toString());
    myHeaders.append("X-Custom-Header", "ProcessThisImmediately");

    也可以传一个多维数组或者对象字面量:

    myHeaders = new Headers({
      "Content-Type": "text/plain",
      "Content-Length": content.length.toString(),
      "X-Custom-Header": "ProcessThisImmediately",
    });

    自定义Request

    除了上面的用法,我们还可以通过使用Request()构造函数来创建一个request对象,然后再作为参数传给fetch()。这里就相当于把url和header或者其他的参数综合起来!
    比如:

    var url='https://mywebsite.com/endpoint/';
    var myOptions = { method: 'GET',
                   headers: myHeaders,
                   mode: 'cors',
                   cache: 'default' };
    var myRequest = new Request(url, myOptions );
    
    fetch(myRequest)
      .then()
      ...

    Response

    属性:

    • status (number) - HTTP请求结果参数,在100–599 范围
    • statusText (String) - 服务器返回的状态报告
    • ok (boolean) - 如果返回200表示请求成功则为true
    • headers (Headers) - 返回头部信息,下面详细介绍
    • url (String) - 请求的地址

    方法:

    • text() - 以string的形式生成请求text
    • json() - 生成JSON.parse(responseText)的结果
    • blob() - 生成一个Blob
    • arrayBuffer() - 生成一个ArrayBuffer
    • formData() - 生成格式化的数据,可用于其他的请求

    其他方法:

    • clone()
    • Response.error()
    • Response.redirect()

    上边就是Fetch常用的API,其他的API可以参考Fetch_API,也就是上边的网站!

    参考文章
    【译】fetch用法说明
    传统 Ajax 已死,Fetch 永生

    作者:aiynmimi 发表于2017/8/18 11:56:47 原文链接
    阅读:12 评论:0 查看评论

    React Native入门(九)之导航组件React Navigation(1)StackNavigator

    $
    0
    0

    前言

    本篇文章了解一下RN中导航组件的使用,且使用的是官方推荐的一个单独的导航库react-navigation,来分别了解一下这个库里边的StackNavigatorTabNavigatorDrawerNavigator这三个导航组件的基本使用!需要更加深入的了解的可以去官网查看其他相关的API:
    React Navigation

    使用

    安装react-navigation库

    首先呢,这个库是单独的,所以需要我们把这个库添加到我们的项目中去,在我们的项目目录执行:

    yarn add react-navigation

    这里写图片描述
    然后等待下载安装成功即可!

    StackNavigator

    使用

    这个导航组件主要用于不同页面之间的切换!,这个非常类似于Android中的Intent,来进行不同页面的挑战和数据的传值!
    而且如果一个页面添加了StackNavigator,就在页面顶部多了一个导航栏,相当于Android中的Toolbar!
    下面来看一下具体的使用:

    //首先需要将StackNavigator引入
    import {
      StackNavigator,
    } from 'react-navigation';
    
    class MainScreen extends Component{
      //设置navigationOptions
      static navigationOptions = {
        title: '主页面',//标题
      };
      render() {
        //navigation属性
        const { navigate } = this.props.navigation;
        return (
          <View>
            <Text style={{fontSize:20}}>我是MainScreen!</Text>
            <Button title="跳到SecondScreen"
                    //点击跳转,参数为下边RouteConfigs中要跳转的routeName
                    onPress={()=>navigate('Sencond')}/>
          </View>
    
        );
      }
    }
    
    class SecondScreen extends Component{
      static navigationOptions ={
        title: 次页面 
      };
    
      render() {
        return (
          <View >
            <Text style={{fontSize:20}}>我是SecondScreen!</Text>
          </View>
    
        );
      }
    }
    
    //设置StackNavigator(RouteConfigs, StackNavigatorConfig)
    const App = StackNavigator({
      Main: {screen: MainScreen},
      Sencond: {screen: SecondScreen},
    });
    
    AppRegistry.registerComponent('AwesomeProject', () => App);

    上边就是StackNavigator的基本用法,如果要在页面跳转的时候传递一些数据要这样写:

    onPress={()=>navigate('Sencond',{ hello: '你好!' })}

    navigate()方法中添加第二个参数,格式为{ key: value }的形式!然后在第二个页面中接收数据:

    render() {
      const { params } = this.props.navigation.state;
      return (
        <View >
          <Text style={{fontSize:20}}>{params.hello}我是SecondScreen!</Text>
        </View>
      );
    }

    要先拿到传值页面中navigation属性的state:
    const { params } = this.props.navigation.state;
    然后取值params.key的形式拿到传递的值,例子中为params.hello

    另外如果我们在第二个接收页面设置navigationOptions的时候需要传递过来的参数,该怎么做呢?有两种方式都可以拿到:
    ①在接收页面中:

    static navigationOptions = ({ navigation }) => ({
        title: `次页面 ${navigation.state.params.hello}`,
    });

    可以将navigationOptions定义为这个页面属性的函数,比如这里在设置title属性的时候,使用${navigation.state.params.hello}这个变量表示传递的值,需要注意的是外边需要`` 包围,这个是键盘Esc下边的键!这点要注意!
    ②我们在设置StackNavigator的时候,可以配置RouteConfigs!

    StackNavigator({
      Main: {screen: MainScreen},
      Sencond: {
        screen: SecondScreen,
        navigationOptions: ({navigation}) => ({
          title: `次页面 ${navigation.state.params.hello}`,
        }),
      },
    });

    直接在配置第二个页面的RouteConfigs时,去设置navigationOptions,相当于重写了我们在SecondScreen中设置的navigationOptions,那么当然在跳转到第二个页面时会显示这里配置的navigationOptions,而不是在SecondScreen中设置的!

    可以试一下:

    class SecondScreen extends Component{
      static navigationOptions = {
        title: '我就不信我显示不了!',
      };
      ...
    }

    运行一下:
    这里写图片描述
    是吧,你就是显示不了!哈哈哈…

    配置

    那么说到这里,我们在设置StackNavigator的时候都可以设置哪些东东?

    StackNavigator(RouteConfigs, StackNavigatorConfig)

    RouteConfigs:路由设置

    这里参照官网:

    StackNavigator({
      // For each screen that you can navigate to, create a new entry like this:
      //路由名字
      Profile: {
    
        // `ProfileScreen` is a React component that will be the main content of the screen.
        //具体的页面
        screen: ProfileScreen,
        // When `ProfileScreen` is loaded by the StackNavigator, it will be given a `navigation` prop.
    
        // Optional: When deep linking or using react-navigation in a web app, this path is used:
        //页面的路径
        path: 'people/:name',
        // The action and route params are extracted from the path.
    
        // Optional: Override the `navigationOptions` for the screen
        //导航栏选项
        navigationOptions: ({navigation}) => ({
          title: `${navigation.state.params.name}'s Profile'`,
        }),
      },
    
      ...MyOtherRoutes,
    });

    StackNavigatorConfig

    Options for the router:

    • initialRouteName - Sets the default screen of the stack. Must match one of the keys in route configs. 设置默认页面
    • initialRouteParams - The params for the initial route.设置默认页面的传值
    • navigationOptions - Default navigation options to use for screens. 设置默认的导航栏选项
    • paths - A mapping of overrides for the paths set in the route configs. 重新路由设置中的path

    还有一些视觉效果上面的配置,比如设置页面切换模式mode ,设置过渡动画transition等!可以参考官方文档!

    那么具体到一个页面中,navigationOptions都可以设置哪些参数?

    • title设置标题(默认,在没有设置header相关属性下显示)
    • header 设置导航栏头部,接收参数为React Element或者是一个返回React Element的给定HeaderProps参数的方法。如果设置为null,则整个标题栏就不显示了!
      这个属性的存在,我们就可以自定义我们的Header!
    • headerRight 显示在header右边的组件,比如Android中常见的菜单按钮等,都可以通过这一属性设置!

    其他的属性参数的设置,就不再说了,可以参考官方文档:
    StackNavigator

    结语

    本篇文章主要介绍了React Navigation导航库中StackNavigator的使用,剩余的两个导航组件,我们放在下篇博客中了解!
    好了,就这样,我们下篇文章再见!

    作者:aiynmimi 发表于2017/8/18 19:18:46 原文链接
    阅读:0 评论:0 查看评论

    Chromium插件(Plugin)执行3D渲染的过程分析

    $
    0
    0

           Chromium为网页的<embed>标签创建了Plugin之后,Plugin就负责渲染<embed>标签的内容。Chromium为Plugin提供了OpenGL接口,使得Plugin可在网页上渲染3D内容。当然,我们也可通过WebGL接口在网页上渲染3D内容。不过,前者渲染效率会更高些,因为它是Native接口,后者是JavaScript接口。本文接下来就详细分析Plugin执行3D渲染的过程。

    老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!

    《Android系统源代码情景分析》一书正在进击的程序员网(http://0xcc0xcd.com)中连载,点击进入!

           我们分析Plugin执行3D渲染的过程,更多的是为了理解Chromium与Plugin的交互过程,包括Chromium操作Plugin的过程,以及Plugin调用Chromium提供的接口的过程。接下来我们就以Chromium插件(Plugin)机制简要介绍和学习计划一文提到的GLES2 Example为例,分析Plugin执行3D渲染的过程。

           从前面Chromium网页加载过程简要介绍和学习计划这个系列的文章可以知道,WebKit会将网页抽象为一个DOM Tree。网页中的每一个<embed>标签在这个DOM Tree中都会有一个对应的节点。从前面Chromium网页渲染机制简要介绍和学习计划这个系列的文章又可以知道,网页的DOM Tree又会被Chromium转化为一个CC Layer Tree。其中,DOM Tree中的<embed>节点将对应于CC Layer Tree中的一个Texture Layer,如图1所示:


    图1 DOM Tree中的<embed>标签与CC Layer Tree中的Texture Layer

           Plugin在调用Chromium提供的OpenGL接口的时候,实际上是将<embed>标签的内容渲染在它在CC Layer Tree中对应的Texture Layer上。当Chromium对网页的CC Layer Tree进行绘制的时候,它内部的Texture Layer的内容就会显示在屏幕上。Texture Layer描述的实际上是一个纹理。在前面Chromium硬件加速渲染的UI合成过程分析一文中,我们提到,网页的canvas标签在CC Layer Tree同样是对应有一个Texture Layer。因此,<embed>标签对应的Texture Layer与canvas标签对应的Texture Layer显示在屏幕上的过程是一样的。这一点可以参考前面Chromium硬件加速渲染的UI合成过程分析一文。

           在Android平台上,Chromium提供给Plugin调用的OpenGL接口称为PPB_OPENGLES2_INTERFACE接口。PPB_OPENGLES2_INTERFACE接口提供了一系列的函数,每一个函数都对应于一个glXXX接口,如图2所示:


    图2 Plugin调用OpenGL接口的过程

           在调用PPB_OPENGLES2_INTERFACE接口提供的函数时,GLES2Implementation类的相应成员函数会被调用。例如,当PPB_OPENGLES2_INTERFACE接口提供的函数ActiveTexture被调用时,GLES2Implementation类的成员函数ActiveTexture。GLES2Implementation类的成员函数会将要执行的GPU命令写入到一个缓冲区中去,然后通过一个PpapiCommandBufferProxy对象通知Render进程执行缓冲区中的GPU命令。Render进程又会通过一个CommandBufferProxyImpl对象将要执行的GPU命令转发给GPU进程中的一个GpuCommandBufferStub处理。这意味Plugin最终是通过GPU进程执行GPU命令的。关于GLES2Implementation、CommandBufferProxyImpl和GpuCommandBufferStub这三类执行GPU命令的过程,可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。

           Plugin在通过PPB_OPENGLES2_INTERFACE接口执行OpenGL函数(GPU命令)之前,首先要初始化一个OpenGL环境。这个初始化操作发生在Plugin第一次知道自己的视图大小时,也就是知道它对应的<embed>标签的视图大小时。初始化过程如图3所示:


    图3 Plugin的OpenGL环境初始化过程

           Plugin的视图大小是由WebKit计算出来的。计算出来之后,WebKit会通过运行在Render进程中的Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象,向运行Plugin进程中的Plugin Instance发出通知。Plugin Instance获得了这个通知之后,就可以初始化一个OpenGL环境了。

           Plugin Instance Proxy是通过调用PPP_INSTANCE_INTERFACE_1_1接口提供的一个函数DidChangeView向Plugin Instance发出通知的,后者又是通过向目标Plugin进程发送一个类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息发出该通知的。

           类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息携带了一个参数PpapiMsg_PPPInstance_DidChangeView。这个参数描述的是一个Routing ID,表示Plugin进程要将类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息交给一个PPP_Instance_Proxy对象处理。这个PPP_Instance_Proxy对象获得该消息后,又会在当前Plugin进程中获得一个PPP_INSTANCE_INTERFACE_1_1接口,并且调用该接口提供的函数DidChangeView。该函数会找到目标Plugin Instance,并且调用它的成员函数DidChangeView。这样,Plugin Instance就可以初始化OpenGL环境了。以我们在前面Chromium插件(Plugin)机制简要介绍和学习计划一文提到的GLES2 Example为例,它的成员函数DidChangeView就通过调用另外一个成员函数InitGL初始化OpenGL环境的。

           Plugin在初始化OpenGL环境的过程中,有两件重要的事情要做。第一件事情是创建一个OpenGL上下文,过程如图4所示:

      

    图4 Plugin的OpenGL上文创建过程

           在Plugin进程中,OpenGL上下文通过Graphics3D类描述。因此,创建OpenGL上下文意味着是创建一个Graphics3D对象。这个Graphics3D对象在创建的过程中,会调用PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的一个函数Create。该函数又会通过一个APP_ID_RESOURCE_CREATION接口向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息。在Plugin进程中,APP_ID_RESOURCE_CREATION接口是通过一个ResourceCreationProxy对象实现的,因此,Plugin进程实际上是通过ResourceCreationProxy类向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息的。

           Plugin进程在向Render进程发送类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息时,会指定一个参数APP_ID_PPB_GRAPHICS_3D,表示Render进程在接收到该消息后,要将其分发给一个PPB_Graphics3D_Proxy对象处理。这个PPB_Graphics3D_Proxy对象又会通过调用PPB_Graphics3D_Impl类的静态成员函数CreateRaw创建一个PPB_Graphics3D_Impl对象。这个PPB_Graphics3D_Impl对象在Render进程描述的就是一个OpenGL上下文。

           Plugin在初始化OpenGL环境的过程中做的第二件事情就是将刚刚创建出来的OpenGL上下文指定为当前要使用的OpenGL上下文。这个过程称为OpenGL上下文绑定,如图5所示:


    图5 Plugin绑定OpenGL上下文的过程

           Plugin是通过调用PPB_INSTANCE_INTERFACE_1_0接口提供的函数BindGraphics进行OpenGL上下文绑定的。该函数又会通过一个APP_ID_PPB_INSTANCE接口向Render进程发送一个类型为PpapiHostMsg_PPBIntance_BindGraphics的IPC消息。在Plugin进程中,APP_ID_PPB_INSTANCE接口是通过一个PPB_Instance_Proxy对象实现的,因此,Plugin进程实际上是通过PPB_Instance_Proxy类向Render进程发送一个类型为PpapiHostMsg_PPBIntance_BindGraphics的IPC消息的。

           Plugin进程在向Render进程发送类型为PpapiHostMsg_PPBIntance_BindGraphics的IPC消息时,会指定一个参数APP_ID_PPB_INSTANCE,表示Render进程在接收到该消息后,要将其分发给一个PPB_Instance_Proxy对象处理。这个PPB_Instance_Proxy对象又会找到目标Plugin Instance在Render进程中对应的Proxy,也就是一个PepperPluginInstanceImpl对象,并且调用该PepperPluginInstanceImpl对象的成员函数BindGraphics。

           PepperPluginInstanceImpl类的成员函数BindGraphics在执行的过程中,会将指定的OpenGL上下文,也就是前面创建一个PPB_Graphics3D_Impl对象标记为被绑定,这样它接下来就会作为Plugin当前使用的OpenGL上下文了。

           OpenGL环境初始化完成之后,Plugin就可以使用图2所示的PPB_OPENGLES2_INTERFACE接口来执行3D渲染了。一帧渲染完毕,Plugin需要交换当前使用的OpenGL上下文的前后两个缓冲区,也就是执行一个SwapBuffers操作。这个操作的执行过程如图6所示:


    图6 Plugin执行SwapBuffers操作的过程

          前面提到,在Plugin进程中,OpenGL上下文是通过一个Graphics3D对象描述的。这个Graphics3D对象可以通过PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的成员函数SwapBuffers完成SwapBuffers操作。

          PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的成员函数SwapBuffers在执行的过程中,会向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_SwapBuffers的IPC消息。这个消息携带了一个参数API_ID_PPB_GRAPHICS_3D,表示Render进程需要将该消息分发给一个PPB_Graphics3D_Proxy对象处理。这个PPB_Graphics3D_Proxy对象会找到Plugin当前绑定的OpenGL上下文,也就是一个PPB_Graphics3D_Impl对象,并且调用该PPB_Graphics3D_Impl对象的成员函数DoSwapBuffers。这时候就可以完成一个SwapBuffers操作,从而也完成一个帧的渲染流程。

          接下来,我们就从WebKit通知Plugin视图大小开始,分析Plugin执行3D渲染的完整流程。从前面Chromium插件(Plugin)模块(Module)加载过程分析一文可以知道,WebKit为<embed>标签创建了一个Plugin Instance之后,会将其放在一个由WebPluginContainerImpl类描述的Plugin View中。当<embed>标签的视图大小发生变化时,WebPluginContainerImpl类的成员函数reportGeometry就会被调用,它的实现如下所示:

    void WebPluginContainerImpl::reportGeometry()
    {
        ......
    
        IntRect windowRect, clipRect;
        Vector<IntRect> cutOutRects;
        calculateGeometry(frameRect(), windowRect, clipRect, cutOutRects);
    
        m_webPlugin->updateGeometry(windowRect, clipRect, cutOutRects, isVisible());
    
        ......
    }
           这个函数定义在文件external/chromium_org/third_party/WebKit/Source/web/WebPluginContainerImpl.cpp中。

           WebPluginContainerImpl类的成员函数reportGeometry首先是调用成员函数calulateGeometry计算<embed>标签的视图大小,然后再将计算得到的信息告知Content层,这是通过成员变量m_webPlugin指向的一个PepperWebPluginImpl对象的成员函数updateGeometry实现的。这个PepperWebPluginImpl对象的创建过程可以参考前面Chromium的Plugin进程启动过程分析一文。

           接下来我们继续分析PepperWebPluginImpl类的成员函数updateGeometry的实现,以便了解Render进程通知Plugin它描述的<embed>标签的大小的过程,如下所示:

    void PepperWebPluginImpl::updateGeometry(
        const WebRect& window_rect,
        const WebRect& clip_rect,
        const WebVector<WebRect>& cut_outs_rects,
        bool is_visible) {
      plugin_rect_ = window_rect;
      if (!instance_->FlashIsFullscreenOrPending()) {
        std::vector<gfx::Rect> cut_outs;
        for (size_t i = 0; i < cut_outs_rects.size(); ++i)
          cut_outs.push_back(cut_outs_rects[i]);
        instance_->ViewChanged(plugin_rect_, clip_rect, cut_outs);
      }
    }
           这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_webplugin_impl.cc中。

           PepperWebPluginImpl类的成员变量instance_指向的是一个PepperPluginInstanceImpl对象。从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,这个PepperPluginInstanceImpl对象是用来在Render进程中描述一个Plugin Instance Proxy的。

           PepperWebPluginImpl类的成员函数updateGeometry首先调用上述PepperPluginInstanceImpl对象的成员函数FlashIsFullscreenOrPending判断当前正在处理的Plugin是否是一个Flash Plugin。如果是一个Flash Plugin,并且它当前处于全屏状态或者即将进入全屏状态,那么PepperWebPluginImpl类的成员函数updateGeometry就不会通知Plugin它描述的<embed>标签的视图大小发生了变化。

           我们假设当前正在处理的Plugin不是一个Flash Plugin。这时候PepperWebPluginImpl类的成员函数updateGeometry就会调用上述PepperPluginInstanceImpl对象的成员函数ViewChanged通知Plugin它描述的<embed>标签的视图大小发生了变化。

           PepperPluginInstanceImpl类的成员函数ViewChanged的实现如下所示:

    void PepperPluginInstanceImpl::ViewChanged(
        const gfx::Rect& position,
        const gfx::Rect& clip,
        const std::vector<gfx::Rect>& cut_outs_rects) {
      ......
    
      view_data_.rect = PP_FromGfxRect(position);
      view_data_.clip_rect = PP_FromGfxRect(clip);
      ......
    
      SendDidChangeView();
    }
           这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cc中。

           PepperPluginInstanceImpl类的成员函数ViewChanged首先将当前正在处理的Plugin描述的<embed>标签的视图大小信息保存在成员变量view_data_描述的一个ppapi::ViewData对象中,然后再调用另外一个成员函数SendDidChangeView向运行在Plugin进程中的Plugin Instance发送一个视图大小变化通知。

           PepperPluginInstanceImpl类的成员函数SendDidChangeView的实现如下所示:

    void PepperPluginInstanceImpl::SendDidChangeView() {
      ......
    
      ScopedPPResource resource(
          ScopedPPResource::PassRef(),
          (new PPB_View_Shared(ppapi::OBJECT_IS_IMPL, pp_instance(), view_data_))
              ->GetReference());
      ......
    
      if (instance_interface_) {
        instance_interface_->DidChangeView(
            pp_instance(), resource, &view_data_.rect, &view_data_.clip_rect);
      }
    }

           这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cc中。

           PepperPluginInstanceImpl类的成员函数SendDidChangeView首先是将成员变量view_data_描述的ppapi::ViewData对象封装在一个PPB_View_Shared对象。从前面的分析可以知道,被封装的ppapi::ViewData对象描述的当前正在处理的Plugin描述的<embed>标签的视图大小信息。封装得到的PPB_View_Shared对象接下来会传递给PPP_INSTANCE_INTERFACE_1_1接口提供的函数DidChangeView使用。

           从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,PepperPluginInstanceImpl类的成员变量instance_interface_指向的是一个PPP_Instance_Combined对象。这个PPP_Instance_Combined对象描述的是一个PPP_INSTANCE_INTERFACE_1_1接口。运行在Render进程中的Plugin Instance Proxy可以通过这个接口与运行在Plugin进程中的Plugin Instance通信。

           PepperPluginInstanceImpl类的成员函数SendDidChangeView主要就是调用上述PPP_INSTANCE_INTERFACE_1_1接口提供的函数DidChangeView通知运行在Plugin进程中的Plugin Instance,它描述的<embed>标签的视图大小发生了变化,也就是通过调用成员变量instance_interface_指向的PPP_Instance_Combined对象的成员函数DidChangeView进行通知。

           PPP_Instance_Combined类的成员函数DidChangeView的实现如下所示:

    void PPP_Instance_Combined::DidChangeView(PP_Instance instance,
                                              PP_Resource view_changed_resource,
                                              const struct PP_Rect* position,
                                              const struct PP_Rect* clip) {
      if (instance_1_1_.DidChangeView) {
        CallWhileUnlocked(
            instance_1_1_.DidChangeView, instance, view_changed_resource);
      } 
    
      ......
    }
           这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppp_instance_combined.cc中。

           从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,PPP_Instance_Combined类的成员变量instance_1_1_描述的是一个PPP_Instance_1_1对象。这个PPP_Instance_1_1对象描述的就是上述的PPP_INSTANCE_INTERFACE_1_1接口。PPP_Instance_Combined类的成员函数DidChangeView通过一个帮助函数CallWhilleUnlocked调用这个PPP_INSTANCE_INTERFACE_1_1接口提供的函数DidChangeView,以便向运行在Plugin进程中的Plugin Instance发出一个视图大小变化通知。

           从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文还可以知道,上述PPP_INSTANCE_INTERFACE_1_1接口提供的函数DidChangeView实现在ppp_instance_proxy.cc文件中,如下所示:

    void DidChangeView(PP_Instance instance, PP_Resource view_resource) {
      HostDispatcher* dispatcher = HostDispatcher::GetForInstance(instance);
    
      EnterResourceNoLock<PPB_View_API> enter_view(view_resource, false);
      ......
    
      dispatcher->Send(new PpapiMsg_PPPInstance_DidChangeView(
          API_ID_PPP_INSTANCE, instance, enter_view.object()->GetData(),
          flash_fullscreen));
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/ppp_instance_proxy.cc中。

           参数instance的类型为PP_Instance。PP_Instance描述的实际上是一个32位的有符号整数。这个32位的有符号整数是用来描述一个Plugin的ID。通过这个ID,运行在Render进程中的Plugin Instance Proxy与运行在Plugin进程中的Plugin Instance就能一一对应起来的。

           函数DidChangeView首先通过调用HostDispatcher类的静态成员函数GetForInstance获得一个HostDispatcher对象。从前面Chromium插件(Plugin)模块(Module)加载过程分析一文可以知道,每一个Plugin进程在Render进程中都有一个对应的HostDispatcher对象。这个HostDispatcher对象就是用来与它对应的Plugin进程通信的。给出一个PP_Instance,HostDispatcher类的静态成员函数GetForInstance就可以获得这个PP_Instance描述的Plugin Instance所运行在的Plugin进程对应的HostDispatcher对象。

           获得了目标Plugin进程对应的HostDispatcher对象之后,就可以向它发送一个类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息。这个IPC消息携带了四个参数:

           1. API_ID_PPP_INSTANCE,要求Plugin进程将该IPC消息分发给API_ID_PPP_INSTANCE接口处理。

           2. instance,表示目标Plugin Instance。

           3. ppapi::ViewData,表示目标Plugin Instance的当前视图大小。

           4. flash_fullscreen,表示目标Plugin Instance是否处于全屏状态。

           其中,第3个参数描述的ppapi::ViewData对象来自于前面在PepperPluginInstanceImpl类的成员函数SendDidChangeView中封装的PPB_View_Shared对象。这个PPB_View_Shared对象对象是通过函数DidChangeView的第2个参数view_resource传递进来的。

           函数DidChangeView首先将上述PPB_View_Shared对象封装在一个EnterResourceNoLock<PPB_View_API>对象中。接下来通过调用这个EnterResourceNoLock<PPB_View_API>对象的成员函数object就可以获得它封装的PPB_View_Shared对象。再调用这个PPB_View_Shared对象的成员函数GetData即可以获得它内部封装的一个ppapi::ViewData对象。这个ppapi::ViewData对象内部保存了目标Plugin Instance的当前视图大小信息,因此可以作为上述IPC消息的第3个参数。

           从前面Chromium的Plugin进程启动过程分析一文可以知道,每一个Plugin进程都存在一个PluginDispatcher对象。Plugin进程将会通过这个PluginDispatcher对象的成员函数OnMessageReceived接收Render进程发送过来的IPC消息。这意味着前面从Render进程发送过来的类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息是通过PluginDispatcher类的成员函数OnMessageReceived接收的。

           PluginDispatcher类的成员函数OnMessageReceived是从父类Dispatcher继承下来的,它的实现如下所示:

    bool Dispatcher::OnMessageReceived(const IPC::Message& msg) {  
      ......  
      
      InterfaceProxy* proxy = GetInterfaceProxy(  
          static_cast<ApiID>(msg.routing_id()));  
      ......  
      
      return proxy->OnMessageReceived(msg);  
    }  
           这个函数定义在文件external/chromium_org/ppapi/proxy/dispatcher.cc中。

           从前面的分析可以知道,此时参数msg指向的Message对象描述的是一个类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息,该消息的Routing ID为API_ID_PPP_INSTANCE,表示要将该消息分发给一个API_ID_PPP_INSTANCE接口处理。这个API_ID_PPP_INSTANCE接口可以通过调用调用另外一个成员函数GetInterfaceProxy获得。

           在前面Chromium插件(Plugin)实例(Instance)创建过程分析一文中,我们已经分析过Dispatcher类的成员函数GetInterfaceProxy的实现,因此这里不再复述。再结合前面Chromium插件(Plugin)模块(Module)加载过程分析一文,我们可以知道,在Plugin进程中,API_ID_PPP_INSTANCE接口是由一个PPP_Instance_Proxy对象实现的,Dispatcher类的成员函数GetInterfaceProxy接下来会将参数msg描述的类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息分发给它处理,也就是调用它的成员函数OnMessageReceived。

           PPP_Instance_Proxy类的成员函数OnMessageReceived的实现如下所示:

    bool PPP_Instance_Proxy::OnMessageReceived(const IPC::Message& msg) {
      ......
    
      bool handled = true;
      IPC_BEGIN_MESSAGE_MAP(PPP_Instance_Proxy, msg)
        ......
        IPC_MESSAGE_HANDLER(PpapiMsg_PPPInstance_DidChangeView,
                            OnPluginMsgDidChangeView)
        ......
        IPC_MESSAGE_UNHANDLED(handled = false)
      IPC_END_MESSAGE_MAP()
      return handled;
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/ppp_instance_proxy.cc中。

           从这里可以看到,PPP_Instance_Proxy类的成员函数OnMessageReceived将类型为PpapiMsg_PPPInstance_DidChangeView的IPC消息分发给另外一个成员函数OnPluginMsgDidChangeView处理,如下所示:

    void PPP_Instance_Proxy::OnPluginMsgDidChangeView(
        PP_Instance instance,
        const ViewData& new_data,
        PP_Bool flash_fullscreen) {
      ......
    
      combined_interface_->DidChangeView(instance, resource,
                                         &new_data.rect,
                                         &new_data.clip_rect);
    }

           这个函数定义在文件external/chromium_org/ppapi/proxy/ppp_instance_proxy.cc中。

           从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,PPP_Instance_Proxy类的成员变量combined_interface_指向的是一个PPP_Instance_Combined对象。PPP_Instance_Proxy类的成员函数OnPluginMsgDidChangeView主要是调用这个PPP_Instance_Combined对象的成员函数DidChangeView通知参数instance描述的Plugin Instance,它的视图大小发生了变化。

           PPP_Instance_Combined类的成员函数DidChangeView的实现如下所示:

    void PPP_Instance_Combined::DidChangeView(PP_Instance instance,
                                              PP_Resource view_changed_resource,
                                              const struct PP_Rect* position,
                                              const struct PP_Rect* clip) {
      if (instance_1_1_.DidChangeView) {
        CallWhileUnlocked(
            instance_1_1_.DidChangeView, instance, view_changed_resource);
      } 
      ......
    }
           这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppp_instance_combined.cc中。

           从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,PPP_Instance_Combined类的成员变量instance_1_1_指向的是一个PPP_Instance对象。这个PPP_Instance对象的成员变量DidChangeView是一个函数指针,它指向的函数为Instance_DidChangeView。PPP_Instance_Combined类的成员函数DidCreate主要是调用这个函数通知参数instance描述的Plugin Instance,它的视图大小发生了变化。

           函数Instance_DidChangeView的实现,如下所示:

    void Instance_DidChangeView(PP_Instance pp_instance,
                                PP_Resource view_resource) {
      Module* module_singleton = Module::Get();
      ......
      Instance* instance = module_singleton->InstanceForPPInstance(pp_instance);
      ......
      instance->DidChangeView(View(view_resource));
    }
           这个函数定义在文件external/chromium_org/ppapi/cpp/module.cc中。

           函数Instance_DidChangeView首先调用Module类的静态成员函数Get获得当前Plugin进程中的一个pp::Module单例对象。从前面Chromium插件(Plugin)模块(Module)加载过程分析一文可以知道,这个pp::Module单例对象描述的就是在当前Plugin进程中加载的Plugin Module。

           从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,一个Plugin Module创建的所有Plugin Instance都会以它们的ID为键值,保存在一个std::map中。因此,函数Instance_DidChangeView可以在这个std::map中找到与参数pp_instance对应的Plugin Instance,即一个pp::Instance对象。这个通过调用前面获得的pp::Module单例对象的成员函数InstanceForPPInstance实现的。获得了目标pp::Instance对象之后,就可以调用它的成员函数DidChangeView,以便通知它视图大小发生了变化。

           我们在开发一个Plugin的时候,会自定义一个pp::Instance类。例如,在前面Chromium插件(Plugin)机制简要介绍和学习计划一文提到的GLES2 Example,它自定义的pp::Instance类为GLES2DemoInstance。自定义的GLES2DemoInstance类是从pp::Instance类继承下来的,并且会重写成员函数DidChangeView。这意味着接下来GLES2DemoInstance类的成员函数DidChangeView会被调用。

           GLES2DemoInstance类的成员函数DidChangeView的实现如下所示:

    void GLES2DemoInstance::DidChangeView(
        const pp::Rect& position, const pp::Rect& clip_ignored) {
      ......
      plugin_size_ = position.size();
    
      // Initialize graphics.
      InitGL(0);
    }
           这个函数定义在文件external/chromium_org/ppapi/examples/gles2/gles2.cc中。

           参数position描述的一个pp::Rect对象记录了当前正在处理的Plugin Instance的当前视图大小。GLES2DemoInstance类的成员函数DidChangeView首先将这个视图大小记录在成员变量plugin_size_中,接下来又调用另外一个成员函数InitGL初始化一个OpenGL环境。

           GLES2DemoInstance类的成员函数InitGL的实现如下所示:

    void GLES2DemoInstance::InitGL(int32_t result) {
      ......
    
      if (context_) {
        context_->ResizeBuffers(plugin_size_.width(), plugin_size_.height());
        return;
      }
      int32_t context_attributes[] = {
        PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8,
        PP_GRAPHICS3DATTRIB_BLUE_SIZE, 8,
        PP_GRAPHICS3DATTRIB_GREEN_SIZE, 8,
        PP_GRAPHICS3DATTRIB_RED_SIZE, 8,
        PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 0,
        PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 0,
        PP_GRAPHICS3DATTRIB_SAMPLES, 0,
        PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0,
        PP_GRAPHICS3DATTRIB_WIDTH, plugin_size_.width(),
        PP_GRAPHICS3DATTRIB_HEIGHT, plugin_size_.height(),
        PP_GRAPHICS3DATTRIB_NONE,
      };
      context_ = new pp::Graphics3D(this, context_attributes);
      ......
      assert(BindGraphics(*context_));
    
      ......
    
      FlickerAndPaint(0, true);
    }
           这个函数定义在文件external/chromium_org/ppapi/examples/gles2/gles2.cc中。

           初始化Plugin的OpenGL环境需要执行两个操作:

           1. 创建一个OpenGL上下文。这个OpenGL上下文用一个pp::Graphics3D对象描述。

           2. 将创建出来的OpenGL上下文与Plugin进行绑定,也就是将它指定为Plugin当前使用的OpenGL上下文。这可以通过调用父类pp::Instance继承下来的成员函数BindGraphics来完成。

           GLES2DemoInstance类的成员变量context_就是用来描述Plugin当前使用的OpenGL上下文。如果这个OpenGL上下文还没有创建,那么GLES2DemoInstance类的成员函数InitGL就会根据Plugin当前的视图大小进行创建。否则的话,就只会改变这个OpenGL上下文描述的绘图缓冲区的大小。

           完成以上两个操作之后,GLES2DemoInstance类的成员函数InitGL就会调用另外一个成员函数FlickerAndPaint渲染Plugin的视图。渲染出来的内容最后就会合成在网页的UI中显示出来。

           接下来我们先分析OpenGL上下文的创建过程,也就是一个pp::Graphics3D对象的创建过程,我们pp::Graphics3D类的构造函数开始分析,它的实现如下所示:

    Graphics3D::Graphics3D(const InstanceHandle& instance,
                           const int32_t attrib_list[]) {
      if (has_interface<PPB_Graphics3D_1_0>()) {
        PassRefFromConstructor(get_interface<PPB_Graphics3D_1_0>()->Create(
            instance.pp_instance(), 0, attrib_list));
      }
    }
           这个函数定义在文件external/chromium_org/ppapi/cpp/graphics_3d.cc中。

           pp::Graphics3D类的构造函数首行调用模板函数has_interface<PPB_Graphics3D_1_0>检查在当前Plugin进程中加载的Plugin Module是否支持PPB_GRAPHICS_3D_INTERFACE_1_0。如果支持的话,那么就会再调用另外一个模板函数get_interface<PPB_Graphics3D_1_0>获得这个PPB_GRAPHICS_3D_INTERFACE_1_0接口。获得了PPB_GRAPHICS_3D_INTERFACE_1_0接口之后,就可以调用它提供的函数Create请求Render进程创建一个OpenGL上下文了。

           从后面的分析我们可以知道,PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的函数Create创建出来的OpenGL上下文用一个proxy::proxy::Graphics3D对象描述,不过它返回给调用者的是分配给proxy::proxy::Graphics3D对象的一个资源ID。pp::Graphics3D类的构造函数会调用从父类pp::Resource继承下来的成员函数PassRefFromConstructor将这个资源ID保存在成员变量pp_resource_中,如下所示:

    void Resource::PassRefFromConstructor(PP_Resource resource) {
      PP_DCHECK(!pp_resource_);
      pp_resource_ = resource;
    }
           这个函数定义在文件external/chromium_org/ppapi/cpp/resource.cc中。

           以后通过调用pp::Resource类的成员函数pp_resource即可以获得这个资源ID,如下所示:

    class Resource {
     public:
      ......
    
      PP_Resource pp_resource() const { return pp_resource_; }
    
      ......
    };
           这个函数定义在文件external/chromium_org/ppapi/cpp/resource.h中。

           回到 pp::Graphics3D类的构造函数中,接下来我们首先分析PPB_GRAPHICS_3D_INTERFACE_1_0接口的获取过程,也就是模板函数get_interface<PPB_Graphics3D_1_0>的实现,如下所示:

    template <typename T> inline T const* get_interface() {
      static T const* funcs = reinterpret_cast<T const*>(
          pp::Module::Get()->GetBrowserInterface(interface_name<T>()));
      return funcs;
    }
           这个模板函数定义在文件external/chromium_org/ppapi/cpp/module_impl.h中。

           这里的模板参数T为PPB_Graphics3D_1_0,展开后得到模板函数get_interface<PPB_Graphics3D_1_0>的具体实现为:

    inline PPB_Graphics3D_1_0 const* get_interface() {
      static PPB_Graphics3D_1_0 const* funcs = reinterpret_cast<PPB_Graphics3D_1_0 const*>(
          pp::Module::Get()->GetBrowserInterface(interface_name<PPB_Graphics3D_1_0>()));
      return funcs;
    }
           它首先会调用另外一个模板函数interface_name<PPB_Graphics3D_1_0>获得要获取的接口名称,接着再调用前面提到的pp::Module类的静态成员函数Get获得当前Plugin进程中的一个pp::Module单例对象。有了这个pp::Module单例对象之后,就可以调用它的成员函数GetBrowserInterface根据名称获得接口。

           我们首先分析模板函数interface_name<PPB_Graphics3D_1_0>的实现,以便知道要获取的接口名称是什么,如下所示:

    template <> const char* interface_name<PPB_Graphics3D_1_0>() {
      return PPB_GRAPHICS_3D_INTERFACE_1_0;
    }
           这个模板函数定义在文件ppapi/cpp/graphics_3d.cc中。

           从这里可以看到,要获取的接口名称为PPB_GRAPHICS_3D_INTERFACE_1_0,也就我们要获取的是PPB_GRAPHICS_3D_INTERFACE_1_0接口。这个接口可以通过调用前面获得的pp::Module单例对象的GetBrowserInterface获得。

           在前面Chromium插件(Plugin)模块(Module)加载过程分析一文中,我们已经分析过了pp::Module类的GetBrowserInterface的实现,并且我们也知道,在Plugin进程中,PPB_GRAPHICS_3D_INTERFACE_1_0接口是由一个PPB_Graphics3D_1_0对象实现的。这个PPB_Graphics3D_1_0对象的定义如下所示:

    const PPB_Graphics3D_1_0 g_ppb_graphics3d_thunk_1_0 = {  
      &GetAttribMaxValue,  
      &Create,  
      &IsGraphics3D,  
      &GetAttribs,  
      &SetAttribs,  
      &GetError,  
      &ResizeBuffers,  
      &SwapBuffers  
    };  
          这个对象定义在文件external/chromium_org/ppapi/thunk/ppb_graphics_3d_thunk.cc中。

          这个PPB_Graphics3D_1_0对象的成员变量Create是一个函数指针。这个函数指针指向了函数Create。这个函数也是定义在文件ppb_graphics_3d_thunk.cc中。

          回到Graphics3D类的构造函数中,现在我们就可以知道,它实际上是通过调用上述函数Create请求Render进程为当前正在处理的Plugin创建一个OpenGL上下文的,如下所示:

    PP_Resource Create(PP_Instance instance,
                       PP_Resource share_context,
                       const int32_t attrib_list[]) {
      ......
      EnterResourceCreation enter(instance);
      ......
      return enter.functions()->CreateGraphics3D(instance,
                                                 share_context,
                                                 attrib_list);
    }
           这个函数定义在文件external/chromium_org/ppapi/thunk/ppb_graphics_3d_thunk.cc中。

           OpenGL上下文属于一种资源。在Plugin中,创建资源要使用到接口API_ID_RESOURCE_CREATION。函数Create通过构造一个EnterResourceCreation对象来封装对接口API_ID_RESOURCE_CREATION的调用。封装过程如下所示:

    EnterResourceCreation::EnterResourceCreation(PP_Instance instance)
        : EnterBase(),
          functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) {
      ......
    }
           这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。

           在Plugin进程中,存在一个PluginGlobals单例对象。这个PluginGlobals单例对象可以通过调用PpapiGlobals类的静态成员函数获得。注意,PluginGlobals类是从PpapiGlobals继承下来的。

           获得上述PluginGlobals单例对象之后,EnterResourceCreation类的构造函数就调用它的成员函数GetResourceCreationAPI获得一个API_ID_RESOURCE_CREATION接口,如下所示:

    thunk::ResourceCreationAPI* PluginGlobals::GetResourceCreationAPI(
        PP_Instance instance) {
      PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance);
      if (dispatcher)
        return dispatcher->GetResourceCreationAPI();
      return NULL;
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/plugin_globals.cc中。

           从前面Chromium的Plugin进程启动过程分析一文可以知道,在Plugin进程中加载的Plugin Module对应有一个PluginDispatcher对象。这个PluginDispatcher对象与运行在Render进程中的HostDispatcher对象对应。所有属于该Plugin Module的Instance都使用相同的PluginDispatcher对象与Render进程通信。

           PluginGlobals类的成员函数GetResourceCreationAPI首先调用PluginDispatcher类的静态成员函数GetForInstance获得与参数instance描述的Plugin Instance对应的PluginDispatcher对象。有了这个PluginDispatcher对象之后,就可以调用它的成员函数GetResourceCreationAPI获得一个API_ID_RESOURCE_CREATION接口,如下所示:

    thunk::ResourceCreationAPI* PluginDispatcher::GetResourceCreationAPI() {
      return static_cast<ResourceCreationProxy*>(
          GetInterfaceProxy(API_ID_RESOURCE_CREATION));
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/plugin_dispatcher.cc中。

           PluginDispatcher类的成员函数GetResourceCreationAPI调用另外一个成员函数GetInterfaceProxy获得一个API_ID_RESOURCE_CREATION接口。PluginDispatcher类的成员函数GetInterfaceProxy是从父类Dispatcher继承下来的,在前面Chromium插件(Plugin)实例(Instance)创建过程分析一文中,我们已经分析过它的实现了。

           结合前面Chromium插件(Plugin)模块(Module)加载过程分析一文,我们可以知道,在Plugin进程中,API_ID_RESOURCE_CREATION接口被指定为ResourceCreationProxy类的静态成员函数Create,Dispatcher类的成员函数GetInterfaceProxy将会调用这个函数创建一个ResourceCreationProxy对象,如下所示:

    InterfaceProxy* ResourceCreationProxy::Create(Dispatcher* dispatcher) {
      return new ResourceCreationProxy(dispatcher);
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/resource_creation_proxy.cc中。

           参数dispatcher指向的是一个PluginDispatcher对象。这个PluginDispatcher对象就是在前面分析的PluginGlobals类的成员函数GetResourceCreationAPI中获取的PluginDispatcher对象。ResourceCreationProxy类的静态成员函数Create使用该PluginDispatcher对象创建了一个ResourceCreationProxy对象,并且返回给最初的调用者,也就是EnterResourceCreation类的构造函数。EnterResourceCreation类的构造函数又会将这个ResourceCreationProxy对象保存在成员变量functions_中。

           这一步执行完成后,回到前面分析的函数Create中。这时候就它构造了一个EnterResourceCreation对象,并且这个EnterResourceCreation对象的成员变量functions_指向了一个ResourceCreationProxy对象。

           函数Create接下来会调用上述EnterResourceCreation对象的成员函数functions它的成员变量functions_指向的ResourceCreationProxy对象。有了这个ResourceCreationProxy对象之后,就可以调用它的成员函数CreateGraphics3D请求Render进程为当前正在处理的Plugin Instance创建一个OpenGL上下文了。

           ResourceCreationProxy类的成员函数CreateGraphics3D的实现如下所示:

    PP_Resource ResourceCreationProxy::CreateGraphics3D(
        PP_Instance instance,
        PP_Resource share_context,
        const int32_t* attrib_list) {
      return PPB_Graphics3D_Proxy::CreateProxyResource(
          instance, share_context, attrib_list);
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/resource_creation_proxy.cc中。

           ResourceCreationProxy类的成员函数CreateGraphics3D调用PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource请求Render进程为当前正在处理的Plugin Instance创建一个OpenGL上下文,如下所示:

    PP_Resource PPB_Graphics3D_Proxy::CreateProxyResource(
        PP_Instance instance,
        PP_Resource share_context,
        const int32_t* attrib_list) {
      PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance);
      ......
    
      HostResource share_host;
      gpu::gles2::GLES2Implementation* share_gles2 = NULL;
      if (share_context != 0) {
        EnterResourceNoLock<PPB_Graphics3D_API> enter(share_context, true);
        if (enter.failed())
          return PP_ERROR_BADARGUMENT;
    
        PPB_Graphics3D_Shared* share_graphics =
            static_cast<PPB_Graphics3D_Shared*>(enter.object());
        share_host = share_graphics->host_resource();
        share_gles2 = share_graphics->gles2_impl();
      }
    
      std::vector<int32_t> attribs;
      if (attrib_list) {
        for (const int32_t* attr = attrib_list;
             attr[0] != PP_GRAPHICS3DATTRIB_NONE;
             attr += 2) {
          attribs.push_back(attr[0]);
          attribs.push_back(attr[1]);
        }
      }
      attribs.push_back(PP_GRAPHICS3DATTRIB_NONE);
    
      HostResource result;
      dispatcher->Send(new PpapiHostMsg_PPBGraphics3D_Create(
          API_ID_PPB_GRAPHICS_3D, instance, share_host, attribs, &result));
      ......
    
      scoped_refptr<Graphics3D> graphics_3d(new Graphics3D(result));
      if (!graphics_3d->Init(share_gles2))
        return 0;
      return graphics_3d->GetReference();
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。

           PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource首先调用PluginDispatcher类的静态成员函数GetForInstance获得与参数instance描述的Plugin Instance对应的一个PluginDispatcher对象。后面将会通过这个PluginDispatcher对象向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息,用来请求Render进程创建一个OpenGL上下文了。

           类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息传递了4个参数给Render进程,分别是:

           1. API_ID_PPB_GRAPHICS_3D,表示Render进程要将该IPC消息分发给API_ID_PPB_GRAPHICS_3D接口处理。

           2. instance,描述要创建OpenGL上下文的Plugin Instance。

           3. share_host,描述另外一个OpenGL上下文,新创建的OpenGL上下文要与它在同一个共享组中。

           4. attribs,描述新创建的OpenGL上下文应具有的属性。

           Render进程根据要求为目标Plugin Instance创建了一个OpenGL上下文之后,会为该OpenGL上下文分配一个ID,并且会将该ID返回给Plugin进程。Plugin进程再将这个ID封装在一个HostResource对象,然后将这个HostResource对象返回给PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource。

           PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource再根据获得的HostResource对象创建一个ppapi::proxy::Graphics3D对象,并且调用它的成员函数Init对它进行初始化,相当于是对新创建的OpenGL上下文进行初始化。初始化完成后,就可以进行使用了。

           新创建的Graphics3D对象初始化完成之后,PPB_Graphics3D_Proxy类的静态成员函数CreateProxyResource再调用它的成员函数GetReference获得一个类型为PP_Resource的引用。以后通过这个引用就可以使用该Graphics3D对象,也就是新创建的OpenGL上下文。

           接下来,我们首先分析Render进程为目标Plugin Instance创建OpenGL上下文的过程,即处理类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息的过程,然后再分析新创建的OpenGL上下文在Plugin进程的初始化过程。

           前面提到,Render进程会将类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息分发给API_ID_PPB_GRAPHICS_3D接口处理。从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文可以知道,在Render进程中,API_ID_PPB_GRAPHICS_3D接口是由一个PPB_Graphics3D_Proxy对象实现的,也就是Render进程会将类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息分发给该PPB_Graphics3D_Proxy对象处理,这是通过调用它的成员函数OnMessageReceived实现的。

           类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息在Render进程中的分发过程类似我们在前面Chromium插件(Plugin)实例(Instance)创建过程分析一文提到的类型为PpapiMsg_PPPInstance_DidCreate的IPC消息在Plugin进程中的分发过程,因此这里不再详细描述。

           接下来我们继续分析PPB_Graphics3D_Proxy类的成员函数OnMessageReceived处理类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息的过程,如下所示:

    bool PPB_Graphics3D_Proxy::OnMessageReceived(const IPC::Message& msg) {
      bool handled = true;
      IPC_BEGIN_MESSAGE_MAP(PPB_Graphics3D_Proxy, msg)
    #if !defined(OS_NACL)
        IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_Create,
                            OnMsgCreate)
        ......
    #endif  // !defined(OS_NACL)
    
        ......
        IPC_MESSAGE_UNHANDLED(handled = false)
    
      IPC_END_MESSAGE_MAP()
      // FIXME(brettw) handle bad messages!
      return handled;
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。

           从这里可以看到,PPB_Graphics3D_Proxy类的成员函数OnMessageReceived将类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息分发给另外一个成员函数OnMsgCreate处理,如下所示:

    void PPB_Graphics3D_Proxy::OnMsgCreate(PP_Instance instance,
                                           HostResource share_context,
                                           const std::vector<int32_t>& attribs,
                                           HostResource* result) {
      ......
    
      thunk::EnterResourceCreation enter(instance);
    
      if (enter.succeeded()) {
        result->SetHostResource(
          instance,
          enter.functions()->CreateGraphics3DRaw(instance,
                                                 share_context.host_resource(),
                                                 &attribs.front()));
      }
    }
          这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。

          参数instance要描述的是要创建OpenGL上下文的Plugin Instance在Render进程中对应的Proxy,也就是一个PepperPluginInstanceImpl对象。PPB_Graphics3D_Proxy类的成员函数OnMsgCreate首先是使用它构造一个EnterResourceCreation对象。前面提到,EnterResourceCreation类是用来封装资源创建接口API_ID_RESOURCE_CREATION的。因此,有了前面构造的EnterResourceCreation对象之后,PPB_Graphics3D_Proxy类的成员函数OnMsgCreate就可以使用API_ID_RESOURCE_CREATION接口来为参数instance描述的Plugin Instance创建一个OpenGL上下文了。

           EnterResourceCreation对象在Render进程的构造过程与在Plugin进程的构造过程是不一样的。前面我们已经分析过EnterResourceCreation对象在Plugin进程的构造过程,接下来我们继续分析EnterResourceCreation对象在Render进程的构造过程,以便了解Render进程是如何实现API_ID_RESOURCE_CREATION接口的。

           我们从EnterResourceCreation类的构造函数开始分析EnterResourceCreation对象在Render进程的构造过程,它的实现如下所示:

    EnterResourceCreation::EnterResourceCreation(PP_Instance instance)
        : EnterBase(),
          functions_(PpapiGlobals::Get()->GetResourceCreationAPI(instance)) {
      ......
    }
           这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。

           在Render进程中,存在一个HostGlobals单例对象。这个HostGlobals单例对象可以通过调用PpapiGlobals类的静态成员函数Get获得。HostGlobals类与前面提到的PluginGlobals类一样,都是从PpapiGlobals类继承下来的。

           获得上述HostGlobals单例对象之后,EnterResourceCreation类的构造函数就调用它的成员函数GetResourceCreationAPI获得一个API_ID_RESOURCE_CREATION接口,如下所示:

    ppapi::thunk::ResourceCreationAPI* HostGlobals::GetResourceCreationAPI(
        PP_Instance pp_instance) {
      PepperPluginInstanceImpl* instance = GetInstance(pp_instance);
      if (!instance)
        return NULL;
      return &instance->resource_creation();
    }
           这个函数定义在文件external/chromium_org/content/renderer/pepper/host_globals.cc中。

           HostGlobals类的成员函数GetResourceCreationAPI首先调用成员函数GetInstance获得参数pp_instance描述的一个Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象。有了这个PepperPluginInstanceImpl对象之后,就可以调用它的成员函数resource_creation获得一个API_ID_RESOURCE_CREATION接口,如下所示:

    class CONTENT_EXPORT PepperPluginInstanceImpl
        : public base::RefCounted<PepperPluginInstanceImpl>,
          public NON_EXPORTED_BASE(PepperPluginInstance),
          public ppapi::PPB_Instance_Shared,
          public NON_EXPORTED_BASE(cc::TextureLayerClient),
          public RenderFrameObserver {
     public:
      ......
    
      ppapi::thunk::ResourceCreationAPI& resource_creation() {
        return *resource_creation_.get();
      }
    
      ......
    
     private:
      ......
    
      scoped_ptr<ppapi::thunk::ResourceCreationAPI> resource_creation_;
    
      ......
    };
           这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.h中。

           PepperPluginInstanceImpl类的成员变量resource_creation_指向的是一个PepperInProcessResourceCreation对象。PepperPluginInstanceImpl类的成员函数resource_creation将这个PepperInProcessResourceCreation对象返回给调用者。这意味在Render进程中,API_ID_RESOURCE_CREATION接口是通过PepperInProcessResourceCreation类实现的。

           这一步执行完成后,回到前面分析的PPB_Graphics3D_Proxy类的成员函数OnMsgCreate中。这时候就它构造了一个EnterResourceCreation对象,并且这个EnterResourceCreation对象的成员变量functions_指向了一个PepperInProcessResourceCreation对象。

           PPB_Graphics3D_Proxy类的成员函数OnMsgCreate接下来会调用上述EnterResourceCreation对象的成员函数functions它的成员变量functions_指向的PepperInProcessResourceCreation对象。有了这个PepperInProcessResourceCreation对象之后,就可以调用它的成员函数CreateGraphics3DRaw为参数pp_instance描述的Plugin Instance创建一个OpenGL上下文。

           PepperInProcessResourceCreation类的成员函数CreateGraphics3DRaw是从父类ResourceCreationImpl继承下来的,它的实现如下所示:

    PP_Resource ResourceCreationImpl::CreateGraphics3DRaw(
        PP_Instance instance,
        PP_Resource share_context,
        const int32_t* attrib_list) {
      return PPB_Graphics3D_Impl::CreateRaw(instance, share_context, attrib_list);
    }
          这个函数定义在文件external/chromium_org/content/renderer/pepper/resource_creation_impl.cc中。

          ResourceCreationImpl类的成员函数CreateGraphics3DRaw调用PPB_Graphics3D_Impl类的静态成员函数CreateRaw为参数pp_instance描述的Plugin Instance创建一个OpenGL上下文,如下所示:

    PP_Resource PPB_Graphics3D_Impl::CreateRaw(PP_Instance instance,
                                               PP_Resource share_context,
                                               const int32_t* attrib_list) {
      PPB_Graphics3D_API* share_api = NULL;
      if (share_context) {
        EnterResourceNoLock<PPB_Graphics3D_API> enter(share_context, true);
        if (enter.failed())
          return 0;
        share_api = enter.object();
      }
      scoped_refptr<PPB_Graphics3D_Impl> graphics_3d(
          new PPB_Graphics3D_Impl(instance));
      if (!graphics_3d->InitRaw(share_api, attrib_list))
        return 0;
      return graphics_3d->GetReference();
    }
           这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。

           在Render进程中,Plugin使用的OpenGL上下文通过一个PPB_Graphics3D_Impl对象描述。因此,PPB_Graphics3D_Impl类的静态成员函数CreateRaw会创建一个PPB_Graphics3D_Impl对象,并且调用它的成员函数InitRaw对它进行初始化,也就是对它描述的OpenGL上下文进行初始化。初始化完成后,就会获得它的一个PP_Resource引用。这个引用将会返回给Plugin进程。Plugin进程以后就可以通过这个引用来使用前面创建出来的OpenGL上下文了。

           接下来我们继续分析Render进程为Plugin创建的OpenGL上下文的初始化过程,也就是PPB_Graphics3D_Impl类的成员函数InitRaw的实现,如下所示:

    bool PPB_Graphics3D_Impl::InitRaw(PPB_Graphics3D_API* share_context,
                                      const int32_t* attrib_list) {
      ......
    
      RenderThreadImpl* render_thread = RenderThreadImpl::current();
      ......
    
      channel_ = render_thread->EstablishGpuChannelSync(
          CAUSE_FOR_GPU_LAUNCH_PEPPERPLATFORMCONTEXT3DIMPL_INITIALIZE);
      ......
    
      command_buffer_ = channel_->CreateOffscreenCommandBuffer(
          surface_size, share_buffer, attribs, GURL::EmptyGURL(), gpu_preference);
      ......
    
      return true;
    }
           这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。

           PPB_Graphics3D_Impl类的成员函数InitRaw首先调用RenderThreadImpl类的静态成员函数current获得一个RenderThreadImpl对象。这个RenderThreadImpl对象描述的是当前Render进程的Render线程。有了这个RenderThreadImpl对象之后,就可以调用它的成员函数EstablishGpuChannelSync创建一个GPU通道,也就是一个GpuChannelHost对象。这个GPU通道的详细创建过程,可以参考前面Chromium的GPU进程启动过程分析一文。

           PPB_Graphics3D_Impl类的成员函数InitRaw接下来又会调用前面创建出来的GpuChannelHost对象的成员函数CreateOffscreenCommandBuffer请求GPU进程为其创建一个离屏渲染类型的OpenGL上下文。这个离屏渲染类型的OpenGL上下文是通过一个CommandBufferProxyImpl对象描述的。Render进程请求GPU进程创建OpenGL上下文的过程,可以参考前面Chromium硬件加速渲染的OpenGL上下文创建过程分析一文。以后Plugin执行GPU命令时,将会作用在上述离屏渲染的OpenGL上下文中。

           这一步执行完成后,Render进程就为Plugin创建了一个OpenGL上下文。这个OpenGL上下文通过一个PPB_Graphics3D_Impl对象描述。这个PPB_Graphics3D_Impl对象会被分配一个ID,并且这个ID会返回给Plugin进程。如前所述,Plugin进程获得了这个ID之后,就会将其封装在一个Graphics3D对象中。这个Graphics3D对象是用来Plugin进程描述一个OpenGL上下文的。

           回到前面分析的PPB_Graphics3D_Proxy类的成员函数CreateProxyResource中,这时候它就获得了一个OpenGL上下文之后。这个OpenGL上下文在Plugin进程中同样需要进行初始化。如前所述,这是通过调用ppapi::proxy::Graphics3D类的成员函数Init实现的。接下我们就继续分析Plugin进程初始化一个OpenGL上下文的过程,也就是分析ppapi::proxy::Graphics3D类的成员函数Init的实现,如下所示:

    bool Graphics3D::Init(gpu::gles2::GLES2Implementation* share_gles2) {
      ......
    
      command_buffer_.reset(
          new PpapiCommandBufferProxy(host_resource(), dispatcher));
    
      return CreateGLES2Impl(kCommandBufferSize, kTransferBufferSize,
                             share_gles2);
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。

           ppapi::proxy::Graphics3D类的成员函数Init首先会创建一个PpapiCommandBufferProxy对象。这个PpapiCommandBufferProxy对象是用来与前面在Render进程中创建的CommandBufferProxyImpl对象通信的,也就是前者会将提交给它的GPU命令转发给后者处理,后者又会将接收到的GPU命令发送给GPU进程执行。

           ppapi::proxy::Graphics3D类的成员函数Init接下来又会调用成员函数CreateGLES2Impl根据上述PpapiCommandBufferProxy对象创建一个Command Buffer GL接口。有了这个Command Buffer GL接口之后,Plugin就可以调用它提供的OpenGL函数执行3D渲染了。

           ppapi::proxy::Graphics3D类的成员函数CreateGLES2Impl是从父类PPB_Graphics3D_Shared继承下来的,它的实现如下所示:

    bool PPB_Graphics3D_Shared::CreateGLES2Impl(
        int32 command_buffer_size,
        int32 transfer_buffer_size,
        gpu::gles2::GLES2Implementation* share_gles2) {
      gpu::CommandBuffer* command_buffer = GetCommandBuffer();
      DCHECK(command_buffer);
    
      // Create the GLES2 helper, which writes the command buffer protocol.
      gles2_helper_.reset(new gpu::gles2::GLES2CmdHelper(command_buffer));
      if (!gles2_helper_->Initialize(command_buffer_size))
        return false;
    
      // Create a transfer buffer used to copy resources between the renderer
      // process and the GPU process.
      const int32 kMinTransferBufferSize = 256 * 1024;
      const int32 kMaxTransferBufferSize = 16 * 1024 * 1024;
      transfer_buffer_.reset(new gpu::TransferBuffer(gles2_helper_.get()));
      ......
    
      // Create the object exposing the OpenGL API.
      gles2_impl_.reset(new gpu::gles2::GLES2Implementation(
          gles2_helper_.get(),
          share_gles2 ? share_gles2->share_group() : NULL,
          transfer_buffer_.get(),
          bind_creates_resources,
          lose_context_when_out_of_memory,
          GetGpuControl()));
    
      if (!gles2_impl_->Initialize(
               transfer_buffer_size,
               kMinTransferBufferSize,
               std::max(kMaxTransferBufferSize, transfer_buffer_size),
               gpu::gles2::GLES2Implementation::kNoLimit)) {
        return false;
      }
    
      ......
    
      return true;
    }

           这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppb_graphics_3d_shared.cc。

           在Chromium中,Command Buffer GL接口是通过GLES2Implementation类描述的。因此,PPB_Graphics3D_Shared类的成员函数CreateGLES2Impl将会创建一个GLES2Implementation对象,并且保存在成员变量gles2_impl_中。

           创建这个GLES2Implementation对象需要一个Command Buffer和一个Transfer Buffer。前者通过一个GLES2CmdHelper对象描述,后者通过一个TransferBuffer对象描述的。在创建GLES2CmdHelper对象的过程中,又需要用到前面创建的一个PpapiCommandBufferProxy对象,以便可以将Plugin要执行的GPU命令转发给Render进程处理。这个PpapiCommandBufferProxy对象可以通过调用PPB_Graphics3D_Shared类的成员函数GetCommandBuffer获得。关于Command Buffer GL接口的创建和使用,可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。

           这一步执行完成后,回到前面分析的GLES2DemoInstance类的成员函数InitGL中,这时候它就创建了一个OpenGL上下文,并且这个OpenGL上下文已经初始化完成。接下来,GLES2DemoInstance类的成员函数InitGL会将前面创建出来的OpenGL上下文绑定到当前正在处理的Plugin Instance中去。执行了这个操作之后,Plugin Instance才可以在该OpenGL上下文中执行GPU命令。

           给一个Plugin Instance绑定OpenGL上下文是通过调用pp::Instance类的成员函数BindGraphics实现的,如下所示:

    bool Instance::BindGraphics(const Graphics3D& graphics) {
      if (!has_interface<PPB_Instance_1_0>())
        return false;
      return PP_ToBool(get_interface<PPB_Instance_1_0>()->BindGraphics(
          pp_instance(), graphics.pp_resource()));
    }
           这个函数定义在文件external/chromium_org/ppapi/cpp/instance.cc中。

           pp::Instance类的成员函数BindGraphics需要通过PPB_INSTANCE_INTERFACE_1_0接口为当前正在处理的Plugin Instance绑定一个OpenGL上下文。这个OpenGL上下文由参数graphics指向的一个Graphics3D对象描述。

           因此,pp::Instance类的成员函数BindGraphics首先会检查当前的Plugin进程是否支持PPB_INSTANCE_INTERFACE_1_0接口。如果支持的话,那么就会通过一个模板函数get_interface<PPB_Instance_1_0>获得该PPB_INSTANCE_INTERFACE_1_0接口,如下所示:

    template <typename T> inline T const* get_interface() {
      static T const* funcs = reinterpret_cast<T const*>(
          pp::Module::Get()->GetBrowserInterface(interface_name<T>()));
      return funcs;
    }
           这个模板函数定义在文件external/chromium_org/ppapi/cpp/module_impl.h中。

           这里的模板参数T为PPB_Instance_1_0,展开后得到模板函数get_interface<PPB_Instance_1_0>的具体实现为:

    inline PPB_Instance_1_0 const* get_interface() {
      static PPB_Instance_1_0 const* funcs = reinterpret_cast<PPB_Instance_1_0 const*>(
          pp::Module::Get()->GetBrowserInterface(interface_name<PPB_Instance_1_0>()));
      return funcs;
    }
           它首先会调用另外一个模板函数interface_name<PPB_Instance_1_0>获得要获取的接口名称,接着再调用前面提到的pp::Module类的静态成员函数Get获得当前Plugin进程中的一个pp::Module单例对象。有了这个pp::Module单例对象之后,就可以调用它的成员函数GetBrowserInterface根据名称获得接口。

          我们首先分析模板函数interface_name<PPB_Instance_1_0>的实现,以便知道要获取的接口名称是什么,如下所示:

    template <> const char* interface_name<PPB_Instance_1_0>() {
      return PPB_INSTANCE_INTERFACE_1_0;
    }
           这个函数定义在文件external/chromium_org/ppapi/cpp/instance.cc中。

           从这里可以看到,要获取的接口名称为PPB_INSTANCE_INTERFACE_1_0,也就我们要获取的是PPB_INSTANCE_INTERFACE_1_0接口。这个接口可以通过调用前面获得的pp::Module单例对象的GetBrowserInterface获得。

           在前面Chromium插件(Plugin)模块(Module)加载过程分析一文中,我们已经分析过了pp::Module类的GetBrowserInterface的实现,并且我们也知道,在Plugin进程中,PPB_INSTANCE_INTERFACE_1_0接口是由一个PPB_Instance_1_0对象实现的。这个PPB_Graphics3D_1_0对象的定义如下所示:

    const PPB_Instance_1_0 g_ppb_instance_thunk_1_0 = {
      &BindGraphics,
      &IsFullFrame
    };
           这个对象定义在文件external/chromium_org/ppapi/thunk/ppb_instance_thunk.cc中。

           这个PPB_Instance_1_0对象的成员变量BindGraphics是一个函数指针。这个函数指针指向了函数BindGraphics。这个函数也是定义在文件ppb_instance_thunk.cc中。

           回到pp::Instance类的成员函数BindGraphics中,现在我们就可以知道,它实际上是通过调用上述函数BindGraphics请求Render进程为当前正在处理的Plugin绑定一个OpenGL上下文的,如下所示:

    PP_Bool BindGraphics(PP_Instance instance, PP_Resource device) {
      ......
      EnterInstance enter(instance);
      ......
      return enter.functions()->BindGraphics(instance, device);
    }

           这个函数定义在文件external/chromium_org/ppapi/thunk/ppb_instance_thunk.cc中。

           函数BindGraphics需要通过另外一个接口API_ID_PPB_INSTANCE向Render进程发送一个IPC消息,以便请求后者为参数instance描述的Plugin Instance绑定另外一个参数device描述的OpenGL上下文。

           函数BindGraphics是通过EnterInstance类来使用接口API_ID_RESOURCE_CREATION,因此它首先会构造一个EnterInstance对象,如下所示:

    EnterInstance::EnterInstance(PP_Instance instance)
        : EnterBase(),
          functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
      ......
    }
           这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。

           前面提到,在Plugin进程中,存在一个PluginGlobals单例对象。这个PluginGlobals单例对象可以通过调用PpapiGlobals类的静态成员函数获得。获得了这个PluginGlobals单例对象之后,EnterInstance类的构造函数就调用它的成员函数GetInstanceAPI获得一个API_ID_PPB_INSTANCE接口,如下所示:

    thunk::PPB_Instance_API* PluginGlobals::GetInstanceAPI(PP_Instance instance) {
      PluginDispatcher* dispatcher = PluginDispatcher::GetForInstance(instance);
      if (dispatcher)
        return dispatcher->GetInstanceAPI();
      return NULL;
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/plugin_globals.cc中。

           PluginGlobals类的成员函数GetInstanceAPI首先调用PluginDispatcher类的静态成员函数GetForInstance获得一个与参数instance描述的Plugin Instance对应的PluginDispatcher对象。有了这个PluginDispatcher对象之后,再调用它的成员函数GetInstanceAPI获得一个API_ID_PPB_INSTANCE接口,如下所示:

    thunk::PPB_Instance_API* PluginDispatcher::GetInstanceAPI() {
      return static_cast<PPB_Instance_Proxy*>(
          GetInterfaceProxy(API_ID_PPB_INSTANCE));
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/plugin_dispatcher.cc中。

           PluginDispatcher类的成员函数GetInstanceAPI调用另外一个成员函数GetInterfaceProxy获得一个API_ID_PPB_INSTANCE接口。PluginDispatcher类的成员函数GetInterfaceProxy是从父类Dispatcher继承下来的。Dispatcher类的成员函数GetInterfaceProxy的实现,可以参考前面Chromium插件(Plugin)实例(Instance)创建过程分析一文中。

           再结合前面Chromium插件(Plugin)模块(Module)加载过程分析一文,我们可以知道,在Plugin进程中,API_ID_PPB_INSTANCE接口被指定为模板函数ProxyFactory<PPB_Instance_Proxy>。Dispatcher类的成员函数GetInterfaceProxy将会调用这个函数创建一个PPB_Instance_Proxy对象。这意味着在Plugin进程中,API_ID_PPB_INSTANCE接口是通过一个PPB_Instance_Proxy对象实现的。这个PPB_Instance_Proxy对象会返回给EnterInstance类的构造函数。

           回到前面分析的函数BindGraphics,这时候就它构造了一个EnterInstance对象,并且这个EnterInstance对象的成员变量functions_指向了一个PPB_Instance_Proxy对象。这个PPB_Instance_Proxy对象可以通过调用上述构造的EnterInstance对象的成员函数functions获得。有了这个PPB_Instance_Proxy对象之后,函数BindGraphics就可以调用它的成员函数BindGraphics向Render进程发送一个IPC消息,以便请求它为当前正在处理的Plugin绑定一个OpenGL上下文,如下所示:

    PP_Bool PPB_Instance_Proxy::BindGraphics(PP_Instance instance,
                                             PP_Resource device) {
      // If device is 0, pass a null HostResource. This signals the host to unbind
      // all devices.
      HostResource host_resource;
      PP_Resource pp_resource = 0;
      if (device) {
        Resource* resource =
            PpapiGlobals::Get()->GetResourceTracker()->GetResource(device);
        if (!resource || resource->pp_instance() != instance)
          return PP_FALSE;
        host_resource = resource->host_resource();
        pp_resource = resource->pp_resource();
      } else {
        // Passing 0 means unbinding all devices.
        dispatcher()->Send(new PpapiHostMsg_PPBInstance_BindGraphics(
            API_ID_PPB_INSTANCE, instance, 0));
        return PP_TRUE;
      }
    
      // We need to pass different resource to Graphics 2D and 3D right now.  Once
      // 3D is migrated to the new design, we should be able to unify this.
      EnterResourceNoLock<PPB_Compositor_API> enter_compositor(device, false);
      EnterResourceNoLock<PPB_Graphics2D_API> enter_2d(device, false);
      EnterResourceNoLock<PPB_Graphics3D_API> enter_3d(device, false);
      if (enter_compositor.succeeded() || enter_2d.succeeded()) {
        dispatcher()->Send(new PpapiHostMsg_PPBInstance_BindGraphics(
            API_ID_PPB_INSTANCE, instance, pp_resource));
        return PP_TRUE;
      } else if (enter_3d.succeeded()) {
        dispatcher()->Send(new PpapiHostMsg_PPBInstance_BindGraphics(
            API_ID_PPB_INSTANCE, instance, host_resource.host_resource()));
        return PP_TRUE;
      }
      return PP_FALSE;
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_instance_proxy.cc中。

           PPB_Instance_Proxy类的成员函数BindGraphics首先判断参数device的值是否不等于NULL。如果不等于NULL,那么它描述的就是一个OpenGL上下文,并且这个OpenGL上下文要与参数instance描述的Plugin Instance进行绑定。否则的话,就说明要将参数instance描述的Plugin Instance当前使用的OpenGL上下文设置为NULL。

           在我们这个情景中,参数device的值不等于NULL。在这种情况下,PPB_Instance_Proxy类的成员函数BindGraphics首先会根据这个参数获得一个Resource对象。这个Resource对象描述的就是一个OpenGL上下文。这个OpenGL上下文可能是用来执行2D渲染的,也可能是用来执行3D渲染的。PPB_Instance_Proxy类的成员函数BindGraphics会对这两种情况进行区别,不过最终都会向Render进程发送一个类型为PpapiHostMsg_PPBInstance_BindGraphics的IPC消息。这个IPC消息携带了3个参数:

           1. API_ID_PPB_INSTANCE,要求Render进程将该IPC消息分发给API_ID_PPB_INSTANCE接口处理。

           2. instance,表示目标Plugin Instance。

           3. 通过参数device获得一个PP_Resource对象,描述的是一个要与目标Plugin Instance进行绑定的OpenGL上下文。

           Render进程接收类型为PpapiHostMsg_PPBInstance_BindGraphics的IPC消息的过程与前面分析的类型为PpapiHostMsg_PPBGraphics3D_Create的IPC消息是类似的,只不过前者最后会分发给API_ID_PPB_INSTANCE接口处理,而后者会分发给API_ID_PPB_GRAPHICS_3D接口处理。它们在Render进程的分发过程都可以参考在前面Chromium插件(Plugin)实例(Instance)创建过程分析一文提到的类型为PpapiMsg_PPPInstance_DidCreate的IPC消息在Plugin进程的分发过程。

           从前面Chromium插件(Plugin)实例(Instance)创建过程分析一文还可以知道,在Render进程中,API_ID_PPB_INSTANCE接口是由一个PPB_Instance_Proxy对象实现的,也就是Render进程会将类型为PpapiHostMsg_PPBInstance_BindGraphics的IPC消息分发给该PPB_Instance_Proxy对象处理,这是通过调用它的成员函数OnMessageReceived实现的,如下所示:

    bool PPB_Instance_Proxy::OnMessageReceived(const IPC::Message& msg) {
      ......
    
      bool handled = true;
      IPC_BEGIN_MESSAGE_MAP(PPB_Instance_Proxy, msg)
    #if !defined(OS_NACL)
        ......
        IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBInstance_BindGraphics,
                            OnHostMsgBindGraphics)
        ......
    #endif  // !defined(OS_NACL)
    
        ......
    
        IPC_MESSAGE_UNHANDLED(handled = false)
      IPC_END_MESSAGE_MAP()
      return handled;
    }
          这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_instance_proxy.cc中。

          从这里可以看到,PPB_Instance_Proxy类的成员函数OnMessageReceived会将类型为PpapiHostMsg_PPBInstance_BindGraphics的IPC消息分发给另外一个成员函数OnHostMsgBindGraphics处理,如下所示:

    void PPB_Instance_Proxy::OnHostMsgBindGraphics(PP_Instance instance,
                                                   PP_Resource device) {
      // Note that we ignroe the return value here. Otherwise, this would need to
      // be a slow sync call, and the plugin side of the proxy will have already
      // validated the resources, so we shouldn't see errors here that weren't
      // already caught.
      EnterInstanceNoLock enter(instance);
      if (enter.succeeded())
        enter.functions()->BindGraphics(instance, device);
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_instance_proxy.cc中。

           参数instance要描述的是要绑定OpenGL上下文的Plugin Instance在Render进程中对应的Proxy,也就是一个PepperPluginInstanceImpl对象。PPB_Instance_Proxy类的成员函数OnHostMsgBindGraphics首先是使用它构造一个EnterInstanceNoLock对象。这个EnterInstanceNoLock对象是用来封装一个Instance API的。这个Instance API可以用来给一个Plugin Instance Proxy绑定一个OpenGL上下文。

           接下来我们从EnterInstanceNoLock类的构造函数开始分析一个EnterInstanceNoLock对象的构造过程,如下所示:

    EnterInstanceNoLock::EnterInstanceNoLock(PP_Instance instance)
        : EnterBase(),
          functions_(PpapiGlobals::Get()->GetInstanceAPI(instance)) {
      ......
    }
           这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。

           前面提到,在Render进程中,存在一个HostGlobals单例对象。这个HostGlobals单例对象可以通过调用PpapiGlobals类的静态成员函数Get获得。获得了这个HostGlobals单例对象之后,EnterInstanceNoLock类的构造函数就调用它的成员函数GetInstanceAPI获得一个Instance API,如下所示:

    ppapi::thunk::PPB_Instance_API* HostGlobals::GetInstanceAPI(
        PP_Instance instance) {
      // The InstanceAPI is just implemented by the PluginInstance object.
      return GetInstance(instance);
    }
           这个函数定义在文件external/chromium_org/content/renderer/pepper/host_globals.cc中。

           HostGlobals类的成员函数GetInstanceAPI调用另外一个成员函数GetInstance获得参数instance描述的一个Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象,如下所示:

    PepperPluginInstanceImpl* HostGlobals::GetInstance(PP_Instance instance) {
      ......
      InstanceMap::iterator found = instance_map_.find(instance);
      if (found == instance_map_.end())
        return NULL;
      return found->second;
    }
           这个函数定义在文件external/chromium_org/content/renderer/pepper/host_globals.cc。

           在Render进程中,每一个Plugin Instance Proxy都会以分配给它的ID保存在HostGlobals类的成员变量instance_map_描述的一个std::map中。因此,给出一个ID,就可以在这个std::map中找到它对应的Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象。

           在Render进程中,Instance API是通过PPB_Instance_API类描述的。PepperPluginInstanceImpl类又是从PPB_Instance_API类继承下来的。因此,一个PepperPluginInstanceImpl对象可以当作一个Instance API使用。这个PepperPluginInstanceImpl对象将会返回给EnterInstanceNoLock类的构造函数,并且保存在EnterInstanceNoLock类的成员变量functions_中。

           这一步执行完成后,回到前面分析的PPB_Instance_Proxy类的成员函数OnMessageReceived中。这时候就它构造了一个EnterInstanceNoLock对象,并且这个EnterInstanceNoLock对象的成员变量functions_指向了一个PepperInProcessResourceCreation对象。注意,这个PepperPluginInstanceImpl对象在用作Instance API的同时,也是即将要绑定OpenGL上下文的Plugin Instance Proxy。这个绑定操作是通过调用它的成员函数BindGraphics实现的,如下所示:

    PP_Bool PepperPluginInstanceImpl::BindGraphics(PP_Instance instance,
                                                   PP_Resource device) {
      ......
    
      scoped_refptr<ppapi::Resource> old_graphics = bound_graphics_3d_.get();
      if (bound_graphics_3d_.get()) {
        bound_graphics_3d_->BindToInstance(false);
        bound_graphics_3d_ = NULL;
      }
      if (bound_graphics_2d_platform_) {
        bound_graphics_2d_platform_->BindToInstance(NULL);
        bound_graphics_2d_platform_ = NULL;
      }
      if (bound_compositor_) {
        bound_compositor_->BindToInstance(NULL);
        bound_compositor_ = NULL;
      }
    
      ......
    
      const ppapi::host::PpapiHost* ppapi_host =
          RendererPpapiHost::GetForPPInstance(instance)->GetPpapiHost();
      ppapi::host::ResourceHost* host = ppapi_host->GetResourceHost(device);
      PepperGraphics2DHost* graphics_2d = NULL;
      PepperCompositorHost* compositor = NULL;
      if (host) {
        if (host->IsGraphics2DHost()) {
          graphics_2d = static_cast<PepperGraphics2DHost*>(host);
        } else if (host->IsCompositorHost()) {
          compositor = static_cast<PepperCompositorHost*>(host);
        } 
        ......
      }
    
      EnterResourceNoLock enter_3d(device, false);
      PPB_Graphics3D_Impl* graphics_3d =
          enter_3d.succeeded()
              ? static_cast(enter_3d.object())
              : NULL;
      
      if (compositor) {
        if (compositor->BindToInstance(this)) {
          bound_compositor_ = compositor;
          ......
          return PP_TRUE;
        }
      } else if (graphics_2d) {
        if (graphics_2d->BindToInstance(this)) {
          bound_graphics_2d_platform_ = graphics_2d;
          ......
          return PP_TRUE;
        }
      } else if (graphics_3d) {
        // Make sure graphics can only be bound to the instance it is
        // associated with.
        if (graphics_3d->pp_instance() == pp_instance() &&
            graphics_3d->BindToInstance(true)) {
          bound_graphics_3d_ = graphics_3d;
          ......
          return PP_TRUE;
        }
      }
    
      // The instance cannot be bound or the device is not a valid resource type.
      return PP_FALSE;
    }
           这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cpp。

           Plugin Instance不仅可以将UI渲染在一个2D上下文或者一个3D上下文中,还可以渲染在一个Compositor上下文中。一个2D上下文和一个3D上下文描述的都只是一个Layer,而一个Compositor上下文可以描述多个Layer。也就是说,Plugin Instance可以通过Compositor上下文将自己的UI划分为多个Layer进行渲染。Chromium提供了一个PPB_COMPOSITOR_INTERFACE_0_1接口,Plugin Instance可以通过这个接口为自己创建一个Compositor上下文。

           其中,2D上下文通过一个PepperGraphics2DHost类描述,3D上下文通过PPB_Graphics3D_Impl类描述,而Compositor上下文通过PepperCompositorHost类描述。PepperPluginInstanceImpl类分别通过bound_graphics_2d_platform_、bound_graphics_3d_和bound_compositor_三个成员变量描述上述三种不同的绘图上下文。

           PepperPluginInstanceImpl类的成员函数BindGraphics主要做的工作就是将参数device描述的绘图上下文绑定为当前正在处理的Plugin Instance Proxy的绘图上下文。如果当前正在处理的Plugin Instance Proxy以前绑定过其它的绘图上下文,那么PepperPluginInstanceImpl类的成员函数BindGraphics就会先解除对它们的绑定。

           PepperPluginInstanceImpl类的成员函数BindGraphics接下来再判断参数device描述的绘图上下文是2D上下文、3D上下文,还是Compositor上下文,并且获取它所对应的PepperGraphics2DHost对象、PPB_Graphics3D_Impl对象和PepperCompositorHost对象,然后保存在对应的成员变量中。这样,当前正在处理的Plugin Instance Proxy就知道了自己最新绑定的绘图上下文是什么。最后,这些获得的PepperGraphics2DHost对象、PPB_Graphics3D_Impl对象和PepperCompositorHost对象的成员函数BindToInstance也会被调用,表示它们当前处于被绑定状态。

           在我们这个情景中,参数device描述的绘图上下文是一个3D上下文,也就是一个3D的OpenGL上下文。因此, PepperPluginInstanceImpl类的成员函数BindGraphics最终会获得一个PPB_Graphics3D_Impl对象,并且保存在成员变量bound_graphics_3d_中。最后,这个PPB_Graphics3D_Impl对象的成员函数BindToInstance会被调用,表示它当前处于被绑定的状态,如下所示:

    bool PPB_Graphics3D_Impl::BindToInstance(bool bind) {
      bound_to_instance_ = bind;
      return true;
    }
          这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。

          从前面的调用过程可以知道,参数bind的值等于true。这时候PPB_Graphics3D_Impl类的成员变量bound_to_instance_的值也会被设置为true,表示当前正在处理的PPB_Graphics3D_Impl对象描述的3D上下文处于被绑定状态。

          这一步执行完成后,GLES2DemoInstance类就通过PPB_GRAPHICS_3D_INTERFACE_1_0和PPB_INSTANCE_INTERFACE_1_0这两个接口创建和绑定了一个OpenGL上下文,也就是初始化好一个OpenGL环境。接下来,GLES2DemoInstance类就可以通过PPB_OPENGLES2_INTERFACE接口进行3D渲染了。这个PPB_OPENGLES2_INTERFACE接口是在GLES2DemoInstance类的构造函数中获取的,如下所示:

    GLES2DemoInstance::GLES2DemoInstance(PP_Instance instance, pp::Module* module)
        : pp::Instance(instance), pp::Graphics3DClient(this),
          callback_factory_(this),
          module_(module),
          context_(NULL),
          fullscreen_(false) {
      assert((gles2_if_ = static_cast<const PPB_OpenGLES2*>(
          module->GetBrowserInterface(PPB_OPENGLES2_INTERFACE))));
      ......
    }

           这个函数定义在文件external/chromium_org/ppapi/examples/gles2/gles2.cc中。

           GLES2DemoInstance类的构造函数是通过调用参数module指向的一个pp::Module对象的成员函数GetBrowserInterface获取PPB_OPENGLES2_INTERFACE接口的。在前面Chromium插件(Plugin)模块(Module)加载过程分析一文中,我们已经分析过了pp::Module类的GetBrowserInterface的实现,并且我们也知道,在Plugin进程中,PPB_OPENGLES2_INTERFACE接口是由一个PPB_OpenGLES2对象实现的。这个PPB_OpenGLES2对象的定义如下所示:

      static const struct PPB_OpenGLES2 ppb_opengles2 = {  
          &ActiveTexture,                       &AttachShader,  
          &BindAttribLocation,                  &BindBuffer,  
          &BindFramebuffer,                     &BindRenderbuffer,  
          &BindTexture,                         &BlendColor,  
          &BlendEquation,                       &BlendEquationSeparate,  
          &BlendFunc,                           &BlendFuncSeparate,  
          &BufferData,                          &BufferSubData,  
          &CheckFramebufferStatus,              &Clear,  
          ......  
          &UseProgram,                          &ValidateProgram,  
          &VertexAttrib1f,                      &VertexAttrib1fv,  
          &VertexAttrib2f,                      &VertexAttrib2fv,  
          &VertexAttrib3f,                      &VertexAttrib3fv,  
          &VertexAttrib4f,                      &VertexAttrib4fv,  
          &VertexAttribPointer,                 &Viewport}; 
           这个对象定义在文件这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppb_opengles2_shared.cc中。

           从这里我们就可以看到PPB_OPENGLES2_INTERFACE接口提供的函数,例如函数Clear,就相当于是OpenGL函数glClear。

           有了这个PPB_OPENGLES2_INTERFACE接口之后,GLES2DemoInstance类就会在成员函数FlickerAndPaint中调用它提供的函数进行3D渲染,如下所示:

    void GLES2DemoInstance::FlickerAndPaint(int32_t result, bool paint_blue) {
      ......
    
      float r = paint_blue ? 0 : 1;
      float g = 0;
      float b = paint_blue ? 1 : 0;
      float a = 0.75;
      gles2_if_->ClearColor(context_->pp_resource(), r, g, b, a);
      gles2_if_->Clear(context_->pp_resource(), GL_COLOR_BUFFER_BIT);
      ......
    
      context_->SwapBuffers(cb);
      ......
    }
           这个函数定义在文件external/chromium_org/ppapi/examples/gles2/gles2.cc中。

           GLES2DemoInstance类的成员函数FlickerAndPaint执行的3D渲染很简单,仅仅是调用PPB_OPENGLES2_INTERFACE接口提供的函数ClearColor和Clear绘制一个背景色,相当于是调用了OpenGL函数glClearColor和glClear绘制一个背景色。在实际开发中,我们可以调用PPB_OPENGLES2_INTERFACE接口提供的其它函数完成更复杂的3D渲染效果。

           从前面的分析可以知道,GLES2DemoInstance类的成员变量context_指向的是一个pp::Graphics3D对象。这个Graphics3D描述的是一个OpenGL上下文。每完成一帧渲染,GLES2DemoInstance类的成员函数FlickerAndPaint都需要调用这个pp::Graphics3D对象的成员函数SwapBuffers,用来交换当前使用的OpenGL上下文的前后两个缓冲区,相当于是调用了EGL函数eglSwapBuffers。

           接下来,我们就以PPB_OPENGLES2_INTERFACE接口提供的函数Clear的执行过程为例,分析Plugin是如何通过PPB_OPENGLES2_INTERFACE接口执行3D渲染操作的,它的实现如下所示:

    void Clear(PP_Resource context_id, GLbitfield mask) {
      Enter3D enter(context_id, true);
      if (enter.succeeded()) {
        ToGles2Impl(&enter)->Clear(mask);
      }
    }
          这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppb_opengles2_shared.cc中。

          Enter3D定义为一个thunk::EnterResource<thunk::PPB_Graphics3D_API>类,如下所示:

    typedef thunk::EnterResource<thunk::PPB_Graphics3D_API> Enter3D;
          这个类定义在文件external/chromium_org/ppapi/shared_impl/ppb_opengles2_shared.cc中。

          回到函数Clear中, 它的参数context_id描述的是一个OpenGL上下文资源ID。函数Clear首先将这个资源ID封装在一个EnterResource<thunk::PPB_Graphics3D_API>对象中,如下所示:

    template<typename ResourceT, bool lock_on_entry = true>
    class EnterResource
        : public subtle::LockOnEntry<lock_on_entry>,  // Must be first; see above.
          public subtle::EnterBase {
     public:
      EnterResource(PP_Resource resource, bool report_error)
          : EnterBase(resource) {
        Init(resource, report_error);
      }
      ......
    };
           这个函数定义在文件external/chromium_org/ppapi/thunk/enter.h中。

           EnterResource<thunk::PPB_Graphics3D_API>类的构造函数首先会调用父类EnterBase的构造函数。在调用的时候,会将参数resource描述的资源ID传递进去。EnterBase的构造函数通过这个资源ID获得对应的资源对象,保存在成员变量resource_中,如下所示:

    EnterBase::EnterBase(PP_Resource resource)
        : resource_(GetResource(resource)),
          retval_(PP_OK) {
      PpapiGlobals::Get()->MarkPluginIsActive();
    }
           这个函数定义在文件external/chromium_org/ppapi/thunk/enter.cc中。

           从前面的分析可以知道,这个资源对象实际上就是一个ppapi::proxy::Graphics3D对象。与此同时,EnterBase的构造函数会通过调用Plugin进程的一个PluginGlobals单例对象的成员函数MarkPluginIsActive将当前正在执行3D渲染操作的Plugin标记为Active状态。

           回到EnterResource<thunk::PPB_Graphics3D_API>类的构造函数中,它接下来又会调用另外一个成员函数Init执行其它的初始化工作,如下所示:

    template<typename ResourceT, bool lock_on_entry = true>
    class EnterResource
        : public subtle::LockOnEntry<lock_on_entry>,  // Must be first; see above.
          public subtle::EnterBase {
     public:
      .......
    
      ResourceT* object() { return object_; }
      ......
    
     private:
      void Init(PP_Resource resource, bool report_error) {
        if (resource_)
          object_ = resource_->GetAs<ResourceT>();
        ......
      }
    
      ResourceT* object_;
    
      ......
    };
           这个函数定义在文件external/chromium_org/ppapi/thunk/enter.h中。

           EnterResource<thunk::PPB_Graphics3D_API>类的成员函数Init主要是将从父类EnterBase继承下来的成员变量resource_指向的ppapi::proxy::Graphics3D对象转换为一个thunk::PPB_Graphics3D_API对象,并且保存在成员变量object_中。这个thunk::PPB_Graphics3D_API对象以后可以通过调用EnterResource<thunk::PPB_Graphics3D_API>类的成员函数object获得。

           注意,ppapi::proxy::Graphics3D类是从ppapi::PPB_Graphics3D_Shared类继承下来的,ppapi::PPB_Graphics3D_Shared类又是从thunk::PPB_Graphics3D_API类继承下来的,因此,我们可以将ppapi::proxy::Graphics3D对象转换为一个thunk::PPB_Graphics3D_API对象。

           这一步执行完成后,回到前面分析的函数函数Clear中,这时候它就构造了一个EnterResource<thunk::PPB_Graphics3D_API>对象,并且这个EnterResource<thunk::PPB_Graphics3D_API>对象的成员变量object_指向了一个thunk::PPB_Graphics3D_API对象。函数Clear接下来又会调用另外一个函数ToGles2Impl从前面构造的EnterResource<thunk::PPB_Graphics3D_API>对象中获得一个GLES2Implementation对象,如下所示:

    gpu::gles2::GLES2Implementation* ToGles2Impl(Enter3D* enter) {
      ......
      return static_cast<PPB_Graphics3D_Shared*>(enter->object())->gles2_impl();
    }
           这个函数定义在文件ternal/chromium_org/ppapi$ vi shared_impl/ppb_opengles2_shared.cc中。

           函数ToGles2Impl首先调用参数enter指向的EnterResource<thunk::PPB_Graphics3D_API>对象的成员函数object获得它的成员变量object_所指向的一个thunk::PPB_Graphics3D_API对象。

           前面提到,由于EnterResource<thunk::PPB_Graphics3D_API>类的成员变量object_指向的实际上是一个ppapi::proxy::Graphics3D对象,并且ppapi::proxy::Graphics3D类是从ppapi::PPB_Graphics3D_Shared类继承下来的。因此函数ToGles2Impl又可以将它获得的thunk::PPB_Graphics3D_API对象转换为一个ppapi::PPB_Graphics3D_Shared对象。

           有了这个ppapi::PPB_Graphics3D_Shared对象之后,函数ToGles2Impl就可以调用它的成员函数gles2_impl获得它内部维护的一个GLES2Implementation对象。这个GLES2Implementation对象就是在前面分析的ppapi::proxy::Graphics3D类的成员函数CreateGLES2Impl中创建的。

           函数ToGles2Impl最后会将获得的GLES2Implementation对象返回给函数Clear。函数Clear获得了这个GLES2Implementation对象之后,就会调用它的成员函数Clear给参数context_id描述的OpenGL上下文设置一个背景色。

           从前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文可以知道,当我们调用GLES2Implementation类的成员函数执行GPU命令时,这些GPU命令实际上只是写入到了一个Command Buffer中。这个Command Buffer在合适的时候将会调用它的成员函数WaitForGetOffsetInRange通知GPU进程执行它里面的GPU命令。

           从前面的分析可以知道,为Plugin创建的GLES2Implementation对象所用的Command Buffer是通过一个PpapiCommandBufferProxy对象描述的。当这个WaitForGetOffsetInRange对象的成员函数WaitForGetOffsetInRange被调用的时候,它实际上只是向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange的IPC消息,如下所示:

    void PpapiCommandBufferProxy::WaitForTokenInRange(int32 start, int32 end) {
      ......
    
      bool success;
      gpu::CommandBuffer::State state;
      if (Send(new PpapiHostMsg_PPBGraphics3D_WaitForTokenInRange(
              ppapi::API_ID_PPB_GRAPHICS_3D,
              resource_,
              start,
              end,
              &state,
              &success)))
        UpdateState(state, success);
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/ppapi_command_buffer_proxy.cc中。

           这个类型为PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange的IPC消息携带了一个参数ppapi::API_ID_PPB_GRAPHICS_3D,表示Render进程接收到这个IPC消息之后,要分发一个API_ID_PPB_GRAPHICS_3D接口处理。

           前面提到,在Render进程中,API_ID_PPB_GRAPHICS_3D接口是由一个PPB_Graphics3D_Proxy对象实现的,也就是Render进程会将类型为PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange的IPC消息分发给该PPB_Graphics3D_Proxy对象处理,这是通过调用它的成员函数OnMessageReceived实现的,如下所示:

    bool PPB_Graphics3D_Proxy::OnMessageReceived(const IPC::Message& msg) {
      bool handled = true;
      IPC_BEGIN_MESSAGE_MAP(PPB_Graphics3D_Proxy, msg)
    #if !defined(OS_NACL)
        ......
        IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange,
                            OnMsgWaitForGetOffsetInRange)
        ......
    #endif  // !defined(OS_NACL)
    
        ......
        IPC_MESSAGE_UNHANDLED(handled = false)
    
      IPC_END_MESSAGE_MAP()
      // FIXME(brettw) handle bad messages!
      return handled;
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc。

           从这里可以看到,PPB_Graphics3D_Proxy类的成员函数OnMessageReceived会将类型为PpapiHostMsg_PPBGraphics3D_WaitForGetOffsetInRange的IPC消息分发给另外一个成员函数OnMsgWaitForGetOffsetInRange处理,如下所示:

    void PPB_Graphics3D_Proxy::OnMsgWaitForGetOffsetInRange(
        const HostResource& context,
        int32 start,
        int32 end,
        gpu::CommandBuffer::State* state,
        bool* success) {
      EnterHostFromHostResource<PPB_Graphics3D_API> enter(context);
      ......
      *state = enter.object()->WaitForGetOffsetInRange(start, end);
      *success = true;
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc。

           从前面的分析可以知道,参数context描述的OpenGL上下文在Render进程中用一个PPB_Graphics3D_Impl对象描述。PPB_Graphics3D_Proxy类的成员函数OnMsgWaitForGetOffsetInRange会通过构造一个EnterHostFromHostResource<PPB_Graphics3D_API>对象来获得这个PPB_Graphics3D_Impl对象,并且会调用这个PPB_Graphics3D_Impl对象的成员函数WaitForGetOffsetInRange,用来通知它将Plugin写入在Command Buffer中的GPU命令发送给GPU进程处理。

            PPB_Graphics3D_Impl类的成员函数WaitForGetOffsetInRange的实现如下所示:

    gpu::CommandBuffer::State PPB_Graphics3D_Impl::WaitForGetOffsetInRange(
        int32_t start,
        int32_t end) {
      GetCommandBuffer()->WaitForGetOffsetInRange(start, end);
      return GetCommandBuffer()->GetLastState();
    }
           这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。

           PPB_Graphics3D_Impl类的成员函数WaitForGetOffsetInRange首先调用成员函数GetCommandBuffer获得一个CommandBuffer对象,如下所示:

    gpu::CommandBuffer* PPB_Graphics3D_Impl::GetCommandBuffer() {
      return command_buffer_;
    }
           这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。

           PPB_Graphics3D_Impl类的成员函数GetCommandBuffer返回的是成员变量command_buffer_指向的一个CommandBuffer对象。从前面的分析可以知道,这个CommandBuffer对象实现上是一个CommandBufferProxyImpl对象,它是在前面分析的PPB_Graphics3D_Impl类的成员函数InitRaw中创建的。

           回到PPB_Graphics3D_Impl类的成员函数WaitForGetOffsetInRange中,它获得了一个CommandBufferProxyImpl对象之后,就会调用它的成员函数WaitForGetOffsetInRange通知GPU进程执行Plugin写入在Command Buffer中的GPU命令。这个通知过程可以参考前面Chromium硬件加速渲染的OpenGL命令执行过程分析一文。

           这一步执行完成后,回到前面分析的GLES2DemoInstance类的成员函数FlickerAndPaint中,它通过PPB_OPENGLES2_INTERFACE接口提供的函数渲染好一帧后,就会调用其成员变量context_指向的一个pp::Graphics3D对象的成员函数SwapBuffers交换Plugin当前使用的OpenGL上下文的前后两个缓冲区,也就是相当于是执行EGL函数eglSwapBuffers。

           接下来,我们就从pp::Graphics3D类的成员函数SwapBuffers开始,分析Plugin交换当前使用的OpenGL上下文的前后两个缓冲区的过程,如下所示:

    int32_t Graphics3D::SwapBuffers(const CompletionCallback& cc) {
      ......
    
      return get_interface<PPB_Graphics3D_1_0>()->SwapBuffers(
          pp_resource(),
          cc.pp_completion_callback());
    }
           这个函数定义在文件external/chromium_org/ppapi/cpp/graphics_3d.cc中。

           pp::Graphics3D类的成员函数SwapBuffers首先调用我们前面分析过的模板函数get_interface<PPB_Graphics3D_1_0>获得一个PPB_GRAPHICS_3D_INTERFACE_1_0接口。获得了PPB_GRAPHICS_3D_INTERFACE_1_0接口之后,再调用它提供的函数SwapBuffers请求Render进程交换正在处理的pp::Graphics3D对象描述的OpenGL上下文的前后缓冲区。

           PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的函数SwapBuffers的实现如下所示:

    int32_t SwapBuffers(PP_Resource context,
                        struct PP_CompletionCallback callback) {
      ......
      EnterResource<PPB_Graphics3D_API> enter(context, callback, true);
      if (enter.failed())
        return enter.retval();
      return enter.SetResult(enter.object()->SwapBuffers(enter.callback()));
    }
           这个函数定义在文件external/chromium_org/ppapi/thunk/ppb_graphics_3d_thunk.cc中。

           与前面分析的PPB_OPENGLES2_INTERFACE接口提供的函数Clear类似,PPB_GRAPHICS_3D_INTERFACE_1_0接口提供的函数SwapBuffers也是通过构造一个EnterResource<thunk::PPB_Graphics3D_API>对象来获得参数context描述的一个OpenGL上下文资源对象,也就是一个ppapi::proxy::Graphics3D对象。有了这个ppapi::proxy::Graphics3D对象之后,就可以调用它的成员函数SwapBuffers请求Render进程交换它描述的OpenGL上下文的前后缓冲区了。

           ppapi::proxy::Graphics3D类的成员函数SwapBuffers是从父类ppapi::PPB_Graphics3D_Shared继承下来的,它的实现如下所示:

    int32_t PPB_Graphics3D_Shared::SwapBuffers(
        scoped_refptr<TrackedCallback> callback) {
      ......
      return DoSwapBuffers();
    }
           这个函数定义在文件external/chromium_org/ppapi/shared_impl/ppb_graphics_3d_shared.cc中。

           ppapi::proxy::Graphics3D类的成员函数SwapBuffers又会调用由子类实现的成员函数DoSwapBuffers请求Render进程交换当前正在处理的ppapi::proxy::Graphics3D对象描述的OpenGL上下文的前后缓冲区。

           在我们这个情景中,这个子类即为ppapi::proxy::Graphics3D,因此接下来ppapi::proxy::Graphics3D类的成员函数DoSwapBuffers会被调用,它的实现如下所示:

    int32 Graphics3D::DoSwapBuffers() {
      gles2_impl()->SwapBuffers();
      IPC::Message* msg = new PpapiHostMsg_PPBGraphics3D_SwapBuffers(
          API_ID_PPB_GRAPHICS_3D, host_resource());
      msg->set_unblock(true);
      PluginDispatcher::GetForResource(this)->Send(msg);
    
      return PP_OK_COMPLETIONPENDING;
    }
          这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。

          ppapi::proxy::Graphics3D类的成员函数DoSwapBuffers首先是通过调用成员函数gles2_impl获得前面为Plugin创建的一个Command Buffer GL接口。有了这个Command Buffer GL接口之后,就可以调用它的成员函数SwapBuffers向Command Buffer写入一个gles2::cmds::SwapBuffers命令。GPU进程在执行这个命令的时候,就会交换当前正在处理的ppapi::proxy::Graphics3D对象描述的OpenGL上下文的前后缓冲区。

          ppapi::proxy::Graphics3D类的成员函数DoSwapBuffers接下来又会调用PluginDispatcher类的静态成员函数GetForResource获得与当前正在处理的Plugin对应的一个PluginDispatcher对象,并且通过这个PluginDispatcher对象向Render进程发送一个类型为PpapiHostMsg_PPBGraphics3D_SwapBuffers的IPC消息,用来通知Render进程更新当前正在处理的Plugin在网页上的视图。这个IPC消息包含两个参数:

          1. API_ID_PPB_GRAPHICS_3D,表示Render进程要将该IPC消息分发给API_ID_PPB_GRAPHICS_3D接口处理。

          2. 通过调用成员函数host_resource获得的一个HostResource对象,这个HostResource对象描述的就是要交换前面缓冲区的OpenGL上下文。

          前面提到,在Render进程中,API_ID_PPB_GRAPHICS_3D接口是由一个PPB_Graphics3D_Proxy对象实现的,也就是Render进程会将类型为PpapiHostMsg_PPBGraphics3D_SwapBuffers的IPC消息分发给该PPB_Graphics3D_Proxy对象处理,这是通过调用它的成员函数OnMessageReceived实现的,如下所示:

    bool PPB_Graphics3D_Proxy::OnMessageReceived(const IPC::Message& msg) {
      bool handled = true;
      IPC_BEGIN_MESSAGE_MAP(PPB_Graphics3D_Proxy, msg)
    #if !defined(OS_NACL)
        ......
        IPC_MESSAGE_HANDLER(PpapiHostMsg_PPBGraphics3D_SwapBuffers,
                            OnMsgSwapBuffers)
        ......
    #endif  // !defined(OS_NACL)
    
        ......
        IPC_MESSAGE_UNHANDLED(handled = false)
    
      IPC_END_MESSAGE_MAP()
      // FIXME(brettw) handle bad messages!
      return handled;
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。

           从这里可以看到,PPB_Graphics3D_Proxy类的成员函数OnMessageReceived会将类型为PpapiHostMsg_PPBGraphics3D_SwapBuffers的IPC消息分发给另外一个成员函数OnMsgSwapBuffers处理,如下所示:

    void PPB_Graphics3D_Proxy::OnMsgSwapBuffers(const HostResource& context) {
      EnterHostFromHostResourceForceCallback<PPB_Graphics3D_API> enter(
          context, callback_factory_,
          &PPB_Graphics3D_Proxy::SendSwapBuffersACKToPlugin, context);
      if (enter.succeeded())
        enter.SetResult(enter.object()->SwapBuffers(enter.callback()));
    }
           这个函数定义在文件external/chromium_org/ppapi/proxy/ppb_graphics_3d_proxy.cc中。

           参数context描述的是一个要交换前后缓冲区的OpenGL上下文。从前面的分析可以知道,在Render进程中,这个OpenGL上下文是通过一个PPB_Graphics3D_Impl对象描述的。PPB_Graphics3D_Proxy类的成员函数OnMsgSwapBuffers通过构造一个EnterHostFromHostResourceForceCallback<PPB_Graphics3D_API>来获得这个PPB_Graphics3D_Impl对象,并且调用这个PPB_Graphics3D_Impl对象的成员函数SwapBuffers更新当前正在处理的Plugin在网页上的视图。

           PPB_Graphics3D_Impl类的成员函数SwapBuffers是从父类ppapi::PPB_Graphics3D_Shared继承下来的。前面我们已经分析过ppapi::PPB_Graphics3D_Shared类的成员函数SwapBuffers的实现了,它最终会调用由子类实现的成员函数DoSwapBuffers,即PPB_Graphics3D_Impl类的成员函数DoSwapBuffers。

           PPB_Graphics3D_Impl类的成员函数DoSwapBuffers的实现如下所示:

    int32 PPB_Graphics3D_Impl::DoSwapBuffers() {
      ......
    
      if (bound_to_instance_) {
        // If we are bound to the instance, we need to ask the compositor
        // to commit our backing texture so that the graphics appears on the page.
        // When the backing texture will be committed we get notified via
        // ViewFlushedPaint().
        //
        // Don't need to check for NULL from GetPluginInstance since when we're
        // bound, we know our instance is valid.
        HostGlobals::Get()->GetInstance(pp_instance())->CommitBackingTexture();
        commit_pending_ = true;
      } 
    
      ......
    
      return PP_OK_COMPLETIONPENDING;
    }
           这个函数定义在文件external/chromium_org/content/renderer/pepper/ppb_graphics_3d_impl.cc中。

           从前面的分析可以知道,当前正在处理的PPB_Graphics3D_Impl对象描述的OpenGL上下文此时处于绑定状态,也就是它的成员变量bound_to_instance_的值等于true。在这种情况下,PPB_Graphics3D_Impl类的成员函数DoSwapBuffers首先会通过HostGlobals类的静态成员函数Get获得当前Render进程中的一个HostGlobals单例对象。有了这个HostGlobals单例对象之后,就可以调用它的成员函数GetInstance获得与当前正在处理的PPB_Graphics3D_Impl对象进行绑定的一个Plugin Instance Proxy,也就是一个PepperPluginInstanceImpl对象。

          有了上述PepperPluginInstanceImpl对象之后,PPB_Graphics3D_Impl类的成员函数DoSwapBuffers就可以调用它的成员函数CommitBackingTexture更新它在网页上的视图,如下所示:

    void PepperPluginInstanceImpl::CommitBackingTexture() {
      ......
      gpu::Mailbox mailbox;
      uint32 sync_point = 0;
      bound_graphics_3d_->GetBackingMailbox(&mailbox, &sync_point);
      ......
      texture_layer_->SetTextureMailboxWithoutReleaseCallback(
          cc::TextureMailbox(mailbox, GL_TEXTURE_2D, sync_point));
      texture_layer_->SetNeedsDisplay();
    }
           这个函数定义在文件external/chromium_org/content/renderer/pepper/pepper_plugin_instance_impl.cc。 

           从前面的分析可以知道,PepperPluginInstanceImpl类的成员变量bound_graphics_3d_指向的是一个PPB_Graphics3D_Impl对象。这个PPB_Graphics3D_Impl对象描述的是一个OpenGL上下文。这个OpenGL上下文就是当前正在处理的PepperPluginInstanceImpl对象绑定的OpenGL上下文。

           PepperPluginInstanceImpl类的另外一个成员变量texture_layer_指向的是一个cc::TextureLayer对象。前面提到,网页中的每一个<embed>标签在网页的CC Layer Tree中都对应有一个Texture Layer。这个Texture Layer就是通过上述cc::TextureLayer对象描述的。

           有了上述PPB_Graphics3D_Impl对象之后,PepperPluginInstanceImpl类的成员函数CommitBackingTexture就可以调用它的成员函数GetBackingMailbox获得已经被交换到后端的缓冲区。这个缓冲区实际上是一个纹理缓冲区。这个纹理缓冲区将会作为<embed>标签的内容,因此PepperPluginInstanceImpl类的成员函数CommitBackingTexture就会将它设置上述cc::TextureLayer对象所要绘制的内容。这是通过调用该cc::TextureLayer对象的成员函数SetTextureMailboxWithoutReleaseCallback实现的。

           最后,PepperPluginInstanceImpl类的成员函数CommitBackingTexture调用上述cc::TextureLayer对象的成员函数SetNeedsDisplay将自己标记为需要更新。这样在下一个VSync信号到来的时候,前面设置为它的内容的纹理缓冲区就会被渲染和合成在网页的UI上。这个渲染和合成过程可以参考前面Chromium网页渲染机制简要介绍和学习计划这个系列的文章。

           这样,我们就分析完成了Plugin执行3D渲染的过程,也就是Plugin为<embed>标签绘制3D视图的过程,这是通过调用Chromium提供的PPB_OPENGLES2_INTERFACE接口完成的。从这个过程我们就可以看到Plugin和Chromium的交互过程,也就是它们可以互相调用对方提供的接口,从而使得Plugin可以像我们在前面Chromium扩展(Extension)机制简要介绍和学习计划这个系列的文章提到的Extension一样,增强网页的功能。

           至此,我们也分析完成了Chromium的Plugin机制。重新学习可以参考前面Chromium插件(Plugin)机制简要介绍和学习计划一文。更多的信息也可以关注老罗的新浪微博:http://weibo.com/shengyangluo

    作者:Luoshengyang 发表于2016/11/14 1:00:15 原文链接
    阅读:30786 评论:4 查看评论
    Viewing all 5930 articles
    Browse latest View live