我是靠谱客的博主 害羞萝莉,这篇文章主要介绍PackageManagerService源码分析之第一阶段(二),现在分享给大家,希望可以做个参考。

##1、背景##

在前面一篇文章中,我们介绍了和PackageManagerService相关的一些类和关系,如果大家不是很明白的可以先去看一下这篇文章PackageManagerService源码分析之入门(一)。
由于PackageManagerService(后面简称为PKMS)是系统的核心服务,所以我准备多写几篇文章来分析。

在之前的博客文章中我们讲解了PKMS是通过SystemServer被创建出来的,具体可以参考SystemServer启动流程之SystemServer分析(三)这篇文章,其相关代码如下:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void startBootstrapServices() { // Start the package manager. Slog.i(TAG, "Package Manager"); //调用PackageManagerService的main方法 mPackageManagerService = PackageManagerService.main(mSystemContext, installer, mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore); //判断系统是否第一次启动 mFirstBoot = mPackageManagerService.isFirstBoot(); mPackageManager = mSystemContext.getPackageManager(); //dex优化 mPackageManagerService.performBootDexOpt(); //通知系统进入就绪状态 mPackageManagerService.systemReady(); }

这里可以看到在SystemServer中创建了PKMS服务,并调用其main方法,我们进入其main方法。

复制代码
1
2
3
4
5
6
7
8
9
10
public static final PackageManagerService main(Context context, Installer installer,boolean factoryTest, boolean onlyCore) { //调用PKMS的main方法 PackageManagerService m = new PackageManagerService(context, installer, factoryTest, onlyCore); //向ServiceManager注册PKMS ServiceManager.addService("package", m); return m; }

这里我们需要注意的是向ServiceManager中注册PKMS,其key值是package,通过ContextImpl.getPackageManager()来获得PackageManager对象时会使用到。

可以看出其main方法还是比较简单的只有几行代码,但是其执行时间却比较长,因为其执行的都是重体力活,这就是Android启动慢的原因之一。

我们先来大致说明一下PKMS的构造函数的主要功能:扫描系统中几个特定文件夹下的apk,从而建立合适的数据结构来管理Package信息,四大组件信息,权限信息等(PKMS主要解析apk文件的AndroidManifest.xml文件)。由于其工作是如此的繁重,所以我准备分3个阶段来分析。

  • 扫描文件夹前的准备工作
  • 扫描目标文件夹
  • 扫描之后的处理工作

好啦,接下来将正式的进入我们的第一阶段分析。

##2、扫描文件夹前的准备工作##

我们进入PKMS的构造函数,由于其构造函数相关的长,所以我们将分段解析。

(1)初识Settings

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
final int mSdkVersion = Build.VERSION.SDK_INT; public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START, SystemClock.uptimeMillis()); //mSdkVersion始PKMS的成员变量,其值取自系统属性ro.build.version.sdk if (mSdkVersion <= 0) { Slog.w(TAG, "**** ro.build.version.sdk not set!"); } mContext = context; mFactoryTest = factoryTest; mOnlyCore = onlyCore; //如果是eng版,则扫描Package后,不对package进行dex优化 mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type")); //显示屏相关属性(屏幕尺寸和分辨率) mMetrics = new DisplayMetrics(); //重点分析对象 mSettings = new Settings(context); mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID, ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.log", LOG_UID, ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID, ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID, ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID, ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); }

我们先来看一下Settings这个类的构造函数。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Settings(Context context) { this(context, Environment.getDataDirectory()); } Settings(Context context, File dataDir) { //创建data/system目录 mSystemDir = new File(dataDir, "system"); mSystemDir.mkdirs(); FileUtils.setPermissions(mSystemDir.toString(), FileUtils.S_IRWXU|FileUtils.S_IRWXG |FileUtils.S_IROTH|FileUtils.S_IXOTH, -1, -1); //新建几个文件 mSettingsFilename = new File(mSystemDir, "packages.xml"); mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml"); mPackageListFilename = new File(mSystemDir, "packages.list"); FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID); // Deprecated: Needed for migration mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml"); mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); }

Settings类在data目录下新建了system目录,并在该目录下新建几个重要的文件,我们首先来看一下packages.xml文件。

1.packages.xml:记录系统中所有已安装应用的相关信息

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<package name="com.android.calendar" codePath="/system/app/MtkCalendar" nativeLibraryPath="/system/app/MtkCalendar/lib" publicFlags="940162629" privateFlags="0" pkgFlagsEx="0" ft="157a9940280" it="157a9940280" ut="157a9940280" version="23" userId="10059"> <sigs count="1"> <cert index="1" /> </sigs> <perms> <item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" /> <item name="android.permission.USE_CREDENTIALS" granted="true" flags="0" /> <item name="android.permission.MANAGE_ACCOUNTS" granted="true" flags="0" /> <item name="android.permission.NFC" granted="true" flags="0" /> <item name="android.permission.WRITE_SYNC_SETTINGS" granted="true" flags="0" /> <item name="android.permission.RECEIVE_BOOT_COMPLETED" granted="true" flags="0" /> <item name="android.permission.INTERNET" granted="true" flags="0" /> <item name="android.permission.INTERACT_ACROSS_USERS_FULL" granted="true" flags="0" /> <item name="android.permission.INTERACT_ACROSS_USERS" granted="true" flags="0" /> <item name="android.permission.READ_SYNC_SETTINGS" granted="true" flags="0" /> <item name="android.permission.VIBRATE" granted="true" flags="0" /> <item name="android.permission.WAKE_LOCK" granted="true" flags="0" /> </perms> <proper-signing-keyset identifier="1" /> </package>

2.packages-backup.xml:上面文件的备份
3.packages-stopped.xml:被强制停止运行的应用信息
4.packages-stopped-backup.xml:上面文件的备份
5.packages.list:保存普通应用的数据目录和uid信息等

复制代码
1
2
3
4
5
6
7
8
9
10
...... com.android.calculator2 10035 0 /data/data/com.android.calculator2 default none com.slightech.slife.zte 10081 0 /data/data/com.slightech.slife.zte default 3003 com.mediatek.lbs.em2.ui 10056 0 /data/data/com.mediatek.lbs.em2.ui platform 3003 com.android.wallpaper 10054 0 /data/data/com.android.wallpaper default none com.android.vpndialogs 10026 0 /data/data/com.android.vpndialogs platform none com.android.email 10043 0 /data/data/com.android.email platform 1023,1015,3003 com.android.music 10062 0 /data/data/com.android.music platform 1023,1015,3003 ......

当Android对文件packages.xml和packages-stopped.xml写之前会先将它们备份,如果文件写入成功了就把备份文件删除掉,如果写的时候,系统出问题重启了,重启后会读取这两份文件时,发现有备份文件,会使用备份文件的内容,因为此时原文件已经损坏了。

Settings类的构造器已经分析完毕了,我们回到PackageManagerService的构造器中,此时Settings对象已经创建好了,接下来将调用其addSharedUserLPw()方法,我们以第一个为例。

复制代码
1
2
3
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED);

第一个参数是字符串android.uid.system,第二个参数是SYSTEM_UID值为1000,表示系统进程的用户id,第三个参数FLAG_SYSTEM表示是系统Package、而FLAG_PRIVILEGED表示其具有高权限。

我们进入addSharedUserLPw方法。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
final ArrayMap<String, SharedUserSetting> mSharedUsers = new ArrayMap<String, SharedUserSetting>(); SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags) { //mSharedUsers是一个ArrayMap,其key为字符串,值为SharedUserSetting对象 SharedUserSetting s = mSharedUsers.get(name); if (s != null) { if (s.userId == uid) { return s; } return null; } //创建SharedUserSetting对象,并设置其userId s = new SharedUserSetting(name, pkgFlags); s.userId = uid; //这里调用addUserIdLPw()方法 if (addUserIdLPw(uid, s, name)) { //将name设置为key值存储s mSharedUsers.put(name, s); return s; } return null; }

从以上代码分析可知,Settings有一个成员变量mSharedUsers,该ArrayMap以字符串为key,SharedUserSetting为值的键值对,接下来我们来看一下和SharedUserSetting相关类的关系图。

SharedUserSetting相关类类图

可以看出:

  1. 可以看出Settings类定义了一个mSharedUsers变量(ArrayMap类型),以字符串为key(如android.uid.system),对应的Value是SharedUserSetting对象。
  2. SharedUserSetting继承于GrantedPermissions类,从命名可知它和权限相关,SharedUserSetting类中定义了一个成员变量packages(类型是ArraySet),用于保存声明了相同sharedUserId的Package权限设置信息。
  3. 每个Package有自己的权限设置,权限由PackageSetting类表达,该类继承于PackageSettingBase,而PackageSettingBase类又继承于GrantedPermissions类。
  4. Settings类中还有两个成员变量mUserIds(类型是ArrayList)和mOtherUserIds(类型是SparseArray),其目的是通过UID找到对应的SharedUserSetting对象。

下面来分析一下addUserIdLPw()方法。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private boolean addUserIdLPw(int uid, Object obj, Object name) { //应用APK所在进程的uid>10000,而系统APK所在进程的uid<10000 //uid不能超过限制,LAST_APPLICATION_UID的值是19999 if (uid > Process.LAST_APPLICATION_UID) { return false; } //表示应用APK的uid if (uid >= Process.FIRST_APPLICATION_UID) { int N = mUserIds.size(); final int index = uid - Process.FIRST_APPLICATION_UID; while (index >= N) { mUserIds.add(null); N++; } if (mUserIds.get(index) != null) { return false; } //mUserIds保存应用Package mUserIds.set(index, obj); } else { if (mOtherUserIds.get(uid) != null) { return false; } //mOtherUserIds保存系统Package mOtherUserIds.put(uid, obj); } return true; }

好啦,到此Settings的分析就结束啦,我们接着PKMS的构造器往下分析。

(2)扫描XML文件

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//将传入的installer对象赋值给本类的mInstaller对象,以便后续安装应用时使用 mInstaller = installer; //获取当前的显示屏信息 getDefaultDisplayMetrics(context, mMetrics); //创建SystemConfig对象,并调用其相关方法 SystemConfig systemConfig = SystemConfig.getInstance(); mGlobalGids = systemConfig.getGlobalGids(); mSystemPermissions = systemConfig.getSystemPermissions(); mAvailableFeatures = systemConfig.getAvailableFeatures(); private static void getDefaultDisplayMetrics(Context context, DisplayMetrics metrics) { DisplayManager displayManager = (DisplayManager) context.getSystemService( Context.DISPLAY_SERVICE); displayManager.getDisplay(Display.DEFAULT_DISPLAY).getMetrics(metrics); }

这里我们主要来分析SystemConfig类,我们进入该类的getInstance()方法。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static SystemConfig getInstance() { synchronized (SystemConfig.class) { if (sInstance == null) { sInstance = new SystemConfig(); } return sInstance; } } SystemConfig() { // Read configuration from system readPermissions(Environment.buildPath( Environment.getRootDirectory(), "etc", "sysconfig"), false); // Read configuration from the old permissions dir readPermissions(Environment.buildPath( Environment.getRootDirectory(), "etc", "permissions"), false); // Only read features from OEM config readPermissions(Environment.buildPath( Environment.getOemDirectory(), "etc", "sysconfig"), true); readPermissions(Environment.buildPath( Environment.getOemDirectory(), "etc", "permissions"), true); } public static File buildPath(File base, String... segments) { File cur = base; for (String segment : segments) { if (cur == null) { cur = new File(segment); } else { cur = new File(cur, segment); } } return cur; }

可以看出这是一个单例类,然后一层一层的建立文件夹,依次为system/etc/sysconfig,system/etc/permissions,oem/etc/sysconfig,oem/etc/permissions四个文件夹(可能有的文件夹手机中不存在)。

当四个文件夹创建完成后,接下来就是调用readPermissions方法对其循环遍历。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void readPermissions(File libraryDir, boolean onlyFeatures) { // Read permissions from given directory. if (!libraryDir.exists() || !libraryDir.isDirectory()) { return; } if (!libraryDir.canRead()) { return; } // Iterate over the files in the directory and scan .xml files File platformFile = null; //获取libraryDir下所有文件路径,然后通过for循环遍历操作该目录下所有文件 for (File f : libraryDir.listFiles()) { // We'll read platform.xml last //当遇到该目录下的platform.xml文件时,将其保存到变量platformFile中后续处理 if (f.getPath().endsWith("etc/permissions/platform.xml")) { platformFile = f; continue; } if (!f.getPath().endsWith(".xml")) { continue; } if (!f.canRead()) { continue; } //调用readPermissionsFromXml方法对结尾为xml文件进行处理 readPermissionsFromXml(f, onlyFeatures); } // Read platform permissions last so it will take precedence //最后也是调用readPermissionsFromXml方法单独处理platform.xml文件 if (platformFile != null) { readPermissionsFromXml(platformFile, onlyFeatures); } }

可以看出该方法主要就是将上面四个目录下的xml文件进行解析,同时保存platform.xml文件至最后单独处理,我们再次进入readPermissionsFromXml方法。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//该方法源码去除了大量的条件判断语句,方便代码阅读 private void readPermissionsFromXml(File permFile, boolean onlyFeatures) { //...... permReader = new FileReader(permFile); //...... while (true) { //...... if ("group".equals(name) && !onlyFeatures) { String gidStr = parser.getAttributeValue(null, "gid"); if (gidStr != null) { int gid = android.os.Process.getGidForName(gidStr); mGlobalGids = appendInt(mGlobalGids, gid); } continue; } else if ("permission".equals(name) && !onlyFeatures) { String perm = parser.getAttributeValue(null, "name"); //这里再次调用readPermission方法来进行处理保存 readPermission(parser, perm); } else if ("assign-permission".equals(name) && !onlyFeatures) { int uid = Process.getUidForName(uidStr); ArraySet<String> perms = mSystemPermissions.get(uid); if (perms == null) { perms = new ArraySet<String>(); mSystemPermissions.put(uid, perms); } } else if ("library".equals(name) && !onlyFeatures) { String lname = parser.getAttributeValue(null, "name"); String lfile = parser.getAttributeValue(null, "file"); mSharedLibraries.put(lname, lfile); continue; } else if ("feature".equals(name)) { String fname = parser.getAttributeValue(null, "name"); fi.name = fname; mAvailableFeatures.put(fname, fi); continue; } else if ("unavailable-feature".equals(name)) { String fname = parser.getAttributeValue(null, "name"); mUnavailableFeatures.add(fname); continue; } else if ("allow-in-power-save".equals(name)&& !onlyFeatures) { String pkgname = parser.getAttributeValue(null, "package"); mAllowInPowerSave.add(pkgname); continue; } else if ("fixed-ime-app".equals(name) && !onlyFeatures) { String pkgname = parser.getAttributeValue(null, "package"); mFixedImeApps.add(pkgname); continue; } }

从以上源码可知,该方法主要是对xml文件进行标签解读,当解读到不同的标签时对其值进行保存处理,我们来看一下其保存不同值的成员变量。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Group-ids that are given to all packages as read from etc/permissions/*.xml. int[] mGlobalGids; // These are the built-in uid -> permission mappings that were read from the // system configuration files. final SparseArray<ArraySet<String>> mSystemPermissions = new SparseArray<>(); // These are the built-in shared libraries that were read from the // system configuration files. Keys are the library names; strings are the // paths to the libraries. final ArrayMap<String, String> mSharedLibraries = new ArrayMap<>(); // These are the features this devices supports that were read from the // system configuration files. final ArrayMap<String, FeatureInfo> mAvailableFeatures = new ArrayMap<>(); // These are the features which this device doesn't support; the OEM // partition uses these to opt-out of features from the system image. final ArraySet<String> mUnavailableFeatures = new ArraySet<>(); // These are the permission -> gid mappings that were read from the // system configuration files. final ArrayMap<String, PermissionEntry> mPermissions = new ArrayMap<>(); // These are the packages that are white-listed to be able to run in the // background while in power save mode, as read from the configuration files. final ArraySet<String> mAllowInPowerSave = new ArraySet<>(); // These are the app package names that should not allow IME switching. final ArraySet<String> mFixedImeApps = new ArraySet<>();

以上就是SystemConfig中的主要成员变量,通过解析不同路径下的xml文件,然后保存到各个变量中后面可以直接拿来使用,这里我们在来简单的看一下platform.xml文件。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="utf-8"?> <permissions> <!--......--> <permission name="android.permission.BLUETOOTH_ADMIN" > <group gid="net_bt_admin" /> </permission> <permission name="android.permission.BLUETOOTH" > <group gid="net_bt" /> </permission> <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="media" /> <assign-permission name="android.permission.WAKE_LOCK" uid="media" /> <library name="android.test.runner" file="/system/framework/android.test.runner.jar" /> <library name="javax.obex" file="/system/framework/javax.obex.jar"/> <allow-in-power-save package="com.android.providers.downloads" /> <!--......--> </permissions>

我们来看一下SystemConfig的数据结构关系图。

SystemConfig数据结构图

接下来我们继续往下分析。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//(1)创建一个ServiceThread对象,其实就是一个带消息循环处理的线程,用于程序的安装与卸载 mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/); mHandlerThread.start(); mHandler = new PackageHandler(mHandlerThread.getLooper()); //(2)将该Handler添加到Watchdog中进行监听 Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT); //(3)创建多个文件目录,用于后期apk扫描 //data目录 File dataDir = Environment.getDataDirectory(); mAppDataDir = new File(dataDir, "data"); mAppInstallDir = new File(dataDir, "app"); mAppLib32InstallDir = new File(dataDir, "app-lib"); mAsecInternalPath = new File(dataDir, "app-asec").getPath(); mUserAppDataDir = new File(dataDir, "user"); mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); mPreloadInstallDir = new File(Environment.getRootDirectory(), "preloadapp"); mDeleteRecord = new File(mAppInstallDir, ".delrecord"); sUserManager = new UserManagerService(context, this, mInstallLock, mPackages); //(4)调用Settings.readLPw方法 mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false), mSdkVersion, mOnlyCore);

这段代码所进行的操作还是蛮多的,我们一步一步分析。

(1)首先创建了一个ServiceThread对象,以便用于程序的按照和卸载,这个我们后面会单独分析,再次就不详细阐述了。

(2)将处理应用程序安装和卸载的Handler添加到Watchdog监听器中,以监听其是否发生死锁等现象,如果大家对Watchdog不是很熟悉的,可以先看一下我的另外一篇博客SystemServer启动流程之WatchDog分析(四)。

(3)此处会创建多个data/目录下的文件,主要用于后面的apk扫描,这个我们下篇博客将详细阐述。

(4)此处会调用Settings类的readLPw方法,我们进入该方法。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
boolean readLPw(PackageManagerService service, List<UserInfo> users, int sdkVersion,boolean onlyCore) { FileInputStream str = null; //首先判断packages-backup.xml文件是否存在,如果存在就删除packages.xml文件 if (mBackupSettingsFilename.exists()) { try { str = new FileInputStream(mBackupSettingsFilename); if (mSettingsFilename.exists()) { // If both the backup and settings file exist, we // ignore the settings since it might have been // corrupted. mSettingsFilename.delete(); } } catch (java.io.IOException e) { } } if (str == null) { //如果不存在packages-backup.xml文件就加载packages.xml文件 if (!mSettingsFilename.exists()) { mInternalSdkPlatform = mExternalSdkPlatform = sdkVersion; mFingerprint = Build.FINGERPRINT; return false; } str = new FileInputStream(mSettingsFilename); } XmlPullParser parser = Xml.newPullParser(); parser.setInput(str, null); //接下来主要就对packages.xml文件进行xml解析 //...... }

这个方法主要是对packages.xml文件进行解析,在文章的开头Settings构造器中创建的这些文件。

好啦,到这里PKMS构造器的第一阶段就分析完毕啦,其主要工作就是扫描并解析XML文件,下一篇文章我们将分析其构造器的第二、三阶段PackageManagerService源码分析之第二、三阶段(三)。

最后

以上就是害羞萝莉最近收集整理的关于PackageManagerService源码分析之第一阶段(二)的全部内容,更多相关PackageManagerService源码分析之第一阶段(二)内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(60)

评论列表共有 0 条评论

立即
投稿
返回
顶部