看什么看!点我呀!全栈程序员,免费入门到精通!
作者丨就叫yang
https://www.jianshu.com/p/5b132f0e31a3
在iOS开发中经常需要靠记录日志来调试应用程序、解决崩溃问题等,整理常用的日志输出和崩溃日志分析。
基于CocoaLumberjack 的 Swift使用封装库
https://github.com/xilankong/YangLogger
1、应用中有Bug。
2、Watchdog 超时机制
3、用户强制退出
4、低内存终止
5、其他违反系统规则的操作,大部分是内存问题
发生崩溃,系统会生成一份崩溃日志在本地,或者上传 ITC
NSRangeException等 NSException类
信号中断(SGIABRT)、非法指令信号(SIGILL)、总线错误信号(SIGBUS)、段错误信号(SIGSEGV)、访问一个已经释放的对象(EXC_BAD_ACCESS)
只能捕获一些异常崩溃,如 unrecognized selector、NSRangeException beyond bounds越界等Exception属错误
在Appdelegate 的 didFinishLaunchingWithOptions 中 添加
NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
方法实现如下
void UncaughtExceptionHandler(NSException *exception) {
/**
* 获取异常崩溃信息
*/
NSArray *callStack = [exception callStackSymbols];
NSString *reason = [exception reason];
NSString *name = [exception name];
NSString *content = [NSString stringWithFormat:@"========异常错误报告========\nname:%@\nreason:\n%@\ncallStackSymbols:\n%@",name,reason,[callStack componentsJoinedByString:@"\n"]];
//将崩溃信息持久化在本地,下次程序启动时、或者后台,将崩溃信息作为日志发送给开发者。
[[NSUserDefaults standardUserDefaults] setObject:content forKey:@"ExceptionContent"];
}
数组越界错误
NSMutableArray *array = [NSMutableArray array];
NSLog(@"%@",array[1]);
信号类型崩溃捕获,测试的时候如果测试Signal类型的崩溃,不要在xcode下的debug模式进行测试。因为系统的debug会优先去拦截。应该build好应用之后直接点击运行app进行测试。
在计算机科学中,信号(英语:Signals)是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。
在iOS中就是未被捕获的Objective-C异常(NSException),导致程序向自身发送了SIGABRT信号而崩溃。
SIGABRT–程序中止命令中止信号
SIGALRM–程序超时信号
SIGFPE–程序浮点异常信号
SIGILL–程序非法指令信号
SIGHUP–程序终端中止信号
SIGINT–程序键盘中断信号
SIGKILL–程序结束接收中止信号
SIGTERM–程序kill中止信号
SIGSTOP–程序键盘中止信号
SIGSEGV–程序无效内存中止信号
SIGBUS–程序内存字节未对齐中止信号
SIGPIPE–程序Socket发送失败中止信号
Appdelegate 的 didFinishLaunchingWithOptions 中 添加
signal(SIGHUP, SignalHandler);
signal(SIGINT, SignalHandler);
signal(SIGQUIT, SignalHandler);
signal(SIGABRT, SignalHandler);
signal(SIGILL, SignalHandler);
signal(SIGSEGV, SignalHandler);
signal(SIGFPE, SignalHandler);
signal(SIGBUS, SignalHandler);
signal(SIGPIPE, SignalHandler);
方法实现如下
void SignalHandler(int signal){
int32_t exceptionCount = OSAtomicIncrement32(&UncaughtExceptionCount);
if (exceptionCount > UncaughtExceptionMaximum)
{
return;
}
void* callstack[128];
int frames = backtrace(callstack, 128);
char **strs = backtrace_symbols(callstack, frames);
int i;
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (
i = UncaughtExceptionHandlerSkipAddressCount;
i < UncaughtExceptionHandlerSkipAddressCount &#43;
UncaughtExceptionHandlerReportAddressCount;
i&#43;&#43;)
{
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
NSLog(&#64;"%&#64;",backtrace);
NSLog(&#64;"%&#64;", [NSString stringWithFormat:
NSLocalizedString(&#64;"Signal %d was raised.", nil),
signal]);
}
UIView *tempView &#61; [[UIView alloc]init];
[tempView release];
//对象已经被释放&#xff0c;内存不合法&#xff0c;此块内存地址又没被覆盖&#xff0c;所以此内存内垃圾内存&#xff0c;所以调用方法的时候会导致SIGSEGV的错误
[tempView setNeedsDisplay];
或者说 我在堆内存中找栈内存地址
id x_id &#61; [self performSelector:&#64;selector(createNum)];
- (int)createNum {
return 10;
}
这种情况也是会导致SIGSEGV的错误的
如果在内存中释放不存在的空间&#xff0c;就会导致SIGABRT错误
Test * test &#61; {1, 2};
free(test);
内存地址不对齐会导致SIGBUS错误
char *s &#61; "hello world";
*s &#61; &#39;H&#39;;
信号捕获后 app卡死了
大部分这类型的错误会报错 EXC_BAD_ACCESS &#xff0c;而这种错误都是发生在内存问题&#xff0c;例如
1、访问数据为空、数据类型不对
2、操作了不该操作的对象&#xff0c;野指针
1、xcode可以用僵尸模式打印出对象 然后通过对象查找对应的代码位置
1、Edit Scheme - Diagnositics - Memory Management 勾选 Zombie Objects 和 Malloc Stack
2、会打印出
cyuyan[7756:17601127] *** -[UIViewController respondsToSelector:]: message sent to deallocated instance 0x7fe71240d390
这句开启僵尸模式后打出来的输出&#xff0c;包含了我们需要的 进程pid、崩溃地址&#xff0c;终端通过下面命令查看堆栈日志来找到崩溃代码
3、查找日志
sudo malloc_history 7756 0x7fe71240d390
2、覆写一个object的respondsToSelector方法
在 other c flags中加入-D FOR_DEBUG&#xff08;记住请只在Debug Configuration下加入此标记&#xff09;。这样当你程序崩溃时&#xff0c;Xcode的console上就会准确地记录了最后运行的object的方法。重写一个object的respondsToSelector方法&#xff0c;打印报错前的
#ifdef _FOR_DEBUG_
-(BOOL) respondsToSelector:(SEL)aSelector {
printf("SELECTOR: %s\n", [NSStringFromSelector(aSelector) UTF8String]);
return [super respondsToSelector:aSelector];
}
#endif
3、通过instruments的Zombies
引申&#xff1a;怎么定位到野指针的地方。如果还没定位到&#xff0c;这个对象被提前释放了&#xff0c;怎么知道该对象在什么地方释放的
一种是多线程&#xff0c;一种是野指针。这两种Crash都带随机性&#xff0c;我们要让随机crash变成不随机
把这一随机的过程变成不随机的过程。对象释放后在内存上填上不可访问的数据&#xff0c;其实这种技术其实一直都有&#xff0c;xcode的Enable Scribble就是这个作用。
1、Edit Scheme - Diagnositics - Memory Management 勾选 Malloc Scribble
但是此方法测试后暂时未解决
1、野指针
1、对象释放后内存没被改动过&#xff0c;原来的内存保存完好&#xff0c;可能不Crash或者出现逻辑错误&#xff08;随机Crash&#xff09;。
2、对象释放后内存没被改动过&#xff0c;但是它自己析构的时候已经删掉某些必要的东西&#xff0c;可能不Crash、Crash在访问依赖的对象比如类成员上、出现逻辑错误&#xff08;随机Crash&#xff09;。
3、对象释放后内存被改动过&#xff0c;写上了不可访问的数据&#xff0c;直接就出错了很可能Crash在objc_msgSend上面&#xff08;必现Crash&#xff0c;常见&#xff09;。
4、对象释放后内存被改动过&#xff0c;写上了可以访问的数据&#xff0c;可能不Crash、出现逻辑错误、间接访问到不可访问的数据&#xff08;随机Crash&#xff09;。
5、对象释放后内存被改动过&#xff0c;写上了可以访问的数据&#xff0c;但是再次访问的时候执行的代码把别的数据写坏了&#xff0c;遇到这种Crash只能哭了&#xff08;随机Crash&#xff0c;难度大&#xff0c;概率低&#xff09;&#xff01;&#xff01;
6、对象释放后再次release&#xff08;几乎是必现Crash&#xff0c;但也有例外&#xff0c;很常见&#xff09;。
2、内存处理不当、内存泄露
3、主线程UI长时间卡死&#xff0c;系统强制销毁app
死锁&#xff1f;
4、多线程切换访问引起的crash
多线程抢写数据库&#xff1f;
5、和服务端约定的数据结构变更导致操作数据类型问题、或者操作空
路径&#xff1a;
ios 10之后&#xff1a;设置 -> 隐私 -> 分析 -> 数据分析
ios 10之前&#xff1a;设置 -> 隐私 -> 诊断与用量
mac路径&#xff1a;~/Library/Logs/CrashReporter/MobileDevice/
可以看到所有和该电脑同步过的设备的崩溃日志(.crash文件)
为什么有部分crash无法收集到&#xff1f;
xcode查看设备日志并导出日志
Window - Devices - 选择设备 - 点击View Device Logs -> All logs可以看到所有的崩溃日志。
1、三方&#xff1a;bugly、crashlytics
2、后台、异步线程将上面描述的捕获到的崩溃上传服务器
3、线上的ITC上可能会有部分日志&#xff0c;可以通过Xcode同步下来崩溃与能耗日志
Xcode Window - Organizer - Crashes
下面是一份测试过程产生的崩溃日志
//进程信息
Incident Identifier: 3C3F8BF8-3099-4E82-92E1-8690212E8FF9
CrashReporter Key: bb5f9839ae661ab755f25eff65fee8fd41369628
Hardware Model: iPod5,1
Process: demo [973]
Path: /private/var/containers/Bundle/Application/0D3657DE-DE1E-4FF0-A0F7-C09EBC002763/demo.app/demo
Identifier: com.yanghuang.demo
Version: 17 (1.1.9)
Code Type: ARM (Native)
Parent Process: launchd [1]
//基本信息
Date/Time: 2017-08-22 16:11:49.49 &#43;0800
Launch Time: 2017-08-22 16:11:40.40 &#43;0800
OS Version: iOS 9.3.5 (13G36)
Report Version: 104
//异常
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000000e7ffdefe
Triggered by Thread: 0
Filtered syslog:
None found
//线程回溯
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libswiftCore.dylib 0x0033788c 0x1ac000 &#43; 1620108
1 ...wiftSwiftOnoneSupport.dylib 0x009b4830 0x9ac000 &#43; 34864
2 demo 0x00029288 0x24000 &#43; 21128
3 demo 0x00029414 0x24000 &#43; 21524
4 UIKit 0x25cd2754 0x25c87000 &#43; 309076
5 UIKit 0x25cd26e0 0x25c87000 &#43; 308960
6 UIKit 0x25cba6d2 0x25c87000 &#43; 210642
7 UIKit 0x25cd2004 0x25c87000 &#43; 307204
8 UIKit 0x25cd1c7e 0x25c87000 &#43; 306302
9 UIKit 0x25cca68e 0x25c87000 &#43; 276110
10 UIKit 0x25c9b124 0x25c87000 &#43; 82212
11 UIKit 0x25c996d2 0x25c87000 &#43; 75474
12 CoreFoundation 0x216e1dfe 0x21626000 &#43; 769534
13 CoreFoundation 0x216e19ec 0x21626000 &#43; 768492
14 CoreFoundation 0x216dfd5a 0x21626000 &#43; 761178
15 CoreFoundation 0x2162f228 0x21626000 &#43; 37416
16 CoreFoundation 0x2162f014 0x21626000 &#43; 36884
17 GraphicsServices 0x22c1fac8 0x22c16000 &#43; 39624
18 UIKit 0x25d03188 0x25c87000 &#43; 508296
19 demo 0x0002ff48 0x24000 &#43; 48968
20 libdyld.dylib 0x212d7872 0x212d5000 &#43; 10354
Thread 1 name: Dispatch queue: com.apple.libdispatch-manager
Thread 1:
0 libsystem_kernel.dylib 0x213ac2f8 0x21396000 &#43; 90872
1 libdispatch.dylib 0x212a1d60 0x2128b000 &#43; 93536
2 libdispatch.dylib 0x212a1abe 0x2128b000 &#43; 92862
... 省略部分内容
//二进制映像
Binary Images
0x24000 - 0x33fff demo armv7
0x140000 - 0x15bfff Masonry armv7 <9615e97c54d335f7821568396c65d324> /var/containers/Bundle/Application/0D3657DE-DE1E-4FF0-A0F7-C09EBC002763/demo.app/Frameworks/Masonry.framework/Masonry
第一部分是闪退进程的相关信息。
Incident Identifier 是崩溃报告的唯一标识符。
CrashReporter Key 是与设备标识相对应的唯一键值。虽然它不是真正的设备标识符&#xff0c;但也是一个非常有用的情报:如果你看到100个崩溃日志的CrashReporter Key值都是相同的&#xff0c;或者只有少数几个不同的CrashReport值&#xff0c;说明这不是一个普遍的问题&#xff0c;只发生在一个或少数几个设备上。
Hardware Model 标识设备类型。 如果很多崩溃日志都是来自相同的设备类型&#xff0c;说明应用只在某特定类型的设备上有问题。上面的日志里&#xff0c;崩溃日志产生的设备是iPhone 4s。
Process 是应用名称。中括号里面的数字是闪退时应用的进程ID。
这部分给出了一些基本信息&#xff0c;包括闪退发生的日期和时间&#xff0c;设备的iOS版本。如果有很多崩溃日志都来自iOS 6.0&#xff0c;说明问题只发生在iOS 6.0上。
Version: 崩溃进程的版本号. 这个值包含在 CFBundleVersion and CFBundleVersionString中.
Code Type: 崩溃日志所在设备的架构. 会是ARM-64, ARM, x86-64, or x86中的一个.
OS Version: 崩溃发生时的系统版本
异常信息会列出异常的类型、位置。
在这部分&#xff0c;你可以看到闪退发生时抛出的异常类型。还能看到异常编码和抛出异常的线程。根据崩溃报告类型的不同&#xff0c;在这部分你还能看到一些另外的信息。
Exception Codes: 处理器的具体信息有关的异常编码成一个或多个64位进制数。通常情况下&#xff0c;这个区域不会被呈现&#xff0c;因为将异常代码解析成人们可以看懂的描述是在其它区域进行的。
Exception Subtype: 供人们可读的异常代码的名字
Exception Message: 从异常代码中提取的额外的可供人们阅读的信息.
Exception Note: 不是特定于一个异常类型的额外信息.如果这个区域包含SIMULATED (这不是一个崩溃)然后进程没有崩溃&#xff0c;但是被watchdog杀掉了
Termination Reason: 当一个进程被终止的时的原因。
Triggered by Thread: 异常所在的线程.
这部分提供应用中所有线程的回溯日志。 回溯是闪退发生时所有活动帧清单。它包含闪退发生时调用函数的清单。看下面这行日志:
2 demo 0x00029288 0x24000 &#43; 21128
它包括四列:
帧编号—— 此处是2。
二进制库的名称 ——此处是 demo.
调用方法的地址 ——此处是 0x00029288.
第四列分为两个子列&#xff0c;一个基本地址和一个偏移量。此处是0×0x24000 &#43; 21128, 第一个数字指向文件&#xff0c;第二个数字指向文件中的代码行。
这部分列出了闪退时已经加载的二进制文件。
第一次看到崩溃日志上的回溯时&#xff0c;你或许会觉得它没什么意义。我们习惯使用方法名和行数&#xff0c;而非像这样的神秘位置:
2 demo 0x00029288 0x24000 &#43; 21128
将这些十六进制地址转化成方法名称和行数的过程称之为符号化。
从Xcode的Organizer窗口获取崩溃日志后过几秒钟&#xff0c;崩溃日志将被自动符号化。上面那行被符号化后的版本如下 :
2 demo 0x00029288 ViewController.crashAction(Any) -> () (ViewController.swift:36)
Xcode符号化崩溃日志时&#xff0c;需要访问与App Store上对应的应用二进制文件以及生成二进制文件时产生的 .dSYM 文件。必需完全匹配才行。否则&#xff0c;日志将无法被完全符号化。
所以&#xff0c;保留每个分发给用户的编译版本非常重要。提交应用前进行归档时&#xff0c;Xcode将保存应用的二进制文件。可以在Xcode Organizer的Archives标签栏下找到所有已归档的应用文件。
在发现崩溃日志时&#xff0c;如果有相匹配的.dSYM文件和应用二进制文件&#xff0c;Xcode会自动对崩溃日志进行符号化。如果你换到别的电脑或创建新的账户&#xff0c;务必将所有二进制文件移动到正确的位置&#xff0c;使Xcode能找到它们。
注意:
1、你必需同时保留应用二进制文件和.dSYM文件才能将崩溃日志完整符号化。每次提交到iTunes Connect的构建都必需归档。 .dSYM文件和二进制文件是特定绑定于每一次构建和后续构建的&#xff0c;即使来自相同的源代码文件&#xff0c;每一次构建也与其他构建不同&#xff0c;不能相互替换。如果你使用Build 和 Archive 命令,这些文件会自动放在适当位置。 如果不是使用Build 和 Archive命令&#xff0c;放在Spotlight能够搜索到的位置&#xff08;比如Home目录&#xff09;即可。
2、xcode debug方式打包默认没有DSYM文件&#xff0c;只需要修改对应的build options即可
build settings -> build options
把debug 项改成 DWARF with dSYM File 即可
需要文件&#xff1a;
1、demo.app
2、demo.app.dSYM
3、demo.crash (已获得)
4、symbolicatecrash
符号化前先检查一下三者的uuid是不是一致的,只有是一致的才能符号化成功。
查看xx.app文件的uuid的方法&#xff1a;
dwarfdump --uuid xxx.app/xxx (xxx工程名)
查看xx.app.dSYM文件的uuid的方法令&#xff1a;
dwarfdump --uuid xxx.app.dSYM (xxx工程名)
而.crash的uuid位于&#xff0c;crash日志中的Binary Images:中的第一行尖括号内。如&#xff1a;armv7<8bdeaf1a0b233ac199728c2a0ebb4165>
1、首先我们找到Archives目录(/Users/用户名/Library/Developer/Xcode/Archives/2017-08-22/demo)
2、找到对应app目录、对应的Archives文件、显示包内容打开。在dSYMs文件夹中找到demo.app.dSYM
在Products->Applications文件夹中找到 demo.app
3、找到symbolicatecrash
find /Applications/Xcode.app -name symbolicatecrash -type f
//终端输入上面命令、得到一个路径&#xff0c;这个路径就是symbolicatecrash的路径
拷贝到和上面文件同一目录
3: 在终端中输入以下命令
./symbolicatecrash -v demo.crash demo.app.dSYM
如果出现Error: "DEVELOPER_DIR" is not defined 再执行下面一句后再次执行
export DEVELOPER_DIR&#61;"/Applications/XCode.app/Contents/Developer"
然后用控制台打开你的demo.crash文件, 你就会看到编译后的crash文件, 同Xcode看到的崩溃日志一致。通过查看崩溃日志&#xff0c;可以轻易的找到崩溃原因并修正。
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 libswiftCore.dylib 0x0033788c 0x1ac000 &#43; 1620108
1 ...wiftSwiftOnoneSupport.dylib 0x009b4830 0x9ac000 &#43; 34864
2 demo 0x00029288 ViewController.crashAction(Any) -> () (ViewController.swift:36)
3 demo 0x00029414 &#64;objc ViewController.crashAction(Any) -> () (ViewController.swift:0)
4 UIKit 0x25cd2754 -[UIApplication sendAction:to:from:forEvent:] &#43; 80
5 UIKit 0x25cd26e0 -[UIControl sendAction:to:forEvent:] &#43; 64
6 UIKit 0x25cba6d2 -[UIControl _sendActionsForEvents:withEvent:] &#43; 466
7 UIKit 0x25cd2004 -[UIControl touchesEnded:withEvent:] &#43; 604
8 UIKit 0x25cd1c7e -[UIWindow _sendTouchesForEvent:] &#43; 646
9 UIKit 0x25cca68e -[UIWindow sendEvent:] &#43; 642
10 UIKit 0x25c9b124 -[UIApplication sendEvent:] &#43; 204
11 UIKit 0x25c996d2 _UIApplicationHandleEventQueue &#43; 5010
12 CoreFoundation 0x216e1dfe __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ &#43; 14
13 CoreFoundation 0x216e19ec __CFRunLoopDoSources0 &#43; 452
14 CoreFoundation 0x216dfd5a __CFRunLoopRun &#43; 794
15 CoreFoundation 0x2162f228 CFRunLoopRunSpecific &#43; 520
16 CoreFoundation 0x2162f014 CFRunLoopRunInMode &#43; 108
17 GraphicsServices 0x22c1fac8 GSEventRunModal &#43; 160
18 UIKit 0x25d03188 UIApplicationMain &#43; 144
19 demo 0x0002ff48 main (AppDelegate.swift:13)
20 libdyld.dylib 0x212d7872 start &#43; 2
因为低内存崩溃日志与普通崩溃日志略有不同。
iOS设备检测到低内存时&#xff0c;虚拟内存系统发出通知请求应用释放内存。这些通知发送到所有正在运行的应用和进程&#xff0c;试图收回一些内存。
如果内存使用依然居高不下&#xff0c;系统将会终止后台线程以缓解内存压力。如果可用内存足够&#xff0c;应用将能够继续运行而不会产生崩溃报告。否则&#xff0c;应用将被iOS终止&#xff0c;并产生低内存崩溃报告。
低内存崩溃日志上没有应用线程的堆栈回溯。相反&#xff0c;上面显示的是以内存页数为单位的各进程内存使用量。
被iOS因释放内存页终止的进程名称后面你会看到jettisoned 字样。如果看到它出现在你的应用名称后面&#xff0c;说明你的应用因使用太多内存而被终止了。
低内存崩溃日志看起来像这样&#xff1a;
通常&#xff0c;异常编码以一些文字开头&#xff0c;紧接着是一个或多个十六进制值&#xff0c;此数值正是说明闪退根本性质的所在。 从这些编码中&#xff0c;可以区分出闪退是因为程序错误、非法内存访问或者是其他原因。
下面是一些常见的异常编码:
0x8badf00d: 该编码表示应用是因为发生watchdog超时而被iOS终止的。 通常是应用花费太多时间而无法启动、终止或响应用系统事件。
0xbad22222: 该编码表示 VoIP 应用因为过于频繁重启而被终止。
0xdead10cc: 该代码表明应用因为在后台运行时占用系统资源&#xff0c;如通讯录数据库不释放而被终止 。
0xdeadfa11: 该代码表示应用是被用户强制退出的。根据苹果文档, 强制退出发生在用户长按开关按钮直到出现 “滑动来关机”, 然后长按 Home按钮。强制退出将产生 包含0xdeadfa11 异常编码的崩溃日志, 因为大多数是强制退出是因为应用阻塞了界面。
EXC_CRASH // SIGABRT: 进程异常退出。该异常类型崩溃最常见的原因是未捕获的Objective-C和C&#43;&#43;异常和调用abort()。如果他们需要太多的时间来初始化&#xff0c;程序将被终止&#xff0c;因为触发了看门狗。如果是因为启动的时候被挂起&#xff0c;所产生的崩溃报告异常类型(Exception Subtype)将是launch_hang。
EXC_BREAKPOINT // SIGTRAP&#xff1a;进程试图执行非法或未定义指令。这个进程可能试图通过一个配置错误的函数指针&#xff0c;跳到一个无效的地址。
SIGSEGV、SIGBUS 这些在前面捕获异常的内容有描述
注意: 在后台任务列表中关闭已挂起的应用不会产生崩溃日志。 一旦应用被挂起&#xff0c;它何时被终止都是合理的。所以不会产生崩溃日志。
思考&#xff1a;
1、工作中是否有遇到过线上反馈回来的问题&#xff0c;你是如何定位问题处理的
2、如果有特殊账户才出现的问题&#xff0c;但是又拿不到用户账户&#xff0c;该如何处理
推荐↓↓↓
长
按
关
注
?【16个技术公众号】都在这里&#xff01;
涵盖&#xff1a;程序员大咖、源码共读、程序员共读、数据结构与算法、黑客技术和网络安全、大数据科技、编程前端、Java、Python、Web编程开发、Android、iOS开发、Linux、数据库研发、幽默程序员等。