• 【系统之音】Activity启动流程源码分析


            Activity的启动流程是一个资深Android工程师必须掌握的内容,也是高职级面试中的高频面试知识点,无论是从事应用层开发,还是Framework开发,其重要性都无需我多言。而要真正理解它,就不可避免地要深入到源码了,本文将从Android8.1系统源码入手,来抽丝剥茧。由于Activity的启动流程涉及到的细节非常多而且复杂,为了便于读理解,本文将摒弃众多的细节,而着重于关键流程的梳理。

           尽管简化了很多细节,但流程还是不少,为了便于读者阅读和理解,笔者会先给出重要的结论和UML序列图,读者正确的阅读方式也是先知道梗概,再结合UML序列图来看源码,同时最好能自己再IDE上打开源码,顺着笔者的思路去阅读,否者会看得晕头转向。

           本文主要包含如下内容

    1、启动Activity的若干场景

           Activity的启动有多种途径,比较常见的有:

        (1)点击Launcher中的快捷图标,这种方式进入的是根Activity;

        (2)从其它应用跳转到某个应用的activity,这种场景下启动的可以是根Activity,也可以是其它Activity,如:从某些应用拨打电话、开启相机、打开浏览器等;

        (3)同一个应用种从某个组件中启动Activity。

          而启动某个Activity的时候,也可能有两种情形:

        (1)目标Activity所在应用程序进程不存在,也就是此时该应用还没有启动的情形;

        (2)目标Activity所在应用程序进程存在,也就是该应用之前启动过。

          上面这些场景,Activity的启动流程肯定是存在一定差异的,但核心流程基本一致,都是在基本流程基础上或增或减部分流程。从Launcher中点击快捷图标启动一个根Activity的场景,就比较有代表性,本文将以此情形来介绍Activity的启动流程。

    2、根Activity启动流程概貌

           这里,我先给出结论,读者们先宏观看看这其中大概有哪几步。先上图:

           从Launcher中点击快捷图标到启动根Activity过程中,主要涉及到4个进程的交互:Launcher所在应用进程、ActivityManagerService(后文简称AMS)所在的SystemServe系统进程、Zygote系统进程、目标根Activity所在的应用程序进程(这里请读者注意一下不同颜色所表示的不同进程,后文会与此保持一致)。

      (1)Launcher进程请求AMS创建根Activity。我们知道,在系统启动过程中,会启动SystemServer进程, AMS、PackageManagerService(后文简称PMS)也是在这个环节中启动的,所以AMS是运行在SystemServer进程当中的。应用的根Activity会在AndroidManifest.xml文件中注册,PMS解析出这些信息,并在Launcher中对这些包名、Activity路径及名称等信息进行封装,当点击快捷图标时,Launcher会调用startActivity方法去启动该图标所对应的根Activity。然后在Luancher进程中通过层层调用,直到通过Binder方式实现IPC,流程就进入到AMS中,也就是SystemServer进程中。

      (2)AMS请求创建根Activity所在的进程。AMS收到Launcher进程启动根Activity的请求后,会先判断根Activity所在的进程是否已经创建过了,如果没有创建过,则会向Zygote进程请求创建该进程,我们目前讨论的情形就是根Activity所在进程没有创建过的情况。我们知道,Zygote进程在启动的时候,会作为服务端创建一个名为“zygote”的Socket,用于监听AMS发起的创建新应用进程请求,所以此时流程进入到Zygote进程中。

      (3)Zygote进程fork出目标进程。Zygote收到AMS的请求后,会以fork的方式创建这个新的应用进程,此过程中会实例化一个ActivityThread对象,也就是一般所说的主线程,运行其入口main方法。

      (4)AMS调度应用进程创建和启动根Activity。根Activity所在的应用程序进程被创建后,AMS在SystemServer进程中也经过层层调用,最终又通过Binder方式实现IPC,将启动Activity的任务交给应用程序进程中的ApplicationThread本地代理,此后,流程进入到根Activity所在的应用程序进程中。这部分流程中,SystemServer中所做的工作主要是根Actifity创建和启动前的一些准备工作,比如判单当前用户权限,判断目标进程是否存在,判断activity是否已经创建,判断启动模式,判断是否注册等。

      (5)在应用进程中完成根Activity的创建和启动。在这里将创建根Activity实例、Applicaiton实例,调用各个生命周期方法,并将DecorView(布局文件中的View会添加到DecorView中)添加到Window中显示出来。 

           上文中涉及到系统启动流程相关知识,比如Zygote、SystemServer、AMS、PMS 的启动以及Zygote的功能,读者不清楚的画,最好先阅读这篇文章:【系统之音】Android系统启动篇

    3、从Launcher到AMS

           先上UML序列图 

           前面说过,点击Luancher中的快捷图标的时候,会通过startActivity启动其对应的Activity,Launcher进程中的这部分流程源码如下:

           Launcher.java的源码路径:/packages/apps/Launcher3/src/com/android/launcher3/Launcher.java

     1 //==========Launcher.java========
     2 private void startAppShortcutOrInfoActivity(View v) {
     3      ......
     4      boolean success = startActivitySafely(v, intent, item);
     5      ......
     6 }
     7 
     8 public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
     9      ......
    10      startActivity(intent, optsBundle);
    11      ......
    12 }
    13 
    14 //===========Activity.java========
    15 /**
    16  * Launch a new activity.
    17  * ......
    18  */
    19 @Override
    20 public void startActivity(Intent intent, @Nullable Bundle options) {
    21     if (options != null) {
    22         startActivityForResult(intent, -1, options);
    23     } else {
    24         ......
    25         startActivityForResult(intent, -1);
    26     }
    27 }
    28 
    29 public void startActivityForResult(...) {
    30    if (mParent == null) { //表示当前根Activity还没有创建
    31             ......
    32             Instrumentation.ActivityResult ar =
    33                 mInstrumentation.execStartActivity(
    34                     this, mMainThread.getApplicationThread(), mToken, this,
    35                     intent, requestCode, options);
    36             ......
    37         }
    38 }
    39 
    40 //===========Instrumentation.java==============
    41 public ActivityResult execStartActivity(...){
    42        ......
    43        int result = ActivityManager.getService()
    44                        .startActivity(whoThread, who.getBasePackageName(), intent,
    45                                intent.resolveTypeIfNeeded(who.getContentResolver()),
    46                                token, target != null ? target.mEmbeddedID : null,
    47                                requestCode, 0, null, options); //代码①
    48        ......
    49 }

    通过上述代码后,流程就从Launcher进程进入到AMS所在的SystemServer进程了, 这部分流程比较简单,这里就不做过多解释了。这里重点看一下代码①处(第43行)的ActivityManager.getService():

     1 //=========ActivityManager.java=========
     2 public static IActivityManager getService() {
     3     return IActivityManagerSingleton.get();
     4 }
     5 private static final Singleton<IActivityManager> IActivityManagerSingleton =
     6         new Singleton<IActivityManager>() {
     7             @Override
     8             protected IActivityManager create() {
     9                 final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
    10                 final IActivityManager am = IActivityManager.Stub.asInterface(b);
    11                 return am;
    12             }
    13         };
    14 
    15 //=================Singleton.java=========
    16 /**
    17  * Singleton helper class for lazily initialization.
    18  * ......
    19  */
    20 public abstract class Singleton<T> {
    21     private T mInstance;
    22 
    23     protected abstract T create();
    24 
    25     public final T get() {
    26         synchronized (this) {
    27             if (mInstance == null) {
    28                 mInstance = create();
    29             }
    30             return mInstance;
    31         }
    32     }
    33 }

    ActivityManager.getService()这句代码实际上就是通过单例模式获取AMS在Launcher进程中的远程代理,类似这样的代码实现在系统源码种还是比较常见的。

    4、从AMS到应用程序进程

          先上UML序列图:

            这一部分的启动流程调用非常繁琐,可谓是“峰回路转”,笔者在跟进其调用流程时差点昏厥了,大有“山重水复疑无路”的困惑,直到看到下面代码:

    1 final boolean realStartActivityLocked(...){
    2       ......
    3       app.thread.scheduleLaunchActivity(...); //代码②
    4       ......
    5 }

    此时又深感“柳暗花明又一村”了。ApplicationThread是ActivityThread中的一个内部类,也是应用程序进程中的一个服务(Stub),通过Binder方式对外提供服务:

    1 //=======ActivityThread.java======
    2 private class ApplicationThread extends IApplicationThread.Stub {
    3       ......
    4       public final void scheduleLaunchActivity(...){
    5             ......
    6       }
    7       ......
    8 }

    这里我们需要重点理解下图中的模型:

           每个应用程序都运行在一个独立的进程中(当然也可以声明为多个进程,这里不做讨论),不同进程之间内存等资源是不能直接共享的,只能通过Binder方式来和外界交互。这就好比系统像个大海,应用程序进程就像一座座孤岛,而Binder就是孤岛之间的桥梁或者船只。上图中模拟了应用程序进程与SystemServer进程的交互方式,应用程序进程持有了SystemServer进程中AMS/WMS等系统服务的远程代理Proxy,通过这个Proxy来调用SystemServer进程中的系统服务;SystemServer进程中也持有了应用程序进程中的ApplicationThread的远程代理Proxy,通过这个Proxy来调用应用程序进程中的方法。

           代码②处的app.thread就是ApplicationThread在SystemServer端的远程代理(Proxy),正式通过这个远程代理调用根Activity所在应用程序进程中的相关方法的从这里开始,流程就进入到目标应用程序进程了。

           由于该部分又繁琐,又没有涉及到直接创建和启动Activity的代码,所以这里就不贴代码了。对于这一部分的流程,个人建议读者没有必要太纠结细节,知道其大概做了些什么就够了。

    5、在应用程序进程中创建和启动根Activity

           先看看ActivityThread类主要结构

           ActivityThread一般被称作主线程(当然它不是真正的线程),它包含两个很重要的内部类,ApplicationThread(前面已经介绍过了)和H。这个H是Handler的子类,拥有主线程的looper,所以其Callback的回调函数handleMessage运行在主现在当中,所以这个H类的作用其实就是将线程切换到主线程。

           我们结合上图来看看如下源码:

     1 //=======ActivityThread.java======
     2 private class ApplicationThread extends IApplicationThread.Stub {
     3       ......
     4       public final void scheduleLaunchActivity(...){
     5             ActivityClientRecord r = new ActivityClientRecord();
     6             ......
     7             r.intent = intent;
     8             ......
     9             sendMessage(H.LAUNCH_ACTIVITY, r);
    10       }
    11       ......
    12 }
    13 
    14 private void sendMessage(int what, Object obj) {
    15     sendMessage(what, obj, 0, 0, false);
    16 }
    17 
    18 private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
    19     ......
    20     Message msg = Message.obtain();
    21     msg.what = what;
    22     msg.obj = obj;
    23     msg.arg1 = arg1;
    24     msg.arg2 = arg2;
    25     ......
    26     mH.sendMessage(msg);
    27 }
    28 
    29 final H mH = new H();
    30 private class H extends Handler {
    31     public static final int LAUNCH_ACTIVITY = 100;
    32     public void handleMessage(Message msg) {
    33         switch (msg.what) {
    34                 case LAUNCH_ACTIVITY: {
    35                       ......
    36                       handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");//代码③
    37                       ......
    38                 }
    39     }
    40      ......
    41 }

    这样就明确了,此时的流程,又由ApplicationThread经过Handler进入到了主线程(ActivityThread)中了。

    从代码③处开始,流程就由主线程ActivityThread来处理了,还是先上UML序列图:

     参照该图和如下源码,来看看应用程序进程是如何创建和启动Activity的:

     1 //=======ActivityThread.java=======
     2 private void handleLaunchActivity(...){
     3      ......
     4      WindowManagerGlobal.initialize();//代码④
     5      ......
     6      Activity a = performLaunchActivity(r, customIntent);//代码⑤
     7      if (a != null) {
     8          handleResumeActivity(...);//代码⑫
     9      }
    10      ......
    11 }

    代码④,其作用是通过单例模式获取一个WMS在应用程序进程中的远程代理Proxy,我们知道,后面Activity中setContentView加载的layout文件,就需要通过WMS添加到Window中来显示。该方法代码比较简单:

     1 //========WindowManagerGlobal.java======
     2 ......
     3 private static IWindowManager sWindowManagerService;
     4 ......
     5 public static void initialize() {
     6     getWindowManagerService();
     7 }
     8 ......
     9 public static IWindowManager getWindowManagerService() {
    10     synchronized (WindowManagerGlobal.class) {
    11         if (sWindowManagerService == null) {
    12             sWindowManagerService = IWindowManager.Stub.asInterface(
    13                     ServiceManager.getService("window"));
    14             ......
    15         }
    16         return sWindowManagerService;
    17     }
    18 }

    进入到代码⑤处:

     1 //===========ActivityThread.java=======
     2 private Activity performLaunchActivity(...){
     3         ......
     4         Activity activity = null;
     5         try {
     6             java.lang.ClassLoader cl = appContext.getClassLoader();
     7             activity = mInstrumentation.newActivity(
     8                     cl, component.getClassName(), r.intent);//代码⑥
     9             ......
    10         } catch (Exception e) {
    11             ......
    12         }
    13         try {
    14             Application app = r.packageInfo.makeApplication(false, mInstrumentation);//代码⑦
    15               ......
    16               activity.attach(...);
    17               ......
    18              if (r.isPersistable()) {
    19                     mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); //代码⑩-1
    20                 } else {
    21                     mInstrumentation.callActivityOnCreate(activity, r.state);//代码⑩-2
    22                 }
    23               ......
    24                 if (!r.activity.mFinished) {
    25                     activity.performStart();//代码⑪
    26                     ......
    27                 }
    28               ......
    29              } catch (Exception e) {
    30             ......
    31         }
    32         ......
    33 }

    代码⑥处,以ClassLoader的方式创建Activity实例:

     1 //================Instrumentation.java=====
     2 /**
     3  * Perform instantiation of the process's {@link Activity} object.
     4  * ......
     5  * @return The newly instantiated Activity object.
     6  */
     7 public Activity newActivity(ClassLoader cl, String className,
     8         Intent intent)
     9         throws InstantiationException, IllegalAccessException,
    10         ClassNotFoundException {
    11     return (Activity)cl.loadClass(className).newInstance();
    12 }

    从代码⑦处深入,该代码的作用在于处理Application相关的业务:

     1 //=========LoadedApk.java=========
     2 public Application makeApplication(...){
     3     if (mApplication != null) {
     4             return mApplication;
     5         }
     6      ......
     7      Application app = null;
     8      ......
     9         try {
    10             java.lang.ClassLoader cl = getClassLoader();
    11             ......
    12             app = mActivityThread.mInstrumentation.newApplication(
    13                     cl, appClass, appContext); //代码⑧
    14             ......
    15         } catch (Exception e) {
    16            ......
    17         }
    18         mActivityThread.mAllApplications.add(app);
    19         mApplication = app;
    20         if (instrumentation != null) {
    21             try {
    22                 instrumentation.callApplicationOnCreate(app);//代码⑨
    23             } catch (Exception e) {
    24                 ......
    25             }
    26         }
    27         ......
    28 }

    代码⑧处,也是以ClassLoader方式创建Application实例:

     1 //================Instrumentaion.java=========
     2 /**
     3  * Perform instantiation of the process's {@link Application} object.
     4  * ......
     5  * @return The newly instantiated Application object.
     6  */
     7 public Application newApplication(ClassLoader cl, String className, Context context)
     8         throws InstantiationException, IllegalAccessException,
     9         ClassNotFoundException {
    10     return newApplication(cl.loadClass(className), context);
    11 }
    12 
    13 /**
    14  * Perform instantiation of the process's {@link Application} object.
    15  * ......
    16  * @return The newly instantiated Application object.
    17  */
    18 static public Application newApplication(Class<?> clazz, Context context)
    19         throws InstantiationException, IllegalAccessException,
    20         ClassNotFoundException {
    21     Application app = (Application)clazz.newInstance();
    22     app.attach(context);
    23     return app;
    24 }

    代码⑨处,调用Application的onCreate方法,在自定义的Application中,重写的onCreate方法开始执行:

     1 //=============LoadedApk.java========
     2 /**
     3  * Perform calling of the application's {@link Application#onCreate} method.
     4  * ......
     5  * @param app The application being created.
     6  */
     7 public void callApplicationOnCreate(Application app) {
     8     app.onCreate();
     9 }
    10 
    11 //=========Application.java========
    12 @CallSuper
    13 public void onCreate() {
    14 }

    代码⑩(代码⑩-1或代码⑩-2)中,执行Activity的onCreate方法,根Activity中的onCreate方法执行:

     1 //==============Instrumentation.java========
     2 /**
     3  * Perform calling of an activity's {@link Activity#onCreate} method.
     4  * ......
     5  */
     6 public void callActivityOnCreate(Activity activity, Bundle icicle) {
     7     prePerformCreate(activity);
     8     activity.performCreate(icicle);
     9     postPerformCreate(activity);
    10 }
    11 /**
    12  * Perform calling of an activity's {@link Activity#onCreate} method.
    13  * ......
    14  */
    15 public void callActivityOnCreate(Activity activity, Bundle icicle,
    16         PersistableBundle persistentState) {
    17     prePerformCreate(activity);
    18     activity.performCreate(icicle, persistentState);
    19     postPerformCreate(activity);
    20 }
    21 
    22 //==========Activity.java========
    23 final void performCreate(Bundle icicle) {
    24     performCreate(icicle, null);
    25 }
    26 final void performCreate(Bundle icicle, PersistableBundle p
    27     ......
    28     if (persistentState != null) {
    29         onCreate(icicle, persistentState);
    30     } else {
    31         onCreate(icicle);
    32     }
    33     ......
    34 }
    35 
    36 public void onCreate(@Nullable Bundle savedInstanceState,
    37         @Nullable PersistableBundle persistentState) {
    38     onCreate(savedInstanceState);
    39 }
    40 
    41 @MainThread
    42 @CallSuper
    43 protected void onCreate(@Nullable Bundle savedInstanceState) {
    44     ......
    45 }
    代码⑪开始执行Activity的onStart方法,根Activity的onStart方法开始执行:
     1 //======Activity.java======
     2 final void performStart() {
     3     ......
     4     mInstrumentation.callActivityOnStart(this);
     5     ......
     6 }
     7 
     8 //=========Instrumentation.java=========
     9 /**
    10  * Perform calling of an activity's {@link Activity#onStart} method.
    11  * ......
    12  */
    13 public void callActivityOnStart(Activity activity) {
    14     activity.onStart();
    15 }
    16 
    17 //======Activity.java======
    18 @CallSuper
    19 protected void onStart() {
    20     ......
    21 }
    代码⑫的handleResumeActivity方法用于处理resume相关的业务:
     1 //========ActivityThread.java======
     2 final void handleResumeActivity(...){
     3       ......
     4       r = performResumeActivity(token, clearHide, reason);//代码⑬
     5       ......
     6       //如下过程将DecorView添加到窗口中 代码段⑯
     7       r.window = r.activity.getWindow(); //PhoneWindow实例
     8       View decor = r.window.getDecorView(); //DecorView实例
     9       decor.setVisibility(View.INVISIBLE);
    10       ViewManager wm = a.getWindowManager();
    11       WindowManager.LayoutParams l = r.window.getAttributes();
    12       a.mDecor = decor;
    13       ......
    14       ViewRootImpl impl = decor.getViewRootImpl();
    15       ......
    16       wm.addView(decor, l);
    17       ......
    18 }

    深入代码⑬中:

     1 //======ActivityThread=======
     2 public final ActivityClientRecord performResumeActivity(...){
     3       ......
     4       r.activity.performResume();
     5       ......
     6 }
     7 
     8 //======Activity.java=====
     9 final void performResume() {
    10      performRestart();//代码⑭
    11      ......
    12      mInstrumentation.callActivityOnResume(this);//代码⑮
    13      ......
    14 }

    代码⑭用于处理reStart相关的业务,当前场景是新创建根Activity,所以不会走这个流程;如果是从其它界面回退到这个activity,就会走调用onRestart和onStart的流程:

     1 //===========Activity.java========
     2 final void performRestart() {
     3      ......
     4      if (mStopped) {
     5           mStopped = false;
     6           ......
     7           mInstrumentation.callActivityOnRestart(this);
     8           ......
     9           performStart();
    10      }
    11 }
    12 
    13 //============Instrumentation.java===========
    14 /**
    15  * Perform calling of an activity's {@link Activity#onRestart} method.
    16  * ......
    17  * @param activity The activity being restarted.
    18  */
    19 public void callActivityOnRestart(Activity activity) {
    20     activity.onRestart();
    21 }
    22 
    23 //===========Activity.java===========
    24 @CallSuper
    25 protected void onRestart() {
    26     mCalled = true;
    27 }
    28 
    29 final void performStart() {
    30      ......
    31      mInstrumentation.callActivityOnStart(this);
    32      ......
    33 }
    34 
    35 //============Instrumentation.java==========
    36 /**
    37  * Perform calling of an activity's {@link Activity#onStart} method.
    38  *  ......
    39  * @param activity The activity being started.
    40  */
    41 public void callActivityOnStart(Activity activity) {
    42     activity.onStart();
    43 }
    44 //==============Activity.java============
    45 @CallSuper
    46 protected void onStart() {
    47    ......
    48 }

    代码⑮处调用acitvity的onResume方法,这样一来根Activity的onResume回调方法就执行了:

     1 //========Instrumentation.java=========
     2 /**
     3  * Perform calling of an activity's {@link Activity#onResume} method.
     4  *  ......
     5  * @param activity The activity being resumed.
     6  */
     7 public void callActivityOnResume(Activity activity) {
     8     ......
     9     activity.onResume();
    10     ......
    11 }
    12 
    13 @CallSuper
    14 protected void onResume() {
    15     ......
    16 }

    代码段⑯(对应第7~16行)的作用在于将DecorView添加到Window,并完成界面的绘制流程。我们知道,根Activity在onCreate生命周期回调方法中会通过setContentView方法加载layout布局文件,将其加入到DecorView中,绘制部分详情可以阅读【【朝花夕拾】Android自定义View篇之(一)View绘制流程】。

           这样,应用程序进程就完成了根Activity的创建和启动,界面也完成了显示。从上面的UML图和源码分析,可以发现这部分笔者是按照Activity的生命周期为主线来介绍的,实际上读者完全可以结合Activity的生命周期来理解和记忆这部分的主要流程。下图再次总结了这部分的主要流程:

     

           想必读者应该对第4~7步的生命周期顺序非常熟悉了。需要注意的是,第2和第3步可能会和我们平时的认知有些出入,实际上Activity的实例比Application的实例要更早创建。第1和第8步是关于图形界面显示的,也需要重点关心。

    6、其它Activity启动场景流程

           到目前为止,从Laucher中点击一个快捷图标来启动根Activity的整个流程就介绍完毕了,这个场景搞清楚了,其它场景就不在话下了。比如,从Launcher中启动一个应用程序进程已经启动的应用的根Activity,就在上述流程基础上少了Zytote创建应用程序进程这一步,如下图:

     比如,应用程序内部启动另外一个新Activity时,就只需要考虑应用程序进程和SystemSever两个进程,如下图:

    还有从其它应用中启动指定某另外应用中的Activity的场景和从Launcher启动的流程类似;再次打开一个已经启动的Activity,就无需走create流程,而是走onRestart-onStart-onResume生命周期流程,等等,这里就不一一列举了。 

           本文已经尽可能地包含了完整的流程,但同时力求简洁易懂,摒弃大量具体细节,只抽取关键流程。尽管如此,但是这部分内容确实比较复杂,还是花了这么长的篇幅,感谢读者的耐心阅读。由于笔者水平有限,有些描述不准确或者不妥之处,请读者朋友不吝赐教,感谢!

    参考资料

         【朝花夕拾】Android自定义View篇之(一)View绘制流程

         【系统之音】Android系统启动篇

         Activity启动流程详解

         刘望舒《Android进阶解密》

  • 相关阅读:
    pycharm使用常见设置
    LeetCode OJ:Insertion Sort List (插入排序链表)
    LeetCode OJ:Reverse Linked List (反转链表)
    Foundations of Qt Development 学习笔记 Part1 Tips1-50
    TCPL学习毕节:第六章hash表
    TCPL学习笔记:4-12以及4-13。关于使用递归的问题。
    几种常见排序算法的C++描述
    一些灵巧的求并算法
    vs中: 错误,未定义的标识符getline 的解决方法
    QT中给程序加上主界面的图标
  • 原文地址:https://www.cnblogs.com/andy-songwei/p/13508185.html
Copyright © 2020-2023  润新知