版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、Android Wifi的工作流程一、WIFI工作相关部分Wifi 网卡状态1. WIFI_STATE_DISABLED: WIFI网卡不可用2. WIFI_STATE_DISABLING: WIFI正在关闭3. WIFI_STATE_ENABLED:WIFI网卡可用4. WIFI_STATE_ENABLING:WIFI网卡正在打开5. WIFI_STATE
2、_UNKNOWN:未知网卡状态 WIFI 访问网络需要的权限"android.permission.CHANGE_NETWORK_STATE">修改网络状态的权限 android:name="android.permission.CHANGE_WIFI_STATE">修改WIFI状态的权限"android.permission.ACCESS_NETWORK_STATE">访问网络权限"android.permission.ACCESS_WIFI_STATE">访问WIFI
3、权限 WIFI 核心模块n WifiService由SystemServer启动的时候生成的ConnecttivityService创建,负责启动关闭wpa_supplicant,启动和关闭WifiMonitor线程,把命令下发给wpa_supplicant以及跟新WIFI的状态n WifiMonitor 负责从wpa_supplicant接收事件通知n Wpa_supplicant1、读取配置文件2、初始化配置参数,驱动函数3、让驱动scan当前所有的bssid4、检查扫描的参数是否和用户设置的
4、想否5、如果相符,通知驱动进行权限 认证操作6、连上APn Wifi驱动模块厂商提供的source,主要进行load firmware和kernel的wireless进行通信n Wifi电源管理模块主要控制硬件的GPIO和上下电,让CPU和Wifi模组之间通过sdio接口通信 二、Wifi工作步骤1 Wifi模块初期化2 Wifi启动3 查找热点(AP)4 配置AP5 配置AP参数6 Wifi连接7
5、160;IP地址配置三、WIFI的架构和流程1、WIFI的基本架构 1、wifi用户空间的程序和库: external/wpa_supplicant/ 生成库libwpaclient.so和守护进程wpa_supplicant 2、hardware/libhardware_legary/wifi/是wifi管理库
6、60;3、JNI部分: frameworks/base/core/jni/android_net_wifi_Wifi.cpp 4、JAVA部分: frameworks/base/services/java/com/android/server/ f
7、rameworks/base/wifi/java/android/net/wifi/ 5、WIFI Settings应用程序位于: packages/apps/Settings/src/com/android/settings/wifi/ 6、WIFI 驱动模块 wlan.ko wpa_supplicant通过wireless_ext 接口和
8、驱动通信 7、WIFI 硬件模块 2、WIFI在Android中如何工作 Android使用一个修改版wpa_supplicant作为daemon来控制WIFI,代码位于external/wpa_supplicant。wpa_supplicant是通过socket与hardware/libhardware_legacy/wifi/wifi.c通信。UI通过.wifi package(frameworks/base/wifi/java/android/net/wifi/)
9、发送命令给wifi.c。相应的JNI实现位于frameworks/base/core/jni/android_net_wifi_Wifi.cpp。更高一级的网络管理位于frameworks/base/core/java/android/net。3、配置Android支持WIFI 在BoardConfig.mk中添加: BOARD_HAVE_WIFI := true BOARD_WPA_SUPPLICANT_DRIVER :
10、= WEXT 这将在external/wpa_supplicant/Android.mk设置WPA_BUILD_SUPPLICANT为true,默认使用驱动driver_wext.c。 如果使用定制的wpa_supplicant驱动(例如 wlan0),可以设置: BOARD_WPA_SUPPLICANT_DRIVER := wlan04、使能wpa_supplicant调试信息 默认wpa_supplicant
11、设置为MSG_INFO,为了输出更多信息,可修改: 1、在common.c中设置wpa_debug_level = MSG_DEBUG; 2、在common.c中把#define wpa_printf宏中的 if (level) >= MSG_INFO) 改为 if (level) >= MSG_DEBUG)
12、5、配置wpa_supplicant.conf wpa_supplicant是通过wpa_supplicant.conf中的ctrl_interface=来指定控制socket的,应该在 AndroidBoard.mk中配置好复制到$(TARGET_OUT_ETC)/wifi(也就是/system/etc/wifi/wpa_supplicant.conf)这个位置会在init.rc中再次检测的。 一般的wpa_supplicant.conf配置为: ctrl
13、_interface=DIR=/data/system/wpa_supplicant GROUP=wifi update_config=1 fast_reauth=1 有时,驱动需要增加: ap_scan=1 如果遇到AP连接问题,需要修改ap_scan=0来让驱动连接,代替wpa_supplicant。&
14、#160; 如果要连接到non-WPA or open wireless networks,要增加: network= key_mgmt=NONE 6、配置路径和权限 Google修改的wpa_supplicant要运行在wifi用户和组
15、下的。代码可见wpa_supplicant/os_unix.c 中的os_program_init()函数。 如果配置不对,会出现下面错误: E/WifiHW ( ): Unable to open connection to supplicant on "/data/system/wpa_supplicant/wlan0"
16、;: No such file or directory will appear. 确认init.rc中有如下配置: mkdir /system/etc/wifi 0770 wifi wifi chmod 0770 /system/etc/wifi chmod 0660 /system/etc/wifi/wpa_sup
17、plicant.conf chown wifi wifi /system/etc/wifi/wpa_supplicant.conf # wpa_supplicant socket mkdir /data/system/wpa_supplicant 0771 wifi wifi
18、60;chmod 0771 /data/system/wpa_supplicant #wpa_supplicant control socket for android wifi.c mkdir /data/misc/wifi 0770 wifi wifi mkdir /data/misc/wifi/sockets 0770 wifi wifi
19、0; chmod 0770 /data/misc/wifi chmod 0660 /data/misc/wifi/wpa_supplicant.conf 如果系统的/system目录为只读,那应该使用路径/data/misc/wifi/wpa_supplicant.conf。7、运行wpa_supplicant和dhcpcd 在init.rc中确保有如下语句:
20、60; service wpa_supplicant /system/bin/logwrapper /system/bin/wpa_supplicant -dd -Dwext -iwlan0 -c /data/misc/wifi/wpa_supplicant.conf
21、 user root group wifi inet socket wpa_wlan0 dgram 660 wifi wifi oneshot service dhcpcd /system/bin/lo
22、gwrapper /system/bin/dhcpcd -d -B wlan0 disabled oneshot 根据所用的WIFI驱动名字,修改wlan0为自己驱动的名字。8、编译WIFI驱动为module或kernel built in 1、编译为module
23、0; 在BoardConfig.mk中添加: WIFI_DRIVER_MODULE_PATH := "/system/lib/modules/xxxx.ko" WIFI_DRIVER_MODULE_ARG := "" #for example nohwcrypt
24、160; WIFI_DRIVER_MODULE_NAME := "xxxx" #for example wlan0 WIFI_FIRMWARE_LOADER := "" 2、编译为kernel built in
25、160; 1)在hardware/libhardware_legacy/wifi/wifi.c要修改interface名字, 2)在init.rc中添加: setprop erface "wlan0" 3)在hardware/libhardware_legacy/wifi/wifi.c中当insmod/rmmod时, &
26、#160; 直接return 0。9、WIFI需要的firmware Android不使用标准的hotplug binary,WIFI需要的firmware要复制到/etc/firmware。 或者复制到WIFI驱动指定的位置,然后WIFI驱动会自动加载。10、修改WIFI驱动适合Android Google修改的wpa_supplicant要求SIOCSIWPRIV ioctl发送命令到驱动,及接收信息,例如signal streng
27、th, mac address of the AP, link speed等。所以要正确实现WIFI驱动,需要从SIOCSIWPRIV ioctl返回RSSI (signal strength)和MACADDR信息。 如果没实现这个ioctl,会出现如下错误: E/wpa_supplicant( ): wpa_driver_priv_driver_cmd failed
28、60; wpa_driver_priv_driver_cmd RSSI len = 4096 E/wpa_supplicant( ): wpa_driver_priv_driver_cmd failed
29、60;D/wpa_supplicant( ): wpa_driver_priv_driver_cmd LINKSPEED len = 4096 E/wpa_supplicant( ): wpa_driver_priv_driver_cmd failed I/wpa_supplicant( ): CTRL-EVENT-DRIVER-STATE HANGED10、设置dhcpcd.conf 一般/sys
30、tem/etc/dhcpcd/dhcpcd.conf的配置为: interface wlan0 option subnet_mask, routers, domain_name_server四、WIFI模块分析Wifi模块学习流程最近研究Wifi模块,查了不少的相关资料,但发现基本上是基于android2.0版本的的分析,而现在研发的android移动平台基本上都是2.3的版本,跟2.0版本的差别,在Wifi模块上也是显而易见的。2.3版本Wifi模块没有
31、了WifiLayer,之前的WifiLayer主要负责一些复杂的Wifi功能,如AP选择等以提供给用户自定义,而新的版本里面的这块内容基本上被WifiSettings所代替本文就是基于android2.3版本的Wifi分析,主要分为两部分来分别说明:(a) Wifi的启动流程(有代码供参考分析)(b) Wifi模块相关文件的解析(c) Wpa_supplicant解析Awifi的基本运行流程(针对代码而言)首先给一张我网上down下来的图,针对2.3版本之前
32、的,由于不怎么擅长画这些,大家也就将就点,只要能助理解就可以了(一)初始化a.流程1.在SystemServer启动的时候会生成一个ConnectivityService的实例2.ConnectivityService的构造函数会创建WifiService3.WifiStateTracker会创建WifiMonitor接受来自底层的事件,WifiService和WifiMonitor是整个wifi模块的核心,WifiService负责启动和关闭wpa_supplicant,启动和关闭WifiMonitor监视线程和把命令下方给wpa_supplicant,而WifiMonitor则负责从wpa
33、_supplicant接受事件通知b.代码分析要想使用Wifi模块,必须首先使能Wifi,当你第一次按下Wifi使能按钮时,WirelessSettings会实例化一个WifiEnabler对象,实例化代码如下:packages/apps/settings/src/com/android/settings/WirelessSettings.javajava view plaincopy1. protected void onCreate(Bundle savedInstanceState) 2.
34、3. super.onCreate(savedInstanceState); 4. 5. CheckBoxPreferencewifi = (CheckBoxPreference) findPreference(KEY_TOGGLE_WIFI); 6
35、. 7. mWifiEnabler= new WifiEnabler(this, wifi); 8. 9. WifiEnabler类的定义大致如下,它实现了一个监听接口,当WifiEnabler对象被初始化后,它监听到你按键的动作,会调用响应函数 onPreferenceChange(),这个函数会调用Wi
36、fiManager的setWifiEnabled()函数。java view plaincopy1. public class WifiEnabler implementsPreference.OnPreferenceChangeListener 2. 3. public boolean onPreferenceChange(Preference preference,Object value) 4.
37、60; booleanenable = (Boolean) value; 5. 6. if (mWifiManager.setWifiEnabled(enable) 7. mCheckBox.setEnabled(false);
38、 8. 9. 10. 11. 我们都知道Wifimanager只是个服务代理,所以它会调用WifiService的setWifiEnabled()函数,而这个函数会调用 sendEnableMessage()函数,了解android消息处理机制的都知道,这个函数最终会给自己发送一个 MESSAGE_ENABLE_WIFI的消息,被WifiService里面定义的handlermessage()函数处理,会调用 setWifiEnabledBlocking()函数。下面是调用流程:mWifiEnabler.onpr
39、eferencechange()=>mWifiManage.setWifienabled()=>mWifiService.setWifiEnabled()=>mWifiService.sendEnableMessage()=>mWifiService.handleMessage()=>mWifiService.setWifiEnabledBlocking().在setWifiEnabledBlocking()函数中主要做如下工作:加载Wifi驱动,启动wpa_supplicant,注册广播接收器,启动WifiThread监听线程。代码如下:1. 2.
40、if (enable) 3. if (!mWifiStateTracker.loadDriver() 4. Slog.e(TAG, "Failed toload Wi-Fi
41、;driver."); 5. setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); 6. return false;
42、60;7. 8. if (!mWifiStateTracker.startSupplicant() 9. &
43、#160;mWifiStateTracker.unloadDriver(); 10. Slog.e(TAG, "Failed tostart supplicant daemon."); 11.
44、 setWifiEnabledState(WIFI_STATE_UNKNOWN, uid); 12. return false; 13.
45、;14. registerForBroadcasts(); 15. mWifiStateTracker.startEventLoop(); 16. 至此,Wifi使能(开启)结束,自动进入扫描阶段。(二)扫描AP当驱动加载成功后,如果配置文件的AP_SCAN = 1,扫描会自动开始,Wi
46、fiMonitor将会从supplicant收到一个消息EVENT_DRIVER_STATE_CHANGED,调用 handleDriverEvent(),然后调用mWifiStateTracker.notifyDriverStarted(),该函数向消息队列添加EVENT_DRIVER_STATE_CHANGED,handlermessage()函数处理消息时调用scan()函数,并通过 WifiNative将扫描命令发送到wpa_supplicant。看代码Frameworks/base/wifi/java/android/net/wifi/WifiMonitor.javajava
47、0;view plaincopy1. private void handleDriverEvent(Stringstate) 2. if (state = null) 3. re
48、turn; 4. 5. if (state.equals("STOPPED") 6. &
49、#160;mWifiStateTracker.notifyDriverStopped(); 7. else if (state.equals("STARTED") 8. mWifiStat
50、eTracker.notifyDriverStarted(); 9. else if (state.equals("HANGED") 10. mWifiStateTracker.notif
51、yDriverHung(); 11. 12. Frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.javajava view plaincopy1. . 2. case EVENT_DRIVER_STATE_CHANGED: 3.
52、160; switch(msg.arg1) 4. case DRIVER_STARTED: 5. &
53、#160; 6. setNumAllowedChannels(); 7.
54、 synchronized (this) 8. if (mRunState = RUN_STATE_STARTING) 9.
55、160; mRunState = RUN_STATE_RUNNING; 10.
56、; if (!mIsScanOnly) 11. reconnectCommand();
57、 12. else 13.
58、; / In somesituations, supplicant needs to be kickstarted to 14.
59、; / start thebackground scanning 15.
60、0; scan(true); 16. 17. &
61、#160; 18. 19.
62、60; break; 20. . 上面是启动Wifi时,自动进行的AP的扫描,用户当然也可以手动扫描AP,这部分实现在WifiService里面,WifiService通过startScan()接口函数发送扫描命令到supplicant。看代码:Frameworks/base/wifi/java/android/net/wifi
63、/WifiStateTracker.javajava view plaincopy1. public boolean startScan(booleanforceActive) 2. enforceChangePermission(); 3. switch (mWifiStateTracker.getSupplicantState()
64、60;4. case DISCONNECTED: 5. case INACTIVE: 6. case SCANNING: 7.
65、 case DORMANT: 8. break; 9. default: 10.
66、60; mWifiStateTracker.setScanResultHandling( 11. WifiStateTracker.SUPPL_SCAN_HA
67、NDLING_LIST_ONLY); 12. break; 13. 14. return mWifiStateTracker.scan(forceActive); 15.
68、0; 然后下面的流程同上面的自动扫描,我们来分析一下手动扫描从哪里开始的。我们应该知道手动扫描是通过菜单键的扫描键来响应的,而响应该动作的应该是 WifiSettings类中Scanner类的handlerMessage()函数,它调用WifiManager的 startScanActive(),这才调用WifiService的startScan()。如代码:packages/apps/Settings/src/com/android/settings/wifiwifisettings.javajava view plaincopy1. public boolea
69、n onCreateOptionsMenu(Menu menu) 2. menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.wifi_menu_scan) 3. .setIcon(R.drawable.ic_m
70、enu_scan_network); 4. menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced) 5. .setIcon(android.R.drawable.ic_menu_manage); &
71、#160;6. return super.onCreateOptionsMenu(menu); 7. 当按下菜单键时,WifiSettings就会调用这个函数绘制菜单。如果选择扫描按钮,WifiSettings会调用onOptionsItemSelected()。packages/apps/Settings/src/com/android/settings/wifiwifisettings.java1. public booleanonOptionsIte
72、mSelected(MenuItem item) 2. switch (item.getItemId() 3. case MENU_ID_SCAN: 4.
73、60; if(mWifiManager.isWifiEnabled() 5. mScanner.resume(); 6.
74、 7. return true; 8. case MENU_ID_ADVANCED: 9.
75、0; startActivity(new Intent(this,AdvancedSettings.class); 10. return true; 11. 12.
76、60; return super.onOptionsItemSelected(item); 13. Handler类:1. private class Scanner extends Handler 2. private int mRetry = 0; 3.
77、 void resume() 4. if (!hasMessages(0) 5. sendEmptyM
78、essage(0); 6. 7. 8. void pause() 9. mRetr
79、y = 0; 10. mAccessPoints.setProgress(false); 11. removeMessages(0); 12. 13.
80、60; Override 14. public void handleMessage(Message message) 15. if (mWifiManager.startScanActive() 16.
81、; mRetry = 0; 17. else if (+mRetry >= 3) 18. &
82、#160; mRetry = 0; 19. Toast.makeText(WifiSettings.this, R.string.wifi_fail_to_scan, 20.
83、 Toast.LENGTH_LONG).show(); 21. return; 22.
84、; 23. mAccessPoints.setProgress(mRetry != 0); 24. sendEmptyMessageDelayed(0, 6000); 25.
85、0;26. 这里的mWifiManager.startScanActive()就会调用WifiService里的startScan()函数,下面的流程和上面的一样,这里不赘述。当supplicant完成了这个扫描命令后,它会发送一个消息给上层,提醒他们扫描已经完成,WifiMonitor会接收到这消息,然后再发送给WifiStateTracker。Frameworks/base/wifi/java/android/net/wifi/WifiMonitor.java1. void handleEvent(int event, String
86、160;remainder) 2. switch (event) 3. caseDISCONNECTED: 4.
87、0; handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED,remainder); 5. break; 6.
88、 case CONNECTED: 7. handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTE
89、D,remainder); 8. break; 9. case SCAN_RESULTS: 10.
90、; mWifiStateTracker.notifyScanResultsAvailable(); 11. break;
91、12. case UNKNOWN: 13. break; 14.
92、0; 15. WifiStateTracker将会广播SCAN_RESULTS_AVAILABLE_ACTION消息:代码如:Frameworks/base/wifi/java/android/net/wifi/WifiStateTracker.java1. public voidhandleMessage(Message msg) 2. Inten
93、t intent; 3. 4. case EVENT_SCAN_RESULTS_AVAILABLE: 5. if(ActivityManagerNative.isSystemReady() 6. &
94、#160; mContext.sendBroadcast(new Intent(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 7. 8. &
95、#160; sendScanResultsAvailable(); 9. 10.
96、160; setScanMode(false); 11. break; 12. 13. 由于WifiSettings类注册了intent,能够处理SCAN_RESULTS_AVAILABLE_ACTION消息,它会调用handleEvent(),调用流程如下所示。WifiSettings.handleEvent() =>W
97、ifiSettings.updateAccessPoints() => mWifiManager.getScanResults()=> mService.getScanResults()=>mWifiStateTracker.scanResults() => WifiNative.scanResultsCommand()将获取AP列表的命令发送到supplicant,然后supplicant通过Socket发送扫描结果,由上层接收并显示。这和前面的消息获取流程基本相同。(三)配置,连接AP当用户选择一个活跃的AP时,Wi
98、fiSettings响应打开一个对话框来配置AP,比如加密方法和连接AP的验证模式。配置好AP后,WifiService添加或更新网络连接到特定的AP。代码如:packages/apps/settings/src/com/android/settings/wifi/WifiSetttings.java1. public booleanonPreferenceTreeClick(PreferenceScreen screen, Preference preference) 2.
99、0; if (preference instanceof AccessPoint) 3. mSelected = (AccessPoint) preference; 4. showDialog(
100、mSelected, false); 5. else if (preference = mAddNetwork) 6. mSelected = null; 7.
101、; showDialog(null, true); 8. else if (preference = mNotifyOpenNetworks) 9. Secure.putInt(getContentResolver(),
102、0; 10. Secure.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 11. mN
103、otifyOpenNetworks.isChecked() ? 1 : 0); 12. else 13. return super.onPreferenceTreeClick(screen, preference); 14. &
104、#160; 15. return true; 16. 配置好以后,当按下“Connect Press”时,WifiSettings通过发送LIST_NETWORK命令到supplicant来检查该网络是否配置。如果没有该网络或没有配置它,WifiService调用addorUpdateNetwork()函数来添加或更新网络,然后发送命令给supplicant,连接到这个网络。下面是从响应连接按钮到WifiService
105、发送连接命令的代码:packages/apps/settings/src/com/android/settings/wifi/WifiSetttings.javajava view plaincopy1. public void onClick(DialogInterfacedialogInterface, int button) 2. if (button = WifiDialog.BUTTON_FORGE
106、T && mSelected != null) 3. forget(mSworkId); 4. else if (button = WifiDialog.BUTTON_SUBMIT && mDialog&
107、#160;!=null) 5. WifiConfiguration config = mDialog.getConfig(); 6. if (config = null) 7. &
108、#160; if (mSelected != null&& !requireKeyStore(mSelected.getConfig() 8.
109、60;connect(mSworkId); 9. 10. else if (workId != -1) 11. &
110、#160; if (mSelected != null) 12. mWifiManager.updateNetwork(config); 13.
111、0;14. saveNetworks(); 15. 16.
112、; else 17. int networkId =mWifiManager.addNetwork(config); 18.
113、 if (networkId != -1) 19. mWifiManager.enableNetwork(networkId, false); 20. &
114、#160; workId =networkId; 21. if (mDialog.edit | requireKeyStore(config) 22.
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026年广东省云浮市单招职业适应性考试题库附参考答案详解(研优卷)
- 2026年山西省晋中市单招职业倾向性考试题库含答案详解(预热题)
- 2026年山西铁道职业技术学院单招职业技能测试题库带答案详解(典型题)
- 2026年广东机电职业技术学院单招职业适应性测试题库带答案详解(研优卷)
- 2026年广州卫生职业技术学院单招职业技能考试题库带答案详解(基础题)
- 2026年广东女子职业技术学院单招职业技能测试题库附参考答案详解(夺分金卷)
- 2026年广州番禺职业技术学院单招职业技能考试题库带答案详解(新)
- 2026年广州铁路职业技术学院单招职业技能测试题库含答案详解(培优)
- 2026年山西药科职业学院单招职业适应性测试题库附参考答案详解(预热题)
- 2026年广元中核职业技术学院单招职业倾向性考试题库含答案详解(模拟题)
- 教科版三年级下册科学实验报告(20 篇)
- 2026年人教版新教材数学三年级下册教学计划(含进度表)
- 2026年江西交通职业技术学院单招职业技能测试题库及答案解析(名师系列)
- 部编人教版五年级下册小学道德与法治教案
- 新质生产力下制造业质量管理数字化转型白皮书-深圳市质量强市促进会
- 新人教版一年级数学下册全册教案(表格式)
- 复测分坑作业指导书
- 现代汉语词汇学精选课件
- 一二次深度融合成套柱上断路器汇报课件
- 部编版一年级下册知识树说教材公开课一等奖省优质课大赛获奖课件
- 青蓝色简约风《活着》名著导读好书推荐PPT模板
评论
0/150
提交评论