文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android高手进阶之Activity.setContentView渲染流程详解

2024-12-14 01:24

关注

接下来我们会跟着源码大概走一遍这个方法,一起总结下

一、DecorView的创建

Activity.setContentView

  1. public void setContentView(@LayoutRes int layoutResID) { 
  2.      getWindow().setContentView(layoutResID); //最终调用mWindow的setContentView方法 
  3.      initWindowDecorActionBar(); 
  4.  } 

getWindow返回的是mWindow, mWindow在Activity的attach方法里被赋值,是个PhoneWindow对象。

(PhoneWindow是Window的唯一实现类)

  1. final void attach(Context context, ActivityThread aThread, 
  2.           Instrumentation instr, IBinder token, int ident, 
  3.           Application application, Intent intent, ActivityInfo info, 
  4.           CharSequence title, Activity parent, String id, 
  5.           NonConfigurationInstances lastNonConfigurationInstances, 
  6.           Configuration config, String referrer, IVoiceInteractor voiceInteractor, 
  7.           Window window, ActivityConfigCallback activityConfigCallback) { 
  8.       attachBaseContext(context); 
  9.       mFragments.attachHost(null ); 
  10.       mWindow = new PhoneWindow(this, window, activityConfigCallback); 
  11.       ... 
  12.       mWindow.setWindowManager( //设置WindowManager 
  13.               (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), 
  14.               mToken, mComponent.flattenToString(), 
  15.               (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); 
  16.       if (mParent != null) { 
  17.           mWindow.setContainer(mParent.getWindow()); 
  18.       } 
  19.       mWindowManager = mWindow.getWindowManager(); 
  20.   } 

PhoneWindow.setContentView

几个关键变量

mDecor 是Window的最顶层的View,是个FrameLayout。

mContentParent 是用来真正装载Activity传入的布局文件的容器,本身是个ViewGroup。

  1. public void setContentView(int layoutResID) { 
  2.        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window 
  3.        // decor, when theme attributes and the like are crystalized. Do not check the feature 
  4.        // before this happens. 
  5.        if (mContentParent == null) { 
  6.            installDecor(); //如果mContentParent为空,则执行installDecor方法 
  7.        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 
  8.            mContentParent.removeAllViews(); //否则remove掉mContentParent的所有子view 
  9.        } 
  10.        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { 
  11.            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, 
  12.                    getContext()); 
  13.            transitionTo(newScene); 
  14.        } else { 
  15.            mLayoutInflater.inflate(layoutResID, mContentParent); //将activity传入的布局文件加载到mContentParent里 
  16.        } 
  17.        mContentParent.requestApplyInsets(); 
  18.        final Callback cb = getCallback(); 
  19.        if (cb != null && !isDestroyed()) { 
  20.            cb.onContentChanged(); 
  21.        } 
  22.        mContentParentExplicitlySet = true
  23.    } 

PhoneWindow.installDecor

  1. private void installDecor() { 
  2.        mForceDecorInstall = false
  3.        if (mDecor == null) { 
  4.            mDecor = generateDecor(-1); //如果之前没有创建,直接创建一个 
  5.            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 
  6.            mDecor.setIsRootNamespace(true); 
  7.            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { 
  8.                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); 
  9.            } 
  10.        } else { 
  11.            mDecor.setWindow(this); //将PhoneWindow传递给DecorView 
  12.        } 
  13.        if (mContentParent == null) { 
  14.            mContentParent = generateLayout(mDecor); //赋值mContentParent 
  15.        ... 
  16.    } 

PhoneWindow.installDecor

  1. protected DecorView generateDecor(int featureId) { 
  2.        // System process doesn't have application context and in that case we need to directly use 
  3.        // the context we have. Otherwise we want the application context, so we don't cling to the 
  4.        // activity. 
  5.        Context context; 
  6.        if (mUseDecorContext) { 
  7.            Context applicationContext = getContext().getApplicationContext(); 
  8.            if (applicationContext == null) { 
  9.                context = getContext(); 
  10.            } else { 
  11.                context = new DecorContext(applicationContext, getContext()); 
  12.                if (mTheme != -1) { 
  13.                    context.setTheme(mTheme); 
  14.                } 
  15.            } 
  16.        } else { 
  17.            context = getContext(); 
  18.        } 
  19.        return new DecorView(context, featureId, this, getAttributes()); //创建DecorView 
  20.    } 

PhoneWindow.generateLayout

这一步是挑选合适的DecorView布局文件并将其添加大盘DecorView,同时给mContentParent赋值。

  1. protected ViewGroup generateLayout(DecorView decor) { 
  2.         // Apply data from current theme. 
  3.         TypedArray a = getWindowStyle(); 
  4.         ...... 
  5.         // Inflate the window decor. 
  6.         //根据不同的features来选择DecorView的布局 
  7.         int layoutResource; 
  8.         int features = getLocalFeatures(); 
  9.         // System.out.println("Features: 0x" + Integer.toHexString(features)); 
  10.         if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { 
  11.             layoutResource = R.layout.screen_swipe_dismiss; 
  12.             setCloseOnSwipeEnabled(true); 
  13.         } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { 
  14.             if (mIsFloating) { 
  15.                 TypedValue res = new TypedValue(); 
  16.                 getContext().getTheme().resolveAttribute( 
  17.                         R.attr.dialogTitleIconsDecorLayout, res, true); 
  18.                 layoutResource = res.resourceId; 
  19.             } else { 
  20.                 layoutResource = R.layout.screen_title_icons; 
  21.             } 
  22.             // XXX Remove this once action bar supports these features. 
  23.             removeFeature(FEATURE_ACTION_BAR); 
  24.             // System.out.println("Title Icons!"); 
  25.         } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 
  26.                 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { 
  27.             // Special case for a window with only a progress bar (and title). 
  28.             // XXX Need to have a no-title version of embedded windows. 
  29.             layoutResource = R.layout.screen_progress; 
  30.             // System.out.println("Progress!"); 
  31.         } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { 
  32.             // Special case for a window with a custom title. 
  33.             // If the window is floating, we need a dialog layout 
  34.             if (mIsFloating) { 
  35.                 TypedValue res = new TypedValue(); 
  36.                 getContext().getTheme().resolveAttribute( 
  37.                         R.attr.dialogCustomTitleDecorLayout, res, true); 
  38.                 layoutResource = res.resourceId; 
  39.             } else { 
  40.                 layoutResource = R.layout.screen_custom_title; 
  41.             } 
  42.             // XXX Remove this once action bar supports these features. 
  43.             removeFeature(FEATURE_ACTION_BAR); 
  44.         } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { 
  45.             // If no other features and not embedded, only need a title. 
  46.             // If the window is floating, we need a dialog layout 
  47.             if (mIsFloating) { 
  48.                 TypedValue res = new TypedValue(); 
  49.                 getContext().getTheme().resolveAttribute( 
  50.                         R.attr.dialogTitleDecorLayout, res, true); 
  51.                 layoutResource = res.resourceId; 
  52.             } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { 
  53.                 layoutResource = a.getResourceId( 
  54.                         R.styleable.Window_windowActionBarFullscreenDecorLayout, 
  55.                         R.layout.screen_action_bar); 
  56.             } else { 
  57.                 layoutResource = R.layout.screen_title; 
  58.             } 
  59.             // System.out.println("Title!"); 
  60.         } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { 
  61.             layoutResource = R.layout.screen_simple_overlay_action_mode; 
  62.         } else { 
  63.             // Embedded, so no decoration is needed. 
  64.             layoutResource = R.layout.screen_simple; 
  65.             // System.out.println("Simple!"); 
  66.         } 
  67.         mDecor.startChanging(); 
  68.         mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //挑选出来的布局添加到DecorView中 
  69.         ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); //从DecorView找出id为com.android.internal.R.id.content的容器,提供给Activity使用。 
  70.         if (contentParent == null) { 
  71.             throw new RuntimeException("Window couldn't find content container view"); 
  72.         } 
  73.         ...... 
  74.         return contentParent; 
  75.     } 

R.layout.test

这一步纯粹是为了看下DecorView布局文件长啥样

  1. "http://schemas.android.com/apk/res/android" 
  2.     android:layout_width="match_parent" 
  3.     android:layout_height="match_parent" 
  4.     android:fitsSystemWindows="true" 
  5.     android:orientation="vertical"
  6.     "@+id/action_mode_bar_stub" 
  7.               android:inflatedId="@+id/action_mode_bar" 
  8.               android:layout="@layout/action_mode_bar" 
  9.               android:layout_width="match_parent" 
  10.               android:layout_height="wrap_content" 
  11.               android:theme="?attr/actionBarTheme" /> 
  12.     --真正存放Activity布局的容器--> 
  13.     
  14.          android:id="@android:id/content" 
  15.          android:layout_width="match_parent" 
  16.          android:layout_height="match_parent" 
  17.          android:foregroundInsidePadding="false" 
  18.          android:foregroundGravity="fill_horizontal|top" 
  19.          android:foreground="?android:attr/windowContentOverlay" /> 
  20.  

DecorView.onResourcesLoaded

  1. void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { 
  2.        if (mBackdropFrameRenderer != null) { 
  3.            loadBackgroundDrawablesIfNeeded(); 
  4.            mBackdropFrameRenderer.onResourcesLoaded( 
  5.                    this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, 
  6.                    mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState), 
  7.                    getCurrentColor(mNavigationColorViewState)); 
  8.        } 
  9.        mDecorCaptionView = createDecorCaptionView(inflater); 
  10.        final View root = inflater.inflate(layoutResource, null); //解析出布局文件 
  11.        if (mDecorCaptionView != null) { 
  12.            if (mDecorCaptionView.getParent() == null) { 
  13.                addView(mDecorCaptionView, 
  14.                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 
  15.            } 
  16.            mDecorCaptionView.addView(root, 
  17.                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT)); 
  18.        } else { 
  19.            // Put it below the color views. 
  20.            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); //添加到DecorView 
  21.        } 
  22.        mContentRoot = (ViewGroup) root; //mContentRoot保存的是整个跟布局容器 
  23.        initializeElevation(); 
  24.    } 

 

二、DecorView绘制到屏幕

Activity 执行到 onCreate 时并不可见,只有执行完 onResume 之后 Activity 中的内容才是屏幕可见状态。onCreate 阶段只是初始化了 Activity 需要显示的内容,而在 onResume 阶段才会将 PhoneWindow 中的 DecorView 真正的绘制到屏幕上。

在ActivityThread的handleResumeActivity方法中,调用WindowManager将decor作为窗口添加到 WMS 。

  1. @Override 
  2.   public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, 
  3.           String reason) { 
  4.         ...... 
  5.         if (a.mVisibleFromClient) { 
  6.               if (!a.mWindowAdded) { 
  7.                   a.mWindowAdded = true
  8.                   wm.addView(decor, l); //通过WindowManager将decor添加到WMS 
  9.               } else { 
  10.                   // The activity will get a callback for this {@link LayoutParams} change 
  11.                   // earlier. However, at that time the decor will not be set (this is set 
  12.                   // in this method), so no action will be taken. This call ensures the 
  13.                   // callback occurs with the decor set
  14.                   a.onWindowAttributesChanged(l); 
  15.               } 
  16.           } 
  17.         ...... 

实现WindowManager接口的是WindowManagerImpl类,从WINDOW_SERVICE注册时也能看出来。

  1. registerService(Context.WINDOW_SERVICE, WindowManager.class, new CachedServiceFetcher() { 
  2.        @Override 
  3.        public WindowManager createService(ContextImpl ctx) { 
  4.            return new WindowManagerImpl(ctx); 
  5.        }}); 

WindowManagerImpl.addView调用mGlobal.addView方法,mGlobal是WindowManagerGlobal类型变量。

  1. @Override 
  2.    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { 
  3.        applyDefaultToken(params); 
  4.        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); 
  5.    } 

WindowManagerGlobal.addView最终调的是ViewRootImpl的setView方法

  1. public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { 
  2.       ...... 
  3.       ViewRootImpl root; 
  4.       View panelParentView = null
  5.       synchronized (mLock) { 
  6.           ...... 
  7.           root = new ViewRootImpl(view.getContext(), display); //创建ViewRootImpl对象 
  8.           view.setLayoutParams(wparams); 
  9.           mViews.add(view); 
  10.           mRoots.add(root); 
  11.           mParams.add(wparams); 
  12.           // do this last because it fires off messages to start doing things 
  13.           try { 
  14.               root.setView(view, wparams, panelParentView); //最终,调的是ViewRootImpl的setView方法 
  15.           } catch (RuntimeException e) { 
  16.               // BadTokenException or InvalidDisplayException, clean up. 
  17.               if (index >= 0) { 
  18.                   removeViewLocked(indextrue); 
  19.               } 
  20.               throw e; 
  21.           } 
  22.       } 
  23.   } 

ViewRootImpl.setView

  1. public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { 
  2.         synchronized (this) { 
  3.             if (mView == null) { 
  4.                 ...... 
  5.                 int res;  
  6.                 // Schedule the first layout -before- adding to the window 
  7.                 // manager, to make sure we do the relayout before receiving 
  8.                 // any other events from the system. 
  9.                 requestLayout(); //调用此方法后 ViewRootImpl 所关联的 View 也执行 measure - layout - draw 操作,确保在 View 被添加到 Window 上显示到屏幕之前,已经完成测量和绘制操作。 
  10.                 ...... 
  11.                 try { 
  12.                     mOrigWindowType = mWindowAttributes.type; 
  13.                     mAttachInfo.mRecomputeGlobalAttributes = true
  14.                     collectViewAttributes(); 
  15.                     // mWindowSession 的 addToDisplay 方法将 View 添加到 WMS 中。 
  16.                     res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, 
  17.                             getHostVisibility(), mDisplay.getDisplayId(), mWinFrame, 
  18.                             mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, 
  19.                             mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel); 
  20.                 } catch (RemoteException e) { 
  21.                     mAdded = false
  22.                     mView = null
  23.                     mAttachInfo.mRootView = null
  24.                     mInputChannel = null
  25.                     mFallbackEventHandler.setView(null); 
  26.                     unscheduleTraversals(); 
  27.                     setAccessibilityFocus(nullnull); 
  28.                     throw new RuntimeException("Adding window failed", e); 
  29.                 } finally { 
  30.                     if (restore) { 
  31.                         attrs.restore(); 
  32.                     } 
  33.                 } 
  34.             } 
  35.             ...... 
  36.         } 
  37.     } 

WindowSession实例获取,是IWindowSession类型,通过Binder机制调用System 进程中的 Session实现。

  1. public static IWindowSession getWindowSession() { 
  2.       synchronized (WindowManagerGlobal.class) { 
  3.           if (sWindowSession == null) { 
  4.               try { 
  5.                   InputMethodManager imm = InputMethodManager.getInstance(); 
  6.                   IWindowManager windowManager = getWindowManagerService(); 
  7.                   sWindowSession = windowManager.openSession( 
  8.                           new IWindowSessionCallback.Stub() { 
  9.                               @Override 
  10.                               public void onAnimatorScaleChanged(float scale) { 
  11.                                   ValueAnimator.setDurationScale(scale); 
  12.                               } 
  13.                           }, 
  14.                           imm.getClient(), imm.getInputContext()); 
  15.               } catch (RemoteException e) { 
  16.                   throw e.rethrowFromSystemServer(); 
  17.               } 
  18.           } 
  19.           return sWindowSession; 
  20.       } 
  21.   } 

addToDisplay真正实现。

  1. @Override 
  2.   public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, 
  3.           int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets, 
  4.           Rect outStableInsets, Rect outOutsets, 
  5.           DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel, 
  6.           InsetsState outInsetsState) { 
  7.       return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame, 
  8.               outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel, 
  9.               outInsetsState); 
  10.   } 

至此,Window 已经成功的被传递给了 WMS。剩下的工作就全部转移到系统进程中的 WMS 来完成最终的添加操作。

三、触摸事件处理

ViewRootImpl 中的 setView 方法中,除了调用 IWindowSession 执行跨进程添加 View 之外,还有一项重要的操作就是设置输入事件的处理:

  1. public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { 
  2.      ...... 
  3.      // Set up the input pipeline. 
  4.      CharSequence counterSuffix = attrs.getTitle(); 
  5.      mSyntheticInputStage = new SyntheticInputStage(); 
  6.      InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage); 
  7.      InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage, 
  8.              "aq:native-post-ime:" + counterSuffix); 
  9.      InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage); 
  10.      InputStage imeStage = new ImeInputStage(earlyPostImeStage, 
  11.              "aq:ime:" + counterSuffix); 
  12.      InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage); 
  13.      InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage, 
  14.            "aq:native-pre-ime:" + counterSuffix); 
  15.      ...... 
  16.  } 

最终会经过ViewPostImeInputStage的onProcess处理

  1. final class ViewPostImeInputStage extends InputStage { 
  2.         public ViewPostImeInputStage(InputStage next) { 
  3.             super(next); 
  4.         } 
  5.         @Override 
  6.         protected int onProcess(QueuedInputEvent q) { 
  7.             if (q.mEvent instanceof KeyEvent) { 
  8.                 return processKeyEvent(q); 
  9.             } else { 
  10.                 final int source = q.mEvent.getSource(); 
  11.                 if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { 
  12.                     return processPointerEvent(q); 
  13.                 } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { 
  14.                     return processTrackballEvent(q); 
  15.                 } else { 
  16.                     return processGenericMotionEvent(q); 
  17.                 } 
  18.             } 
  19.         } 

processPointerEvent方法,调用mView的dispatchPointerEvent 分发事件。mView是DecorView对象

  1. private int processPointerEvent(QueuedInputEvent q) { 
  2.            ...... 
  3.            boolean handled = mView.dispatchPointerEvent(event); 
  4.            ...... 
  5.            return handled ? FINISH_HANDLED : FORWARD
  6.        } 

dispatchPointerEvent是View实现的,最终调的是dispatchTouchEvent方法。

  1. public final boolean dispatchPointerEvent(MotionEvent event) { 
  2.        if (event.isTouchEvent()) { 
  3.            return dispatchTouchEvent(event); 
  4.        } else { 
  5.            return dispatchGenericMotionEvent(event); 
  6.        } 
  7.    } 

DecorView.dispatchTouchEvent最终调用PhoneWindow的Callback分发事件。

  1. @Override 
  2.    public boolean dispatchTouchEvent(MotionEvent ev) { 
  3.        final Window.Callback cb = mWindow.getCallback(); 
  4.        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 
  5.                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); 
  6.    } 

PhoneWindow的Callback是在Activity的attach时设置的

  1. final void attach(Context context, ActivityThread aThread, 
  2.            Instrumentation instr, IBinder token, int ident, 
  3.            Application application, Intent intent, ActivityInfo info, 
  4.            CharSequence title, Activity parent, String id, 
  5.            NonConfigurationInstances lastNonConfigurationInstances, 
  6.            Configuration config, String referrer, IVoiceInteractor voiceInteractor, 
  7.            Window window, ActivityConfigCallback activityConfigCallback) { 
  8.        attachBaseContext(context); 
  9.        mFragments.attachHost(null ); 
  10.        mWindow = new PhoneWindow(this, window, activityConfigCallback); 
  11.        mWindow.setWindowControllerCallback(this); 
  12.        mWindow.setCallback(this); //将Activity对象传递给PhoneWindow 
  13.        ...... 
  14.    } 

下面就回到了我们熟悉的Activity的dispatchTouchEvent方法:

  1. public boolean dispatchTouchEvent(MotionEvent ev) { 
  2.        if (ev.getAction() == MotionEvent.ACTION_DOWN) { 
  3.            onUserInteraction(); 
  4.        } 
  5.        if (getWindow().superDispatchTouchEvent(ev)) { //调用PhoneWindow的superDispatchTouchEvent方法 
  6.            return true
  7.        } 
  8.        return onTouchEvent(ev); 
  9.    } 

PhoneWindow.superDispatchTouchEvent,归根结底还是调的DecorView的superDispatchTouchEvent方法

  1. @Override 
  2.   public boolean superDispatchTouchEvent(MotionEvent event) { 
  3.       return mDecor.superDispatchTouchEvent(event); 
  4.   } 

DecorView.superDispatchTouchEvent,调用ViewGroup的dispatchTouchEvent方法。

  1. public boolean superDispatchTouchEvent(MotionEvent event) { 
  2.        return super.dispatchTouchEvent(event); 
  3.    } 

总结

整个过程Activity参与度很低,基本靠PhoneWindow实现。

onCreate阶段创建了DecorView,onResume阶段将DecorView添加到WMS并展示,ViewRootImple对象也是onResume阶段创建的,所以也解释了onCreate阶段子线程加载view并不会报错。

ViewRootImpl 的 setView 方法中主要完成两件事情:View 渲染(requestLayout)以及接收触屏事件。

一个 Activity 中有一个 window,也就是 PhoneWindow 对象,每一个 PhoneWindow 对应一个 ViewRootImple 对象。

本文转载自微信公众号「Android开发编程」

 

来源:Android开发编程内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯