iOS后台任务的影响

背景

App后台存活时间长短直接对用户体验、业务功能、用户卸载几个方面都有较大的影响。如果存活时间过短,用户每次退到后台后都需要重启App,对于用户来说都是非常不友好的,非常有可能导致用户卸载App,这将是非常大的损失。因此我们着重调研了影响App后台存活时间的具体因素。经过调研发现,除了设备性能影响,后台任务是一个比较重要的方面,大家可能平时并没有重点关注过后台任务对后台存活时间的影响,对后台任务相关的内容并不了解。为了搞清楚后台任务对存活时间的影响和后台任务的使用场景笔者做了深入的调研,一方面是为了解决上面的两个问题,另一方面也是为了对这块的知识做储备。下面笔者将主要介绍一下后台任务使用时存在的坑,以及我们是如何模拟此类场景的。希望这篇文章结能够对你有帮助。

后台任务介绍

什么是后台任务

正常情况下,当App进入后台,会立即变为挂起状态,App中的任何执行都会停止。但是由于需求原因我们并不希望进入后台后马上停止我们的App中的操作,而是让我们的操作执行完成后在变为挂起状态。此时就用到了后台任务,开启后台任务后系统会继续让你的App保持活跃状态,直到任务结束后变为挂起状态。因此通过一句话总结就是,后台任务是系统提供给的让App在后台短暂保持活跃状态的功能。

后台任务与后台常驻App的区别

后台任务只是短暂的让App在后台保持活跃状态,任务结束后App会变为挂起状态,不用额外的权限申请。而后台常驻App是在后台一直保持活跃状态,例如音频类App、地图类App,即使进入后台,App一致是活跃状态。此类型App属于特殊类型,需要申请特殊权限。

后台任务使用

正确开启后台任务

  • api介绍
//Marks the start of a task that should continue if the app enters the background.
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithExpirationHandler:(void (^)(void))handler;
}

//Marks the end of a specific long-running background task.
- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)identifier;
  • 任务开启和结束
UIApplication *app;
UIBackgroundTaskIdentifier wbTaskID;
- (void)WBStartTask{
    bgTaskID = [app beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"wb__==: %lu", (unsigned long)wbTaskID);
        [app endBackgroundTask:wbTaskID];
    }];
}

- (void)WBEndTask{
    [app endBackgroundTask:wbTaskID];
}
  • 注意事项
    beginBackgroundTaskWithExpirationHandler:、endBackgroundTask:必须成对出现,否则会触发存后台任务泄漏。

后台任务的执行时间

不同版本的系统后台任务执行时间也不一样,明细如下:

系统理论时间(s)实测时间(s)
<= iOS6600-
iOS7 - iOS12180176
>= iOS133026

以上时间为打点测试,测试的是任务开始到结束的时间,并不是App由活跃到挂起的时间,挂起时间可以通过xcode控制台进行测试,这里就不详细介绍了。

后台任务对App后台存活时间的影响

错误的使用后台任务都会导致App在后台被系统强杀。主要包括两个方面:后台任务超时后台任务泄漏。后台任务超时是后台任务规定时间内没有完成相关的耗时操作并且没有结束任务,导致超时;而后台任务泄露是由于开启了后台任务,当任务执行完成后没有将任务结束导致的。因此如果不合理的使用后台任务,App大概率会被系统杀死,大大降低了App在后台的存活时间。

后台任务超时

什么时后台任务超时

一句话总结就是在后台任务中执行耗时操作,任务执行结束后耗时操作仍然在执行。

什么场景会导致后台任务超时

苹果已经给出了任务超时不同场景,笔者对场景进行了模拟,下面是笔者通过demo触发任务超时后的crash日志,通过code码进行了归类。

  • 0xbada5e47 开启的后台任务超过阈值,目前阈值为1000个,如果开启的后台任务超过1000,进入后台后会被系统强杀。

复现场景:开启大于1000个后台任务,demo验证后日志如下:

Incident Identifier: 58790B23-E0E9-4650-AC5E-4023A662C7BE
CrashReporter Key:   1610ccc74368b31360a22139adc06b185565627b
Hardware Model:      iPhone8,1
Process:             OOMTestProjcet [63403]
Path:                /private/var/containers/Bundle/Application/11D48709-CDFB-4A60-A730-FA84E8CFB989/OOMTestProjcet.app/OOMTestProjcet
Identifier:          wmm.OOMTestProjcet
Version:             1 (1.0)
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           wmm.OOMTestProjcet [6715]

Date/Time:           2019-08-29 11:02:07.5557 +0800
Launch Time:         2019-08-29 11:01:47.5072 +0800
OS Version:          iPhone OS 11.2.1 (15C153)
Baseband Version:    4.30.02
Report Version:      104
Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Termination Reason: Namespace ASSERTIOND, Code 0xbada5e47
Triggered by Thread:  0
Filtered syslog:
None found
Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib        	0x00000001824c17c4 kevent_id + 8
1   libdispatch.dylib             	0x0000000182346498 _dispatch_kq_poll + 208
2   libdispatch.dylib             	0x0000000182346e88 _dispatch_event_loop_wait_for_ownership$VARIANT$mp + 432
3   libdispatch.dylib             	0x0000000182338b44 _dispatch_sync_wait + 416
4   AssertionServices             	0x0000000184eeac54 -[BKSAssertion invalidate] + 84
5   UIKit                         	0x000000018bedbfc4 -[_UIBackgroundTaskInfo invalidate] + 44
6   UIKit                         	0x000000018c10d904 -[UIApplication _endBackgroundTask:] + 84
7   libdispatch.dylib             	0x000000018232aa14 _dispatch_client_callout + 16
8   libdispatch.dylib             	0x0000000182332200 _dispatch_block_invoke_direct$VARIANT$mp + 288
9   FrontBoardServices            	0x00000001850a67f8 __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 36
10  FrontBoardServices            	0x00000001850a649c -[FBSSerialQueue _performNext] + 404
11  FrontBoardServices            	0x00000001850a6a38 -[FBSSerialQueue _performNextFromRunLoopSource] + 56
12  CoreFoundation                	0x000000018295697c __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
13  CoreFoundation                	0x00000001829568fc __CFRunLoopDoSource0 + 88

此外,还有一种场景会触发0xbada5e47,当在任务到期后执行ExpirationHandler,如果此时ExpirationHandler中存在耗时操作,大概率会触发被系统强杀,错误码同样为0xbada5e47。因此要切记不要在到期ExpirationHandler中执行耗时操作。

  • 0xdead10cc这个类型是App挂起之后仍然有访问资源操作导致的,并且网上有人也反馈过此类型,如果挂起后,还存在数据库访问、系统资源访问等,App会马上被系统强杀。因此推测这应该是苹果为保护资源数据强制将App杀死的。

此类型笔者没有模拟出来,有模拟出来的同学可以一起交流一下。

  • 0x8badf00d这个code码大家都很熟悉了,这个是出发了苹果的“看门狗”机制,导致App被强杀了,主要是因为主线程卡住的时间太长导致的。后台任务中如果耗时太长卡住时间太久,同样也会触发“看门狗”。

复现场景:1、在后台任务中开启信号量耗时操作,demo验证日志如下:

Incident Identifier: DDF069DB-99FE-4BC7-A21B-AE4D7B0F7EFE
CrashReporter Key:   1610ccc74368b31360a22139adc06b185565627b
Hardware Model:      iPhone8,1
Process:             OOMTestProjcet [64970]
Path:                /private/var/containers/Bundle/Application/07A24A30-F1BE-4816-9167-E71D3E9350B1/OOMTestProjcet.app/OOMTestProjcet
Identifier:          wmm.OOMTestProjcet
Version:             1 (1.0)
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           wmm.OOMTestProjcet [7485]

Date/Time:           2019-08-29 16:03:07.1169 +0800
Launch Time:         2019-08-29 16:01:50.2218 +0800
OS Version:          iPhone OS 11.2.1 (15C153)
Baseband Version:    4.30.02
Report Version:      104
Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Termination Reason: Namespace ASSERTIOND, Code 0x8badf00d
Triggered by Thread:  0
Filtered syslog:
None found
Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib        	0x000000018249f5a4 semaphore_wait_trap + 8
1   libdispatch.dylib             	0x000000018232cf04 _dispatch_sema4_wait$VARIANT$mp + 24
2   libdispatch.dylib             	0x000000018232d8b4 _dispatch_semaphore_wait_slow + 140
3   OOMTestProjcet                	0x00000001029a5440 0x1029a0000 + 21568
4   UIKit                         	0x000000018bec46b4 -[UIApplication sendAction:to:from:forEvent:] + 96
5   UIKit                         	0x000000018bec4634 -[UIControl sendAction:to:forEvent:] + 80
6   UIKit                         	0x000000018beaf1dc -[UIControl _sendActionsForEvents:withEvent:] + 440
7   UIKit                         	0x000000018bec3f28 -[UIControl touchesEnded:withEvent:] + 576
8   UIKit                         	0x000000018bec3a48 -[UIWindow _sendTouchesForEvent:] + 2544
9   UIKit                         	0x000000018bebef60 -[UIWindow sendEvent:] + 3208
10  UIKit                         	0x000000018be8ff64 -[UIApplication sendEvent:] + 340
11  UIKit                         	0x000000018c7e531c __dispatchPreprocessedEventFromEventQueue + 2364
12  UIKit                         	0x000000018c7e78a8 __handleEventQueueInternal + 4760
13  UIKit                         	0x000000018c7e07c0 __handleHIDEventFetcherDrain + 152
14  CoreFoundation                	0x000000018295697c __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24
15  CoreFoundation                	0x00000001829568fc __CFRunLoop

复现场景:2、开启后台任务,进入后台开始在沙箱中写大量数据,保证写操作超过3分钟,日志如下:

Incident Identifier: 17143F71-7CB2-4A70-A7EA-1862B13C18AE
CrashReporter Key:   1610ccc74368b31360a22139adc06b185565627b
Hardware Model:      iPhone8,1
Process:             OOMTestProjcet [65618]
Path:                /private/var/containers/Bundle/Application/0C35DEAD-4D08-4F40-A0D3-B005580B3FE1/OOMTestProjcet.app/OOMTestProjcet
Identifier:          wmm.OOMTestProjcet
Version:             1 (1.0)
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           wmm.OOMTestProjcet [7875]

Date/Time:           2019-08-29 17:58:46.0048 +0800
Launch Time:         2019-08-29 17:55:42.5324 +0800
OS Version:          iPhone OS 11.2.1 (15C153)
Baseband Version:    4.30.02
Report Version:      104
Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Termination Reason: Namespace ASSERTIOND, Code 0x8badf00d
Triggered by Thread:  0
Filtered syslog:
None found
Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib        	0x00000001824a1014 write + 8
1   Foundation                    	0x00000001832bf950 _NSWriteToFileDescriptorWithProgress + 216
2   Foundation                    	0x000000018338ab34 ___NSWriteDataToFileWithExtendedAttributes_block_invoke + 76
3   Foundation                    	0x00000001832bf864 -[NSData+ 141412 (NSData) enumerateByteRangesUsingBlock:] + 88
4   Foundation                    	0x00000001832bdc08 _NSWriteDataToFileWithExtendedAttributes + 576
5   Foundation                    	0x00000001833a7a24 writeStringToURLOrPath + 224
6   OOMTestProjcet                	0x0000000102665c5c 0x102660000 + 23644
7   OOMTestProjcet                	0x0000000102665960 0x102660000 + 22880
8   UIKit                         	0x000000018c0f38f0 -[UIApplication workspaceNoteAssertionExpirationImminent:] + 284
9   libdispatch.dylib             	0x000000018232aa14 _dispatch_client_callout + 16
10  libdispatch.dylib             	0x0000000182332200 _dispatch_block_invoke_direct$VARIANT$mp + 288
11  FrontBoardServices            	0x00000001850a67f8 __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 36
12  FrontBoardServices            	0x00000001850a649c -[FBSSerialQueue _performNext] + 404
13  FrontBoardServices            	0x00000001850a6a38 -[FBSSerialQueue _performNextFromRunLoopSource] + 56
14  CoreFoundation                	0x000000018295697c __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 24

以上两种场景复现的都是ASSERTIOND类型的0x8badf00d

复现场景:3、开启后台任务,在任务块外执行大量的写文件操作,写操作耗时超过3分钟,日志如下:

Incident Identifier: 1AFADC47-B4ED-4E3A-9214-337A21980B76
CrashReporter Key:   1610ccc74368b31360a22139adc06b185565627b
Hardware Model:      iPhone8,1
Process:             CPUTestTwo [66835]
Path:                /private/var/containers/Bundle/Application/BCAC7616-0A9B-419F-BE1C-B4809BC06A01/CPUTestTwo.app/CPUTestTwo
Identifier:          wmm.CPUTestTwo
Version:             1 (1.0)
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           wmm.CPUTestTwo [8128]

Date/Time:           2019-08-30 11:37:11.3806 +0800
Launch Time:         2019-08-30 11:36:10.0978 +0800
OS Version:          iPhone OS 11.2.1 (15C153)
Baseband Version:    4.30.02
Report Version:      104
Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Termination Reason: Namespace SPRINGBOARD, Code 0x8badf00d
Termination Description: SPRINGBOARD, scene-update watchdog transgression: wmm.CPUTestTwo exhausted real (wall clock) time allowance of 10.00 seconds |  | ProcessVisibility: Foreground | ProcessState: Running | WatchdogEvent: scene-update | WatchdogVisibility: Foreground | WatchdogCPUStatistics: ( | "Elapsed total CPU time (seconds): 1.870 (user 1.870, system 0.000), 22% CPU", | "Elapsed application CPU time (seconds): 1.284, 15% CPU" | )
Triggered by Thread:  0
Filtered syslog:
None found
Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib        	0x00000001824a1014 write + 8
1   Foundation                    	0x00000001832bf950 _NSWriteToFileDescriptorWithProgress + 216
2   Foundation                    	0x000000018338ab34 ___NSWriteDataToFileWithExtendedAttributes_block_invoke + 76
3   Foundation                    	0x00000001832bf864 -[NSData+ 141412 (NSData) enumerateByteRangesUsingBlock:] + 88
4   Foundation                    	0x00000001832bdc08 

上面场景复现的是SPRINGBOARD类型的0x8badf00d

后台任务泄露

什么是后台任务泄露

后台任务泄露是开启后台任务后当任务执行完成没有执行结束任务操作,导致开启的任务无法结束,此时就出现了任务泄漏。

什么场景会导致后台任务泄露

首先我们来先看一段代码:

- (void)WBStartTask{
    self.wbtaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"wbtaskid__==: %lu", (unsigned long)self.wbtaskId);
        [[UIApplication sharedApplication] endBackgroundTask:self.wbtaskId];
    }];
}

- (void)WBEndBgTask{
    [[UIApplication sharedApplication] endBackgroundTask:self.wbtaskId];
}

上面的代码如果WBStartTask执行两次,就一定会出现后台任务泄露,因为self.wbtaskId第二次会被赋予一个新的id,之前的self.wbtaskId就丢失了,无法正确调用end。日志如下:

Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFYTermination
Reason: Namespace SPRINGBOARD,Code 0x8badf00d

可以看到日志code也是0x8badf00d, 那怎么判断触发0x8badf00d是“看门狗”导致的还是任务泄露导致的呢?这就要看主线程的崩溃日志了,如下:

Thread 0 Crashed:0  
libsystem_kernel.dylib         0x000000018472be08 0x18472b000 + 35921  
libsystem_kernel.dylib         0x000000018472bc80 0x18472b000 + 32002  
CoreFoundation                 0x0000000184c6ee40 0x184b81000 + 9744003  
CoreFoundation                 0x0000000184c6c908 0x184b81000 + 9648724  
CoreFoundation                 0x0000000184b8cda8 0x184b81000 + 485525  
GraphicsServices               0x0000000186b6f020 0x186b64000 + 450886  
UIKit                         0x000000018eb6d78c 0x18e850000 + 32664447  
Messenger                     0x0000000103015ee4 0x102ff8000 + 1225968   libdyld.dylib                 0x000000018461dfc0 0x18461d000 + 4032

这个日志表示当前的UI线程处于闲置状态,这种状态下被系统杀死,大概率就是因为后台任务泄露导致的。注意:不是说所有的这种日志都是后台任务导致的,而是错误码为0x8badf00d,此时被杀的原因大概率是后台任务泄露,不是绝对的。下面我们来复现一下。

复现场景:1、多次开启后台任务,并且没有在后台任务块中执行endBackgroundTask。2、开启一个后台任务,在ExpirationHandler块中开子线程做耗时操作,然后回到主线程执行endBackgroundTask。3、开启一个后台任务,然后开启子线程执行大量耗时操作,经过demo验证日志如下:

Incident Identifier: 3E16AE66-9CBE-43EC-AABE-CB136E415BAA
CrashReporter Key:   1610ccc74368b31360a22139adc06b185565627b
Hardware Model:      iPhone8,1
Process:             OOMTestProjcet [65256]
Path:                /private/var/containers/Bundle/Application/6D8FA48B-6DCE-4E7B-821E-3A152ED47A04/OOMTestProjcet.app/OOMTestProjcet
Identifier:          wmm.OOMTestProjcet
Version:             1 (1.0)
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           wmm.OOMTestProjcet [7645]

Date/Time:           2019-08-29 16:57:19.0419 +0800
Launch Time:         2019-08-29 16:54:13.7964 +0800
OS Version:          iPhone OS 11.2.1 (15C153)
Baseband Version:    4.30.02
Report Version:      104
Exception Type:  EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note:  EXC_CORPSE_NOTIFY
Termination Reason: Namespace ASSERTIOND, Code 0x8badf00d
Triggered by Thread:  0
Filtered syslog:
None found
Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libsystem_kernel.dylib        	0x000000018249f568 mach_msg_trap + 8
1   libsystem_kernel.dylib        	0x000000018249f3e0 mach_msg + 72
2   CoreFoundation                	0x0000000182956308 __CFRunLoopServiceMachPort + 196
3   CoreFoundation                	0x0000000182953ed4 __CFRunLoopRun + 1424
4   CoreFoundation                	0x0000000182873e58 CFRunLoopRunSpecific + 436
5   GraphicsServices              	0x0000000184720f84 GSEventRunModal + 100
6   UIKit                         	0x000000018bef367c UIApplicationMain + 236
7   OOMTestProjcet                	0x0000000100d3d838 0x100d38000 + 22584
8   libdyld.dylib                 	0x000000018239056c start + 4

此外,在iOS11.0.2上,出现了“看门狗”相关的crash,该crash跟通知相关,这是iOS11系统上的一个bug,在前台收到通知后,进入后台,短时间内会被杀死。该问题苹果官方解释11.2系统上已解决。日志如下:

Exception Type: EXC_CRASH (SIGKILL)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Termination Reason: Namespace &#x3c;0xF&#x3e;, Code 0x8badf00d
Triggered by Thread: 0
Filtered syslog:
None found
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libsystem_kernel.dylib         0x0000000186554bc4 mach_msg_trap + 8
1 libsystem_kernel.dylib         0x0000000186554a3c mach_msg + 72
2 CoreFoundation                  0x0000000186a03ce4 __CFRunLoopServiceMachPort + 196
3 CoreFoundation                  0x0000000186a018b0 __CFRunLoopRun + 1424
4 CoreFoundation                  0x00000001869222d8 CFRunLoopRunSpecific + 436
5 GraphicsServices                0x00000001887b3f84 GSEventRunModal + 100
6 UIKit                                    0x000000018fecf880 UIApplicationMain + 208
7 TerminateTest                     0x0000000104d6096c 0x104d58000 + 35180
8 libdyld.dylib                         0x000000018644656c start + 4

总结

本文从后台任务与后台常驻App的区别慢慢引出了后台任务的相关技术,并且在后台任务使用上和后台任务存在的壁垒分析了不同场景下使用后台任务存在的问题,以及不同场景下触发的错误日志。笔者希望这些场景分析以及错误日志可以帮助大家更好的了解后台任务,能够在平时使用后台任务时引起警惕,当然这里并不是强调后台任务不好或者后台任务存在风险,只是让大家快速了解并认识后台任务,将来减少踩坑风险,仅此而已。如有纰漏,敬请指正。

作者介绍

王盟盟,58 同城 – 平台技术部 – iOS 技术部 高级研发工程师,主要负责直播、性能优化相关工作。

参考文献

https://mp.weixin.qq.com/s/r0Q7um7P1p2gIb0aHldyNw 微信内存监控方案

https://www.jianshu.com/p/deee6fedb510 iOS内存分析下-前台内存耗尽闪退(FOOM)

https://satanwoo.github.io/2017/10/18/abort/ iOS内存abort(Jetsam) 原理探究

http://www.cocoachina.com/articles/23281 基于 JetsamEvent 探究 APP 最大内存占用上限

https://engineering.fb.com/ios/reducing-fooms-in-the-facebook-ios-app/ facebook内存警告的处理方案

http://mrpeak.cn/blog/ios-hard-stall-detection/ 检测内存oom的分析

https://juejin.im/post/5c28646f5188257abf1d947d iOS Out-Of-Memory 原理阐述及方案调研

http://www.cocoachina.com/articles/23981 OOM探究:XNU 内存状态管理

https://juejin.im/post/5bec0efcf265da61273cf333 iOS 内存管理研究

https://jinxuebin.cn/2019/07/OOM%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6/ iOS中OOM底层原理探究

https://blog.csdn.net/u011146511/article/details/76653168 iOS 之苹果运行机制总结

https://www.jianshu.com/p/7c0bec45f7c1 iOS App 后台任务的坑

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值