在日常工作中,剖析源码解决相关问题

Posted 刘兆贤

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在日常工作中,剖析源码解决相关问题相关的知识,希望对你有一定的参考价值。

 

 本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处!

2011年开始做安卓开发,今年是第9个年头。回首这些年,从开发做起,做过组长,也做过架构,主要时间还是在做开发,也就是跟业务打交道。第5个年头,没能再上一个台阶,受2016年的“互联网企业倒闭潮流”的影响,先后在2家公司待了不长时间,来到现在的公司,时间紧-一周一个版本、负担重-几年的代码积累、人员新-小组司龄不超过半年、业务多-1(原业务)+4(新业务)模块开发,过去的1年多是在赶进度,往往是在持续做业务,主要是直播,很少有机会去整理框架或者Sdk,只能做到核心Api封装,遇到不少未曾重视过的UI细节。今天来讲一讲常用的ScrollView+LinearLayout和PopupWindow问题,用于记录,以防后面再发生这种小问题。

1、为什么ViewGroup的addView方法和PopupWindow的show方法都不支持设置width和height?

通常情况下,遇到item不多且会变动的功能,往往考虑使用ScrollView+LinearLayout的方案,ListView之类的控件过于庞大且复用性不强,LinearLayout的Orientation有横向和纵向两个添加子View的方式,比较适用这种场景。以往都是针对子View设置padding来保障LinearLayout的宽度和高度;这次想通过设置子View的宽高,让子View进行自适应,可是实验失败:

实验方案:linearLayout.addView(view),未使用LayoutParams。

翻看源码:

    public void addView(View child, int index) 
        if (child == null) 
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        
        LayoutParams params = child.getLayoutParams();
        if (params == null) 
            params = generateDefaultLayoutParams();
            if (params == null) 
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            
        
        addView(child, index, params);
    

确认如果不添加LayoutParams,则默认生成一个(通过LayoutInflate生成的View无LayoutParams)

    protected LayoutParams generateDefaultLayoutParams() 
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    

可以得知width和height设置失效。但padding设置有效,因为它是从AttributSet时取的值。

因此:方案是设置LinearLayout和子View的padding即可。

 

接下来是PopupWindow的宽高设置问题,同理,见源码

    public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) 
        if (isShowing() || !hasContentView()) 
            return;
        

        TransitionManager.endTransitions(mDecorView);

        attachToAnchor(anchor, xoff, yoff, gravity);

        mIsShowing = true;
        mIsDropdown = true;

        final WindowManager.LayoutParams p =
                createPopupLayoutParams(anchor.getApplicationWindowToken());
        preparePopup(p);

        final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
                p.width, p.height, gravity, mAllowScrollingAnchorParent);
        updateAboveAnchor(aboveAnchor);
        p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;

        invokePopup(p);
    
    protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) 
        final WindowManager.LayoutParams p = new WindowManager.LayoutParams();

        // These gravity settings put the view at the top left corner of the
        // screen. The view is then positioned to the appropriate location by
        // setting the x and y offsets to match the anchor's bottom-left
        // corner.
        p.gravity = computeGravity();
        p.flags = computeFlags(p.flags);
        p.type = mWindowLayoutType;
        p.token = token;
        p.softInputMode = mSoftInputMode;
        p.windowAnimations = computeAnimationResource();

        if (mBackground != null) 
            p.format = mBackground.getOpacity();
         else 
            p.format = PixelFormat.TRANSLUCENT;
        

        if (mHeightMode < 0) 
            p.height = mLastHeight = mHeightMode;
         else 
            p.height = mLastHeight = mHeight;
        

        if (mWidthMode < 0) 
            p.width = mLastWidth = mWidthMode;
         else 
            p.width = mLastWidth = mWidth;
        

        p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH
                | PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;

        // Used for debugging.
        p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));

        return p;
    

又是LayoutParams的设置问题。

解决方案:给整体布局设置宽高以及padding之后,再嵌套一个布局,即可预防LayoutParams被重置的问题。

2、如果你的弹窗,可能是异步弹出(无论是动画,还是接口的原因)?会报Can not perform this action after onSaveInstanceState

那你最好放弃commit,换用commitInternal

原因:BackStackRecord

    public int commit() 
        return commitInternal(false);
    

    public int commitAllowingStateLoss() 
        return commitInternal(true);
    

 

    int commitInternal(boolean allowStateLoss) 
        if (mCommitted) 
            throw new IllegalStateException("commit already called");
        
        。。。
        mManager.enqueueAction(this, allowStateLoss);
        return mIndex;
    
    public void enqueueAction(OpGenerator action, boolean allowStateLoss) 
        if (!allowStateLoss) 
            checkStateLoss();
        
        。。。
    
    private void checkStateLoss() 
        if (mStateSaved) 
            throw new IllegalStateException(
                    "Can not perform this action after onSaveInstanceState");
        
        if (mNoTransactionsBecause != null) 
            throw new IllegalStateException(
                    "Can not perform this action inside of " + mNoTransactionsBecause);
        
    

3、View.postDelay做延时,在removeCallBack时失效。原因是控件已经不在Window上。

执行任务时,控件已经在Window层,所以就没走下面的getRunQueue方法

    public boolean postDelayed(Runnable action, long delayMillis) 
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) 
            return attachInfo.mHandler.postDelayed(action, delayMillis);
        

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().postDelayed(action, delayMillis);
        return true;
    

结束任务时,控件已经脱离Window层,执行getRunQueue也没用。

    public boolean removeCallbacks(Runnable action) 
        if (action != null) 
            final AttachInfo attachInfo = mAttachInfo;
            if (attachInfo != null) 
                attachInfo.mHandler.removeCallbacks(action);
                attachInfo.mViewRootImpl.mChoreographer.removeCallbacks(
                        Choreographer.CALLBACK_ANIMATION, action, null);
            
            getRunQueue().removeCallbacks(action);
        
        return true;
    

所以这种情况用,还是用Rxandroid吧。

以上是关于在日常工作中,剖析源码解决相关问题的主要内容,如果未能解决你的问题,请参考以下文章

Android高级进阶(源码剖析篇) 前言

Spring Boot 工作原理剖析

Java Review - 并发编程_ThreadPoolExecutor原理&源码剖析

Java Review - 并发编程_ThreadPoolExecutor原理&源码剖析

教你如何剖析源码

本周学习计划 | 微服务搜索引擎架构Hbase源码剖析深度学习……