Android 13 平板Taskbar加载流程
从 Android 12开始,如果是大屏底下导航栏会变成显示一个任务栏,从 NavigationBar 变成 Taskbar 。
注:CentralSurfacesImpl.java 与 原来的 statusbar.java 的作用一样。
CentralSurfacesImpl 的启动流程前面有说过,这里不在说明。
// CentralSurfacesImpl.java
public class CentralSurfacesImpl extends CoreStartable implementsCentralSurfaces {//省略其他代码....protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {//省略其他代码....createNavigationBar(result);//省略其他代码....}protected void createNavigationBar(@Nullable RegisterStatusBarResult result) {// 调用 NavigationBarController 的 createNavigationBars()方法创建 NavigationBarmNavigationBarController.createNavigationBars(true /* includeDefaultDisplay */, result);}}
接着往下看:
NavigationBarController#createNavigationBars()// NavigationBarController.javapublic void createNavigationBars(final boolean includeDefaultDisplay,RegisterStatusBarResult result) {// 如果需要,更新辅助功能按钮模式updateAccessibilityButtonModeIfNeeded();// 这里是重点,Android 12 之前版本的,没有这个判断。可以看下 initializeTaskbarIfNecessary() 方法。// 如果我们初始化TaskBar,则不需要在默认显示上创建导航栏final boolean shouldCreateDefaultNavbar = includeDefaultDisplay&& !initializeTaskbarIfNecessary();Display[] displays = mDisplayManager.getDisplays();for (Display display : displays) {if (shouldCreateDefaultNavbar || display.getDisplayId() != DEFAULT_DISPLAY) {createNavigationBar(display, null /* savedState */, result);}}}/** @return {@code true} if taskbar is enabled, false otherwise */private boolean initializeTaskbarIfNecessary() {// 这里会判断是否是平板。// 跟踪代码发现,如果屏幕的(最小边长度*160/dpi值)< 600,就会判断为设备是平板设备// system/vendor/mediatek/proprietary/packages/apps/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/Utilities.javaif (mIsTablet) {Trace.beginSection("NavigationBarController#initializeTaskbarIfNecessary");// 移除导航栏removeNavigationBar(mContext.getDisplayId());// 加载任务栏mTaskbarDelegate.init(mContext.getDisplayId());Trace.endSection();} else {mTaskbarDelegate.destroy();}return mIsTablet;}
我们分析平板,所以这里直接看 mTaskbarDelegate.init(mContext.getDisplayId()) 方法;
TaskbarDelegate#init()// TaskbarDelegate.javapublic void init(int displayId) {if (mInitialized) {return;}//省略其他代码....// 注册监听,切换导航模式时接收回调。mEdgeBackGestureHandler.onNavigationModeChanged(mNavigationModeController.addListener(this));// 边缘手势处理,注意:这里只加载、处理手势。// 手势中左右两返回图标由这里加载,底部黑色小横条不在这里加载。mEdgeBackGestureHandler.onNavBarAttached();//省略其他代码....}// 导航模式改变回调,进行处理@Overridepublic void onNavigationModeChanged(int mode) {mNavigationMode = mode;mEdgeBackGestureHandler.onNavigationModeChanged(mode);}
1、手势加载
EdgeBackGestureHandler#onNavBarAttached()// EdgeBackGestureHandler.java/*** @see NavigationBarView#onAttachedToWindow()*/public void onNavBarAttached() {mIsAttached = true;mProtoTracer.add(this);mOverviewProxyService.addCallback(mQuickSwitchListener);mSysUiState.addCallback(mSysUiStateCallback);// 重点关注这里,更新视图updateIsEnabled();startTracking();}private void updateIsEnabled() {// mIsAttached 是否已经添加上了,mIsGesturalModeEnabled 是否启用手势模式boolean isEnabled = mIsAttached && mIsGesturalModeEnabled;if (isEnabled == mIsEnabled) {return;}mIsEnabled = isEnabled;// 如果无效的话结束监听 InputdisposeInputChannel();if (mEdgeBackPlugin != null) {// 边缘导航栏销毁mEdgeBackPlugin.onDestroy();mEdgeBackPlugin = null;}// 是否启用手势模式if (!mIsEnabled) {// 注销监听返回手势参数的设置变化mGestureNavigationSettingsObserver.unregister();if (DEBUG_MISSING_GESTURE) {Log.d(DEBUG_MISSING_GESTURE_TAG, "Unregister display listener");}mPluginManager.removePluginListener(this);TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);try {// 注销 WMS 里保存的除外区域监听mWindowManagerService.unregisterSystemGestureExclusionListener(mGestureExclusionListener, mDisplayId);} catch (RemoteException | IllegalArgumentException e) {Log.e(TAG, "Failed to unregister window manager callbacks", e);}} else {// 监听返回手势参数的设置变化mGestureNavigationSettingsObserver.register();updateDisplaySize();if (DEBUG_MISSING_GESTURE) {Log.d(DEBUG_MISSING_GESTURE_TAG, "Register display listener");}TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,mMainExecutor::execute, mOnPropertiesChangedListener);try {// 监听 WMS 里保存的除外区域mWindowManagerService.registerSystemGestureExclusionListener(mGestureExclusionListener, mDisplayId);} catch (RemoteException | IllegalArgumentException e) {Log.e(TAG, "Failed to register window manager callbacks", e);}// 注册名为 edge-swipe 的InputMonitormInputMonitor = InputManager.getInstance().monitorGestureInput("edge-swipe", mDisplayId);mInputEventReceiver = new InputChannelCompat.InputEventReceiver(mInputMonitor.getInputChannel(), Looper.getMainLooper(),Choreographer.getInstance(), this::onInputEvent);// 添加 NavigationBarEdgePanel 为 Edge Back 事件的处理实现setEdgeBackPlugin(new NavigationBarEdgePanel(mContext, mBackAnimation, mLatencyTracker));mPluginManager.addPluginListener(this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);}// 更新 ML 模型资源.updateMLModelState();}// 创建返回手势视图private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {if (mEdgeBackPlugin != null) {mEdgeBackPlugin.onDestroy();}// 缓存 NavigationEdgeBackPlugin 实现mEdgeBackPlugin = edgeBackPlugin;// 向 NavigationEdgeBackPlugin 注册 Back 手势的触发回调mEdgeBackPlugin.setBackCallback(mBackCallback);// 准备好手势视图的 Window 参数mEdgeBackPlugin.setLayoutParams(createLayoutParams());updateDisplaySize();}// 配置返回手势 Window 的参数// 包括 flag、type、title 等属性private WindowManager.LayoutParams createLayoutParams() {Resources resources = mContext.getResources();WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_width),resources.getDimensionPixelSize(R.dimen.navigation_edge_panel_height),WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,PixelFormat.TRANSLUCENT);...layoutParams.setTitle(TAG + mContext.getDisplayId());layoutParams.setFitInsetsTypes(0 /* types */);layoutParams.setTrustedOverlay();return layoutParams;}
到此手势导航创建完成,注意:此时 View 还是不可见的,后续事件产生的时候会进行展示和刷新。
下面粗略的看下手势相关的事件:
// EdgeBackGestureHandler.javaprivate void onMotionEvent(MotionEvent ev) {int action = ev.getActionMasked();if (action == MotionEvent.ACTION_DOWN) {mInputEventReceiver.setBatchingEnabled(false);mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;mMLResults = 0;mLogGesture = false;mInRejectedExclusion = false;boolean isWithinInsets = isWithinInsets((int) ev.getX(), (int) ev.getY());// 根据返回手势是否有效、// 点击区域是否是停用区域等条件// 得到当前是否允许视图处理该手势mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed && isWithinInsets&& !mGestureBlockingActivityRunning&& !QuickStepContract.isBackGestureDisabled(mSysUiFlags)&& isWithinTouchRegion((int) ev.getX(), (int) ev.getY());if (mAllowGesture) {// 更新当前是屏幕左侧还是右侧mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);// 发射事件给视图mEdgeBackPlugin.onMotionEvent(ev);}} else if (mAllowGesture || mLogGesture) {if (!mThresholdCrossed) {mEndPoint.x = (int) ev.getX();mEndPoint.y = (int) ev.getY();// 多个手指按下的话取消事件处理if (action == MotionEvent.ACTION_POINTER_DOWN) {if (mAllowGesture) {logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_MULTI_TOUCH);cancelGesture(ev);}mLogGesture = false;return;} else if (action == MotionEvent.ACTION_MOVE) {// 手指移动超过长按阈值的话// 也要取消事件处理if ((ev.getEventTime() - ev.getDownTime()) > mLongPressTimeout) {if (mAllowGesture) {logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE_LONG_PRESS);cancelGesture(ev);}mLogGesture = false;return;}...}}// 通过上述检查的话// 将 MOVE、UP 交给视图if (mAllowGesture) {// forward touchmEdgeBackPlugin.onMotionEvent(ev);}}mProtoTracer.scheduleFrameUpdate();}
事件间传递到 NavigationBarEdgePanel ,进行展示返回手势和触发返回。
NavigationBarEdgePanel 继续进行后面的工作:手势的判断、视图的刷新、动画的展示。
onMotionEvent() 的逻辑:
DOWN 的时候先让视图变为可见 VISIBLE
MOVE 的处理通过 handleMoveEvent() 判断距离,决定是否要更新赋予 mTriggerBack
UP 的时候将检查该变量决定是否触发返回动作即 triggerBack()
// NavigationBarEdgePanel.javapublic void onMotionEvent(MotionEvent event) {if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(event);switch (event.getActionMasked()) {case MotionEvent.ACTION_DOWN:mDragSlopPassed = false;resetOnDown();mStartX = event.getX();mStartY = event.getY();setVisibility(VISIBLE);updatePosition(event.getY());mRegionSamplingHelper.start(mSamplingRect);mWindowManager.updateViewLayout(this, mLayoutParams);break;case MotionEvent.ACTION_MOVE:handleMoveEvent(event);break;case MotionEvent.ACTION_UP:if (mTriggerBack) {triggerBack();} else {cancelBack();}...}}
手势相关的事件流程到此打住了。
2、按钮导航
按钮导航是在 TouchInteractionService.java 中的 onCreate() 方法开始的。
// TouchInteractionService.java@Overridepublic void onCreate() {super.onCreate();//省略其他代码....mTaskbarManager = new TaskbarManager(this);//省略其他代码....}
用户配置完成后,根据配置,是否加载按钮导航。如果加载则会调用 TaskbarManager.java中的 recreateTaskbar() 方法。
TaskbarManager#recreateTaskbar()// TaskbarManager.javaprivate void recreateTaskbar() {// 销毁现有任务栏destroyExistingTaskbar();// 获取设备配置文件的DeviceProfile dp =mUserUnlocked ? LauncherAppState.getIDP(mContext).getDeviceProfile(mContext) : null;// 任务栏是否启用boolean isTaskBarEnabled = dp != null && dp.isTaskbarPresent;if(dp != null){Log.d("yexiao","yexiao"+(dp != null)+"----"+(dp.isTaskbarPresent));}if (!isTaskBarEnabled) {SystemUiProxy.INSTANCE.get(mContext).notifyTaskbarStatus(/* visible */ false, /* stashed */ false);return;}// TaskbarActivityContext 按钮导航的视图在里面加载,包括那个底部横线也在里面。mTaskbarActivityContext = new TaskbarActivityContext(mContext, dp, mNavButtonController,mUnfoldProgressProvider);// 初始化mTaskbarActivityContext.init(mSharedState);if (mActivity != null) {// 设置相关控制器mTaskbarActivityContext.setUIController(createTaskbarUIControllerForActivity(mActivity));}}
在 TaskbarManager 类中有个 mDispInfoChangeListener 的监听,当导航模式切换时,会在 DisplayController 类中 去回调这里来。
接着往下看TaskbarActivityContext的构造方法:
public TaskbarActivityContext(Context windowContext, DeviceProfile dp,
TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider
unfoldTransitionProgressProvider) {
super(windowContext);
mDeviceProfile = dp.copy(this);
final Resources resources = getResources();mNavMode = DisplayController.getNavigationMode(windowContext);
mImeDrawsImeNavBar = getBoolByName(IME_DRAWS_IME_NAV_BAR_RES_NAME, resources, false);
mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode",() -> getPackageManager().isSafeMode());
mIsUserSetupComplete = SettingsCache.INSTANCE.get(this).getValue(Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0);
mIsNavBarForceVisible = SettingsCache.INSTANCE.get(this).getValue(Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);
mIsNavBarKidsMode = SettingsCache.INSTANCE.get(this).getValue(Settings.Secure.getUriFor(Settings.Secure.NAV_BAR_KIDS_MODE), 0);updateIconSize(resources);// 首先获取显示和焦点,因为视图可能会在构造函数中使用它们。
Display display = windowContext.getDisplay();
Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY? windowContext.getApplicationContext(): windowContext.getApplicationContext().createDisplayContext(display);
mWindowManager = c.getSystemService(WindowManager.class);
mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT);// 初始化 views.
mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate(R.layout.taskbar, null, false);
TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view);
TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim);
FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view);
StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle);mAccessibilityDelegate = new TaskbarShortcutMenuAccessibilityDelegate(this);// 构造控制器 controllers,将会在对应的控制添加对应的图标,以控制逻辑
mControllers = new TaskbarControllers(this,new TaskbarDragController(this),buttonController,getPackageManager().hasSystemFeature(FEATURE_PC)? new DesktopNavbarButtonsViewController(this, navButtonsView) : new NavbarButtonsViewController(this, navButtonsView),new RotationButtonController(this,c.getColor(R.color.taskbar_nav_icon_light_color),c.getColor(R.color.taskbar_nav_icon_dark_color),R.drawable.ic_sysbar_rotate_button_ccw_start_0,R.drawable.ic_sysbar_rotate_button_ccw_start_90,R.drawable.ic_sysbar_rotate_button_cw_start_0,R.drawable.ic_sysbar_rotate_button_cw_start_90,() -> getDisplay().getRotation()),new TaskbarDragLayerController(this, mDragLayer),new TaskbarViewController(this, taskbarView),new TaskbarScrimViewController(this, taskbarScrimView),new TaskbarUnfoldAnimationController(this, unfoldTransitionProgressProvider,mWindowManager, WindowManagerGlobal.getWindowManagerService()),new TaskbarKeyguardController(this),new StashedHandleViewController(this, stashedHandleView),new TaskbarStashController(this),new TaskbarEduController(this),new TaskbarAutohideSuspendController(this),new TaskbarPopupController(this),new TaskbarForceVisibleImmersiveController(this),new TaskbarAllAppsController(this, dp),new TaskbarInsetsController(this));
}
public void init(@NonNull TaskbarSharedState sharedState) {
mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight();
mWindowLayoutParams = createDefaultWindowLayoutParams();
// 在 TaskbarControllers控制器里,初始化前面构造方法中添加的各种控制器。
mControllers.init(sharedState);
updateSysuiStateFlags(sharedState.sysuiStateFlags, true /* fromInit */);
// 生成 LayoutParams 用于将视图作为新窗口直接添加到 WindowManager
mWindowManager.addView(mDragLayer, mWindowLayoutParams);
}
这里以 NavbarButtonsViewController 为例子分析:
// NavbarButtonsViewController.java/*** Initializes the controller*/public void init(TaskbarControllers controllers) {// 省略部分代码......// 强制导航按钮(特别是后退按钮)在设置向导期间可见。boolean isInSetup = !mContext.isUserSetupComplete();boolean isInKidsMode = mContext.isNavBarKidsModeActive();boolean alwaysShowButtons = isThreeButtonNav || isInSetup;// 省略部分代码......if (alwaysShowButtons) {// 初始化按钮initButtons(mNavButtonContainer, mEndContextualContainer,mControllers.navButtonController);} else {}// 省略部分代码......mSeparateWindowParent.recreateControllers();}private void initButtons(ViewGroup navContainer, ViewGroup endContainer,TaskbarNavButtonController navButtonController) {// back ButtonmBackButton = addButton(R.drawable.ic_sysbar_back, BUTTON_BACK,mNavButtonContainer, mControllers.navButtonController, R.id.back);mBackButtonAlpha = new MultiValueAlpha(mBackButton, NUM_ALPHA_CHANNELS);mBackButtonAlpha.setUpdateVisibility(true);mPropertyHolders.add(new StatePropertyHolder(mBackButtonAlpha.getProperty(ALPHA_INDEX_KEYGUARD_OR_DISABLE),flags -> {// Show only if not disabled, and if not on the keyguard or otherwise only when// the bouncer or a lockscreen app is showing above the keyguardboolean showingOnKeyguard = (flags & FLAG_KEYGUARD_VISIBLE) == 0 ||(flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0 ||(flags & FLAG_KEYGUARD_OCCLUDED) != 0;return (flags & FLAG_DISABLE_BACK) == 0&& ((flags & FLAG_KEYGUARD_VISIBLE) == 0 || showingOnKeyguard);}));boolean isRtl = Utilities.isRtl(mContext.getResources());mPropertyHolders.add(new StatePropertyHolder(mBackButton,flags -> (flags & FLAG_IME_VISIBLE) != 0 && !mContext.isNavBarKidsModeActive(),View.ROTATION, isRtl ? 90 : -90, 0));// Translate back button to be at end/start of other buttons for keyguardint navButtonSize = mContext.getResources().getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size);mPropertyHolders.add(new StatePropertyHolder(mBackButton, flags -> (flags & FLAG_ONLY_BACK_FOR_BOUNCER_VISIBLE) != 0|| (flags & FLAG_KEYGUARD_VISIBLE) != 0,VIEW_TRANSLATE_X, navButtonSize * (isRtl ? -2 : 2), 0));// home buttonmHomeButton = addButton(R.drawable.ic_sysbar_home, BUTTON_HOME, navContainer,navButtonController, R.id.home);mHomeButtonAlpha = new MultiValueAlpha(mHomeButton, NUM_ALPHA_CHANNELS);mHomeButtonAlpha.setUpdateVisibility(true);mPropertyHolders.add(new StatePropertyHolder(mHomeButtonAlpha.getProperty(ALPHA_INDEX_KEYGUARD_OR_DISABLE),flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 &&(flags & FLAG_DISABLE_HOME) == 0));// Recents buttonView recentsButton = addButton(R.drawable.ic_sysbar_recent, BUTTON_RECENTS,navContainer, navButtonController, R.id.recent_apps);mHitboxExtender.init(recentsButton, mNavButtonsView, mContext.getDeviceProfile(),() -> {float[] recentsCoords = new float[2];getDescendantCoordRelativeToAncestor(recentsButton, mNavButtonsView,recentsCoords, false);return recentsCoords;}, new Handler());recentsButton.setOnClickListener(v -> {navButtonController.onButtonClick(BUTTON_RECENTS);mHitboxExtender.onRecentsButtonClicked();});mPropertyHolders.add(new StatePropertyHolder(recentsButton,flags -> (flags & FLAG_KEYGUARD_VISIBLE) == 0 && (flags & FLAG_DISABLE_RECENTS) == 0&& !mContext.isNavBarKidsModeActive()));// A11y buttonmA11yButton = (R.drawable.ic_sysbar_accessibility_button, BUTTON_A11Y,endContainer, navButtonController, R.id.accessibility_button,R.layout.taskbar_contextual_button);mPropertyHolders.add(new StatePropertyHolder(mA11yButton,flags -> (flags & FLAG_A11Y_VISIBLE) != 0&& (flags & FLAG_ROTATION_BUTTON_VISIBLE) == 0));}
事件的在 addButton() 的时候直接设置了,而且有特殊需求的,会单独设置。
至此简单分析完成
Android 13 平板Taskbar加载流程相关推荐
- Android SIM卡识别加载流程
文章目录 总述 代码路径 UICC框架 SIM卡识别加载流程 日志分析举例 总述 本文基于Android N(Android 7) 首先要知道SIM卡一般是挂载在CP侧(MODEM侧)的,由MODEM ...
- android Q launcher 数据加载流程
时间:2020/08/24 之前公司不允许csdn,笔记写在其它地方.最近整理过来 下一篇:launcher数据加载(二) 前言 androidQ和androidP上Launcher结构有很大区别. ...
- Android SIM 卡信息加载流程
在PhoneAPP启动关系类初始化中,我们提到监听处理SIM卡状态的两个关键类UiccController和IccCardProxy UiccController:整个UICC事务处理的入口,负责对外 ...
- Android系统字体加载流程
一.背景 视觉同学提了一个需求,要求手机中显示的字体可以支持medium字体,经过分析,android原生的字体库中并没有中文的medium字体,如果使用bold,显示又太粗,为满足需求,需要分析an ...
- Android 系统(169)---Android 7.0 插卡后APN信息的加载流程
Android 7.0 插卡后APN信息的加载流程.UI界面编辑APN的流程及Android中APN配置相关的漏洞 终端中有一个apns-config.xml文件,负责定义各个运营商规定的默认APN参 ...
- Android 4.0 ICS SystemUI浅析——StatusBar加载流程分析
前面两篇文章< Android 4.0 ICS SystemUI浅析--SystemUI启动流程>.< Android 4.0 ICS SystemUI浅析--StatusBar结构 ...
- 从源码分析Android的Glide库的图片加载流程及特点
转载:http://m.aspku.com/view-141093.html 这篇文章主要介绍了从源码分析Android的Glide库的图片加载流程及特点,Glide库是Android下一款人气很高的 ...
- android wifi驱动加载流程
本文基于android 7.0 海思 Hi3798MV200 平台 WiFi驱动加载流程: 一..进入高级设置,无线设置界面(WifiSettings.java)打开WiFi开关(SwitchBar) ...
- 【Android】恢复出厂后静态壁纸加载流程
Android静态壁纸功能实现参与的类 /frameworks/base/core/java/android/app/WallpaperManager.java 给开发者提供方法调用.例:setBit ...
最新文章
- ng-repeat根据多个字段排序
- 宏基因组蚂蚁森林合种——胡杨专车
- Oracle学习笔记:创建logical standby
- 算法笔记之回溯法(2)
- android通过webservice验证用户 .
- String Table MFC
- Shell编程基础(1)
- 谭浩强C语言第四版第九章课后习题7--9题(建立,输出,删除,插入链表处理)...
- 《Ruby程序员修炼之道》(第2版)—第1章1.1节进入Ruby的世界
- python暴力破解
- C语言差分双向码编码,基于c语言的数字基带信号码型变换系统设计1.doc
- 2017腾讯校招面试回忆 成功拿到offer
- mysql cbrt函数_ES6 数值的扩展
- 『机器学习』入门教程汇总
- 用Java(APICloud)开发手机APP
- tmdb电影票房_TMDb Vue.js应用程序:电影数据库应用程序
- 安装kubectl失败:error: unpacking of archive failed on file /usr/bin/kubectl: cpio: rename
- Media.Metrics简介
- A Review on Deep Learning Applications in Prognostics and Health Management (翻译)
- UE4 Niagara扩散环效果及参数化配置(数字孪生用)