日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

作者:eddiecmchen,PCG客戶端開發(fā)工程師

| 導(dǎo)語 把我的iphone XR扶起來,它還能再頂一會(huì)兒~

背景

遠(yuǎn)在IOS 11時(shí)期(2017年),蘋果就發(fā)公告要求所有需要上架AppStore的應(yīng)用都必須支持64位。32位應(yīng)用不再支持上架與運(yùn)行。

一鍵釋放iOS 64位App潛力

 

升級(jí)64位應(yīng)用有什么好處呢?(以下內(nèi)容純摘抄,客官可以直接跳過)

  • 指針字長(zhǎng)更長(zhǎng),可使用的虛擬內(nèi)存更大,擺脫32位下受限的4G內(nèi)存空間
    • 16 bit = 65,536 bytes (64 Kilobytes)
    • 32 bit = 4,294,967,296 bytes (4 Gigabytes)
    • 64 bit = 18,446,744,073,709,551,616 (16 Exabytes)
  • 寄存器更多,減少內(nèi)存讀寫,加快執(zhí)行速度

這里我們要注意的是:虛擬內(nèi)存確實(shí)比純32位多了,但是App到底能用多少,是否跟宣傳一樣接近16EB?下面將會(huì)展開聊聊,我們先來看一個(gè)Crash。

一個(gè)長(zhǎng)期存在的幽靈

我們先來看下面的一個(gè)內(nèi)存導(dǎo)致的崩潰,JSC在使用bmalloc嘗試進(jìn)行內(nèi)存分配時(shí),提示OOM導(dǎo)致了SIGTRAP。

Last Exception :
0  JAVAScriptCore                 0x000000018b777570 _pas_panic_on_out_of_memory_error
1  JavaScriptCore                 0x000000018b72e918 _bmalloc_try_iso_allocate_impl_impl_slow
2  JavaScriptCore                 0x000000018b73d3d8 _bmalloc_heap_config_specialized_local_allocator_try_allocate_small_segregated_slow +  5952
3  JavaScriptCore                 0x000000018b7276f8 _bmalloc_allocate_impl_casual_case +  800
4  JavaScriptCore                 0x000000018c60d494 JSC::PropertyTable::create(JSC::VM&, unsigned int) +  244
5  JavaScriptCore                 0x000000018c66ba74 JSC::Structure::materializePropertyTable(JSC::VM&, bool) +  324
6  JavaScriptCore                 0x000000018c66dfac JSC::Structure::changePrototypeTransition(JSC::VM&, JSC::Structure*, JSC::JSValue, JSC::DeferredStructureTransitionWatchpointFire&) +  612
7  JavaScriptCore                 0x000000018c559930 JSC::JSObject::setPrototypeDirect(JSC::VM&, JSC::JSValue) +  192
8  JavaScriptCore                 0x000000018c559e40 JSC::JSObject::setPrototypeWithCycleCheck(JSC::VM&, JSC::JSGlobalObject*, JSC::JSValue, bool) +  316
9  JavaScriptCore                 0x000000018c4f580c JSC::globalFuncProtoSetter(JSC::JSGlobalObject*, JSC::CallFrame*) +  192
10 JavaScriptCore                 0x000000018ba1f7a8 _vmEntryToNative +  280
11 JavaScriptCore                 0x000000018c1b0cd0 JSC::Interpreter::executeCall(JSC::JSGlobalObject*, JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) +  616
12 JavaScriptCore                 0x000000018c474ecc JSC::GetterSetter::callSetter(JSC::JSGlobalObject*, JSC::JSValue, JSC::JSValue, bool) +  212
13 JavaScriptCore                 0x000000018c5b6264 JSC::JSGenericTypedArrayView<JSC::Uint8Adaptor>::put(JSC::JSCell*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&) +  612
14 JavaScriptCore                 0x000000018c2c2ecc _llint_slow_path_put_by_id +  3244
// 忽略多余重復(fù)堆棧
37 JavaScriptCore                 0x000000018ba1f5fc _vmEntryToJavaScript +  264
38 JavaScriptCore                 0x000000018c1b0c7c JSC::Interpreter::executeCall(JSC::JSGlobalObject*, JSC::JSObject*, JSC::CallData const&, JSC::JSValue, JSC::ArgList const&) +  532
39 JavaScriptCore                 0x000000018bac7ae4 _JSObjectCallAsFunction +  568
40 mttlite                        0x0000000102a54914 hippy::napi::JSCCtx::CallFunction(std::__1::shared_ptr<hippy::napi::CtxValue> const&, unsigned long, std::__1::shared_ptr<hippy::napi::CtxValue> const*) (js_native_api_value_jsc.cc:406)
41 mttlite                        0x0000000102a664e0 _ZNSt3__110__function6__funcIZN11TimerModule5StartERKN5hippy4napi12CallbackInfoEbE3$_4NS_9allocatorIS8_EEFvvEEclEv (memory:3237)
42 mttlite                        0x0000000102a63018 hippy::base::TaskRunner::Run() (memory:3237)
43 mttlite                        0x0000000102a64974 ThreadEntry (thread.cc:0)
44 libsystem_pthread.dylib        0x00000001dc129348 __pthread_start +  116
------

Exception Type: SIGTRAP 
Exception Codes: fault addr: 0x000000018b777570
Crashed Thread: 48 hippy.js

這個(gè)OOM問題,與iOS上常見的OOM不一樣。按照常規(guī)的理解,當(dāng)App內(nèi)存不足的時(shí)候,正常會(huì)觸發(fā)系統(tǒng)的Jetsam機(jī)制殺死App。在系統(tǒng)日志中會(huì)留下Jetsam相關(guān)日志,理論上不會(huì)在Bugly等異常上報(bào)中發(fā)現(xiàn)。但這一類崩潰卻一直在產(chǎn)生上報(bào),并且低內(nèi)存的崩潰堆棧表現(xiàn)形式有很多種。

以上的JSC崩潰問題已經(jīng)存在很長(zhǎng)一段時(shí)間了(至少2年),而且崩潰堆棧都集中在JSC執(zhí)行JS代碼的過程中,長(zhǎng)期缺乏JS相關(guān)的監(jiān)控與Debug工具導(dǎo)致該問題一直無法解決。

雖然堆棧上有明確的原因說明是OOM,但我們觀察到有不少用戶實(shí)際上物理內(nèi)存空間還是足夠的:

一鍵釋放iOS 64位App潛力

 

兩年前,沖浪的時(shí)候偶然看來了來自微視同學(xué)的Case總結(jié):《OOM與內(nèi)存》

當(dāng)時(shí)跟hippy SDK的同事也討論過是否存在類似的內(nèi)存不足情況。但由于大家對(duì)JSC黑盒都不熟悉,而且崩潰的JS堆棧也不確切。當(dāng)時(shí)的建議是:少在后臺(tái)加載JSC。最終也并沒有解決該問題。

兩年后,當(dāng)瀏覽器集成flutter,類似的JS崩潰直接翻倍(21H2 0.08% -> 22H1 0.16%)。沒辦法,還是要看類似JSC和Dart VM的內(nèi)存分配機(jī)制是怎樣的,再挖掘一下是否存在解()決()方案。

JSC、DartVM的虛擬內(nèi)存分配

翻閱相關(guān)虛擬機(jī)的內(nèi)存管理相關(guān)代碼,可以找到底層的內(nèi)存分配基本實(shí)現(xiàn)都是基于mmap處理的。

// WebKit bmalloc VMAllocate
inline void* tryVMAllocate(size_t vmSize, VMTag usage = VMTag::Malloc)
{
    vmValidate(vmSize);
    void* result = mmap(0, vmSize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | BMALLOC_NORESERVE, static_cast<int>(usage), 0);
    if (result == MAP_FAILED)
        return nullptr;
    return result;
}
// Dart VM的虛擬內(nèi)存
VirtualMemory* VirtualMemory::Allocate(intptr_t size,
                                       bool is_executable,
                                       const char* name) {
  ASSERT(Utils::IsAligned(size, PageSize()));

  const int prot = PROT_READ | PROT_WRITE | (is_executable ? PROT_EXEC : 0);

  int map_flags = MAP_PRIVATE | MAP_ANONYMOUS;
#if (defined(DART_HOST_OS_macOS) && !defined(DART_HOST_OS_IOS))
  if (is_executable && IsAtLeastOS10_14()) {
    map_flags |= MAP_JIT;
  }
#endif  // defined(DART_HOST_OS_MACOS)

  // Some 64-bit microarchitectures store only the low 32-bits of targets as
  // part of indirect branch prediction, predicting that the target's upper bits
  // will be same as the call instruction's address. This leads to misprediction
  // for indirect calls crossing a 4GB boundary. We ask mmap to place our
  // generated code near the VM binary to avoid this.
  void* hint = is_executable ? reinterpret_cast<void*>(&Allocate) : nullptr;
  void* address = mmap(hint, size, prot, map_flags, -1, 0);
  if (address == MAP_FAILED) {
    return nullptr;
  }
  return new VirtualMemory(address, size);
}

VirtualMemory::~VirtualMemory() {
  if (address_ != nullptr) {
    if (munmap(address_, size_) != 0) {
      int error = errno;
      const int kBufferSize = 1024;
      char error_buf[kBufferSize];
      FATAL("munmap error: %d (%s)", error,
            Utils::StrError(error, error_buf, kBufferSize));
    }
  }
}

當(dāng)map_flags包含MAP_ANON時(shí),并且fd傳入-1時(shí),mmap將直接使用虛擬內(nèi)存進(jìn)行分配,不需要依賴文件描述符。

mmap在xnu上的實(shí)現(xiàn)

/*
 * mmap stub, with preemptory failures due to extra parameter checking
 * mandated for conformance.
 *
 * This is for UNIX03 only.
 */
void *
mmap(void *addr, size_t len, int prot, int flags, int fildes, off_t off)
{
    /*
     * Preemptory failures:
     * 
     * o    off is not a multiple of the page size
     * o    flags does not contain either MAP_PRIVATE or MAP_SHARED
     * o    len is zero
     */
    extern void cerror_nocancel(int);
    if ((off & PAGE_MASK) ||
        (((flags & MAP_PRIVATE) != MAP_PRIVATE) &&
         ((flags & MAP_SHARED) != MAP_SHARED)) ||
        (len == 0)) {
        cerror_nocancel(EINVAL);
        return(MAP_FAILED);
    }

    void *ptr = __mmap(addr, len, prot, flags, fildes, off);
    
    if (__syscall_logger) {
        int stackLoggingFlags = stack_logging_type_vm_allocate;
        if (flags & MAP_ANON) {
            stackLoggingFlags |= (fildes & VM_FLAGS_ALIAS_MASK);
        } else {
            stackLoggingFlags |= stack_logging_type_mapped_file_or_shared_mem;
        }
        __syscall_logger(stackLoggingFlags, (uintptr_t)mach_task_self(), (uintptr_t)len, 0, (uintptr_t)ptr, 0);
    }

    return ptr;
}

上面的調(diào)用會(huì)傳遞到內(nèi)核kern_mman.c的實(shí)現(xiàn)函數(shù)mmap(proc_t p, struct mmap_args *uap, user_addr_t *retval)

/*
 * XXX Internally, we use VM_PROT_* somewhat interchangeably, but the correct
 * XXX usage is PROT_* from an interface perspective.  Thus the values of
 * XXX VM_PROT_* and PROT_* need to correspond.
 */
int
mmap(proc_t p, struct mmap_args *uap, user_addr_t *retval)
{
        /*
         * 上面忽略了一部分代碼
         */
        result = vm_map_enter_mem_object(user_map,
            &user_addr, user_size,
            0, alloc_flags, vmk_flags,
            tag,
            IPC_PORT_NULL, 0, FALSE,
            prot, maxprot,
            (flags & MAP_SHARED) ?
            VM_INHERIT_SHARE :
            VM_INHERIT_DEFAULT);

        /* If a non-binding address was specified for this anonymous
         * mapping, retry the mapping with a zero base
         * in the event the mapping operation failed due to
         * lack of space between the address and the map's maximum.
         */
        if ((result == KERN_NO_SPACE) && ((flags & MAP_FIXED) == 0) && user_addr && (num_retries++ == 0)) {
            user_addr = vm_map_page_size(user_map);
            goto map_anon_retry;
        }
        /*
         * 下面忽略了一部分代碼
         */
}

其中又會(huì)調(diào)用vm_map.c內(nèi)部的vm_map_enter_mem_object,而該方法最終會(huì)在vm_map_enter中依據(jù)對(duì)象進(jìn)行內(nèi)存分配:

// 下面這個(gè)只截了個(gè)頭,大概帶一下,我也沒調(diào)過代碼~
/*
 *  Routine:    vm_map_enter
 *
 *  Description:
 *      Allocate a range in the specified virtual address map.
 *      The resulting range will refer to memory defined by
 *      the given memory object and offset into that object.
 *
 *      Arguments are as defined in the vm_map call.
 */
kern_return_t
vm_map_enter(
    vm_map_t                map,
    vm_map_offset_t         *address,       /* IN/OUT */
    vm_map_size_t           size,
    vm_map_offset_t         mask,
    int                     flags,
    vm_map_kernel_flags_t   vmk_flags,
    vm_tag_t                alias,
    vm_object_t             object,
    vm_object_offset_t      offset,
    boolean_t               needs_copy,
    vm_prot_t               cur_protection,
    vm_prot_t               max_protection,
    vm_inherit_t            inheritance)

其中vm_map_enter在分配過程中會(huì)對(duì)hole_entry→vme_end作判斷,vme_end即最大的可分配空間。

xnu上虛擬內(nèi)存的分配范圍

本來我只是觀察到蘋果在iOS15上增加了com.apple.developer.kernel.increased-memory-limit的能力聲明。本著死馬當(dāng)活馬醫(yī)的想法,嘗試在新版本上添加該聲明以緩解一部分問題。

結(jié)果偶然看到部分開發(fā)者提問:該能力可配合com.apple.developer.kernel.extended-virtual-addressing使用。看到后我一下子反應(yīng)過來,順手搜到了今年二月國(guó)外有大佬做了相關(guān)的探索:

Size Matters: An Exploration of Virtual Memory on iOS

文章闡述了iOS的內(nèi)存管理機(jī)制和虛擬內(nèi)存空間分配在不同的機(jī)型上存在上限,代碼如下:

#define ARM64_MIN_MAX_ADDRESS (SHARED_REGION_BASE_ARM64 + SHARED_REGION_SIZE_ARM64 + 0x20000000) // end of shared region + 512MB for various purposes
const vm_map_offset_t min_max_offset = ARM64_MIN_MAX_ADDRESS; // end of shared region + 512MB for various purposes

if (arm64_pmap_max_offset_default) {
    max_offset_ret = arm64_pmap_max_offset_default;
} else if (max_mem > 0xC0000000) {
    max_offset_ret = min_max_offset + 0x138000000; // Max offset is 13.375GB for devices with > 3GB of memory
} else if (max_mem > 0x40000000) {
    max_offset_ret = min_max_offset + 0x38000000;  // Max offset is 9.375GB for devices with > 1GB and <= 3GB of memory
} else {
    max_offset_ret = min_max_offset;
}

并且總結(jié)了一個(gè)上限值與機(jī)型表格:

RAM

Address Space

Usable

Devices

> 3 GiB

15.375 GiB

7.375 GiB

- iPhone XS – iPhone 13
- iPad Air (4th generation)
- iPad Pro (12.9-inch), (10.5-inch), (11-inch)

> 1 GiB

11.375 GiB

3.375 GiB

- iPhone 6s – X, SE, XR
- iPad (5th generation) – iPad (8th generation)
- iPad Air 2, iPad Air (3rd generation)
- iPad mini 4, iPad mini (5th generation)
- iPad Pro (9.7-inch)

<= 1 GiB

10.5 GiB

2.5 GiB

- iPhone 5s, iPhone 6
- iPad Air
- iPad mini 2, iPad mini 3

而xnu的源碼(pmap.c)中還透露了內(nèi)核內(nèi)存分配存在jumbo機(jī)制。當(dāng)iOS App帶有指定的能力聲明時(shí),xnu內(nèi)核將會(huì)以jumbo模式運(yùn)行,虛擬內(nèi)存地址空間將會(huì)直接分配為最大值64GB:

if (option == ARM_PMAP_MAX_OFFSET_JUMBO) {
    if (arm64_pmap_max_offset_default) {
        // Allow the boot-arg to override jumbo size
        max_offset_ret = arm64_pmap_max_offset_default;
    } else {
        max_offset_ret = MACH_VM_MAX_ADDRESS;     // Max offset is 64GB for pmaps with special "jumbo" blessing
    }
}

并且該上限值會(huì)在進(jìn)程啟動(dòng)時(shí)進(jìn)行調(diào)整,具體代碼可以在kern_exec.c中找到:

/*
 * Apply the requested maximum address.
 */
if (error == 0 && imgp->ip_px_sa != NULL) {
    struct _posix_spawnattr *psa = (struct _posix_spawnattr *) imgp->ip_px_sa;

    if (psa->psa_max_addr) {
        vm_map_set_max_addr(get_task_map(new_task), (vm_map_offset_t)psa->psa_max_addr);
    }
}

甚少文檔記錄的entitlement

com.apple.developer.kernel.extended-virtual-addressing

蘋果的文檔僅有一句話說明該能力:

Use this entitlement if your app has specific needs that require a larger addressable space. For example, games that memory map assets to stream to the GPU may benefit from a larger address space.

舉個(gè)例子:有的游戲需要將資源通過mmap的形式傳遞到GPU中渲染時(shí),更大的地址空間可提高其運(yùn)行效率。

描述上看,配置該選項(xiàng)時(shí),將開啟上面xnu的jumbo mode,地址的擴(kuò)充剛好能解決上面的崩潰問題。

做一次極限測(cè)試

為驗(yàn)證地址分配的極限值,簡(jiǎn)單做個(gè)實(shí)驗(yàn)(測(cè)試設(shè)備使用iPhone XR iOS 16 Beta 2):

通過malloc進(jìn)行連續(xù)的內(nèi)存分配(也可以用vm_allocate,閾值不一樣),閾值卡在1009字節(jié)(為什么是1009字節(jié),這里可以參考【ios 內(nèi)核】源碼解讀(3) 詳解ios是怎么malloc的(上) - 鐘路成的博客 (luchengzhong.github.io))。

for (size_t i = 0; i < SIZE_T_MAX; i++) {
    void *a = malloc(1009);
    if (a == NULL) {
        NSLog(@"error count: %lu", i);
        break;
    }
}

結(jié)果如下:

size = 1009 > SMALL_THRESHOLD (64位系統(tǒng)下1008字節(jié),32位系統(tǒng)下496)

內(nèi)存擴(kuò)展前malloc失敗閾值約 7065482 * 1009 = 6.63 GB

內(nèi)存擴(kuò)展后malloc失敗閾值約 56753881 * 1009 = 53.33 GB

當(dāng)然,在xnu的單元測(cè)試代碼中,也可找到j(luò)umbo mode相關(guān)的測(cè)試代碼,與上面的測(cè)試結(jié)果完全一致,即最多可分配53GB的空間。

#define GB (1ULL * 1024 * 1024 * 1024)

/*
 * This test expects the entitlement to be the enabling factor for a process to
 * allocate at least this many GB of VA space. i.e. with the entitlement, n GB
 * must be allocatable; whereas without it, it must be less.
 * This value was determined experimentally to fit on applicable devices and to
 * be clearly distinguishable from the default VA limit.
 */
#define ALLOC_TEST_GB 53

T_DECL(TESTNAME,
    "Verify that a required entitlement is present in order to be granted an extra-large "
    "VA space on arm64",
    T_META_NAMESPACE("xnu.vm"),
    T_META_CHECK_LEAKS(false))
{
    int i;
    void    *res;

    if (!dt_64_bit_kernel()) {
        T_SKIP("This test is only applicable to arm64");
    }

    T_LOG("Attemping to allocate VA space in 1 GB chunks.");

    for (i = 0; i < (ALLOC_TEST_GB * 2); i++) {
        res = mmap(NULL, 1 * GB, PROT_NONE, MAP_PRIVATE | MAP_ANON, 0, 0);
        if (res == MAP_FAILED) {
            if (errno != ENOMEM) {
                T_WITH_ERRNO;
                T_LOG("mmap failed: stopped at %d of %d GB allocated", i, ALLOC_TEST_GB);
            }
            break;
        } else {
            T_LOG("%d: %pn", i, res);
        }
    }

#if defined(ENTITLED)
    T_EXPECT_GE_INT(i, ALLOC_TEST_GB, "Allocate at least %d GB of VA space", ALLOC_TEST_GB);
#else
    T_EXPECT_LT_INT(i, ALLOC_TEST_GB, "Not permitted to allocate %d GB of VA space", ALLOC_TEST_GB);
#endif
}

可見,當(dāng)開啟com.apple.developer.kernel.extended-virtual-addressing時(shí),內(nèi)核的可分配空間確實(shí)有明顯提升。

上線效果與結(jié)論

從QQ瀏覽器的上線效果來看,JS相關(guān)的內(nèi)存分配Crash在14.0以上系統(tǒng)幾乎全部消失。上線第一天App崩潰率環(huán)比下降接近50%,效果顯著。

一鍵釋放iOS 64位App潛力

 

簡(jiǎn)單總結(jié):

  1. 蘋果很少在公開文檔中說明64位App在虛擬內(nèi)存使用上存在限制。而且很多App也并沒有像瀏覽器內(nèi)一樣,為業(yè)務(wù)靈活性而選擇將hippy、flutter等技術(shù)進(jìn)行大規(guī)模的組合使用,所以可能很多App其實(shí)并不會(huì)遇到虛擬內(nèi)存不足的情況。
  2. 上線效果也說明瀏覽器在混合開發(fā)的場(chǎng)景下,內(nèi)存優(yōu)化仍然存在很大的空間。因?yàn)镋xtended Virtual Addressing僅能緩解虛擬內(nèi)存不足的情況,并不意味著App的物理內(nèi)存也得到增加,對(duì)FOOM的治理仍然需要持續(xù)。
  3. 鑒于司內(nèi)有不少的著名組件都會(huì)使用mmap機(jī)制進(jìn)行內(nèi)存管理,建議在使用相關(guān)組件時(shí),控制好mmap的大小。
  4. 如果有需要在iPhone 12 Pro、M1 iPad、M1上運(yùn)行應(yīng)用,并希望解放更多的物理內(nèi)存,建議增加com.apple.developer.kernel.increased-memory-limit的能力聲明,實(shí)測(cè)在iPhone 13 Pro下可以增加1GB的可用物理內(nèi)存。
  5. ReactNative和類似框架在項(xiàng)目中使用較多的,建議需要考慮多個(gè)Context的復(fù)用,減少創(chuàng)建重復(fù)內(nèi)容,司內(nèi)外都有實(shí)踐證明該措施十分有效。
  6. 對(duì)于flutter一類的內(nèi)存優(yōu)化,可翻閱engine的相關(guān)代碼。flutter vm在創(chuàng)建時(shí)允許外部傳參控制vm行為,包括:old heap size、leak vm等。合適的參數(shù)可比較有效控制內(nèi)存占用。

以上源碼相關(guān)的內(nèi)容僅個(gè)人閱讀理解,如有錯(cuò)誤請(qǐng)指出。

分享到:
標(biāo)簽:iOS
用戶無頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定