Android Window 9问9答

1.简述一下window是什么?在android体系里 扮演什么角色?

答:window就是一个抽象类,他的实现类是phoneWindow。我们一般通过windowManager 来访问window。就是windowmanager 和windowmanagerservice的交互。

此外 android中 你所有能看到的视图,activity,dialog,toast等 都是附加在window上的。window就是view的直接管理者。

2.如何使用windowmanager添加一个view?

答:

1  Button bt = new Button(this);
2         bt.setText("button here");
3         WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
4                 0, 0, PixelFormat.TRANSPARENT);
5         layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
6         layoutParams.x = 300;
7         layoutParams.y = 300;
8         layoutParams.gravity = Gravity.RIGHT | Gravity.TOP;
9         getWindowManager().addView(bt, layoutParams);

3.总共有几种window类型?

答:三种。应用window,就是activity这种类型。子window,就是dialog这种,系统类,toast,状态栏就是系统类型window。

每种对对应着层级范围,应用1-99 子1000-1999 系统 2000-2999.层级最大的,就是显示在最顶层的window了。

4.使用系统window需要注意什么?

答:注意system_alert_window这个权限。否则要出错

5.尝试简单分析window的添加过程?

答:即window.addView()函数的执行过程:

  1 //首先我们要知道 windwmanger本身就是一个接口,他的实现是交给WindowManagerImpl 来做的。
  2 public final class WindowManagerImpl implements WindowManager {
  3
  4
  5 //他的view方法 一看,发现也是基本没做实际的addview操作 是交给mGlobal来做的
  6  @Override
  7     public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
  8         applyDefaultToken(params);
  9         mGlobal.addView(view, params, mDisplay, mParentWindow);
 10     }
 11
 12
 13 //发现这是一个工厂吗,到这里一看就明白了,WindowManagerImpl的实际操作 都桥接给了WindowManagerGlobal来处理
 14 private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
 15
 16 //先看一下WindowManagerGlobal的 重要变量,注意上面已经分析过了,WindowManagerGlobal本身自己是一个单例,全局唯一,
 17 //所以下面这些参数list ,全局也是唯一的,mViews 就是所有window对应的view,mRoots就是所有viewRootImpl,mParams就是这些
 18 //view的参数,dyingviews 就是正在删除的对象,就是那种你调用了remove操作 但是remove还没有操作完毕的那些view
 19  private final ArrayList<View> mViews = new ArrayList<View>();
 20     private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
 21     private final ArrayList<WindowManager.LayoutParams> mParams =
 22             new ArrayList<WindowManager.LayoutParams>();
 23     private final ArraySet<View> mDyingViews = new ArraySet<View>();
 24
 25
 26
 27
 28 //所以 我们就看看WindowManagerGlobal源码里的addView是如何实现的
 29 public void addView(View view, ViewGroup.LayoutParams params,
 30             Display display, Window parentWindow) {
 31         if (view == null) {
 32             throw new IllegalArgumentException("view must not be null");
 33         }
 34         if (display == null) {
 35             throw new IllegalArgumentException("display must not be null");
 36         }
 37         if (!(params instanceof WindowManager.LayoutParams)) {
 38             throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
 39         }
 40
 41         final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
 42         //如果是子window 就调整一下参数
 43         if (parentWindow != null) {
 44             parentWindow.adjustLayoutParamsForSubWindow(wparams);
 45         } else {
 46             // If there‘s no parent and we‘re running on L or above (or in the
 47             // system context), assume we want hardware acceleration.
 48             final Context context = view.getContext();
 49             if (context != null
 50                     && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
 51                 wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
 52             }
 53         }
 54
 55         ViewRootImpl root;
 56         View panelParentView = null;
 57
 58         synchronized (mLock) {
 59             // Start watching for system property changes.
 60             if (mSystemPropertyUpdater == null) {
 61                 mSystemPropertyUpdater = new Runnable() {
 62                     @Override public void run() {
 63                         synchronized (mLock) {
 64                             for (int i = mRoots.size() - 1; i >= 0; --i) {
 65                                 mRoots.get(i).loadSystemProperties();
 66                             }
 67                         }
 68                     }
 69                 };
 70                 SystemProperties.addChangeCallback(mSystemPropertyUpdater);
 71             }
 72
 73             int index = findViewLocked(view, false);
 74             if (index >= 0) {
 75                 if (mDyingViews.contains(view)) {
 76                     // Don‘t wait for MSG_DIE to make it‘s way through root‘s queue.
 77                     mRoots.get(index).doDie();
 78                 } else {
 79                     throw new IllegalStateException("View " + view
 80                             + " has already been added to the window manager.");
 81                 }
 82                 // The previous removeView() had not completed executing. Now it has.
 83             }
 84
 85             // If this is a panel window, then find the window it is being
 86             // attached to for future reference.
 87             if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
 88                     wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
 89                 final int count = mViews.size();
 90                 for (int i = 0; i < count; i++) {
 91                     if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
 92                         panelParentView = mViews.get(i);
 93                     }
 94                 }
 95             }
 96
 97             //这个代码充分说明了每一个window都对应着一个view 和一个viewrootIMPL,window本身自己不存在,
 98             //他的意义就在于管理view,而管理view 就要通过windowmanager 最终走到windwmanagerglobal这里来完成管理
 99             root = new ViewRootImpl(view.getContext(), display);
100
101             view.setLayoutParams(wparams);
102
103             mViews.add(view);
104             mRoots.add(root);
105             mParams.add(wparams);
106         }
107
108         // do this last because it fires off messages to start doing things
109         try {
110             //view的最终绘制 是在viewrootimpl里完成的,所以这里view的绘制也是在这个里面完成的
111             //我们在viewrootimpl里能找到setview的源码 他在这个函数里调用了requetlayout
112             root.setView(view, wparams, panelParentView);
113         } catch (RuntimeException e) {
114             // BadTokenException or InvalidDisplayException, clean up.
115             synchronized (mLock) {
116                 final int index = findViewLocked(view, false);
117                 if (index >= 0) {
118                     removeViewLocked(index, true);
119                 }
120             }
121             throw e;
122         }
123     }
124
125
126     //而requestLayout里有scheduleTraversals方法 这个就是view绘制的入口处
127     public void requestLayout() {
128         if (!mHandlingLayoutInLayoutRequest) {
129            checkThread();
130             mLayoutRequested = true;
131             scheduleTraversals();
132         }
133     }
134
135     //回到前面提到的setView那个函数
136     //我们可以看到requestLayout 结束以后 mWindowSession.addToDisplay 就有了这个方法的调用
137     //实际上这个方法 完成的就是一个window的添加。
138      requestLayout();
139     if ((mWindowAttributes.inputFeatures
140                          & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
141                     mInputChannel = new InputChannel();
142     }
143     try {
144                     mOrigWindowType = mWindowAttributes.type;
145                     mAttachInfo.mRecomputeGlobalAttributes = true;
146                    collectViewAttributes();
147                     res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
148                             getHostVisibility(), mDisplay.getDisplayId(),
149                             mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel);
150
151     //然后我们很容易就发现这是一个接口 并且代码一看就知道 还是一个binder
152     //所以实际上添加window的功能 就是通过BInder 是调用windwmangerservice的方法 来完成的
153 public interface IWindowSession extends android.os.IInterface
154 {
155 /** Local-side IPC implementation stub class. */
156 public static abstract class Stub extends android.os.Binder implements android.view.IWindowSession
157 {
158 private static final java.lang.String DESCRIPTOR = "android.view.IWindowSession";
159 /** Construct the stub at attach it to the interface. */
160 public Stub()
161 {
162 this.attachInterface(this, DESCRIPTOR);
163 }

6.activity的window是如何创建的?

答:应用类的window创建过程:

  1
  2 //activity的window创建 由activityThread的performLaunchActivity 方法开始
  3  private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  4         ......
  5             if (activity != null) {
  6                 Context appContext = createBaseContextForActivity(r, activity);
  7                 CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
  8                 Configuration config = new Configuration(mCompatConfiguration);
  9                 if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
 10                         + r.activityInfo.name + " with config " + config);
 11                 //其中最主要的就是attach方法 注意是调用的activity的attach方法 不是activitytherad的
 12                 activity.attach(appContext, this, getInstrumentation(), r.token,
 13                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
 14                         r.embeddedID, r.lastNonConfigurationInstances, config,
 15                         r.referrer, r.voiceInteractor);
 16
 17             ......
 18
 19         return activity;
 20     }
 21
 22
 23 final void attach(Context context, ActivityThread aThread,
 24             Instrumentation instr, IBinder token, int ident,
 25             Application application, Intent intent, ActivityInfo info,
 26             CharSequence title, Activity parent, String id,
 27             NonConfigurationInstances lastNonConfigurationInstances,
 28             Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
 29         attachBaseContext(context);
 30
 31         mFragments.attachActivity(this, mContainer, null);
 32         //这里一下就能看出来 Acitity的window对象 是由PolicyManager的makeNewWindow方法构造出来
 33         //有兴趣的还可以看一下 这里set了很多接口 都是我们熟悉的那些方法
 34         mWindow = PolicyManager.makeNewWindow(this);
 35         mWindow.setCallback(this);
 36         mWindow.setOnWindowDismissedCallback(this);
 37         mWindow.getLayoutInflater().setPrivateFactory(this);
 38         if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
 39             mWindow.setSoftInputMode(info.softInputMode);
 40         }
 41         if (info.uiOptions != 0) {
 42             mWindow.setUiOptions(info.uiOptions);
 43         }
 44         mUiThread = Thread.currentThread();
 45
 46         mMainThread = aThread;
 47         mInstrumentation = instr;
 48         mToken = token;
 49         mIdent = ident;
 50         mApplication = application;
 51         mIntent = intent;
 52         mReferrer = referrer;
 53         mComponent = intent.getComponent();
 54         mActivityInfo = info;
 55         mTitle = title;
 56         mParent = parent;
 57         mEmbeddedID = id;
 58         mLastNonConfigurationInstances = lastNonConfigurationInstances;
 59         if (voiceInteractor != null) {
 60             if (lastNonConfigurationInstances != null) {
 61                 mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
 62             } else {
 63                 mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
 64                         Looper.myLooper());
 65             }
 66         }
 67
 68         mWindow.setWindowManager(
 69                 (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
 70                 mToken, mComponent.flattenToString(),
 71                 (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
 72         if (mParent != null) {
 73             mWindow.setContainer(mParent.getWindow());
 74         }
 75         mWindowManager = mWindow.getWindowManager();
 76         mCurrentConfig = config;
 77     }
 78
 79
 80
 81 //makenewWindow就是在这里被调用的,可以看出来 makenewWindow返回的 正是phoneWindow对象
 82 //到这里我们的window对象就生成了,
 83     public class Policy implements IPolicy {
 84     private static final String TAG = "PhonePolicy";
 85
 86     private static final String[] preload_classes = {
 87         "com.android.internal.policy.impl.PhoneLayoutInflater",
 88         "com.android.internal.policy.impl.PhoneWindow",
 89         "com.android.internal.policy.impl.PhoneWindow$1",
 90         "com.android.internal.policy.impl.PhoneWindow$DialogMenuCallback",
 91         "com.android.internal.policy.impl.PhoneWindow$DecorView",
 92         "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
 93         "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
 94     };
 95
 96     static {
 97         // For performance reasons, preload some policy specific classes when
 98         // the policy gets loaded.
 99         for (String s : preload_classes) {
100             try {
101                 Class.forName(s);
102             } catch (ClassNotFoundException ex) {
103                 Log.e(TAG, "Could not preload class for phone policy: " + s);
104             }
105         }
106     }
107
108     public Window makeNewWindow(Context context) {
109         return new PhoneWindow(context);
110     }
111
112     public LayoutInflater makeNewLayoutInflater(Context context) {
113         return new PhoneLayoutInflater(context);
114     }
115
116     public WindowManagerPolicy makeNewWindowManager() {
117         return new PhoneWindowManager();
118     }
119
120     public FallbackEventHandler makeNewFallbackEventHandler(Context context) {
121         return new PhoneFallbackEventHandler(context);
122     }
123 }
124
125
126 //再看activity的方法 就是在这里把我们的布局文件和window给关联了起来
127 //我们上面已经知道window对象就是phonewindow 所以这里就要看看phonewindow的setContentView方法
128 public void setContentView(@LayoutRes int layoutResID) {
129         getWindow().setContentView(layoutResID);
130         initWindowDecorActionBar();
131     }
132
133
134 //phoneWindow的setContentView方法
135
136 //要注意的是 这个方法执行完毕 我们也只是 通过decorView创建好了 我们自己的view对象而已。
137 //但是这个对象还没有被显示出来,只是存在于内存之中。decorview真正被显示 要在makevisible方法里了
138      @Override
139     public void setContentView(int layoutResID) {
140         // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
141         // decor, when theme attributes and the like are crystalized. Do not check the feature
142         // before this happens.
143         if (mContentParent == null) {
144             //这个就是创建decorview的 decorView就是那个framelayout我们的根布局 有一个标题栏和内容栏
145             //其中内容兰 就是android.R.id.content
146             installDecor();
147         } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
148             mContentParent.removeAllViews();
149         }
150
151         if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
152             final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
153                     getContext());
154             transitionTo(newScene);
155         } else {
156             //这里就是我们自己写的布局 layout 给关联到deorview的content布局里面
157             mLayoutInflater.inflate(layoutResID, mContentParent);
158         }
159         final Callback cb = getCallback();
160         if (cb != null && !isDestroyed()) {
161             //添加完毕以后调用回调
162             cb.onContentChanged();
163         }
164     }

7.dialog的window创建过程?

答:子window的创建过程如下:其实和activity的过程差不多 无非是acitivity对于decorView的显示是自动控制,交给actitytherad 按照流程来走 最后makevISIABLE函数来完成最终显示的,而dialog就是需要你手动来完成这个过程也就是show函数

 1 //看Dialog的构造函数 ,和acitivity差不多 也是PhoneWindow 对象。
 2 Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
 3         if (createContextThemeWrapper) {
 4             if (themeResId == 0) {
 5                 final TypedValue outValue = new TypedValue();
 6                 context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
 7                 themeResId = outValue.resourceId;
 8             }
 9             mContext = new ContextThemeWrapper(context, themeResId);
10         } else {
11             mContext = context;
12         }
13
14         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
15
16         final Window w = new PhoneWindow(mContext);
17         mWindow = w;
18         w.setCallback(this);
19         w.setOnWindowDismissedCallback(this);
20         w.setWindowManager(mWindowManager, null, null);
21         w.setGravity(Gravity.CENTER);
22
23         mListenersHandler = new ListenersHandler(this);
24     }
25
26 //Dialog的setContentView方法 也是调用的phonewindow的方法 和acitivity流程也是一样的
27      public void setContentView(@LayoutRes int layoutResID) {
28         mWindow.setContentView(layoutResID);
29     }
30
31
32 //我们都知道dialog必须要show才能显示出来,
33
34      public void show() {
35         if (mShowing) {
36             if (mDecor != null) {
37                 if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
38                     mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
39                 }
40                 mDecor.setVisibility(View.VISIBLE);
41             }
42             return;
43         }
44
45         mCanceled = false;
46
47         if (!mCreated) {
48             dispatchOnCreate(null);
49         }
50
51         onStart();
52         mDecor = mWindow.getDecorView();
53
54         if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
55             final ApplicationInfo info = mContext.getApplicationInfo();
56             mWindow.setDefaultIcon(info.icon);
57             mWindow.setDefaultLogo(info.logo);
58             mActionBar = new WindowDecorActionBar(this);
59         }
60
61         WindowManager.LayoutParams l = mWindow.getAttributes();
62         if ((l.softInputMode
63                 & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
64             WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
65             nl.copyFrom(l);
66             nl.softInputMode |=
67                     WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
68             l = nl;
69         }
70
71         try {
72             //在这里 把decorview给add到这个window中了 与activity流程也是一样的
73             mWindowManager.addView(mDecor, l);
74             mShowing = true;
75
76             sendShowMessage();
77         } finally {
78         }
79     }

8.Dialog的创建是不是必须要有activity的引用?

答:不需要,只要你更改为系统window就可以了。系统window是不需要activity作为引用的。注意别遗漏了权限

1  Dialog dialog=new Dialog(MainActivity.this.getApplicationContext());
2                 dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
3                 TextView textView=new TextView(MainActivity.this);
4                 textView.setText("this is dialog not use activity this");
5                 dialog.setContentView(textView);
6                 dialog.show();

9.toast的window创建过程?

答:这属于系统级别的window创建了,和前面的两种window创建过程稍微不一样。其实主要就是notificationmanagerservice和toast本身之间两者的相互调用而已。

就是简单的ipc过程。前面binder的教程有讲到,如何利用binder来进行双向通信。toast的源码 就是利用了binder的双向通信来完成toast的功能。

源码就不分析了,ipc的东西讲过太多了,有兴趣的可以自己看。

时间: 2024-10-16 15:23:21

Android Window 9问9答的相关文章

Android 动画 6问6答

1.view 动画有哪些需要注意的? 答:view动画 本身比较简单.http://www.cnblogs.com/punkisnotdead/p/5179115.html 看这篇文章的第五问就可以了. 2.如何给viewGroup 子元素出场时增加动画效果? 答:以listview 为例. 1 <?xml version="1.0" encoding="utf-8"?> 2 <layoutAnimation xmlns:android="

Android Window PhoneWindow Activity学习心得--第三弹

Android Window  PhoneWindow Activity学习心得--第三弹 前面 我们完成了从Activity到PhoneWindow的整体跨度 正如我们所知道的与Activity组件关联的一个应用程序窗口视图对象关联一个ViewRoot对象,而将 一个Activity组件的应用程序窗口视图对象与一个ViewRoot对象关联是通过该Activity组件所使用的 窗口管理器(WindowManager)来执行的. 在我们初始化DecorView完成之后,我们需要关联应用程序窗口视图

需求: 客户端与服务端一问一答聊天。

import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.InetAddress; import java.net.Socket; /* 需求: 客户端与服务端一问一答聊天. 1.如果使用BuffrerdReader的readline方法一定要加上\r\n才把数据写出.

真问真答:中国人为何蔑称朝鲜人“棒子”|大象公会

真问真答:中国人为何蔑称朝鲜人“棒子”|大象公会 真问真答:中国人为何蔑称朝鲜人“棒子”|大象公会 大象公会 作者: 小白兔吃猫饼干 2016-06-30 22:56:22 举报 阅读数:41545 关于“棒子”蔑称的起源,网上有很多不靠谱的解释.一种说法称朝鲜人爱种也爱吃玉米,而玉米在东北又被称作“棒子”,但事实上朝鲜人很少种植玉米,基本以水稻和小麦做主食. 另一种说法称朝鲜人在伪满洲国时期帮日本人做警察,日本人却不给他们配以武器,只能挥舞朝鲜妇女的洗衣棒欺压中国人,因而得到“棒子”的蔑称,然

OpenGL快问快答

OpenGL快问快答 本文内容主要来自对(http://www.opengl.org/wiki/FAQ)的翻译,随机加入了本人的观点.与原文相比,章节未必完整,含义未必雷同,顺序未必一致.仅供参考. 我写这个是为了加深印象,好记性不如烂笔头,好记星不如烂键盘. +BIT祝威+悄悄在此留下版了个权的信息说: 名词术语 渲染:等于"画",等于"draw". OpenGL是什么? OpenGL是Open Graphics Library(开源图形库)的缩写.它是一本说明书

第1章 关于Python的问与答

第1章 关于Python的问与答 ? 如果你已经买了这本书,你可能已经知道Python是什么以及为什么它是一个值得学习的重要工具.如果你还不知道,那么你可能不会着迷于Python,直到你通过阅读本书的其余部分学习了这门语言并且使用Python完成了一两个项目.但是在我们深入细节之前,本书的首章将简要介绍一下Python流行背后的一些主要原因.为了给Python镌刻一个定义,本章采取一问一答的对话形式,摆出了初学者提出的一些最常见的问题. 人们为什么使用Python? 时至今天,有许多可用的编程语

WCF入门教程:WCF基础知识问与答(转)

学习WCF已有近两年的时间,其间又翻译了Juval的大作<Programming WCF Services>,我仍然觉得WCF还有更多的内容值得探索与挖掘.学得越多,反而越发觉得自己所知太少,直到现在,我也认为自己不过是初窥WCF的门径而已. 学以致用”,如果仅仅是希望能够在项目中合理地应用WCF,那么对于程序员而言,可以有两种选择,一种是“知其然而不知其所以然”,只要掌握了WCF的基础知识,那么对于一般的应用就足够了.要做到这一点就很容易了,微软秉承了一贯的方式,将WCF这门技术优雅地呈现给

Java 面试题问与答:编译时与运行时

Java 面试题问与答:编译时与运行时 2012/12/17 | 分类: 基础技术, 职业生涯 | 5 条评论 | 标签: RUNTIME, 面试 分享到:58 本文作者: ImportNew - 朱伟杰 未经许可,禁止转载! 在开发和设计的时候,我们需要考虑编译时,运行时以及构建时这三个概念.理解这几个概念可以更好地帮助你去了解一些基本的原理.下面是初学者晋级中级水平需要知道的一些问题. Q.下面的代码片段中,行A和行B所标识的代码有什么区别呢? 1 2 3 4 5 6 7 8 9 10 11

k3 Bos开发百问百答

          K/3 BOS开发百问百答   (版本:V1.1)           K3产品市场部       目录 一.基础资料篇__ 1 [摘要]bos基础资料的显示问题_ 1 [摘要]单据自定义无法看到bos定义的基础资料_ 1 [摘要]在调出基础资料序时簿时,过滤出我需要的基础资料_ 1 [摘要]bos定义的基础资料能否做到按名称而不是按代码进行自动匹配_ 1 二.业务单据篇__ 2 [摘要]是否支持多插件和数据授权_ 2 [摘要]K3BOS单据(新)中的数量字段怎样才能控制到两