设计一个移动应用的本地缓存机制

来源:互联网 发布:淘宝交易指数在哪里看 编辑:程序博客网 时间:2024/06/01 08:58
在手机应用程序开发中,为了减少与服务端的交互次数,加快用户的响应速度,一般都会在iOS设备中加一个缓存的机制,前面一篇文章介绍了iOS设备的内存缓存,这篇文章将设计一个本地缓存的机制。

功能需求

这个缓存机制满足下面这些功能。

1、可以将数据缓存到本地磁盘。

2、可以判断一个资源是否已经被缓存。如果已经被缓存,在请求相同的资源,先到本地磁盘搜索。

3、可以判断文件缓存什么时候过期。这里为了简单起见这里,我们在请求url资源的时候,给每次请求的文件设定一个过期的时间。

4、可以实现:如果文件已经被缓存,而且没有过期,这将本地的数据返回,否则重新请求url。

5、可以实现:如果文件下载不成功或者下载没有完成,下次打开程序的时候,移除这些没有成功或者没有下载完成的文件。

6、可以实现:同时请求或者下载多个资源。

设计实现:

1、设计一个CacheItem类,用来请求一个web连接,它的一个实例表示一个缓存项。这个CacheItem类,需要一个url创建一个NSURLConnection,去请求web资源。使用CacheItem类主要用来请求web资源。

[plain] view plaincopy
  1. /* ---------缓存项-------------- */  
  2.   
  3. @interface CacheItem : NSObject {  
  4. @public  
  5.   id<CacheItemDelegate> delegate;  
  6.     //web地址  
  7.   NSString              *remoteURL;  
  8. @private  
  9.     //是否正在下载  
  10.   BOOL                  isDownloading;  
  11.        //NSMutableData对象  
  12.   NSMutableData         *connectionData;  
  13.     //NSURLConnection对象  
  14.   NSURLConnection       *connection;  
  15. }  
  16.   
  17. /* -------------------------- */  
  18.   
  19. @property (nonatomic, retain) id<CacheItemDelegate> delegate;  
  20. @property (nonatomic, retain) NSString  *remoteURL;  
  21. @property (nonatomic, assign) BOOL      isDownloading;  
  22. @property (nonatomic, retain) NSMutableData *connectionData;  
  23. @property (nonatomic, retain) NSURLConnection *connection;  
  24.   
  25. /* ----------开始下载方法----------- */  
  26.   
  27. - (BOOL) startDownloadingURL:(NSString *)paramRemoteURL;  
  28.   
  29. @end  
2、在NSURLConnection开始请求之前,调用CachedDownloadManager类,来搜索和管理本地的缓存文件。将缓存文件的情况保存到一个字典类中。这个字典设计如下:
[plain] view plaincopy
  1. {  
  2.   "http://www.cnn.com" =     {  
  3.     DownloadEndDate = "2011-08-02 07:51:57 +0100";  
  4.     DownloadStartDate = "2011-08-02 07:51:55 +0100";  
  5.     ExpiresInSeconds = 20;  
  6.     ExpiryDate = "2011-08-02 07:52:17 +0100";  
  7.     LocalURL = "/var/mobile/Applications/ApplicationID/Documents/  
  8.                 httpwww.cnn.com.cache";  
  9.   };  
  10.   "http://www.baidu.com" =     {  
  11.     DownloadEndDate = "2011-08-02 07:51:49 +0100";  
  12.     DownloadStartDate = "2011-08-02 07:51:44 +0100";  
  13.     ExpiresInSeconds = 20;  
  14.     ExpiryDate = "2011-08-02 07:52:09 +0100";  
  15.     LocalURL = "/var/mobile/Applications/ApplicationID/Documents/  
  16.                 httpwww.oreilly.com.cache";  
  17.   };  
  18. }  

 上面这个字典里面嵌套了字典。里面那层字典表示一个缓存项的缓存信息:下载结束时间、下载开始时间、缓存有效时间、缓存过期时间、缓存到本地的路径。

   下面看下CachedDownloadManager类。用它来实现和封装我们的缓存策略。 

 

[plain] view plaincopy
  1. /* -----------CachedDownloadManager-------------- */  
  2.   
  3. @interface CachedDownloadManager : NSObject   
  4.                                    <CacheItemDelegate> {  
  5. @public  
  6.   id<CachedDownloadManagerDelegate>  delegate;  
  7. @private  
  8. //记录缓存数据的字典  
  9.   NSMutableDictionary                *cacheDictionary;  
  10.                                        //缓存的路径  
  11.   NSString                           *cacheDictionaryPath;  
  12. }  
  13.   
  14.   
  15. @property (nonatomic, assign)   
  16. id<CachedDownloadManagerDelegate> delegate;  
  17.   
  18. @property (nonatomic, copy)   
  19. NSMutableDictionary *cacheDictionary;  
  20.   
  21. @property (nonatomic, retain)   
  22. NSString *cacheDictionaryPath;  
  23.   
  24.   
  25. /* 保持缓存字典 */  
  26.   
  27. - (BOOL) saveCacheDictionary;  
  28.   
  29. /* 公有方法:下载 */  
  30.   
  31. - (BOOL)         download:(NSString *)paramURLAsString  
  32.    urlMustExpireInSeconds:(NSTimeInterval)paramURLMustExpireInSeconds  
  33. updateExpiryDateIfInCache:(BOOL)paramUpdateExpiryDateIfInCache;  
  34.   
  35. /* -------------------------- */  
  36.   
  37. @end  


   从上面代码可以看出,这个管理缓存的类中,有一个缓存字典:cacheDictionary,用来表示所有资源的缓存情况;cacheDictionaryPath用来表示缓存的路径;saveCacheDictionary用来将缓存字典归档到本地文件中。download:urlMustExpireInSeconds:updateExpiryDateIfInCache是一个公共接口,通过传递url、缓存过期时间、是否更新缓存过期时间三个参数来方便的使用,实现我们的缓存策略。

3、如果这个文件已经被下载,而且没有过期,则从本地获取文件的数据。如果文件已经过期,则重新下载。我们通过download:urlMustExpireInSeconds:updateExpiryDateIfInCache方法来实现,主要看这个方法的代码:

[plain] view plaincopy
  1. /* ---------下载-------------- */  
  2.   
  3. - (BOOL)         download:(NSString *)paramURLAsString  
  4.    urlMustExpireInSeconds:(NSTimeInterval)paramURLMustExpireInSeconds  
  5. updateExpiryDateIfInCache:(BOOL)paramUpdateExpiryDateIfInCache{  
  6.     
  7.   BOOL result = NO;  
  8.     
  9.   if (self.cacheDictionary == nil ||  
  10.       [paramURLAsString length] == 0){  
  11.     return(NO);  
  12.   }  
  13.     
  14.   paramURLAsString = [paramURLAsString lowercaseString];  
  15.   //根据url,从字典中获取缓存项的相关数据  
  16.   NSMutableDictionary *itemDictionary =   
  17.   [self.cacheDictionary objectForKey:paramURLAsString];  
  18.     
  19.   /* 使用下面这些变量帮助我们理解缓存逻辑 */  
  20.     //文件是否已经被缓存  
  21.   BOOL    fileHasBeenCached = NO;  
  22.     //缓存是否过期  
  23.   BOOL    cachedFileHasExpired = NO;  
  24.     //缓存文件是否存在  
  25.   BOOL    cachedFileExists = NO;  
  26.     //缓存文件能否被加载  
  27.   BOOL    cachedFileDataCanBeLoaded = NO;  
  28.     //缓存文件数据  
  29.   NSData  *cachedFileData = nil;  
  30.     //缓存文件是否完全下载  
  31.   BOOL    cachedFileIsFullyDownloaded = NO;  
  32.     //缓存文件是否已经下载  
  33.   BOOL    cachedFileIsBeingDownloaded = NO;  
  34.   //过期时间  
  35.   NSDate    *expiryDate = nil;  
  36.     //下载结束时间  
  37.   NSDate    *downloadEndDate = nil;  
  38.     //下载开始时间  
  39.   NSDate    *downloadStartDate = nil;  
  40.     //本地缓存路径  
  41.   NSString  *localURL = nil;  
  42.     //有效时间  
  43.   NSNumber  *expiresInSeconds = nil;  
  44.   NSDate    *now = [NSDate date];  
  45.     
  46.   if (itemDictionary != nil){  
  47.     fileHasBeenCached = YES;  
  48.   }  
  49.   //如果文件已经被缓存,则从缓存项相关数据中获取相关的值  
  50.   if (fileHasBeenCached == YES){  
  51.       
  52.     expiryDate = [itemDictionary   
  53.                   objectForKey:CachedKeyExpiryDate];  
  54.       
  55.     downloadEndDate = [itemDictionary  
  56.                        objectForKey:CachedKeyDownloadEndDate];  
  57.       
  58.     downloadStartDate = [itemDictionary  
  59.                          objectForKey:CachedKeyDownloadStartDate];  
  60.       
  61.     localURL = [itemDictionary  
  62.                 objectForKey:CachedKeyLocalURL];  
  63.       
  64.     expiresInSeconds = [itemDictionary  
  65.                         objectForKey:CachedKeyExpiresInSeconds];  
  66.     //如果下载开始和结束时间不为空,表示文件全部被下载  
  67.     if (downloadEndDate != nil &&   
  68.         downloadStartDate != nil){  
  69.       cachedFileIsFullyDownloaded = YES;  
  70.     }  
  71.       
  72.     /* 如果expiresInSeconds不为空,downloadEndDate为空,表示文件已经正在下载 */  
  73.     if (expiresInSeconds != nil &&  
  74.         downloadEndDate == nil){  
  75.       cachedFileIsBeingDownloaded = YES;  
  76.     }  
  77.       
  78.     /* 判断缓存是否过期 */  
  79.     if (expiryDate != nil &&  
  80.         [now timeIntervalSinceDate:expiryDate] > 0.0){  
  81.       cachedFileHasExpired = YES;  
  82.     }  
  83.       
  84.     if (cachedFileHasExpired == NO){  
  85.       /* 如果缓存文件没有过期,加载缓存文件,并且更新过期时间 */  
  86.       NSFileManager *fileManager = [[NSFileManager alloc] init];  
  87.         
  88.       if ([fileManager fileExistsAtPath:localURL] == YES){  
  89.         cachedFileExists = YES;  
  90.         cachedFileData = [NSData dataWithContentsOfFile:localURL];  
  91.         if (cachedFileData != nil){  
  92.           cachedFileDataCanBeLoaded = YES;  
  93.         } /* if (cachedFileData != nil){ */  
  94.       } /* if ([fileManager fileExistsAtPath:localURL] == YES){ */  
  95.         
  96.       [fileManager release];  
  97.         
  98.       /* 更新缓存时间 */  
  99.         
  100.       if (paramUpdateExpiryDateIfInCache == YES){  
  101.           
  102.         NSDate *newExpiryDate =   
  103.         [NSDate dateWithTimeIntervalSinceNow:  
  104.          paramURLMustExpireInSeconds];  
  105.           
  106.         NSLog(@"Updating the expiry date from %@ to %@.",   
  107.               expiryDate,   
  108.               newExpiryDate);  
  109.           
  110.         [itemDictionary setObject:newExpiryDate  
  111.                            forKey:CachedKeyExpiryDate];  
  112.           
  113.         NSNumber *expires =   
  114.         [NSNumber numberWithFloat:paramURLMustExpireInSeconds];  
  115.           
  116.         [itemDictionary setObject:expires  
  117.                            forKey:CachedKeyExpiresInSeconds];  
  118.       }  
  119.         
  120.     } /* if (cachedFileHasExpired == NO){ */  
  121.       
  122.   }  
  123.     
  124.   if (cachedFileIsBeingDownloaded == YES){  
  125.     NSLog(@"这个文件已经正在下载...");  
  126.     return(YES);  
  127.   }  
  128.     
  129.   if (fileHasBeenCached == YES){  
  130.       
  131.     if (cachedFileHasExpired == NO &&  
  132.         cachedFileExists == YES &&  
  133.         cachedFileDataCanBeLoaded == YES &&  
  134.         [cachedFileData length] > 0 &&  
  135.         cachedFileIsFullyDownloaded == YES){  
  136.         
  137.       /* 如果文件有缓存而且没有过期 */  
  138.         
  139.       NSLog(@"文件有缓存而且没有过期.");  
  140.         
  141.       [self.delegate   
  142.        cachedDownloadManagerSucceeded:self  
  143.        remoteURL:[NSURL URLWithString:paramURLAsString]  
  144.        localURL:[NSURL URLWithString:localURL]  
  145.        aboutToBeReleasedData:cachedFileData  
  146.        isCachedData:YES];  
  147.         
  148.       return(YES);  
  149.         
  150.     } else {  
  151.       /* 如果文件没有被缓存,获取缓存失败 */  
  152.       NSLog(@"文件没有缓存.");  
  153.       [self.cacheDictionary removeObjectForKey:paramURLAsString];  
  154.       [self saveCacheDictionary];  
  155.     } /* if (cachedFileHasExpired == NO && */  
  156.       
  157.   } /* if (fileHasBeenCached == YES){ */  
  158.     
  159.   /* 去下载文件 */  
  160.     
  161.     
  162.   NSNumber *expires =   
  163.   [NSNumber numberWithFloat:paramURLMustExpireInSeconds];  
  164.     
  165.   NSMutableDictionary *newDictionary =   
  166.   [[[NSMutableDictionary alloc] init] autorelease];  
  167.     
  168.   [newDictionary setObject:expires   
  169.                     forKey:CachedKeyExpiresInSeconds];  
  170.     
  171.     
  172.   localURL = [paramURLAsString  
  173.               stringByAddingPercentEscapesUsingEncoding:  
  174.               NSUTF8StringEncoding];  
  175.     
  176.   localURL = [localURL stringByReplacingOccurrencesOfString:@"://"  
  177.                                                  withString:@""];  
  178.     
  179.   localURL = [localURL stringByReplacingOccurrencesOfString:@"/"  
  180.                                                  withString:@"{1}quot;];  
  181.     
  182.   localURL = [localURL stringByAppendingPathExtension:@"cache"];  
  183.     
  184.   NSString *documentsDirectory =   
  185.   [self documentsDirectoryWithTrailingSlash:NO];  
  186.     
  187.   localURL = [documentsDirectory   
  188.               stringByAppendingPathComponent:localURL];  
  189.     
  190.   [newDictionary setObject:localURL  
  191.                     forKey:CachedKeyLocalURL];  
  192.     
  193.   [newDictionary setObject:now  
  194.                     forKey:CachedKeyDownloadStartDate];  
  195.     
  196.   [self.cacheDictionary setObject:newDictionary  
  197.                            forKey:paramURLAsString];  
  198.     
  199.   [self saveCacheDictionary];  
  200.     
  201.   CacheItem *item = [[[CacheItem alloc] init] autorelease];  
  202.   [item setDelegate:self];  
  203.   [item startDownloadingURL:paramURLAsString];  
  204.     
  205.   return(result);  
  206.     
  207. }  

4、下面我们设计缓存项下载成功和失败的两个委托方法:

[plain] view plaincopy
  1. @protocol CacheItemDelegate <NSObject>  
  2. //下载成功执行该方法  
  3. - (void) cacheItemDelegateSucceeded  
  4.   :(CacheItem *)paramSender  
  5.   withRemoteURL:(NSURL *)paramRemoteURL  
  6.   withAboutToBeReleasedData:(NSData *)paramAboutToBeReleasedData;  
  7.   
  8. //下载失败执行该方法  
  9. - (void) cacheItemDelegateFailed  
  10.   :(CacheItem *)paramSender  
  11.   remoteURL:(NSURL *)paramRemoteURL  
  12.   withError:(NSError *)paramError;  
  13.   
  14. @end  


   当我们下载成功的时候,修改缓存字典中的下载时间,表示已经下载完成,而且需要将请求的资源数据缓存到本地:

[plain] view plaincopy
  1. //缓存项的委托方法  
  2. - (void) cacheItemDelegateSucceeded:(CacheItem *)paramSender  
  3.          withRemoteURL:(NSURL *)paramRemoteURL  
  4.         withAboutToBeReleasedData:(NSData *)paramAboutToBeReleasedData{  
  5.     
  6.   //从缓存字典中获取该缓存项的相关数据  
  7.   NSMutableDictionary *dictionary =   
  8.   [self.cacheDictionary objectForKey:[paramRemoteURL absoluteString]];  
  9.   //取当前时间  
  10.   NSDate *now = [NSDate date];  
  11.   //获取有效时间  
  12.   NSNumber *expiresInSeconds = [dictionary   
  13.                                 objectForKey:CachedKeyExpiresInSeconds];  
  14.   //转换成NSTimeInterval  
  15.   NSTimeInterval expirySeconds = [expiresInSeconds floatValue];  
  16.   //修改字典中缓存项的下载结束时间  
  17.   [dictionary setObject:[NSDate date]  
  18.                  forKey:CachedKeyDownloadEndDate];  
  19.   //修改字典中缓存项的缓存过期时间  
  20.   [dictionary setObject:[now dateByAddingTimeInterval:expirySeconds]  
  21.                  forKey:CachedKeyExpiryDate];  
  22.   //保存缓存字典  
  23.   [self saveCacheDictionary];  
  24.     
  25.   NSString *localURL = [dictionary objectForKey:CachedKeyLocalURL];  
  26.     
  27.   /* 将下载的数据保持到磁盘 */  
  28.   if ([paramAboutToBeReleasedData writeToFile:localURL  
  29.                                    atomically:YES] == YES){  
  30.     NSLog(@"缓存文件到磁盘成功.");  
  31.   } else{  
  32.     NSLog(@"缓存文件到磁盘失败.");  
  33.   }  
  34.   //执行缓存管理的委托方法  
  35.   [self.delegate   
  36.    cachedDownloadManagerSucceeded:self  
  37.    remoteURL:paramRemoteURL  
  38.    localURL:[NSURL URLWithString:localURL]  
  39.    aboutToBeReleasedData:paramAboutToBeReleasedData  
  40.    isCachedData:NO];  
  41.     
  42.     
  43. }  

   如果下载失败我们需要从缓存字典中移除改缓存项:
[plain] view plaincopy
  1. //缓存项失败失败的委托方法  
  2. - (void) cacheItemDelegateFailed:(CacheItem *)paramSender  
  3.                        remoteURL:(NSURL *)paramRemoteURL  
  4.                        withError:(NSError *)paramError{  
  5.     
  6.   /* 从缓存字典中移除缓存项,并发送一个委托 */  
  7.     
  8.   if (self.delegate != nil){  
  9.       
  10.     NSMutableDictionary *dictionary =   
  11.     [self.cacheDictionary   
  12.      objectForKey:[paramRemoteURL absoluteString]];  
  13.       
  14.     NSString *localURL = [dictionary   
  15.                           objectForKey:CachedKeyLocalURL];  
  16.       
  17.     [self.delegate  
  18.      cachedDownloadManagerFailed:self  
  19.      remoteURL:paramRemoteURL  
  20.      localURL:[NSURL URLWithString:localURL]  
  21.      withError:paramError];  
  22.   }  
  23.     
  24.   [self.cacheDictionary   
  25.    removeObjectForKey:[paramRemoteURL absoluteString]];  
  26.     
  27. }  
5、加载缓存字典的时候,我们可以将没有下载完成的文件移除:
[plain] view plaincopy
  1. //初始化缓存字典  
  2.   NSString *documentsDirectory =   
  3.   [self documentsDirectoryWithTrailingSlash:YES];  
  4.   //生产缓存字典的路径  
  5.   cacheDictionaryPath =   
  6.   [[documentsDirectory   
  7.     stringByAppendingString:@"CachedDownloads.dic"] retain];  
  8.   //创建一个NSFileManager实例  
  9.   NSFileManager *fileManager = [[NSFileManager alloc] init];  
  10.   //判断是否存在缓存字典的数据  
  11.   if ([fileManager   
  12.        fileExistsAtPath:self.cacheDictionaryPath] == YES){  
  13.       NSLog(self.cacheDictionaryPath);  
  14.     //加载缓存字典中的数据  
  15.     NSMutableDictionary *dictionary =   
  16.     [[NSMutableDictionary alloc]   
  17.      initWithContentsOfFile:self.cacheDictionaryPath];  
  18.       
  19.     cacheDictionary = [dictionary mutableCopy];  
  20.       
  21.     [dictionary release];  
  22.       
  23.     //移除没有下载完成的缓存数据  
  24.     [self removeCorruptedCachedItems];  
  25.       
  26.   } else {  
  27.     //创建一个新的缓存字典  
  28.     NSMutableDictionary *dictionary =   
  29.     [[NSMutableDictionary alloc] init];  
  30.       
  31.     cacheDictionary = [dictionary mutableCopy];  
  32.       
  33.     [dictionary release];  
  34.       
  35.   }  
这样就基本上完成了我们需要的功能,下面看看我们如何使用我们设计的缓存功能。

例子场景:

    我们用一个UIWebView来显示stackoverflow这个网站,我们在这个网站的内容缓存到本地20秒,如果在20秒内用户去请求该网站,则从本地文件中获取内容,否则过了20秒,则重新获取数据,并缓存到本地。

    在界面上拖放一个button和一个webview控件,如下图。

 

    这样我们可以很方便使用前面定义好的类。我们在viewDidLoad 中实例化一个CachedDownloadManager,并设置它的委托为self。当下载完成的时候,执行CachedDownloadManager的下载成功的委托方法。

- (void)viewDidLoad {  [super viewDidLoad];    [self setTitle:@"本地缓存测试"];  CachedDownloadManager *newManager = [[CachedDownloadManager alloc] init];  self.downloadManager = newManager;  [newManager release];  [self.downloadManager setDelegate:self];    }

在button的点击事件中加入下面代码,请求stackoverflow :

  static NSString *url = @"http://stackoverflow.com";    [self.downloadManager download:url      urlMustExpireInSeconds:20.0f    updateExpiryDateIfInCache:YES];

    上面的代码表示将这个stackoverflow的缓存事件设置为20s,并且如果在20s内有相同的请求,则从本地获取stackoverflow的内容数据。updateExpiryDateIfInCache设置为yes表示:在此请求的时候,缓存时间又更新为20s,类似我们的session。如果设置成no,则第一次请求20s之后,该缓存就过期。

    请求完成之后会执行CachedDownloadManager的委托方法。我们将数据展示在uiwebview中,代码如下:

- (void) cachedDownloadManagerSucceeded:(CachedDownloadManager *)paramSender                              remoteURL:(NSURL *)paramRemoteURL                               localURL:(NSURL *)paramLocalURL                  aboutToBeReleasedData:(NSData *)paramAboutToBeReleasedData                           isCachedData:(BOOL)paramIsCachedData{      [webview loadData:paramAboutToBeReleasedData MIMEType:@"text/html" textEncodingName:@"UTF-8" baseURL:[NSURL URLWithString:@"http://stackoverflow.com"]];  }

这样我们就实现了20s的缓存。

效果:

第一次点击测试按钮:

 

20s内点击按钮,程序就从本地获取数据,比较快速的就显示出该网页了。

 

总结:

    本文通过代码和实例设计了一个iPhone应用程序本地缓存的方案。当然这个方案不是最好的,如果你有更好的思路,欢迎告诉我。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 电脑桌面上的微信图标不见了怎么办 精子总活力和运动力低怎么办 精子少畸形高怎么办能治好吗 小米无人机只能飞500米远怎么办 考察课作弊被老师抓了怎么办 手机中木马病毒了钱被盗走了怎么办 在搜不到校园网的情况下怎么办 刚买小鸡第四天晚上一直叫怎么办 碘131第四天晚上吃太撑了怎么办 出现请点击画面开始进行校准怎么办 大学毕业后才发现荒废了学业怎么办 房屋面积重新测量超了一倍怎么办 物管把业主的电断了怎么办 在人多的场合紧张不适应怎么办 人寿人身损害勘查结果有争议怎么办 批量性不良再限度样本规格内怎么办 老公说老婆不攒钱都买衣服了怎么办 华为手机获取安装包信息失败怎么办 液压电动脱模器不上升了怎么办 退伍军人证和身份证名字不付怎么办 车辆有违章需要短信提醒要怎么办 1969年退伍的退伍证丢了怎么办 江苏移动没实名认证停机了怎么办呀 亿达老年手机来电音量太大怎么办 挑三十六乘二的内螺纹丝瞳毛怎么办 内六角螺丝的冒滑了怎么办 孔里的内六角滑了怎么办 十字螺丝刀的头卡在螺母里怎么办 内六角扳手断在螺丝孔里怎么办 六棱螺丝刀折进螺丝里了怎么办 内6棱螺丝拧花了怎么办 只有pe模式可以用键盘鼠标怎么办 电脑重做了系统连不上无线网怎么办 联想手机死机了怎么办不可拆卸电池 手机界面弹出后大小无法调整怎么办 方向盘打方向瑟瑟的吱吱响怎么办 三脚架提升杆螺丝滑丝了怎么办 花土里面有很多细长的螺丝怎么办 防盗门外边上边缝宽螺丝松了怎么办 填充墙与框架梁不对齐怎么办 亚轧滚珠丝杠螺帽超程了怎么办