精华内容
下载资源
问答
  • 2022-03-16 22:44:03

    对于很多大型企业而言,尽管他们已经上线了SAP/ERP系统,但是他们的仓储管理方式仍然采用人工手动记账进行日常仓库作业,包括出入库、盘点、调拨、补货、质检等等。随着仓储业务的运作日益频繁,现有的仓储方式不仅工作量大、繁琐、耗时长,且人工操作导致仓库作业数据不准确,数据无法实时共享,加大管理难度。

    仓库管理主要问题点:

      1、库内管理制度难以落实,库位规划、物料/成品的摆放依旧较为混乱,加大了作业难度;

      2、对老员工依赖高,且人员流动较大,仓库整体效率低,出错率高;

      3、业务部门与仓储部门无法有效联动;

      4、仓库主管无法随时掌握库内实时运作动态,出现问题既不能及时发现,也不能快速解决;

      5、缺乏准确的实时库存,导致账务库存与实际库存具有较大差异,库存成本高;

      6、缺乏各类专业报表支撑管理层决策;

      7、引进的仓储系统,必须要可以与SAP/ERP系统集成,否则容易信息孤岛,影响供应链效率。

    开源字节高度成熟的WMS仓储管理系统可以很好满足大型企业仓储管理需求,产品的灵活可配置性也为公司未来业务的扩展提供便利,同时系统 可与公司内部的SAP/ERP、SRM系统和MES系统等无缝集成,实现多系统间数据同步共享,从而建设统一化、标准化、透明化的管理体系。

    1、策略管理

      a、收货策略:部分收货(一单收多次)、合单收货(一人收多单)、并行收货(多人收一单);

      b、上架策略:系统采用分区存储、近发货口优先、ABC规则优先、货品属性优先(性、重、体)等上架策略;

      c、出库策略:先进先出(按入库自然时间)、保质期优先、库内动线优先、批次优先或指定批次出库;

      2、精细化管理

      系统采用逐号管理方式,管理到每一个库位、每一个货物,平台根据上架、拣货等智能策略,精确提供存放、提取货物的优先方案,提升工作效率。

      3、提升各环节作业效率

    系统采用条码技术,对仓储业务管理中物料的入库、在库盘点、出库等各个环节进行数据自动化采集,使各个环节运作流畅,信息也能实现共享,确保储管理人员实时准确的掌握库存的真实情况。

      4、集成内部信息系统

    在很多企业里,往往都会有着多个信息化系统,如SAP/ERP、MES、TMS、SRM等等,而仓储管理系统具有很好的集成性,可与这些系统对接在一起,让数据实时同步,提升公司供应链效率。

      完善的仓储管理不仅可以提高公司仓储资产的利用率,减少库存和降低存货管理成本,而且可以缩短商品的交付时间,提高对顾客的快速反应能力和提高公司的经营利润。而开源字节wms仓储管理系统正好可以帮助企业实现这一点。

    如若转载,请注明出处:开源字节   https://sourcebyte.cn/article/72.html

     

    更多相关内容
  • Android WMS流程

    2019-08-27 16:54:53
    文章目录 WMS流程 Window & Activity & DecorView & ViewRoot关系 流程图 源码分析 ActivityThread#handleLaunchActivity() ActivityThread#performLaunchActivity() Activity#attach() Activity#setContentView() ...

    WMS流程

    Window & Activity & DecorView & ViewRoot关系

    • Activity:
      • 处理器,控制生命周期和处理事件
      • 1个Activity包含1个Window
      • 通过回调与Window、View交互
    • Window:
      • 实现类是PhoneWindow
      • 视图的承载器,用于管理View的显示
      • PhoneWindow内部包含一个DecorView
      • 内部通过WindowManager管理UI,将DecorView交给ViewRoot
    • DecorView:
      • 顶层View,本质是FrameLayout
      • 显示和加载布局
      • View的事件最先经过DecorView,再分发到子View
    • ViewRoot:
      • ViewRootImpl是其实现类
      • 用于连接WindowManagerDecorView
      • 完成View的三大流程:测量、布局、绘制

    在这里插入图片描述

    在这里插入图片描述

    流程图

    在这里插入图片描述

    源码分析

    ActivityThread#handleLaunchActivity()

    • 初始化WMS服务
    • 调用performLaunchActivity()
    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
      
        //初始化WMS服务
        WindowManagerGlobal.initialize();
        
        //调用performLaunchActivity()初始化Activity
        final Activity a = performLaunchActivity(r, customIntent);
    
        return a;
    }
    

    ActivityThread#performLaunchActivity()

    • 创建Activity
    • 调用Activity#attach()
    • 回调Activity#onCreate()
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        Activity activity = null;
        try {
            //创建Activity
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        } catch (Exception e) {       
        }
    
    
        if (activity != null) {
            //调用Activity#attach(),初始化Activity
            activity.attach(appContext, this, getInstrumentation(), r.token,
                            r.ident, app, r.intent, r.activityInfo, title, r.parent,
                            r.embeddedID, r.lastNonConfigurationInstances, config,
                            r.referrer, r.voiceInteractor, window, r.configCallback,
                            r.assistToken);
    
    
            //callActivityOnCreate()方法最终调用Activity#onCreate()
            activity.mCalled = false;
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
        }
        return activity;
    }
    

    Activity#attach()

    • 创建PhoneWindow对象,PhoneWindow是Window的实现类
    • 创建WindowManageImpl,WindowManageImpl是WindowManage的实现类
    final void attach(Context context, ActivityThread aThread,
                      Instrumentation instr, IBinder token, int ident,
                      Application application, Intent intent, ActivityInfo info,
                      CharSequence title, Activity parent, String id,
                      NonConfigurationInstances lastNonConfigurationInstances,
                      Configuration config, String referrer, IVoiceInteractor voiceInteractor,
                      Window window, ActivityConfigCallback activityConfigCallback, 
                      IBinder assistToken) {
        
        attachBaseContext(context);
    
        //创建PhoneWindow
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
    
        //设置软键盘
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
    
        //在Window#setWindowManager()中创建WindowManager
        mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
    }
    

    Activity#setContentView()

    • 最终调用PhoneWindow#setContentView()
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    
    public void setContentView(@LayoutRes int layoutResID) {
        //最终调用PhoneWindow#setContentView()
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
    

    PhoneWindow#setContentView()

    • 创建DecorView
    • 将Activity的XML布局加载到DecorView中
    public void setContentView(int layoutResID) {
        /*
        mContentParent为DecorView里的内容栏
        当mContentParent==null时,会调用installDecor()创建一个DecorView;
        当mContentParent!=null时,会删除所有子View
        */
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
    
        //加载解析XML布局,并添加到mContentParent内容栏里
        mLayoutInflater.inflate(layoutResID, mContentParent);
    
        //内容发生变化通知回调
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }
    

    PhoneWindow#installDecor()

    private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //创建DecorView
            mDecor = generateDecor(-1);
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            //为DecorView设置布局格式
            mContentParent = generateLayout(mDecor);
        }
    }
    
    //创建DecorView
    protected DecorView generateDecor(int featureId) {
        return new DecorView(context, featureId, this, getAttributes());
    }
    
    //为DecorView设置布局格式
    //DecorView本质是一个FrameLayout里面包含:TitleView和ContentView
    protected ViewGroup generateLayout(DecorView decor) {
        //从主题文件中获取样式信息
        TypedArray a = getWindowStyle();
        //根据主题样式,加载窗口布局
        int layoutResource;
        int features = getLocalFeatures();
        //加载layoutResource
        View in = mLayoutInflater.inflate(layoutResource, null);
    
        //往DecorView中添加子View
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 
        mContentRoot = (ViewGroup) in;
    
        //这里获取的是mContentParent = 即为内容栏(content)对应的DecorView = FrameLayout子类
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); 
        return contentParent;
    }
    

    ActivityThread#handleResumeActivity()

    • 回调Activity#onResume()
    • 获取PhoneWindow和DecorView,将Activity与DecorView绑定
    • 调用WindowManageImpl#addView()将DecorView传入
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, 
                                     boolean isForward, String reason) {
    
        //最终会调用Activity#onResume()
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    
        final Activity a = r.activity;
    
        if (r.window == null && !a.mFinished && willBeVisible) {
            //获取PhoneWindow对象
            r.window = r.activity.getWindow();
            //获取DecorView对象
            View decor = r.window.getDecorView();
            
            //DecorView不可见
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            
            //DecorView与Activity建立关联
            a.mDecor = decor;
    
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //调用WindowManageImpl#addView()
                    wm.addView(decor, l);
                } 
            }
        } 
    
        if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
            if (r.activity.mVisibleFromClient) {
                //这时候DecorView才可见
                r.activity.makeVisible();
            }
        }
    }
    

    WindowManagerImpl#addView()

    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        //WindowManagerGlobal#addView()    
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                        mContext.getUserId());
    }
    

    WindowManagerGlobal#addView()

    • 创建ViewRootImpl
    • 将DecorView交给ViewRootImpl处理
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
    
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }
    
        ViewRootImpl root;
        synchronized (mLock) {    
            //创建ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            try {
                //调用ViewRootImpl#setView()
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
            }
        }
    }
    

    ViewRootImpl#setView()

    • ViewRootImpl最终会触发遍历操作
    • 依次执行View的测量、布局、绘制流程
    public void setView(View view, WindowManager.LayoutParams attrs, 
                        View panelParentView, int userId) {
        requestLayout();        
    }
    

    ViewRootImpl#requestLayout()

    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //检查是否在主线程,否则抛出异常
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
    
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    

    ViewRootImpl#doTraversal()

    • 最终调用ViewRootImpl#performTraversals()
    void doTraversal() {  
        //最终调用View的measure、layout、draw流程
        performTraversals();
    }
    

    ViewRootImpl#performTraversals()

    • 依次调用performMeasure() / performLayout() / performDraw() 完成View的三大流程
    private void performTraversals() {
    
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
        performLayout(lp, mWidth, mHeight);
    
        /*		
        viewTreeObserver.addOnGlobalLayoutListener(object:ViewTreeObserver.OnGlobalLayoutListener{
                override fun onGlobalLayout() {
                   //可以在这里获取View的宽高
                }
        })
        */
        mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
    
        performDraw();
    }
    

    ViewRootImpl#performMeasure()

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
    
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
    

    ViewRootImpl#performLayout()

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
                               int desiredWindowHeight) {
        final View host = mView;
        if (host == null) {
            return;
        }
    
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    }
    

    ViewRootImpl#performDraw()

    private void performDraw() {
        boolean canUseAsync = draw(fullRedrawNeeded);
    }
    
    private boolean draw(boolean fullRedrawNeeded) {
        drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                     scalingRequired, dirty, surfaceInsets)
    }
    
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
                                 boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
        mView.draw(canvas);
    }
    

    总结

    • 在performLaunchActivity阶段,会创建Activity、PhoneWindow、WindowManageImpl,并回调Activity#onCreate(),接着执行setContentView流程,创建DecorView并将XML布局解析加载到DecorView中。
    • 在handleResumeActivity阶段,会调用Activity#onResume(),将Activity与DecorView建立关联,创建ViewRootImpl,将DecorView交给ViewRootImpl处理,最终ViewRootImpl依次执行View的三大流程。

    常见问题

    invalidate和requestLayout区别

    • requestLayout:requestLayout会直接递归调用父View的requestLayout,直到ViewRootImpl,然后触发ViewRoomtImpl的peformTraversals()方法,会依次调用onMeasure()onLayout(),不一定会触发onDraw();当在layout过程中发现四个顶点(left、top、right、bottom)和以前不一样时,会触发invalidate,调用onDraw,进行重新绘制。
    • invalidate:View的invalidate不会触发ViewRootImpl的invalidate,而是递归调用父View的invalidateChildParent,知道ViewRootImpl的invalidateChildParent,然后触发perfromTraversals,由于mLayoutRequested为false,不会调用onMeasure和onLayout,只会调用onDraw,ViewGroup的onDraw不会被调用(除非设置背景),倒置当前View被重绘。
    展开全文
  • wms 仓储相关的流程图,包括 进货入库 上架 验货 拣货 出库等
  • Android WMS及绘制流程

    2022-01-24 13:56:27
    Android Handler_暮冬一十四的博客-CSDN博客 Android Binder_暮冬一十四的博客-CSDN博客 Android Zygote_暮冬一十四的博客-CSDN博客 ...获取到wms服务的IBinder,再去创建了一个WMS的代理类WindowMana

    主角:ViewRootImpl、Choreographer、Surfaceflinfer

    WMS扮演了什么角色?

    作为协调者,协调view布局,绘制;

    1. 在ActivityThread中创建Actiivty后,调用activity.attach()时,创建一个窗体对象PhoneWindow
    2. PhoneWindow创建了一个WMS的代理桥接类WindowManagerImpl对象,作为WMS在app中的代表;
    3. WindowManagerImpl对象中的(mGlobal)WindowManagerGlobal专门和WMS通信,在mGlobal里面获取了到了WMS的Binder:getWindowSession()->WMS::openSession();

    setContentView()

    1. 调用PhoneWindow.setContentView(resouseID)
    2. PhoneWindow中:创建mDector:窗体上的整个View:里面有官方的主题布局+用户自己的布局;
    3. PhoneWindow中:创建mContentParent:官方主题布局中提供给用户装载布局的容器:id为content;
    4. 调用mLayoutInflater.inflater(resouseID,mContentParent):
    5. 解析用户的布局xml
    6. 递归调用:解析根布局,通过反射创建根布局;解析子view,通过反射创建view;
    7. 最后PhoneWindow中的mContentParent加载用户的根布局;
    8. 提交view数据

    ps:这里递归调用,若嵌套层级太多,会导致栈溢出;因为递归调用不会释放栈;

    ViewRootImpl

    单例,管理所有View的绘制策略;

    注意onCreate.setContentView后view数据已解析并实例化了;

    1. 在状态机为Resume时:
    2. 调用WindowManagerImpl中的mGlobal.addView(view)
    3. addView中创建ViewRootImpl root=new ViewRootImpl():
    4. root.setView(view);
    5. 在setView总调用requestLayout()
    6. requestLayout()请求绘制,编舞者出场

    帧速率:

    CPU/GPU出图速率;

    刷新率:

    屏幕刷新速率;

    1. 帧速率>刷新率时,出现丢帧(出图好多张了,但是只显示了开头和结尾两张,中间的丢了)
    2. 帧速率<刷新率,出现卡顿(屏幕刷新好多次了,但是还是显示的第一帧)

    Vsync:

    垂直同步绘制信号; 因可能硬件帧速率和刷新率不一致,用来同步刷新的问题;

    Choreographer编舞者:

    负责管理帧率节奏;

    1. 在内部维护了个Haner和Looper,保证绘制发生在UI主线程:Looper.myLooper==mLooper判断是否是主线程,是的话去调同步绘制信号,不是的话发送消息,走主线程去调同步绘制信号
    2. 走native层请求垂直同步信号,实际是找底层驱动要上次绘制的时间
    3. 请求到垂直同步信号后回调onVsync
    4. 走doFrame去逻辑管控, 判断当前时间离上次绘制的时间大于了1帧的时间(16.66毫秒)  就跳帧(卡顿优化有用到),若小于16.66毫秒就再次请求垂直同步信号,防止重叠
    5. 执行callback,让ViewRootImpl去真正绘制,调用ViewRootImpl.performTraversals()

    真正的绘制:

    ViewRootImpl.performTraversals()

    1. 调用relayoutWindow():
    2. 创建用户java层的surface:只有用户提供的画面数据;
    3. 创建native层的surface:包含用户提供的画面数据(java层的surface)+系统的画面数据(状态栏,电池、wifi等等);
    4. 创建完surface后:依次调用:performMeasure(对应view的onMeasure)、performLayout(onLayout)、performDraw(onDraw);

    在performDraw()中:

    1. 将view的数据传至native层的surface
    2. surface中的canvas记录数据
    3. 生成bitmap图像数据(此时数据是在surface中)
    4. 将surface放入队列中;生产者消费者模式;
    5. 通知surfaceflinfer进程去队列中取surface数据
    6. surfaceflinfer拿到不同的surface,进行融合,生成bitmap数据
    7. 将bitmap数据放入framebuffer中,进行展示

    简单版总结:

    Activity.setContentView(R.layout.resId):

    解析xml并实例化;

    1. 调用phoneWindow.setContentView(resId)
    2. 在setContentView中调用installDector():根据不同的主题,找到系统默认的xml,初始化出mDector和mContentParent(反射实例化出对应的ViewGroup)
    3. 初始化完成后,调用mLayoutInflater.inflate(resId,mContentParent):
    4. 解析resId的xml文件,将解析的view反射实例化;递归添加到各节点的viewgroup中;最后将自己定义的xml根布局view添加到mContentParent;

    绘制发生时间:

    在AMS回调ActivityThread中的handleResumeActivity时,也就是Resume时,而不是onCreate();

    1. 获取PhoneWindow
    2. 获取PhoneWindow中的mDector布局视图view
    3. 将mDector布局视图view传给ViewRootImpl
    4. ViewRootImpl中调用requestLayout()
    5. requestLayout()中依次调用:performMeasure()、performLayout()、performDraw()

    Android UI测量、布局、绘制随记_暮冬一十四的博客-CSDN博客

    展开全文
  • WMS业务流程.zip

    2022-04-06 19:32:40
    WMS业务流程.zip
  • Android View的工作流程

    2021-05-27 01:31:01
    //如果控件不需要绘制渐变边界,则可以进入简便绘制流程if(!verticalEdges && !horizontalEdges) { // Step 3, draw the contentif(!dirtyOpaque) onDraw(canvas); //非"实心",则绘制控件本身// Step 4, draw the ...

    //ViewRootImpl

    privatevoidperformLayout(WindowManager.LayoutParams lp, intdesiredWindowWidth,intdesiredWindowHeight){ finalView host = mView; host.layout( 0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); } //ViewGroup

    //尽管ViewGroup也重写了layout方法

    //但是本质上还是会通过super.layout()调用View的layout()方法

    @Override

    publicfinalvoidlayout(intl, intt, intr, intb){ if(!mSuppressLayout && (mTransition == null|| !mTransition.isChangingLayout())) { //如果无动画,或者动画未运行super.layout(l, t, r, b); } else{ //等待动画完成时再调用requestLayout()mLayoutCalledWhileSuppressed = true; } } //View

    publicvoidlayout(intl, intt, intr, intb){ intoldL = mLeft; intoldT = mTop; intoldB = mBottom; intoldR = mRight; //如果布局有变化,通过setFrame重新布局booleanchanged = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if(changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { //如果这是一个ViewGroup,还会遍历子View的layout()方法//如果是普通View,通知具体实现类布局变更通知onLayout(changed, l, t, r, b); //清除PFLAG_LAYOUT_REQUIRED标记mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ...... //布局监听通知} //清除PFLAG_FORCE_LAYOUT标记mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; }

    通过代码可以看到尽管 ViewGroup 也重写了 layout() 方法,但是本质上还是会走 View 的 layout()。

    在 View 的 layout() 方法里,首先通过 setFrame()(setOpticalFrame() 也走 setFrame())将 l、t、r、b 分别设置到 mLeft、mTop、mRight 和 mBottom,这样就可以确定 子View 在父容器的位置了,上面也说过了,这些位置是相对父容器的。

    然后调用 onLayout() 方法,使具体实现类接收到布局变更通知。如果此类是 ViewGroup,还会遍历 子View 的 layout() 方法使其更新布局。如果调用的是 onLayout() 方法,这会导致 子View 无法调用 setFrame(),从而无法更新控件坐标信息。

    //View

    protectedvoidonLayout(booleanchanged, intl, intt, intr, intb){} //ViewGroup

    //abstract修饰,具体实现类必须重写该方法

    @Override

    protectedabstractvoidonLayout(booleanchanged,intl, intt, intr, intb);

    对于普通 View 来说,onLayout() 方法是一个空实现,主要是具体实现类重写该方法后能够接收到布局坐标更新信息。

    对于 ViewGroup 来说,和 measure 一样,不同实现类有它不同的布局特性,在 ViewGroup 中 onLayout() 方法是 abstract 的,具体实现类必须重写该方法,以便接收布局坐标更新信息后,处理自己的 子View 的坐标信息。有兴趣的童鞋可以看 FrameLayout 或者 LinearLayout 的 onLayout() 方法。

    小结

    对比测量 measure 和布局 layout 两个过程有助于加深对它们的理解。(摘自《深入理解Android卷III》)

    measure 确定的是控件的尺寸,并在一定程度上确定了子控件的位置。而布局则是针对测量结果来实施,并最终确定子控件的位置。

    measure 结果对布局过程没有约束力。虽说子控件在 onMeasure() 方法中计算出了自己应有的尺寸,但是由于 layout() 方法是由父控件调用,因此控件的位置尺寸的最终决定权掌握在父控件手中,测量结果仅仅只是一个参考。

    因为 measure 过程是后根遍历(DecorView 最后 setMeasureDiemension()),所以子控件的测量结果影响父控件的测量结果。

    而 Layout 过程是先根遍历(layout() 一开始就调用 setFrame() 完成 DecorView 的布局),所以父控件的布局结果会影响子控件的布局结果。

    完成 performLayout() 后,空间树的所有控件都已经确定了其最终位置,就剩下绘制了。

    draw

    我们先纯粹的看 View 的 draw 过程,因为这个过程相对上面 measure 和 layout 比较简单。

    View 的 draw 过程遵循如下几步 :

    绘制背景 drawBackground();

    绘制自己 onDraw();

    如果是 ViewGroup 则绘制 子View,dispatchDraw();

    绘制装饰(滚动条)和前景,onDrawForeground();

    ddd

    //View

    publicvoiddraw(Canvas canvas){ finalintprivateFlags = mPrivateFlags; //检查是否是"实心(不透明)"控件。(后面有补充)finalbooleandirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null|| !mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */// Step 1, draw the background, if neededintsaveCount; //非"实心"控件,将会绘制背景if(!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case)finalintviewFlags = mViewFlags; booleanhorizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; booleanverticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; //如果控件不需要绘制渐变边界,则可以进入简便绘制流程if(!verticalEdges && !horizontalEdges) { // Step 3, draw the contentif(!dirtyOpaque) onDraw(canvas); //非"实心",则绘制控件本身// Step 4, draw the childrendispatchDraw(canvas); //如果当前不是ViewGroup,此方法则是空实现// Overlay is part of the content and draws beneath Foregroundif(mOverlay != null&& !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas); //绘制装饰和前景// we're done...return; } ...... }

    2d86492a6aaa1e74091529b3fd96d5b4.png

    至此 View 的工作流程的大致整体已经描述完毕了,是否感觉意犹未尽,我们再补充2个知识点作为餐后甜点。

    invalidate

    我们知道 invalidate() (在主线程)和 postInvalidate() (可以在子线程)都是用于请求 View 重绘的方法,那么它是如何实现的呢?

    invalidate() 方法必须在主线程执行,而 scheduleTraversals() 引发的遍历也是在主线程执行。所以调用 invalidate() 方法并不会使得遍历立即开始,因为在调用 invalidate() 的方法执行完毕之前(准确的说是主线程的Looper处理完其他消息之前),主线程根本没有机会处理 scheduleTraversals() 所发出的消息。

    这种机制带来的好处是 : 在一个方法里可以连续调用多个控件的 invalidate() 方法,而不用担心会由于多次重绘而产生的效率问题。

    另外多次调用 invalidate() 方法会使得 ViewRootImpl 多次接收到设置脏区域的请求,ViewRootImpl 会将这些脏区域累加到 mDirty 中,进而在随后的遍历中,一次性的完成所有脏区域的重绘。

    窗口第一次绘制时候,ViewRootImpl 的 mFullRedrawNeeded 成员将会被设置为 true,也就是说 mDirty 所描述的区域将会扩大到整个窗口,进而实现完整重绘。View的脏区域和”实心”控件

    增加两个知识点,能够更好的理解 View 的重绘过程。

    为了保证绘制的效率,控件树仅对需要重绘的区域进行绘制。这部分区域成为”脏区域” Dirty Area。

    当一个控件的内容发生变化而需要重绘时,它会通过 View.invalidate() 方法将其需要重绘的区域沿着控件树自下而上的交给 ViewRootImpl,并保存在 ViewRootImpl 的 mDirty 成员中,最后通过 scheduleTraversals() 引发一次遍历,进而进行重绘工作,这样就可以保证仅位于 mDirty 所描述的区域得到重绘,避免了不必要的开销。

    View的isOpaque() 方法返回值表示此控件是否为”实心”的,所谓”实心”控件,是指在 onDraw() 方法中能够保证此控件的所有区域都会被其所绘制的内容完全覆盖。对于”实心”控件来说,背景和子元素(如果有的话)是被其 onDraw() 的内容完全遮住的,因此便可跳过遮挡内容的绘制工作从而提升效率。

    简单来说透过此控件所属的区域无法看到此控件下的内容,也就是既没有半透明也没有空缺的部分。因为自定义 ViewGroup 控件默认是”实心”控件,所以默认不会调用 drawBackground() 和 onDraw() 方法,因为一旦 ViewGroup 的 onDraw() 方法,那么就会覆盖住它的子元素。但是我们仍然可以通过调用 setWillNotDraw(false) 和 setBackground() 方法来开启ViewGroup 的 onDraw() 功能。

    下面我们从View的invalidate方法,自下(View)而上(ViewRootImpl)的分析。

    invalidate : 使无效; damage : 损毁;dirty : 脏;

    View//View

    publicvoidinvalidate(){ invalidate( true); } voidinvalidate(booleaninvalidateCache){ invalidateInternal( 0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); } voidinvalidateInternal(intl, intt, intr, intb, booleaninvalidateCache, booleanfullInvalidate){ //如果VIew不可见,或者在动画中if(skipInvalidate()) { return; } //根据mPrivateFlags来标记是否重绘if((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || (fullInvalidate && isOpaque() != mLastIsOpaque)) { if(fullInvalidate) { //上面传入为true,表示需要全部重绘mLastIsOpaque = isOpaque(); //mPrivateFlags &= ~PFLAG_DRAWN; //去除绘制完毕标记。} //添加标记,表示View正在绘制。PFLAG_DRAWN为绘制完毕。mPrivateFlags |= PFLAG_DIRTY; //清除缓存,表示由当前View发起的重绘。if(invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } //把需要重绘的区域传递给父ViewfinalAttachInfo ai = mAttachInfo; finalViewParent p = mParent; if(p != null&& ai != null&& l < r && t < b) { finalRect damage = ai.mTmpInvalRect; //设置重绘区域(区域为当前View在父容器中的整个布局)damage.set(l, t, r, b); p.invalidateChild( this, damage); } ...... } }

    上述代码中,会设置一系列的标记位到 mPrivateFlags 中,并且通过父容器的 invalidateChild 方法,将需要重绘的脏区域传给父容器。(ViewGroup 和 ViewRootImpl 都继承了 ViewParent类,该类中定义了子元素与父容器间的调用规范。)

    ViewGroup//ViewGroup

    @Override

    publicfinalvoidinvalidateChild(View child, finalRect dirty){ ViewParent parent = this; finalAttachInfo attachInfo = mAttachInfo; if(attachInfo != null) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); ...... //父容器根据自身对子View的脏区域进行调整transformMatrix.mapRect(boundingRect); dirty.set(( int) Math.floor(boundingRect.left), ( int) Math.floor(boundingRect.top), ( int) Math.ceil(boundingRect.right), ( int) Math.ceil(boundingRect.bottom)); // 这里的do while方法,不断的去调用父类的invalidateChildInParent方法来传递重绘请求//直到调用到ViewRootImpl的invalidateChildInParent(责任链模式)do { View view = null; if(parent instanceofView) { view = (View) parent; } if(drawAnimation) { if(view != null) { view.mPrivateFlags |= PFLAG_DRAW_ANIMATION; } elseif(parent instanceofViewRootImpl){ ((ViewRootImpl) parent).mIsAnimating = true; } } //如果父类是"实心"的,那么设置它的mPrivateFlags标识// If the parent is dirty opaque or not dirty, mark it dirty with the opaque// flag coming from the child that initiated the invalidateif(view != null) { if((view.mViewFlags & FADING_EDGE_MASK) != 0&& view.getSolidColor() == 0) { opaqueFlag = PFLAG_DIRTY; } if((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) { view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag; } } //***往上递归调用父类的invalidateChildInParent***parent = parent.invalidateChildInParent(location, dirty); //设置父类的脏区域//父容器会把子View的脏区域转化为父容器中的坐标区域if(view != null) { // Account for transform on current parentMatrix m = view.getMatrix(); if(!m.isIdentity()) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); m.mapRect(boundingRect); dirty.set(( int) Math.floor(boundingRect.left), ( int) Math.floor(boundingRect.top), ( int) Math.ceil(boundingRect.right), ( int) Math.ceil(boundingRect.bottom)); } } } while(parent != null); } } ViewRootImpl

    我们先验证一下最上层 ViewParent 为什么是 ViewRootImpl

    //ViewRootImpl

    publicvoidsetView(View view, WindowManager.LayoutParams attrs, View panelParentView){ view.assignParent( this); } //View

    voidassignParent(ViewParent parent){ if(mParent == null) { mParent = parent; } elseif(parent == null){ mParent = null; } else{ thrownewRuntimeException("view "+ this+ " being added, but"+ " it already has a parent"); } }

    在 ViewRootImpl 的 setView 方法中,由于传入的 View 正是 DecorView,所以最顶层的 ViewParent 即 ViewRootImpl。另外 ViewGroup 在 addView 方法中,也会调用 assignParent() 方法,设定子元素的父容器为它本身。

    由于最上层的 ViewParent 是 ViewRootImpl,所以我们可以查看 ViewRootImpl 的 invalidateChildInParent 方法即可。

    //ViewRootImpl

    @Override

    publicViewParent invalidateChildInParent(int[] location, Rect dirty){ //检查线程,这也是为什么invalidate一定要在主线程的原因checkThread(); if(dirty == null) { invalidate(); //有可能需要绘制整个窗口returnnull; } elseif(dirty.isEmpty()&& !mIsAnimating){ returnnull; } ..... invalidateRectOnScreen(dirty); returnnull; } //设置mDirty并执行View的工作流程

    privatevoidinvalidateRectOnScreen(Rect dirty){ finalRect localDirty = mDirty; if(!localDirty.isEmpty() && !localDirty.contains(dirty)) { mAttachInfo.mSetIgnoreDirtyState = true; mAttachInfo.mIgnoreDirtyState = true; } // Add the new dirty rect to the current onelocalDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); //在这里,mDirty的区域就变为方法中的dirty,即要重绘的脏区域...... if(!mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals(); //执行View的工作流程} }

    什么?执行 invalidate() 方法居然会引起 scheduleTraversals()!

    那么也就是说 invalidate() 会导致 perforMeasure()、performLayout()、perforDraw() 的调用了???

    这个 scheduleTraversals() 很眼熟,我们一出场就在 requestLayout() 中见过,并且我们还说了 mLayoutRequested 用来表示是否 measure 和 layout。

    //ViewRootImpl

    @Override

    publicvoidrequestLayout(){ if(!mHandlingLayoutInLayoutRequest) { checkThread(); //检查是否在主线程mLayoutRequested = true; //mLayoutRequested 是否measure和layout布局。scheduleTraversals(); } } privatevoidperformTraversals(){ booleanlayoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw); if(layoutRequested) { measureHierarchy(```); //measure} finalbooleandidLayout = layoutRequested && (!mStopped || mReportNextDraw); if(didLayout) { performLayout(lp, mWidth, mHeight); //layout} booleancancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; if(!cancelDraw && !newSurface) { performDraw(); //draw} }

    因为我们 invalidate 的时候,并没有设置 mLayoutRequested,所以放心,它只走 performDraw() 流程,并且在 draw() 流程中会清除 mDirty 区域。

    并且只有设置了标识为的 View 才会调用 draw 方法进而调用 onDraw(),减少开销。「源码工程师各方面的考虑肯定比一般人更周到,我们写的是代码,他们写的是艺术。」

    b9f5270ce1db1970c5acb58876453add.png

    requestLayout

    看完了 invalidate() 流程之后,requestLayout() 流程就比较好上手了。我们在 measure 阶段提到过 :

    在 view.measure() 的方法里,仅当给与的 MeasureSpec 发生变化时,或要求强制重新布局时,才会进行测量。

    强制重新布局 : 控件树中的一个子控件内容发生变化时,需要重新测量和布局的情况,在这种情况下,这个子控件的父控件(以及父控件的父控件)所提供的 MeasureSpec 必定与上次测量时的值相同,因而导致从 ViewRootImpl 到这个控件的路径上,父控件的 measure() 方法无法得到执行,进而导致子控件无法重新测量其布局和尺寸。(在父容器 measure 中遍历子元素)

    解决途径 : 因此,当子控件因内容发生变化时,从子控件到父控件回溯到 ViewRootImpl,并依次调用父控件的 requestLayout() 方法。这个方法会在 mPrivateFlags 中加入标记 PFLAG_FORCE_LAYOUT,从而使得这些父控件的 measure() 方法得以顺利执行,进而这个子控件有机会进行重新布局与测量。这便是强制重新布局的意义所在。

    下面我们看 View 的 requestLayout() 方法

    //View

    @CallSuper

    publicvoidrequestLayout(){ if(mMeasureCache != null) mMeasureCache.clear(); ...... // 增加PFLAG_FORCE_LAYOUT标记,在measure时会校验此属性mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; // 父类不为空&&父类没有请求重新布局(是否有PFLAG_FORCE_LAYOUT标志)//这样同一个父容器的多个子View同时调用requestLayout()就不会增加开销if(mParent != null&& !mParent.isLayoutRequested()) { mParent.requestLayout(); } }

    因为上面说过了,最顶层的 ViewParent 是 ViewRootImpl。

    //ViewRootImpl

    @Override

    publicvoidrequestLayout(){

    if(!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); }}

    同样,requestLayout() 方法会调用 scheduleTraversals();,因为设置了 mLayoutRequested =true 标识,所以在 performTraversals() 中调用 performMeasure(),performLayout(),但是由于没有设置 mDirty,所以不会走 performDraw() 流程。

    但是,requestLayout() 方法就一定不会导致 onDraw() 的调用吗?

    在上面 layout() 方法中说道 :

    在 View 的 layout() 方法里,首先通过 setFrame()(setOpticalFrame() 也走 setFrame())将 l、t、r、b 分别设置到 mLeft、mTop、mRight 和 mBottom,这样就可以确定 子View 在父容器的位置了,上面也说过了,这些位置是相对父容器的。//View --> layout()

    protectedbooleansetFrame(intleft, inttop, intright, intbottom){ booleanchanged = false; if(mLeft != left || mRight != right || mTop != top || mBottom != bottom) { //布局坐标改变了changed = true; intoldWidth = mRight - mLeft; intoldHeight = mBottom - mTop; intnewWidth = right - left; intnewHeight = bottom - top; booleansizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight); // Invalidate our old positioninvalidate(sizeChanged); //调用invalidate重新绘制视图if(sizeChanged) { sizeChange(newWidth, newHeight, oldWidth, oldHeight); } ...... } returnchanged; }

    看完代码我们就很清晰的知道,如果 layout 布局有变化,那么它也会调用 invalidate() 重绘自身。

    下面再借用 Idtk 绘制的 layout 流程图

    5c408c9d25ba57fd4df92df3088ce300.png

    总结

    至此,View 的工作流程分析完毕,文章如果有错误或者不妥之处,还望评论提出。

    理清整体流程对我们android的布局,绘制,自定义View,和分析bug都有一个提升。返回搜狐,查看更多

    展开全文
  • WMS仓库流程优化方案.pptx
  • WMS仓库流程优化方案.pdf
  • WindowManagerService 简称 WMS,主要有如下职责: 窗口管理:负责启动、添加、删除窗口,管理窗口大小、层级,核心成员有:WindowContainer、RootWindowContainer、DisplayContent、TaskStack、Task、...
  • 条码WMS系统与ERP接口实现: 仓库管理软件通过ERP接口访问ERP,从ERP同步出入库指示单据,根据指示单据指导与核对出入库作业,最终根据出入扫描记录调用ERP接口自动在ERP生成出入库实际单据,省去在ERP手工录入...
  • WMS仓库管理系统是对仓库中的物料/成品进行精细化管理,建立标准化的业务操作流程,从而更好地管理和控制仓库中的货物和人员,提高供应链的协调性和效率,进一步完善集团的信息化建设。  因此,在许多企业的...
  • BS .net 4.0 C# Web SQL Server 2012-2017 Fastreport报表 介绍一套仓储管理系统源码,以下为作者留言 吉特仓储管系统基础版本 适合单仓库,基本的仓库入库管理,出库管理,盘点,报损,移库,库位等管理,有着...
  • 导语大家好,我是智能仓储物流技术研习社的社长,老K。WMS是我们仓储物流系统中非常重要的一部分。今天以一个WMS原型案例,拆解下系统的设计难点和业务流程。原型拆解:WMS系统包含什么业务...
  • Android 系统 WMS 模块相关流程分析.xmind

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 9,219
精华内容 3,687
关键字:

wms流程