android APP隐藏NavigationBar

1. 简介

在Android4.4.2(KITKAT<Build.VERSION_CODES.KITKAT>)之前,只能设置:

1)View.SYSTEM_UI_FLAG_HIDE_NAVIGATION

其缺点是当Touch Screen时,Navigation bar将显示出来。

从Android4.4.2起,可以设置:

1)View.SYSTEM_UI_FLAG_HIDE_NAVIGATION

2)View.SYSTEM_UI_FLAG_IMMERSIVE

同时设置以上两个参数,即使Touch Screen时,Navigation bar也不会显示出来。

2. 实现代码

[java]  view plain copy
  1. private static Handler sHandler;
  2. protected void onCreate(Bundle savedInstanceState){
  3. super.onCreate(savedInstanceState);
  4. sHandler = new Handler();
  5. sHandler.post(mHideRunnable); // hide the navigation bar
  6. final View decorView = getWindow().getDecorView();
  7. decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener()
  8. {
  9. @Override
  10. public void onSystemUiVisibilityChange(int visibility)
  11. {
  12. sHandler.post(mHideRunnable); // hide the navigation bar
  13. }
  14. });
  15. }
  16. Runnable mHideRunnable = new Runnable() {
  17. @Override
  18. public void run() {
  19. int flags;
  20. int curApiVersion = android.os.Build.VERSION.SDK_INT;
  21. // This work only for android 4.4+
  22. if(curApiVersion >= Build.VERSION_CODES.KITKAT){
  23. // This work only for android 4.4+
  24. // hide navigation bar permanently in android activity
  25. // touch the screen, the navigation bar will not show
  26. flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
  27. | View.SYSTEM_UI_FLAG_IMMERSIVE
  28. | View.SYSTEM_UI_FLAG_FULLSCREEN;
  29. }else{
  30. // touch the screen, the navigation bar will show
  31. flags = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
  32. }
  33. // must be executed in main thread :)
  34. getWindow().getDecorView().setSystemUiVisibility(flags);
  35. }
  36. };
通过修改framework

android 4.2 隐藏/显示 navigation bar, 实现全屏显示

点击打开链接

Android 4.0 ICS SystemUI浅析——SystemUI启动流程

阅读Android 4.0源码也有一段时间了,这次是针对SystemUI的一个学习过程。本文只是对SystemUI分析的一个开始——启动流程的分析,网上有很多关于2.3的SystemUI的分析,可4.0与2.3的差别还是很大的,为了给自己留下笔记同时也方便大家学习和探讨,遂写此文,后续将有更多关于SystemUI的分析,敬请关注。

转载请注明出处:http://blog.csdn.net/yihongyuelan

1.初始SystemUI

什么是SystemUI?你或许会觉得这个问题很幼稚,界面上的布局UI显示?系统的UI?如果你是这么想的,那么就大错特错了。我们知道Android 4.0 ICS同时适用于Phone和Tablet(TV),因此,对于Phone来说SystemUI指的是:StatusBar(状态栏)、NavigationBar(导航栏)。而对于Tablet或者是TV来说SystemUI指的是:CombinedBar(包括了StatusBar和NavigationBar)。注:关于Android 4.0的UI介绍请参考这篇文章。

根据上面的介绍,我想大家应该知道SystemUI的具体作用了吧!也就是说我们的Phone的信号,蓝牙标志,Wifi标志等等这些状态显示标志都会在StatusBar上显示。当我们的设备开机后,首先需要给用户呈现的就是各种界面同时也包括了我们的SystemUI,因此对于整个Android系统来说,SystemUI都有举足轻重的作用,那接下来就来看看它的启动流程吧!

2.启动流程

这里只是单单的分析启动流程,实际上SystemUI启动过程中涉及到很多东西的调用,这里暂时不分支去介绍,后续会有相关文章的详细分析。那么对于这种分析我还是将自己的分析思路写出来,而不是直接展现已经分析好的结果,当然结果会在最后展示出来。这样做一方面有利于锻炼自己的分析能力,另一方面各位看官也可以找出分析中的利与弊从而更好的取舍。

首先来看看SystemUI的代码位置,路径:SourceCode/frameworks/base/packages/SystemUI;其次看看它的代码梗概:

图 2.1

在Android 4.0中,Google整合了Phone和Tablet(TV)的SystemUI,也就说可以根据设备的类型自动匹配相应的SystemUI。这一点是在Android 2.3中是没有的。那么接下来怎么分析呢?打开AndroidManifest.xml可以看到:

[html]  view plain copy
  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  2. package="com.android.systemui"
  3. coreApp="true"
  4. android:sharedUserId="android.uid.system"
  5. android:process="system"
  6. >
  7. <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
  8. <uses-permission android:name="android.permission.BLUETOOTH" />
  9. <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
  10. <uses-permission android:name="android.permission.GET_TASKS" />
  11. <uses-permission android:name="android.permission.MANAGE_USB" />
  12. <application
  13. android:persistent="true"
  14. android:allowClearUserData="false"
  15. android:allowBackup="false"
  16. android:hardwareAccelerated="true"
  17. android:label="@string/app_label"
  18. android:icon="@drawable/ic_launcher_settings">
  19. <!-- Broadcast receiver that gets the broadcast at boot time and starts
  20. up everything else.
  21. TODO: Should have an android:permission attribute
  22. -->
  23. <service android:name="SystemUIService"
  24. android:exported="true"
  25. />
  26. <!-- started from PhoneWindowManager
  27. TODO: Should have an android:permission attribute -->
  28. <service android:name=".screenshot.TakeScreenshotService"
  29. android:process=":screenshot"
  30. android:exported="false" />
  31. <service android:name=".LoadAverageService"
  32. android:exported="true" />
  33. <service android:name=".ImageWallpaper"
  34. android:permission="android.permission.BIND_WALLPAPER"
  35. android:exported="true" />
  36. <receiver android:name=".BootReceiver" >
  37. <intent-filter>
  38. <action android:name="android.intent.action.BOOT_COMPLETED" />
  39. </intent-filter>
  40. </receiver>
  41. ... ...
  42. </application>
  43. </manifest>

根据以上代码我们可以发现这其中注册了很多Service,同时也包括了广播。但这里我们只关注SystemUIService,这才是本文的主旨啊。那么首先要找到SystemUIService是如何启动的。对于Service的启动,在我以前的博文中已有提到,这里就不多说了,不外乎startService(intent)和bindService(intent),它们都是以intent为对象,那intent的声明也需要SystemUIService啊,因此我们可以据此搜索关键词"SystemUIService"。

经过漫长的搜索和比对之后发现,原来,SystemUIService是在SystemServer.java中被启动的,如下所示:

[java]  view plain copy
  1. static final void startSystemUi(Context context) {
  2. Intent intent = new Intent();
  3. intent.setComponent(new ComponentName("com.android.systemui",
  4. "com.android.systemui.SystemUIService"));
  5. Slog.d(TAG, "Starting service: " + intent);
  6. context.startService(intent);
  7. }

这里的startSystemUi()方法则在ServerThread的run()方法中被调用。这里提到SystemServer就不得不提及Android的启动流程,这里不会展开详细讨论具体的流程,只是简单的介绍一下大概流程,用以表明SystemServer所处的位置。

Android的启动分为内核启动、Android启动、launcher启动,我们的SystemServer就处于Android启动中,以下是大致流程图:

init->ServiceManager->Zygote->SystemServer->... ...

在SystemServer中,初始化了Android系统中的Java层服务,如PowerManagerService、WindowManagerService等等,当然也包括了SystemUIService,它们通过ServiceManager的addService()方法,添加到ServiceManager的管理中。实际上,根据后面的分析这里add了一个很重要的StatusBarManagerService。这个Service在后面会用到的。

既然到这里SystemUIService已经启动,那么我们就继续跟踪该Service吧。

1).首先查看其onCreate()方法,如下:

[java]  view plain copy
  1. public void onCreate() {
  2. // Pick status bar or system bar.
  3. IWindowManager wm = IWindowManager.Stub.asInterface(
  4. ServiceManager.getService(Context.WINDOW_SERVICE));
  5. try {
  6. SERVICES[0] = wm.canStatusBarHide()//根据wm.canStatusBarHide()判断设备类型
  7. ? R.string.config_statusBarComponent
  8. : R.string.config_systemBarComponent;
  9. } catch (RemoteException e) {
  10. Slog.w(TAG, "Failing checking whether status bar can hide", e);
  11. }
  12. final int N = SERVICES.length;
  13. mServices = new SystemUI[N];
  14. for (int i=0; i<N; i++) {
  15. Class cl = chooseClass(SERVICES[i]);
  16. Slog.d(TAG, "loading: " + cl);
  17. try {
  18. mServices[i] = (SystemUI)cl.newInstance();
  19. } catch (IllegalAccessException ex) {
  20. throw new RuntimeException(ex);
  21. } catch (InstantiationException ex) {
  22. throw new RuntimeException(ex);
  23. }
  24. mServices[i].mContext = this;
  25. Slog.d(TAG, "running: " + mServices[i]);
  26. mServices[i].start();
  27. }
  28. }

在这段代码中,通过AIDL的方式获取了WindowManager的对象wm,并调用其方法canStatusBarHide()来判断当前设备的类型,也就是说如果我们使用的Phone那么后续就会加载StatusBar和NivagationBar;而如果我们设备类型是Tablet(TV)之类的(可以在配置文档里面配置), 就会加载CombiedBar。

这里的canStatusBarHide()方法的具体实现是在:frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java。为什么会是这里呢?我们在Eclipse中导入源码之后,找到SystemUIService.java中的wm.canStatusBarHide()方法,通过open Implementation直接跳转到WindowsManagerService中:

[java]  view plain copy
  1. public boolean canStatusBarHide() {
  2. return mPolicy.canStatusBarHide();
  3. }

但这里我们发现canStatusBarHide()实际上是WindowManagerPolicy的对象调用的方法,而WindowManagerPolicy只是一个接口类,根据以往分析的经验可以知道,这里的WindowManagerPolicy对象所调用的canStatusBartHide()方法一定是其实现类中的 方法。因此,继续通过open Implementation跳转,来到了PhoneWindownManager中:

[java]  view plain copy
  1. public boolean canStatusBarHide() {
  2. return mStatusBarCanHide;
  3. }

继续查看mSatuBarCanHide的实现,如下所示:

[java]  view plain copy
  1. // Determine whether the status bar can hide based on the size
  2. // of the screen.  We assume sizes > 600dp are tablets where we
  3. // will use the system bar.
  4. int shortSizeDp = shortSize
  5. * DisplayMetrics.DENSITY_DEFAULT
  6. / DisplayMetrics.DENSITY_DEVICE;
  7. mStatusBarCanHide = shortSizeDp < 600;
  8. mStatusBarHeight = mContext.getResources().getDimensionPixelSize(
  9. mStatusBarCanHide
  10. ? com.android.internal.R.dimen.status_bar_height
  11. : com.android.internal.R.dimen.system_bar_height);
  12. mHasNavigationBar = mContext.getResources().getBoolean(
  13. com.android.internal.R.bool.config_showNavigationBar);

这里通过shortSizeDp来判断当前设备的类型,如果当前屏幕的shortSize Dp<600dp,则系统会认为该设备是Phone反之则认为是Tablet。根据mStatusBarCanHide的值,设定StatusBar或者SystemBar(CombinedBar)的高度,以及是否显示NavigationBar。

继续回到我们的SystemUIService.java的onCreate()方法中,根据前面对canStatusBarHide()的判断,SERVICE[0]中将存放R.string.config_statusBarComponent或者R.string.config_systemBarComponent。它们的值具体是:

[html]  view plain copy
  1. <string name="config_statusBarComponent" translatable="false">com.android.systemui.statusbar.phone.PhoneStatusBar</string>
  2. <string name="config_systemBarComponent" translatable="false">com.android.systemui.statusbar.tablet.TabletStatusBar</string>

因为我的测试设备是Phone,那么现在SERVICE[0]中存放的就是com.android.systemui.statusbart.phone.PhoneStatusBar。查看以下代码:

[java]  view plain copy
  1. final int N = SERVICES.length;
  2. mServices = new SystemUI[N];
  3. for (int i=0; i<N; i++) {
  4. Class cl = chooseClass(SERVICES[i]);
  5. Slog.d(TAG, "loading: " + cl);
  6. try {
  7. mServices[i] = (SystemUI)cl.newInstance();
  8. } catch (IllegalAccessException ex) {
  9. throw new RuntimeException(ex);
  10. } catch (InstantiationException ex) {
  11. throw new RuntimeException(ex);
  12. }
  13. mServices[i].mContext = this;
  14. Slog.d(TAG, "running: " + mServices[i]);
  15. mServices[i].start();
  16. }

这些方法会分别启动两个方法,这两个方法可以从log中知道,分别是PhoneStatusBar.start()和PowerUI.start()。而我们的目的是要弄清SystemUI的启动,因此现关注PhoneStatusBar.start()方法。

log信息:

06-04 13:23:15.379: DEBUG/SystemUIService(396): loading: class com.android.systemui.statusbar.phone.PhoneStatusBar

06-04 13:23:16.739: DEBUG/SystemUIService(396): loading: class com.android.systemui.power.PowerUI

来到PhoneStatusBar.start()方法中,位于:SourceCode/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java,代码如下:

[java]  view plain copy
  1. @Override
  2. public void start() {
  3. mDisplay = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
  4. .getDefaultDisplay();
  5. mWindowManager = IWindowManager.Stub.asInterface(
  6. ServiceManager.getService(Context.WINDOW_SERVICE));
  7. super.start(); // calls makeStatusBarView()
  8. addNavigationBar();
  9. //addIntruderView();
  10. // Lastly, call to the icon policy to install/update all the icons.
  11. mIconPolicy = new PhoneStatusBarPolicy(mContext);
  12. }

这里的重心主要是在super.start()和addNavigationBar() 上。目前市面上很多手机已经刷入了ICS,但是大多数是没有NavigationBar的,也就是说自己修改了源码,屏蔽了NavigationBar。继续跟踪super.start()方法,来到/SourceCode/frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java的start()方法中,代码如下:

[java]  view plain copy
  1. public void start() {
  2. // First set up our views and stuff.
  3. View sb = makeStatusBarView();
  4. // Connect in to the status bar manager service
  5. StatusBarIconList iconList = new StatusBarIconList();
  6. ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
  7. ArrayList<StatusBarNotification> notifications = new ArrayList<StatusBarNotification>();
  8. mCommandQueue = new CommandQueue(this, iconList);
  9. mBarService = IStatusBarService.Stub.asInterface(
  10. ServiceManager.getService(Context.STATUS_BAR_SERVICE));
  11. int[] switches = new int[7];
  12. ArrayList<IBinder> binders = new ArrayList<IBinder>();
  13. try {
  14. mBarService.registerStatusBar(mCommandQueue, iconList, notificationKeys, notifications,
  15. switches, binders);
  16. } catch (RemoteException ex) {
  17. // If the system process isn't there we're doomed anyway.
  18. }
  19. disable(switches[0]);
  20. setSystemUiVisibility(switches[1]);
  21. topAppWindowChanged(switches[2] != 0);
  22. // StatusBarManagerService has a back up of IME token and it's restored here.
  23. setImeWindowStatus(binders.get(0), switches[3], switches[4]);
  24. setHardKeyboardStatus(switches[5] != 0, switches[6] != 0);
  25. // Set up the initial icon state
  26. int N = iconList.size();
  27. int viewIndex = 0;
  28. for (int i=0; i<N; i++) {
  29. StatusBarIcon icon = iconList.getIcon(i);
  30. if (icon != null) {
  31. addIcon(iconList.getSlot(i), i, viewIndex, icon);
  32. viewIndex++;
  33. }
  34. }
  35. // Set up the initial notification state
  36. N = notificationKeys.size();
  37. if (N == notifications.size()) {
  38. for (int i=0; i<N; i++) {
  39. addNotification(notificationKeys.get(i), notifications.get(i));
  40. }
  41. } else {
  42. Log.wtf(TAG, "Notification list length mismatch: keys=" + N
  43. + " notifications=" + notifications.size());
  44. }
  45. // Put up the view
  46. final int height = getStatusBarHeight();
  47. final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
  48. ViewGroup.LayoutParams.MATCH_PARENT,
  49. height,
  50. WindowManager.LayoutParams.TYPE_STATUS_BAR,
  51. WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
  52. | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
  53. | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
  54. // We use a pixel format of RGB565 for the status bar to save memory bandwidth and
  55. // to ensure that the layer can be handled by HWComposer.  On some devices the
  56. // HWComposer is unable to handle SW-rendered RGBX_8888 layers.
  57. PixelFormat.RGB_565);
  58. // the status bar should be in an overlay if possible
  59. final Display defaultDisplay
  60. = ((WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE))
  61. .getDefaultDisplay();
  62. // We explicitly leave FLAG_HARDWARE_ACCELERATED out of the flags.  The status bar occupies
  63. // very little screen real-estate and is updated fairly frequently.  By using CPU rendering
  64. // for the status bar, we prevent the GPU from having to wake up just to do these small
  65. // updates, which should help keep power consumption down.
  66. lp.gravity = getStatusBarGravity();
  67. lp.setTitle("StatusBar");
  68. lp.packageName = mContext.getPackageName();
  69. lp.windowAnimations = R.style.Animation_StatusBar;
  70. WindowManagerImpl.getDefault().addView(sb, lp);
  71. if (SPEW) {
  72. Slog.d(TAG, "Added status bar view: gravity=0x" + Integer.toHexString(lp.gravity)
  73. + " icons=" + iconList.size()
  74. + " disabled=0x" + Integer.toHexString(switches[0])
  75. + " lights=" + switches[1]
  76. + " menu=" + switches[2]
  77. + " imeButton=" + switches[3]
  78. );
  79. }
  80. mDoNotDisturb = new DoNotDisturb(mContext);
  81. }

在这里,完成了SystemUI的整个初始化以及设置过程,并最终呈现到界面上。在StatusBar中的start()方法主要完成了以下几个工作:首先获取需要在StatusBar上显示的各种icons。然后初始化一些属性。最后通过WindowManager的addView方法将StatusBar显示出来。分析到这里可能有人会问了,明明说分析的是SystemUI的嘛,怎么最后变成StatusBar了呢?如果你硬要说我跑题那我也没有办法,回过头去看看addNavigationBar(),你会发现和StatusBar的加载几乎一致,因此没必要再详述了。 如果细心阅读了的朋友肯定会发现这句代码:

mBarService = IStatusBarService.Stub.asInterface(ServiceManager.getService(Context.STATUS_BAR_SERVICE));

这不正是我们前面add的StatusBarManagerSerivce吗?这里通过AIDL的方式来获取它的对象。

整个代码执行的时序图如图2.2所示:

图 2.2

3.总结

Android 4.0的SystemUI加载启动的过程大致就是这样,虽然看似简单,但这仅仅是个开始,master还是后面呢!!各家厂商根据自家的需求,需要定制SystemUI或者美化SystemUI,不同的平台(QCOM、MTK等等)也会有不同的修改,但大体框架是没有变的,无非是在原有基础上的修修改改或者增加一些自己的类等等。通过对Android源码框架性的理解,可以学习到很多设计上的知识(虽然自己还很欠缺)。通过这次分析,开始逐渐用StarUML来画时序图,这也是一个学习的过程。

android APP隐藏NavigationBar,通过修改framework隐藏/显示 navigation bar相关推荐

  1. weex android app例子,weex中修改android app图标和欢迎页

    修改欢迎页背景 1.图片放到platforms/android/app/src/main/res/drawable-xxxx下面,图片必须是png格式,否则会报错:然后修改platforms/andr ...

  2. android app分享到微信让应用来源显示qq浏览器或者是其他应用

    如题:app分享到微信让应用来源显示qq浏览器或者是其他应用. 针对这个问题,大家没有没有什么好的思路,或者解决方案~ 2019-11-18 由于最近看到CSDN上有一些摘抄盈利行为,这里屏蔽一部分代 ...

  3. 改Android app字体,Android APP自定义字体大小修改

    简单记录下今天做的自定义字体大小修改的功能 需求:添加具体字体自定义大小功能.不需要跟随系统字体大小改变而改变 1.首先看一下用到的调节字体大小的控件: 字体大小调节页 控件继承自系统的SeekBar ...

  4. js学习-HTML标签隐藏以及不可修改,设置隐藏标签

    加属性 style="display:none" 比如 <input style="display:none"   type="text&quo ...

  5. Android APP安装后在桌面上不显示应用图标

    前几天在写项目的时候运行的时候突然Android桌面上没有了应用图标,但是应用里面下载的应用有.调试版本和发布正式的版本都没有,之前以为是因为用了不同的keystore发布了两个不同的正式版本造成的问 ...

  6. Android 如何关闭Navigation Bar

    前言          欢迎大家我分享和推荐好用的代码段~~ 声明          欢迎转载,但请保留文章原始出处:          CSDN:http://www.csdn.net        ...

  7. Android 8.1实现Systemui 中的NavigationBar的点击隐藏与滑动显示

    此篇文章只做记录一下这个功能自己实现的喜悦.如果能帮助其他人,那也荣幸之至.我会写的比较细,拿到源码谁都能改.要先谢谢网络上两位大神的博文给予的帮助. 请参考     https://blog.csd ...

  8. java屏蔽虚拟按键代码_Android6.0 源码修改之屏蔽导航栏虚拟按键(Home和RecentAPP)/动态显示和隐藏NavigationBar...

    场景分析, 为了完全实现沉浸式效果,在进入特定的app后可以将导航栏移除,当退出app后再次将导航栏恢复.(下面将采用发送广播的方式来移除和恢复导航栏) ps:不修改源码的情况下,简单的沉浸式效果实现 ...

  9. Android6.0 源码修改之屏蔽导航栏虚拟按键(Home和RecentAPP)/动态显示和隐藏NavigationBar...

    场景分析, 为了完全实现沉浸式效果,在进入特定的app后可以将导航栏移除,当退出app后再次将导航栏恢复.(下面将采用发送广播的方式来移除和恢复导航栏) ps:不修改源码的情况下,简单的沉浸式效果实现 ...

最新文章

  1. Java学习总结:9
  2. 你应该知道的五种IO模型
  3. python画简单图形-python基础教程之turtle的简单绘图
  4. 用JavaScript实现简单的excel列转sql字符串
  5. 用chrome模拟微信浏览器访问需要OAuth2.0网页授权的页面
  6. Windows8 10设置程序为 系统默认浏览器
  7. WordPress SEO插件,免费WordPress插件大全
  8. Ubuntu系统的下载与安装(超详细)
  9. php 监听条码枪输入,使用jQuery监听扫码枪输入并禁止手动输入的实现方法
  10. kubernetes 从入门到实践
  11. 如何使用微信开发者工具调试在微信端访问的网页
  12. 汇编语言(ASCII码)有关除数
  13. eclipse 32位换成64位 maven tomcat svn 集成
  14. html制作电影界面,电影网站界面设计HTML_CSS模板
  15. SSL集训 2021.07.16 提高B组 T1 下棋【博弈论】
  16. 解决vs2013编译时scanf报错的方法
  17. 6s测试信号软件,主流智能机信号强度测试 iPhone6s表现差
  18. 【备忘】麻瓜编程 实用主义学Python视频
  19. Git Extensions 使用
  20. 中台技术:十二年架构演进之路

热门文章

  1. Linux虚拟机占用宿主机磁盘空间压缩
  2. 4星|《基因转》:从孟德尔、达尔文到人类胚胎转基因
  3. UE4 UMG入门——创建和显示游戏菜单
  4. 2017北京国际军民融合装备展览会会刊(参展商名录)
  5. NetBeans删除项目后无法重新打开该项目解决办法
  6. 全国计算机等级考试二级python考纲考点一览
  7. fastadmin 的自定义搜索select框联动
  8. php语法检查修复工具,代码标准修复工具 PHP CS Fixer
  9. 热带气旋强度估计——物理信息融合
  10. 从 Option Explicit 开始的零碎知识点(一)