问题

1. StateMachine寻找公共祖先的算法

状态层级

S3为我们需要切换到的状态,那么寻找S3与S5的公共祖先,步骤如下:

  1. 先将目的状态S3入栈mTempStateStack
  2. 根据目的状态S3往上回溯父节点,如果父节点未激活则入栈mTempStateStack,继续往上回溯,直至父节点为激活状态,处于激活状态的节点P1便是S3与S5的公共祖先。当然也存在没有公共祖先的情况,如目的状态为QuttingState。

入栈mTempStateStack是方便后续状态切换回调,找到公共祖先P1后,则回调用invokeExitMethods()从mStateStack的栈顶,向下依次调用相应State#exit方法,直至公共祖先P1,若无公共祖先,则全部调用exit方法。


初始状态的退出

接下来进入我们目的状态的enter流程,先将mTempStateStack整合至mStateStack,接着调用invokeEnterMethods从mStateStack的栈底到栈顶依次调用相应State的enter方法。

整合目的状态栈
源码分析如下
StateInfo的关键数据结构:
            //当前状态
            stateInfo.state = state;
            //当前状态的父节点信息StateInfo类型
            stateInfo.parentStateInfo = parentStateInfo;
            //当前状态是否激活,调用State#enter方法后会激活置为true
            stateInfo.active = false;

发生时机:
在处理状态转换时,调用setupTempStateStackWithStatesToEnter(destState)中寻找公共祖先。

private void performTransitions(State msgProcessedState, Message msg) {
       //省略状态机日志相关的代码
            .............
      //mDestState通过StateMachine#translationTo(IState state)赋值
       State destState = mDestState;
          if (destState != null) {
                /**
                 * Process the transitions including transitions in the enter/exit methods
                 */
             while (true) {
                    if (mDbg) mSm.log("handleMessage: new destination call exit/enter");
            /**找到mDestState与当前的初始状态的共同祖先,并设置mTempStateStack
              *如果不存在共同祖先则返回null*/
            StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
                    // flag is cleared in invokeEnterMethods before entering the target state
                    mTransitionInProgress = true;
                    //从当前初始状态到公共状态依次调用State.exit方法(不含公共状态)
                    //如果没有公共状态,则整个mStateStack中的State.exit都会被调用
                    invokeExitMethods(commonStateInfo);
              .......
}

算法实现:

 private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {
            /**
             * Search up the parent list of the destination state for an active
             * state. Use a do while() loop as the destState must always be entered
             * even if it is active. This can happen if we are exiting/entering
             * the current state.
             */
            //逻辑上清空mTempStateStack,只是把栈的探头移动到0的位置,内容并未清除
            mTempStateStackCount = 0;
            StateInfo curStateInfo = mStateInfo.get(destState);
            do {
                mTempStateStack[mTempStateStackCount++] = curStateInfo;
                curStateInfo = curStateInfo.parentStateInfo;
            } while ((curStateInfo != null) && !curStateInfo.active);

            if (mDbg) {
                mSm.log("setupTempStateStackWithStatesToEnter: X mTempStateStackCount="
                        + mTempStateStackCount + ",curStateInfo: " + curStateInfo);
            }
            return curStateInfo;
        }

逻辑很简单,先将destState节点存入mTempStateStack,然后根据destState节点往上回溯,如果该节点为非激活状态则存入mTempStateStack,直到该节点的父节点为激活状态,如果没有公共节点那么往上回溯,返回值肯定是null。一旦没有公共祖先,那么invokeExitMethods()方法将会调用mStateStack栈中所有状态的exit方法,源码如下:

      /**
         * Call the exit method for each state from the top of stack
         * up to the common ancestor state.
         *从mStateStack的栈顶依次调用State.exit方法直至公共祖先(不含公共祖先)
         *如果不存在公共祖先,即commonStateInfo为null,则整个状态栈都会调用exit
         */
        private final void invokeExitMethods(StateInfo commonStateInfo) {
            while ((mStateStackTopIndex >= 0)
                    && (mStateStack[mStateStackTopIndex] != commonStateInfo)) {
                State curState = mStateStack[mStateStackTopIndex].state;
                if (mDbg) mSm.log("invokeExitMethods: " + curState.getName());
                curState.exit();
                mStateStack[mStateStackTopIndex].active = false;
                mStateStackTopIndex -= 1;
            }
        }

2. 软件渲染模式下,invalidate导致父View重新渲染

在软件渲染模式下,子View的invalidate导致父View重绘有一个前提条件 —— 父View设置了backgroud

如果未设置background是不会导致父View重绘,只是会调用父View的dispatchDraw()来绘制child,并没有重绘自己。与调用invailidate同级的其他child,则会通过判断自己是否在需要绘制的dirty区域内,来决定自己是否需要重绘,在dirty区域之外则不进行重绘。

反观硬件加速绘制,无论父View是否设置backgroud,都不会重绘自己,所以这个问题可以转换为,为什么软件渲染在父View有设置了backgroud时,一定要重新绘制呢?

从源码的角度来看,只是因为设置了background之后,父View#mPrivateFlag中的SKIP_DRAW标志位为0(即false),这就导致了父View的重绘,但这也无法回答上面的问题,为何设置了background SKIP_DRAW标志位就一定要置为0呢?

参考老罗的博客,关于Dispaly List的文章,发现View的backgroud会被抽象为一个Background Render Node,具备自己的Display List,而软件渲染的子视图,则是先绘制在Bitmap上,然后在记录在父View的Display List当中。
目前关于问题2,原因并不知道,感觉background会被单独抽象成为一个Render Node会是一个突破口,因为它和从源码中提出的问题相关性很大。


老罗博客

3. native Heap内存模型

图-1 Linux进程的内存区域划分

整体划分和JVM的内存划分差异不大,整体也主要包括堆区、栈区、以及方法区

逻辑层上的Heap模型
Pages and Heap

由图1可知,heap是可动态调整大小的,从低地址向高地址增长的,Linux通过一个break指针来表示当前已经映射分配的内存,break指针之后代表未映射的区域,访问这段区域程序会抛出bus error。rlimit则表示可动态映射分配的上限,可通过setrlimit和getrlimit方法对rlimit进行设置及访问。
要增加heap已映射区的大小,可调用如下方法,移动break指针

//直接移动break指针到指定位置
int brk(void*addr);
/**增量移动,返回上一次的break指针或者移动后的指针,
  *但由于返回值并未明确指出是上一次的指针还是移动后的指针,
  *所以返回值不能直接使用,
  *需通过sbrk(0),来明确获取移动后的指针,
  *这种情况下上一次指针和移动后的指针相等
  */
void *sbrk(intptr_tincrement);

图中带虚线的矩形代表页,Linux系统典型的内存页大小为4096B。由于操作系统是按页管理内存的,所以break指针可能并不位于页的边界。

物理层上Heap的组织实现

前面已经对heap整体划分有了一定了解,那么对于已经映射的区域,Linux是如何将他们组织起来,便于我们快速访问到我们分配的对象的呢?



原来是通过一个链表的组织起来的,链表中的节点包括meta-data和data区域,meta-data元数据区域,用来描述数据,如data区的大小,下一个块的指针,是否是空闲块,malloc函数返回的便是分配空间的pointer。

typedef struct s_block *t_block;
struct s_block { 
     size_t size; //data区大小
     t_block next; //指向下一个块
     int free;  //是否是空闲块
}
查找分配

通过上面一小节了解到,在已经映射分配的区域中,是存在空闲块的,那么当我们重新malloc分配空间的时候,便可以复用那些空闲块,以节省内存。空闲块可能有多个,那么是如何决定复用那个空闲块的呢?
一般有二种算法:
First fit:从chunk链表头开始,依此向下查找空闲块,直到查找到一个size大于我们预期分配大小的空闲块,便返回。
Best fit:遍历整个chunk链表,使用数据区大小大于size且差值最小的空闲块作为此次分配的块。
Best fit具有较高的内存利用率(payLoad高),分配的空闲块非常接近我们想要的空间大小;而First fit具有更好的运行效率,不会遍历整个chunk链表。
参考文章:
Malloc_tutorial
Malloc函数实现原理

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

推荐阅读更多精彩内容