前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android 开发艺术探索笔记二

Android 开发艺术探索笔记二

作者头像
Yif
发布2019-12-26 14:59:50
1.7K0
发布2019-12-26 14:59:50
举报
文章被收录于专栏:Android 进阶Android 进阶
undefined
undefined

理解window与windowManager

不管是Activity,Dialog还是Toast,它们视图都是附加在window上的,window才是view的直接管理者。

  1. FLAG_NOT_FOCUSABLE:表示不需要获取焦点,也不需要接收各种输入事件,同时启用FLAG_NOT_TOUCH_MODE 最终事件直接会传递给下层具有焦点的window
  2. FLAG_NOT_TOUCH_MODE ,系统会将当前window区域以外的单击事件传递给底层的window,当前区域内的单击事件自己处理,一般需要开启此标记,否则window将无法收到单击事件。
  3. FLAG_SHOW_WHEN_LOCKED:开启此模式,让window显示在锁屏界面上。

Android 8.0之前源码

Window添加过程
  1. 检查参数是否合法,如果是子window还需调整布局参数
  2. 创建viewRootImpl并将View添加到列表中
  • mViews存储所有window对应的view
  • mRoots存储所有window对应的ViewRootImpl
  • mParams存储所有window对应的布局参数
  • mDyingViews存储正在删除的view对象
  1. 通过ViewRootImpl来更新并完成添加过程
Window删除过程

通过findViewLocked来查找待删除的View索引,查找过程建立数组索引遍历,在调用removeViewLocked进一步删除。

删除提供两个接口,removeView与removeViewImmediate,异步与同步删除,同步删除window会发生错误,而异步删除由viewRootImpl的die方法完成,die发送一个请求删除消息后就返回,将view添加到DyingViews中。

Window更新过程

调用updateViewLayout让新的LayoutParams替换老的LayoutParams,再更新viewRootImpl中的LayoutParams,并在viewRootImpl中的scheduleTraversals对view进行重新布局,还会通过windowSession来更新window视图,这个过程由relayoutWindow具体实现,它是IPC进程。

Android 8.0源码

WMS创建涉及三个线程,分别是system_server、android:display、android:ui之间关系

  1. 首先system_server线程中执行了systemServer的startOtherServices方法,在该方法中调用WMS的main方法,main方法会创建WMS,创建过程在android:display线程中实现,创建WMS优先级更高,因此system_sever线程要等WMS创建完成后,处于等待状态的system_server线程才会被唤醒从而继续执行**
  2. 在WMS构造方法中会调用WMS的initPolicy方法,在该方法中又会调用PowerManagerService的init方法,PWM的init方法会在android:ui线程中运行,它的优先级要高于android:display线程,因此android:display要等待init方法执行完毕后,android:display才会被唤醒继续执行
  3. PWM的init方法执行完毕后,android:display线程就完成了WMS创建,等待的system_server线程被唤醒继续执行WMS的main方法后的逻辑,比如WMS的displayReady方法用来初始化屏幕显示信息。
WMS重要成员
  1. mPolicy:WindowManagerPolicy

WindowManagerPolicy是窗口管理策略接口类,用来定义一个窗口策略遵循的规范,并提供WM所特定的UI行为,具体实现类为PhoneWIndowManager,类在WMS创建时被创建。

  1. mSessions:ArraySet

元素类型为Session,主要用于进程间通信,并且每一个应用程序都会对应一个Session

  1. mWindowMap:WindowHashMap
  • key值为IBindervalue值为WindowsState,用来保存WMS各种窗口集合
  1. mFinishedStarting:arrayList

元素类型为AppWindowToken,是WindowToken子类,作用:

  • 窗口令牌,当应用程序想要向WMS申请创建窗口时,需要出示有效的令牌,应用程序每一个activity都对应一个AppWindowToken
  • 会将同一个组件(比如activity)的窗口(WindowState)集合在一起,方便管理

mFinishedStarting就是用于存储已经完成启动的应用程序窗口的AppWindowToken列表;

Window添加过程
  1. 对所要添加的窗口进行检查,如果窗口不满足一些条件,就不会执行之后的逻辑
  2. WindowToken相关处理,比如有的窗口类型需要提供WindowToken,没有提供就不会执行后面逻辑,有的窗口类型需要由WMS隐式创建WindowToken
  3. WindowState创建相关处理,将WindowTokenWindowState相关联
  4. 创建与配置DisplayContent,完成窗口添加到系统前的准备工作
Window删除过程
  1. 检查删除线程的正确性,不正确抛异常
  2. ViewRootImpl列表,布局参数列表与View列表删除与Window对应的原色
  3. 判断是否可以直接执行删除操作,不能则推迟删除操作
  4. 执行删除操作,清理与释放与Window相关的一切资源

WindowManagerServiceWindowManager的管理者的

WMS职责
  1. 窗口管理

它是窗口管理者,负责窗口启动、添加、删除,另外窗口大小与层级也是由它进行管理。管理的核心成员有:DisplayContent、WindowToken与WindowState

  1. 窗口动画

窗口间进行切换时,使用动画更加炫酷些,是由WMS的动画子系统负责,管理者为WindowAnimator

  1. 输入系统的中转站

通过窗口触摸而产生的触摸事件,InputManagerService会对触摸事件进行管理,寻找最合适的窗口来处理触摸反馈事件

  1. Surface管理

窗口并不具有绘制功能,每个窗口都需要一块Surface来供自己绘制,为每个窗口分配Surface由WMS完成的

Activity的window创建过程

由activity的setContentView看出,它的具体实现交给了PhoneWindow

  1. 如果没有DecorView就创建
  2. 将view添加到DecorViewmContentParent
  3. 回调activityonContentChanged方法通知activity的视图发生改变

Dialog的window创建过程

  1. 创建window
  2. 初始化DecorView并将diaolog视图添加到DecorView
  3. DecorView添加到window

必须采用activity的context,否则采用application的context会报错.是由于没有应用token导致,而token只有activity有,可以指定为系统类型window就可以正常弹出。

  1. dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM_ERROR)
  2. 声明权限 : android.permission.SYSTEM_ALTER_WINDOW

Toast的window创建过程

它有定时功能,采用handler,内部有两类IPC

  1. Toast访问NotificationManagerService
  2. NotificationManagerService回调Toast的TN接口

Toast的延时时长只有2s与3.5s

四大组件工作过程

**Android 7.0与8.0区别是,与AMS进行进程间通信采用的AIDL技术去掉了此前一直沿用的activityManagerProxyapplicationThreadProxy等代理类。

Activity的工作过程

  1. 启动activity真正实现由activityManagerNative,getDefault()startActivity方法完成,AMS是一个Binder,这个binder对象采用单例模式对外提供,Singletn是一个单例封装类,第一次调用它的get通过create来初始化AMS的binder对象。

ActivityManager.getDefault()实际上是ActivityManagerService(AMS)

  1. Activity启动过程在ActivityStackSupervisorActivityStack之间传递顺序 startActivityMayWait -> startActivityLocked -> startActivityUncheckedLocked ->resumeTopActivitiesLocked -> resumeTopActivityInnerLocked  ->startSpecificActivityLocked -> realStartActivityLocked 其中resumeTopActivitiesLockedresumeTopActivityInnerLockedActivityStack中,其余在ActivityStackSupervisor
  2. Activity的启动过程由ActivityThreadhandleLaunchActivity方法实现。

最后performLaunchActivity完成activity对象创建与启动过程。

performLaunchActivity完成事:

  • ActivityClientRecorf中获取待启动activity的组件信息
  • 通过Instrumentationnewactivity方法使用类加载器创建activity对象
  • 通过LoadedApkmakeApplication来创建Application对象,其中Application不会重复创建
  • 创建ContextImpl对象通过activity的attach来完成一些重要数据初始化,ContextImpl是context的具体实现,在attach方法中,activity还会完成window创建并建立关联window
  • 调用activity的onCreate方法

Service

Service的启动过程
  1. Service启动过程从ContextWrapper的startService开始,从ContextWrapper实现看出,大部分操作通过mBase实现,是一个桥接模式。然后调用ContextImplstartService实现,又调用了ActivityManager.getDefault()实际上是ActivityManagerService(AMS)的startServic方法,Services对象类型ActiviteServices是一个辅助AMS进行Service管理,包括启动,绑定,停止。
  2. ActiviteServicesstartServiceLocked尾部的startServiceInnerLocked,ServiceRecord描述一个Service记录,贯穿整个启动过程,又交给了bringUpServiceLocked,然后交给realStartServiceLocked处理,方法里又调用了ApplicationThread的scheduleCreateService通过发消息交给ActivityThread的handleCreateService处理

handleCreateService做的事:

  1. 通过类加载器创建service实例
  2. 创建Application,调用onCreate
  3. 接着创建ContextImplservice的attach建立二者之间关系
  4. 最后调用service的oncreate将servic对象存储在activityThread列表中
Service的绑定过程

ContextWrapper开始,然后调用ContextImpl的bindService,调用bindServiceCommon方法做了两件事:

  1. 将客户端的ServiceConnection对象转化为ServiceDispatcher.InnerConnection对象,因为服务绑定可能跨进程
  2. 接着bindServiceCommon通过AMS完成Service的具体绑定,调用AMS的bindService方法

绑定过程调用ActivityThread的scheduleBindService,实现在ActiviteServicesrequestServiceBindingLocked方法中。然后ApplicationThread的scheduleBindService发消息进行绑定,调用handleBindService,根据servicetoken取出service对象,然后调用onBinder方法,就绑定了,但客户端不知道是否成功连接service还需要调用AMS的publishService方法,多次绑定相同的service,onBinder方法只会执行一次。

BroadcastReceiver的工作过程

广播注册过程
  1. 静态注册有PackManagerService来完成整个注册过程
  2. 动态注册从ContextWrapper的registerReceiver方法开始,然后调用ContextImplregisterReceiverInternal方法, 首先从mPackageInfo中获取IIntentReceiver对象,然后采用跨进程方式向AMS发送广播注册,之所以采用IIntentReceiver,由于BroadcastReceiver作为android不能直接跨进程。getReceiverDispatcher方法重新创建一个ReceiverDispatcher对象并将其板寸的InnerReceiver对象作为返回值返回,其中InnerReceiver和BroadcastReceiver在ReceiverDispatcher的构造方法中保存

广播真正实现在AMS中,最终把远程的InnerReceiver对象以及IntentFilter对象存储起来。

广播发送与接收过程

广播发送仍然是从ContextWrappersendBroadcast开始,交给ContextImplsendBroadcast,然后在AMS的broadcastIntent方法中开始,调用broadcastIntentLocked方法。

intent.addFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES)表示在android 5.0中,默认情况下广播不会发送给已经停止的应用,而FLAG_INCLUDE_STOPPED_PACKAGES表示包含已经停止的应用,广播会发送给停止的应用。当这两种状态共存,以FLAG_INCLUDE_STOPPED_PACKAGES为准。

一个应用处于停止有两种情形:

  1. 应用安装后未运行
  2. 应用被手动或其他应用强停了 从android3.1开始,处于停止的应用无法接收到开机广播

broadcastIntentLocked内部,根据intent-filter查找匹配广播接收者并经过一系列过滤,将满足条件的添加到BroadcastQueue中。然后在BroadcastQueue中调用scheduleBroadcastsLocked方法,通过handler发消息让processNextBroadcast处理。

调用LoadedApk类的performReceive方法,创建Args对象,它实现了Runnable接口,run方法中:

代码语言:javascript
复制
final BroadcastReceiver receiver = mReceiver;                                                           receiver.setPendingResult(this);
receiver.onReceive(mContext, intent);

这个时候BroadcastReceiver的onReceive方法执行,应用已经接收广播了。

ContentProvider工作过程

  1. contentProvider所在的进程启动时,contentProvider会同时启动并发布AMS中,**注意:这个时候ContentProvider的onCreate要先于Application的onCreate方法执行。
  2. 当一个应用启动时,入口方法为activityThread的main方法,main方法是一个静态方法,在main方法中创建activityThread实例并创建主线程的消息队列,然后在activityThreadattach方法中会远程调用AMS的attachApplication方法并将ApplicationThread对象提供给AMS。ApplicationThread是一个Binder对象,主要用于ActivityThread与AMS之间通信。

在AMS的attachApplication方法中,会调用applicationThread的bindApplication方法,同样跨进程完成,bindApplication逻辑经过applicationThreadmH handler切换到activitThread中执行,具体方法是handleBinderApplication,方法中activityThread会优先加载contentProvider然后在调用application的oncreate方法。

  1. ContentProvider一般都是单实例,是由android:multiprocess决定,为false是单实例,为true多实例。
  2. ActivityThread的handleBinderApplication,则完成Application创建以及ContentProvider创建:
  • 创建ContentImplInstrumentation
  • 创建Application对象
  • 启动当前进程的ContentProvider并调用onCreate方法
  • 调用ApplicationonCreate方法

Android消息机制

handler运行底层需要MessageQueueLooper支撑

  1. MessageQueue使用来存储消息的,以队列形式插入与删除消息,内部存储结构并不是真正的队列,而是用单链表数据结构来实现的消息存储。
  2. Looper用来处理消息,以无限循坏的方法是查看是否有新的消息,有的话就进行处理,否则一直处于等待。还有一个特殊的概念ThreadLocal,作用可以在每个线程中存储数据。在handler内部可以通过ThreadLocal来获取每个线程的Looper,它可以在不同线程互不干扰存储并提供数据。 如果线程没有默认的Looper,那么使用handler就必须创建Looper。ActivityThread被创建时会初始化Looper,这就是默认可以在主线程中使用handler。
  3. 通过handler的post方法将一个runnable投递到handler内部的Looper中去处理,也可以通过send发消息。当handler的send方法被调用,它会调用MessageQueue的equeueMessage方法将消息存储到队列中,然后Looper就会处理这个消息,然后handlerMessage方法就会调用。Looper运行在创建handelr所在的线程中,这样handler的处理逻辑就会在创建handler线程中执行。

ThreadLocal使用场景

  1. 当某些数据以线程为作用域并且不同线程具有不同数据副本使用ThreadLocal,比如要获取当前线程的Looper,但不同线程有不同Looper。
  2. 复杂逻辑下的对象传递,比如监听传递

采用ThreadLocal可以让监听器作为线程内的全局对象存在,线程内部只需通过get方法获取监听器。

MessageQueue工作原理

主要包含插入与读取,分别对应equeueMessagenext方法

从源码中看出equeueMessage的实现主要是单链表的插入操作。

next方法是一个无限循坏方法,如果这个消息队列没有消息,next就会一直阻塞在这里,当有消息,就会返回这条消息,并将从单链表移除。

Looper工作原理

查看是否有消息,有就处理,没有就一直阻塞。

通过Looper.prepare()创建Looper,Looper.loop()开启消息循坏

可以在主线程中创建Looper调用prepareMainLooper,调用getMainLooper在主线程获取Looper。

调用quitquitSafely来退出Looper。区别:

  1. quit直接退出Looper
  2. quitSafely设定退出标记,只有消息处理完毕才会安全退出。手动创建Looper,那么在所有事情处理完毕后调用quit来退出Looper来终止消息循坏,否则一直处于等待状态。

loop方法是一个死循环,只有MessageQueue的next方法返回为空时,才会跳出循坏,所以不使用时必须通过quit或者quitSafely退出循环,否则会造成内存泄漏等其他问题

Handler工作原理

消息发送与接收,可以通过post与send来实现,post方法最终通过send方法实现。handler发送消息仅仅向消息队列中插入一条消息。

handler处理消息过程

  1. 检查messagecallback是否为空,不为空,就通过handleCallback处理
  2. 为空检查mCallback是否为空,不为空就调用mCallback.handleMessage处理
  3. 最后调用handler的handlerMessage处理

handler还有一个特殊的构造方法,通过特定的Looper构造Handler,如果当前线程没有Looper,就会抛异常。

主线程消息循坏

  1. android主线程是ActivityThread,入口为main方法,prepareMainLooper创建主线程Looper与messageQueue.
  2. ActivityThread通过ApplicationThread和AMS进行进程间通信,AMS以进程间通信方式完成ActivityThread请求回调ApplicationThread的binder方法,然后ApplicationThreadH发送消息,H收到消息将ApplicationThread逻辑切换到主线程执行。**

Android线程与线程池

Volatile

volatile :保证可见性,有序性,但不能保证原子性

使用volatile必须具备以下两个条件:

  1. 对变量写操作不会依赖于当前值
  2. 该变量没有包含在其它变量的不变式

第一个条件就是:自增,自减

第二个条件就是:包含一个不变式:下界总是小于或等于上界

voliate可以用于很多场景:

  • 状态标志
  • 双重检查模式

Java 阻塞队列

  1. ArrayBlockingQueue:由数组结构组成的有界阻塞队列; 按照先进先出原则对元素排列,默认情况下不保证公平访问队列,公平访问队列指的是:阻塞所有的生产者与消费者,当队列可用时,按照阻塞的先后顺序进行访问,先阻塞生产者,往里面插入元素;阻塞消费者,从队列里面获取元素;
  2. LinkedBlockingQueue:由链表构成的有界阻塞队列,先进先出原则对元素进行排序;只有当队列的缓存区达到缓存容量最大值,才会阻塞队列,直至消费者从队列消费一份数据,生产者线程才会被唤起;
  3. PriorityBlockingQueue:支持优先级排序的无界阻塞队列;默认元素升序排序;可以自定义compareTo()方法进行排序;
  4. DelayQueue:延时获取元素的无界阻塞队列;创建元素可以指定元素的时间,只有到元素到期时,才会取走元素;
  5. SynchronousQueue:不储存元素的阻塞队列;每插入操作必须等待另一个线程的移除操作;因此队列中没有任何元素;
  6. LinkedTransferQueue:由链表构成的无界阻塞队列;
  7. LinkedBlockingDeque:由链表组成的双向阻塞队列;可从队列的两端插入和移除元素;

AsyncTask

AsyncTask是一个轻量级异步任务类,在线程池中执行后台任务,将最终结果传递给主线程中,并在主线程中更新UI,它封装了Thread与handler

AsyncTask线程池配置的参数:

  1. 核心线程数等于CPU核心数+1
  2. 最大线程数为CPU核心数2倍+1
  3. 核心线程无超时时长,非核心线程超时时长为1秒

4.任务队列容量128

内部方法:

  1. onPreExecute在主线程中执行,异步任务开始前调用,用于做准备工作
  2. doInBackground在线程池中执行,调用publishProgress更新任务,publishProgress调用onProgressUpdate方法,返回计算结果给onPostExecute
  3. onProgressUpdate在主线程中执行,当后台任务发生改变时,此方法调用
  4. onPostExecute在主线程执行,result参数是后台任务返回值

AsyncTask一些条件限制:

  1. AsyncTask必须在主线程中加载,第一次访问AsyncTask必须发生在主线程,在android4.1及以上已经被系统自动完成,在android5.0源码中,查看activityThread的main方法,它会调用asyncTask的init方法,这就是它必须在主线程中加载的条件。 从源码可知sHandler是一个静态Handler对象,能够将执行环境切换到主线程中,这就要求sHandler在主线程中创建,静态成员会在加载类时进行初始化,变相要求AsyncTask必须在主线程中加载。
  2. AsyncTask对象必须在主线程中创建
  3. execute必须在UI线程调用
  4. 不要再程序中直接调用onPreExecute,doInBackground,onProgressUpdate方法
  5. 一个AsyncTask对象只能执行一次,只能调用一次execute方法,否则会报异常
  6. 在android1.6以前,它是串行执行,android1.6开始采用线程池处理并行任务,从android3.0开始,采用串行执行任务,仍然可以调用executeOnExecute方法并行执行任务

AsyncTask有两个线程池(SerialExecutor与THREAD_POOL_EXECUTOR)和一个IntentHandler,SerialExecutor用于任务排队,THREAD_POOL_EXECUTOR用于真正执行任务,IntentHandler用于将执行环境从线程池中切换到主线程。

HandlerThread

它继承自Thread,在run方法中通过Looper.prepare创建消息队列,通过Looper.loop开启消息循坏。普通Thread的run方法执行一个耗时任务,而它内部创建消息队列,外界需要通过handler的消息方式来通知它执行一个具体的任务。**

使用场景Intentservice,不需要使用handlerThread时,使用quit,quitsafely终止执行。

IntentService执行后台耗时任务,当任务执行后它会停止。适合高优先级的后台任务。

onHandlerThread方法执行结束后,IntentService采用stopSelf(int startId)来尝试停止服务,而使用stopSelf()方法则会立刻停止所有服务,而stopSelf(int startId)等待所有消息都处理完毕后才会终止,尝试停止服务之前判断最近启动的服务次数是否与startId相等,相等则停止。

线程池

线程池优点
  1. 重用线程池,避免因线程创建与销毁而带来的性能开销
  2. 有效控制线程的并发数,避免因线程互相抢占资源而导致阻塞现象
  3. 能够对线程进行简单管理,提供定时执行与间隔循坏功能
Executor

Executor为一个接口,真正实现是ThreadPoolExecutor ThreadPoolExecutor内部参数:

  1. coorPoolSize 核心线程数
  2. maximumPoolSize 最大线程数,达到这个数值,后台任务会阻塞
  3. keepAliveTime 非核心线程闲置时的超时时长
  4. unit 时间单位
  5. workQueue 任务队列,线程池的execute提交的Runnable对象会存储在这个参数中
  6. threadFactory 线程工厂
  7. 饱和策略 RejectedExecutionHandler:饱和策略,这是当任务队列与线程池都满的情况下,所采取的应对策略,默认是AbordPolicy,表示无法处理新任务,抛出RejectedExecutionExecption异常,还有三种策略:
    • CallerRunsPolicy:用调用者所在的线程来处理任务,提供简单的反馈机制,能够减缓新任务的提交速度
    • DiscardPolicy:不能执行的任务,并将任务删除
    • DiscardOldestPolicy:丢弃列表最近的任务,并执行该任务
线程池分类
  1. FIxedThreadPool

线程数量固定的线程池,当线程处于空闲状态,它们并不会回收,只有核心线程,能够快速响应外界请求,任务队列没有大小限制

  1. CacheThreadPool

只有非核心线程,线程数无限大,空闲线程超时时长为60s,任务队列为SynchronousQueue,是一个无法存储元素的队列。适合执行大量耗时较少的任务。

  1. ScheduleThreadPool

核心线程数量固定,非核心线程数没有限制,用于执行定时任务与具有固定周期的重复任务。采用DelayWorkQueue是无界的。

  1. SingleThreadExecutor

只有一个核心线程,没有非核心线程,任务都在同一个线程中顺序执行,无需考虑同步问题。

  1. Excecutors.newWorkStealingPool:JDK8引入,创建持有足够线程的线程池支持给定的并行度,并通过使用多个队列减少竞争,把CPU数量设置为默认的并行度。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年7月19日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 理解window与windowManager
    • Android 8.0之前源码
      • Window添加过程
      • Window删除过程
      • Window更新过程
    • Android 8.0源码
      • WMS重要成员
      • Window添加过程
      • Window删除过程
      • WMS职责
    • Activity的window创建过程
      • Dialog的window创建过程
        • Toast的window创建过程
        • 四大组件工作过程
          • Activity的工作过程
            • Service
              • Service的启动过程
              • Service的绑定过程
            • BroadcastReceiver的工作过程
              • 广播注册过程
              • 广播发送与接收过程
            • ContentProvider工作过程
            • Android消息机制
              • ThreadLocal使用场景
                • MessageQueue工作原理
                  • Looper工作原理
                    • Handler工作原理
                    • Android线程与线程池
                      • Volatile
                        • Java 阻塞队列
                          • AsyncTask
                            • HandlerThread
                              • 线程池
                                • 线程池优点
                                • Executor
                                • 线程池分类
                            相关产品与服务
                            对象存储
                            对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档