当前位置:   article > 正文

Android 系统时间_android 更新系统时间

android 更新系统时间

Android系统更新时间两类方式:

  • 1.网络提供时间(运营商SIM和WIFI)
  • 2.GPS提供时间(GPS模块接收GPS卫星信号获得,定位成功即生效)

1.时间相关概念

1.1 GMT格林威治标准时间(Greenwich Mean Time)

格林威治标准时间是指位于伦敦郊区的皇家格林尼治天文台的标准时间,本初子午线即是通过该点的经线。

1.2 UTC(Universal TimeCoordinated 世界统一时间)

世界标准时间,国际协调时间 .UTC是基于GMT, 由原子钟提供的更准确的同一时间。

1.3 Time zone

时区,亦作 time belt。以英国格林威治天文台的本初子午线为基点,全球划分为24个时区,每区各占经度15°以本初子午线为中央经线的时区为零时区,由零时区向东、西各分12区。 最后的东、西12区都是半时区,共同使用180°经线的地方时。每个时区的中央经线上的时间就是这个时区内统一采用的时间,称为区时,相邻两个时区的时间相差1小时。

1.4 Local time

地方时,本地时间。是以观测者子午线为参考点的时间标准,反映当地的自然时间。 中国把首都北京所在的东8区的时间作为全国统一的时间,称为北京时间。其实,整个世界可以就使用GMT/UTC就够了,但是各地的日出日落的自然生活反映到时间上就不那么自然了:
- 伦敦人假设8点钟吃早饭, 北京人吃早饭就是16点。
- 时区的划分和本地时间的使用, 就很好的解决了这个问题。

2.网络技术相关概念

通过网络同步时间,涉及到两个协议:NITZNTP。它们使用的条件不同,可以获取的信息也不一样;勾选这个功能后,手机首先会尝试NITZ方式,若获取时间失败,再会尝试使用NTP方式。

2.1 NITZ(network identity and time zone)

NITZ是一种GSM/WCDMA基地台方式,必须插入SIM卡,且需要operator支持; 可以提供时间和时区信息。中国大陆运营商基本是不支持的。(据说成都地区的中国联通支持NITZ,深圳联通却不支持。)

NITZ是自从PHASE 2+ RELEASE 96 的GSM中的可选功能,经常被用来自动更新移动电话的系统时钟。

2.2 NTP(network time protocol)

单纯通过网络(GPRS/EDGE/3G/HSPA/WiFi)获取时间,只提供时间信息,没有时区信息。 NTP无SIM卡或operator不支持NITZ时使用。* 因此在不支持NITZ的地区,自动获取时区功能实际上是无效的。*

它根据获取到的GMT时间,按照手机本身设置的时区信息,计算出本地时间,显示在设备上。NTP还有一种缓存机制:当前成功获取的时间会保存下来,当用户下次开启自动更新时间功能时,会结合手机clock来进行时间更新。 这也是没有任何网络时手机却能自动更新时间的原因
此外,因为NTP是通过网络上的时间服务器获取时间,所以时间较长时间未同步时,可能是你手机访问的时间服务器连接有问题。

NTP(Network Time Protocol)提供准确时间,首先要有准确的时间来源,这一时间应该是国际标准时间UTC。 NTP获得UTC的时间来源可以是原子钟、天文台、卫星,也可以从Internet上获取。这样就有了准确而可靠的时间源。时间按NTP服务器的等级传播。

2.3GPS 提供时间

当GPS 定位成功后,会根据当前时区,将GPS UTC Time转化成对应时区的local time. 为了确保GPS能成功定位,请到室外GPS信号良好的空旷地(视野范围内无建筑物遮挡)进行测试。

3.代码分析

3.1 系统的几个时间

系统的时间主要public final class SystemClock 中提供。

  • SystemClock.sleep():和Thread.sleep类似都是等待给定是时间才返回,但是不会抛出InterruptException.
  • SystemClock.setCurrentTimeMillis:native方法,设置当前实际时间。需要有权限。
  • SystemClock.uptimeMillis():native方法,返回启动后,不包含睡眠的时间。
  • SystemClock.elapsedRealtime():返回启动后,包含睡眠的所有时间。
  • SystemClock.currentThreadTimeMillis():返回当前线程运行的时间。
  • SystemClock.currentTimeMicro():返回当前线程实际时间。
    其中navtive方法,setCurrentTimeMillis。只有root权限才能执行。
static int setCurrentTimes(int64_t mills){
    struct timeval tv;
    struct timespec ts;
    int fd,res,ret=0;
    if(mills<=0||mills/1000LL>=INT_MAX){
        return -1;
    }
    tv.tv_sec=(time_t)(times/1000LL);
    tv_tv_usec=(suseconds_t)((millis%1000LL)*1000LL);
    ALOGD("setting time of day to sec=%d\n",(int)tv.tv_sec);
    fd=open("/dev/alarm",O_RDWR);
    if(fd<0){
        ALOGW("unable to open alarm driver:%s\n",strerror(errno));
        return -1;
    }
    ts.tv_sec=tv.tv_sec;
    ts.tv_nsec=tv.tv_usec*1000;
    res=ioctl(fd,ANDROID_ALRAM_SET_RTC,&ts);
    if(fd<0){
        ALOGW("Unable to open alarm driver:%s\n",strerror(errno));
    }
    close(fd);
    return ret;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

3.2 系统自动获取时间

在setting中勾选“自动确定时间和日期”,“自动确定时区”后只是对key值为AUTO_TIMEAUTO_TIME_ZONE的Preference进行了赋值.。sharepreferenceservice 中对Setting都进行了改变监听.

//packages/apps/Settings/src/com/android/settings/DateTimeSettings.java
@Override
public void onSharePreferenceChanged(SharedPreferences preferences,String key){
    if(key.equals(KEY_DATE_FORMAT)){//更新时间格式
        String format=preferences.getString(key,getResources().getString(R.string.default_data_format));
        Settings.System.putString(getContentResover(),Settings.System.DATE_FORMAT,format);
        updateTimeAndDateDisplay(getActivity());
    }else if(key.equals(KEY_AUTO_TIME)){//自动更新时间
        boolean autoEnabled=preferences.getBoolean(key,true);
        Settings.Global.putInt(getContentResolver(),Setting.Global.AUTO_TIME,autoEnabled?1:0);
        mTimePref.setEnabled(!autoEnabled);
        mDatePref.setEnabled(!autoEnabled);
    }else if(key.equals(KEY_AUTO_ZONE)){//自动更新时区
        boolean autoZoneEnabled=preferences.getBoolean(key,true);
        Settings.Global.putInt(getContentResolver(),Setting.Global.AUTO_ZONE,autoZoneEnabled?1:0);
        mTimeZone.setEnabled(!autoZoneEnabled);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

SystemServer中启动了NetworkTimeUpdateService服务.

//1.注册
if (!disableNetwork) {
    try {
      Slog.i(TAG, "NetworkTimeUpdateService");
          networkTimeUpdater = new NetworkTimeUpdateService(context);
    } catch (Throwable e) {
        reportWtf("starting NetworkTimeUpdate service", e);
}
//2.ActivityService启动后运行
 try {
     if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning();
 } catch (Throwable e) {
     reportWtf("Notifying NetworkTimeService running", e);
 }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

NetworkTimeUpdateServicesystemRunning 中注册观察者SettingsObserver。在对上述的key值进行了监听,在检测到key值改变的时候,就会发送消mHandler.obtainMessage(mMsg).sendToTarget();

private static class SettingsObserver extends ContentObserver {
    private int mMsg;
    private Handler mHandler;
    SettingsObserver(Handler handler, int msg) {
        super(handler);
        mHandler = handler;
        mMsg = msg;
    }
    void observe(Context context) {
        ContentResolver resolver = context.getContentResolver();
        resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME),
                false, this);
    }
    @Override
    public void onChange(boolean selfChange) {
        mHandler.obtainMessage(mMsg).sendToTarget();
    }
}
/** Handler to do the network accesses on */
private class MyHandler extends Handler {
    public MyHandler(Looper l) {
        super(l);
    }
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case EVENT_AUTO_TIME_CHANGED:
            case EVENT_POLL_NETWORK_TIME:
            case EVENT_NETWORK_CONNECTED:
                onPollNetworkTime(msg.what);
                break;
        }
    }
}
  • 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

onPollNetworkTime方法中先判断是否勾选“自动更新时间”,如果没勾选直接退出,如果勾选了再看,如果更新的NITZ时间不为NOT_SET(-1),且更新间隔小于mPollingIntervalMsmPollingIntervalMs=24小时,那么就直接更新NITZ的时间,否则用NTP同步时间。

private void onPollNetworkTime(int event) {
    // If Automatic time is not set, don't bother.
    if (!isAutomaticTimeRequested()) return;
    //refTime:表示boot开始计时,包含睡眠时间。
    final long refTime = SystemClock.elapsedRealtime();
    // If NITZ time was received less than mPollingIntervalMs time ago,
    // no need to sync to NTP.
    if (mNitzTimeSetTime != NOT_SET && refTime - mNitzTimeSetTime < mPollingIntervalMs) {
        resetAlarm(mPollingIntervalMs);
        return;
    }
    //Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC.
    final long currentTime = System.currentTimeMillis();
    if (DBG) Log.d(TAG, "System time = " + currentTime);
    // Get the NTP time
    if (mLastNtpFetchTime == NOT_SET || refTime >= mLastNtpFetchTime + mPollingIntervalMs
            || event == EVENT_AUTO_TIME_CHANGED) {
        if (DBG) Log.d(TAG, "Before Ntp fetch");
        // force refresh NTP cache when outdated
        if (mTime.getCacheAge() >= mPollingIntervalMs) {
            mTime.forceRefresh();
        }
       // only update when NTP time is fresh
        if (mTime.getCacheAge() < mPollingIntervalMs) {
            final long ntp = mTime.currentTimeMillis();
            mTryAgainCounter = 0;
            // If the clock is more than N seconds off or this is the first time it's been
            // fetched since boot, set the current time.
            if (Math.abs(ntp - currentTime) > mTimeErrorThresholdMs
                    || mLastNtpFetchTime == NOT_SET) {
                // Set the system time
                if (DBG && mLastNtpFetchTime == NOT_SET
                        && Math.abs(ntp - currentTime) <= mTimeErrorThresholdMs) {
                    Log.d(TAG, "For initial setup, rtc = " + currentTime);
                }
                if (DBG) Log.d(TAG, "Ntp time to be set = " + ntp);
                // Make sure we don't overflow, since it's going to be converted to an int
                if (ntp / 1000 < Integer.MAX_VALUE) {
                    SystemClock.setCurrentTimeMillis(ntp);
                }
            } else {
                if (DBG) Log.d(TAG, "Ntp time is close enough = " + ntp);
            }
            mLastNtpFetchTime = SystemClock.elapsedRealtime();
        } else {
            // Try again shortly
            mTryAgainCounter++;
            if (mTryAgainTimesMax < 0 || mTryAgainCounter <= mTryAgainTimesMax) {
                resetAlarm(mPollingIntervalShorterMs);
            } else {
                // Try much later
                mTryAgainCounter = 0;
                resetAlarm(mPollingIntervalMs);
            }
            return;
        }
    }
    resetAlarm(mPollingIntervalMs);
}
  • 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
  • 58
  • 59

其中foreceRefresh 实现如下.根据配置的一些ntp服务器进行请求刷新的操作。

//frameworks/base/core/java/android/util/NtpTrustedTime.java
@Override
public /* synchronized */ boolean forceRefresh() {
    if (mServer == null) {
        // missing server, so no trusted time available
        Log.w(TAG, "No need to forceRefresh() since mServer is null!");
        return false;
    }
    if (!mRequestTime) {
        Log.w(TAG, "No need to forceRefresh(), Ueser set disable!");
        return false;
    }
    if(!haveInternet(mContext)) {
        Log.w(TAG, "No need to forceRefresh() since network is not connected!");
        return false;
    }
    if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
    if(defaultServerList != null  && defaultServerList.length > 0){
        if (defaultServerList[mIndex] != null) {
            mServer = defaultServerList[mIndex];
            Log.w(TAG, "update ntp fail change ntp server to " + mServer);
        } else {
            Log.w(TAG, "Ntp server list get Index[" + mIndex + "] is null!");
        }
        if(mIndex < defaultServerList.length - 1)
            mIndex++;
        else
            mIndex = 0;
    } else {
        Log.w(TAG, "Ntp server list is null!");
    }
    /*if (LOGD) */Log.d(TAG, "ntp time server is " + mServer);
    final SntpClient client = new SntpClient();
    if (client.requestTime(mServer, (int) mTimeout)) {
        mHasCache = true;
        mCachedNtpTime = client.getNtpTime();
        mCachedNtpElapsedRealtime = client.getNtpTimeReference();
        mCachedNtpCertainty = client.getRoundTripTime() / 2;
        return true;
    } else {
        return false;
    }
}

/**
 * Sends an SNTP request to the given host and processes the response.
 */
public boolean requestTime(String host, int timeout) {
    DatagramSocket socket = null;
    try {
        socket = new DatagramSocket();
        socket.setSoTimeout(timeout);
        InetAddress address = InetAddress.getByName(host);
        byte[] buffer = new byte[NTP_PACKET_SIZE];
        DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);

        // set mode = 3 (client) and version = 3
        // mode is in low 3 bits of first byte
        // version is in bits 3-5 of first byte
        buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);

        // get current time and write it to the request packet
        //long requestTime = System.currentTimeMillis();
        long requestTime = 1332991872432L;
        /*fix local time up 2036.02.07 6:28:16 cannot get right ntp time, this code only usefull before  2036.02.07 6:28:16, 
        fixme, anyway, if it is still under using, I am happy, frank chen*/
        long requestTicks = SystemClock.elapsedRealtime();
        writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);

        socket.send(request);

        // read the response
        DatagramPacket response = new DatagramPacket(buffer, buffer.length);
        socket.receive(response);
        long responseTicks = SystemClock.elapsedRealtime();
        long responseTime = requestTime + (responseTicks - requestTicks);

        // extract the results
        long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
        long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
        long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
        long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
        // receiveTime = originateTime + transit + skew
        // responseTime = transmitTime + transit - skew
        // clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2
        //             = ((originateTime + transit + skew - originateTime) +
        //                (transmitTime - (transmitTime + transit - skew)))/2
        //             = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2
        //             = (transit + skew - transit + skew)/2
        //             = (2 * skew)/2 = skew
        long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
        // if (false) Log.d(TAG, "round trip: " + roundTripTime + " ms");
        // if (false) Log.d(TAG, "clock offset: " + clockOffset + " ms");

        // save our results - use the times on this side of the network latency
        // (response rather than request time)
        mNtpTime = responseTime + clockOffset;
        mNtpTimeReference = responseTicks;
        mRoundTripTime = roundTripTime;
    } catch (Exception e) {
        Log.e(TAG, "request time failed: " + e);
        return false;
    } finally {
        if (socket != null) {
            socket.close();
        }
    }
    Log.d(TAG, "request time success");
    return true;
}
  • 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
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110

3.3 date命令

命令行data命令同样能够获取和设置系统时间。但是重启将会失效。实现过程比较简单,和SystemClock过程差不多,主要分析一下/system/bin中的cmd命令。android toolbox让一个命令看起来与很多可执行命令一样。(busybox 进行了扩展).

3.3.1 命令的打包

编译过程Android.mk分析,/system/core/toolbox如下:

LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
TOOLS:= ls \
        ifconfig \
        date 
        ....
# patsubst通配替换函数,如:date 替换成date.c
LOCAL_SRC_FILES:= toolbox.c \
                  $(patsubst %,%c,$(TOOLS))
LOCAL_SHARED_LIBRARIES:=libcutils libc
LOCAL_MODULE:=toolbox
# 包含这个将会定义$(intermediates)变量,中间产物
include $(BUILD_EXECUTABLE)

TOOLS_H:=$(intermediates)/tools.h
$(TOOLS_H):PRIVATE_TOOLS:=$(TOOLS)
$(TOOLS_H):PRIVATE_CUSTOM_TOOLS=echo "/*file generated automatically*/" >$@;for t in $(PRIVATE_CUSTOM_TOOLS);echo "TOOL$$t" >> $@; done
$(TOOLS_H):$(LOCAL_PATH)/Android.mk
$(TOOLS_H):
    $(tansform-generated-source)
# Make #!/system/bin/toolbox launchers for each tool
SYMLINKS:=$(addprefix $(TARGET_OUT)/bin/,$(TOOLS))
$(SYMLINKS):=$(LOCAL_INSTALLED_MODULE) $(LOCAL_PATH)/Android.mk
    $echo "Symlink:$@ -> $(TOOLBOX_BINARY)"
    $mkdir -p $(dir $@)
    $rm -rf $@
    $(hide) ln -sf $(TOOLBOX_BINARY) $@
ALL_DEFAULT_INSTALLED_MODULES+=$(SYMLINKS)
# we need this so that the installed files could be picked up based on the local module name
ALL_MODULES.$(LOCAL_MODULE).INSTALLED:=\
    $(ALL_MODULES.$(LOCAL_MODULE).INSTALLED) $(SYMLINKS)
  • 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

该Android.mk的目的是生成通过toolbox来生成不同可执行程序。
1.其中$@即为目标tools.h,并且其中TOOL$$t,定义在toolbox.c中的宏#define TOOL(name) int name##_main(int, char**);和另一个宏的展开#define TOOL(name) { #name, name##_main },
2.还有一点建立生成类似/system/bin/date软连接到TOOLBOX_BINARY也就是toolbox
3.关注tools.h中的内容。由Makefile生成就是组成了类似{"date",date_main}形式。

3.3.2 toolbox 函数分析

toolbox主要功能是用来调用不同的命令如:date的一个通用函数。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int toolbox_main(int argc,char**argv){
    //该函数可以将如:$toolbox date命令行相当于变成了$date命令
    if(argc>1){
        return main(argc-1,argv+1);
    }else{
        return 0;
    }
}
//第一次展开注意这里tools.h展开,有点init.rc解析的意思。。。
#define TOOL(name) int name##_main(int,char**);
#include "tools.h"
#undef TOOL

static struct{
}tools[]={
    {"toolbox",toolbox_main},
//第二次展开
#define TOOL(name) {#name,name##_main},
#include "tools.h"
#undef TOOL
}
int main(int argc,char**argv){
    int i;
    char*name=argv[0];
    if((argc>1)&&(argv[1][0]=='@')){
        name=argv[1]+1;
        argc--;
        argv++;
    }else{
        char *cmd=strchr(argv[0],'/');
        if(cmd)
            name=cmd+1;
    }
    //终止条件为tools[i].name==0即为null
    for(i=0;tools[i].name;i++){
        if(!strcmp(tools[i].name,name)){
            return tools[i].func(argc,argv);
        }
    }
    printf("%s:no such tool\n",argv[0]);
    return -1;
}
  • 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

其中第一此展开中#define TOOL(name) int name##_main(int,char**),其实就是函数申明,toos.h的内容为:

/*file generated automatically*/
int ls_main(int,char**);
int ifconfig_main(int,char**);
int date_main(int,char**);
...
  • 1
  • 2
  • 3
  • 4
  • 5

其中第二次展开,#define TOOL(name) { #name, name##_main },就是结构体的实例。展开内容如下:

    /*file generated automatically*/
    {"ls",ls_main},
    {"ifconfig",ifconfig_main},
    {"date",date_main},
    ...
  • 1
  • 2
  • 3
  • 4
  • 5

分析之后,如果使用date命令就是相当于调用了函数date_main函数。

//PATH:/system/core/toolbox/Date.c
int date_main(int argc, char *argv[])
  • 1
  • 2
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/594085
推荐阅读
相关标签
  

闽ICP备14008679号