分类: IOS技术

IOS技术

ios7 新特性:module研究

在苹果官网下载了名叫PrivacyPrompts的demo,结果在ios7系统真机上编译报错。

发现原因是因为使用了@import UIKit;这种语法引起的,该语法被称为module,具体可参考如下链接:

http://stackoverflow.com/questions/18947516/import-vs-import-ios-7
后来自建了demo测试只改动一行代码,把appdelegate中的

#import 改为@import UIKit;仍然报错,现在没有找到对应的解决办法,在网上也无资料可查。

结论:ios7中的module特性可能尚未成熟或者存在配置上的问题,目前真机无法使用。 也请知道该问题解决办法的朋友给我反馈,谢谢!

tensorflowlite iOS集成实战全记录

首先:tensorflow官网的访问需要fanqiang,请注意。

step1 (*快*基础的体验,官网demo):

https://www.tensorflow.org/lite/demo_ios

这里主要做了哪些事呢?

1.github repo里面包含了demo工程。

2.github repo里面运行脚本可以生成所需要的模型,放到demo工程中。

3.运行pod会获取到tensorflow_lite.framework;通过pod可以很容易的集成库,全程傻瓜式。

那么发散一点,我们如果不想去下载github的完整repo,那么可以从别人那里获取到demo工程,找一个合适的.tflite模型文件,并且手动集成tensorflow_lite.framework(这里包含了头文件和执行文件),也可以把项目跑起来。

项目设置:C++11 support (or later) should be enabled by setting C++ Language Dialect to GNU++11 (or GNU++14), and C++ Standard Library to libc++.

实际测试中发现使用c++11的设置也是可以的,不一定非要选择GNU++(笔者自己的项目另外一个依赖需要使用c++11否则无法编译通过,所以需要研究这个)。

另外,还有一个路径相关的问题,只需要在demo中小小的修改:

//#include “tensorflow/lite/kernels/register.h”//#include “tensorflow/lite/model.h”#include “tensorflow/contrib/lite/kernels/register.h”#include “tensorflow/contrib/lite/model.h”
这是因为用户自己编译的路径和framework的路径有差异引起的,知道这个就很容易解决了,是google的问题。

step2(手动集成tensorflow_lite.framework):

这里主要是需要手动设置Framework Search paths 和 header search paths:把framework的对应路径填充进来,让project能够找到。

例:

Framework Search paths-> ‘${SRCROOT}/tensorflow_lite.framework’

header search paths-> ‘${SRCROOT}/tensorflow_lite.framework/Headers’

另外这个时候跑项目,会报一个错误 “ Undefined symbols “_cblas_sgemm”(google之后就会发现这是因为没有引入accelerate库(高性能数学运算库)引起的,引入之即可解决)。

step3(自己生成.a库,自己集成头文件到项目):

https://www.tensorflow.org/lite/ios

按照上面文章的building部分,应该就会很容易的生成所需要的.a库了;但问题还有头文件。

google建议我们做如下操作:

The Header Search paths needs to contain:

the root folder of tensorflow,
tensorflow/lite/downloads
tensorflow/lite/downloads/flatbuffers/include
首先这里的路径和实际路径是不一致的,我花了一会才找到实际的路径。重要的是这样做对于一个稳定的项目只是简单集成我认为是不科学的,我们需要的只是头文件而已。

所以我先做了如下尝试:

把tensorflow_lite.framework里面的头文件拿到这里来用,岂不是很完美?

理想很丰满,现实很骨感。实践表明不知道什么原因这两者之间是不能匹配的,当你解决了编译不通过的问题,跑起来的时候会crash:”+[CATransaction synchronize] called within transaction Loaded model 1resolved reporter”。

简单粗暴的理解,我认为生成.a库里面的lite文件夹下面所有的.h我们都要包含之,但lite文件夹里面有很多其他的文件,如果全部加入工程中,结果就是一大堆的报错无法编译通过。

因此,我尝试使用python脚本来解决:

#!/usr/bin/python

— coding: utf-8 —
import os
def gci(filepath):
#遍历filepath下所有文件,包括子目录
files = os.listdir(filepath)
for fi in files:
fi_d = os.path.join(filepath,fi)
if os.path.isdir(fi_d):
gci(fi_d)
else:
tempP = os.path.join(filepath,fi_d)
if os.path.splitext(tempP)[1]!=’.h’:
os.remove(tempP)
#print tempP

#递归遍历/root目录下所有文件
gci(’/Users/zhenweiguan/Desktop/lite’)
这个脚本的作用是去除所有除了.h以外的文件。

将之集成到工程中,还是会报一些错误,检查发现是因为里面有子工程工程文件夹和asset资源文件夹,手动删除之。

再次集成这些头文件,并引入path,bingo,终于跑起来了。

结语:以上是我个人的踩坑全记录,对于想要*快速集成tensorflowlite的同学来说,*好的方法应该就是下载*新的.framework文件来进行手动集成(当然了,如果项目支持pod会更简单)。

iOS App后台保活

笔者查询了相关资料后发现,iOS App可以实现后台保活。

短时间保活的方式有beginBackgroundTaskWithName;

App长时间保活的方式有:播放无声音乐、后台持续定位、后台下载资源、BGTaskScheduler等;

唤醒App的方式有:推送、VoIP等;

导读

本文分为如下几部分:

App 运行状态、及状态变化

App 后台保活方式简介

短时间App后台保活

Background Modes AVAudio,AirPlay,and Picture in Picture

Background Modes Location updates

BGTaskScheduler (iOS13.0+)

1

App 运行状态、及状态变化

不低于iOS13.0的设备端App 运行状态

%title插图%num

不低于iOS13.0设备端App 运行状态

iOS13.0+的设备,支持多场景,共有上图中的Unattached、Foreground Inactive、Foreground Active、Forground Inactive、Background、Suspended 6种状态。

1、Unattached:多个场景的情况,如果创建的场景不是当前显示的场景,那么场景处于Unattached状态;

2、Foreground Inactive:应用启动后,显示启动图的过程中,处于Foreground Inactive状态;

3、Forground Active:应用启动后,显示出来我们设置的rootViewController之后,场景处于Forground Active;

4、Foreground Inactive:应用启动后,场景处于显示状态,数据加载完毕,且用户和App没有交互过程中,处于Forground Inactive状态;

5、Background:用户点击Home键、或者是切换App后、锁屏后,应用进入Background状态;

6、Suspended:进入Background后,应用的代码不执行后,应用进入Suspended状态;(代码是否在运行,可以在应用中写定时器,定时输出内容,从Xcode控制台,或Mac端控制台查看是否有输出内容来判断)

低于iOS13.0的设备端App 运行状态

%title插图%num

低于iOS13.0设备端App 运行状态

上图是低于iOS13.0的设备端App的运行状态,分别是Not Running、Foreground Inactive、Foreground Active、Forground Inactive、Background、Suspended 6种状态。

Not Running:指用户没有启动App,或用户Terminate App 后,App处于的状态;其他的五种状态和不低于iOS13.0的设备端App的运行状态意义相同。

App 进入后台状态变化

笔者写了个定时器,定时输出“普通定时器进行中”,可以看到,应用进入后台后,基本上立刻,就没有内容输出了。笔者认为可以认为此时App 已经进入Suspended的状态。

%title插图%num

App 进入后台

下边笔者介绍下,尝试的App后台保活方式。

2

iOS App 后台保活方式简介

短时间App后台保活

beginBackgroundTaskWithName 和 endBackgroundTask

笔者尝试过使用相关API,测试过2款手机。

对于系统版本低于iOS13(iOS 12.3)的设备(iPhone6 Plus)后台运行时间约3分钟(175秒);

对于系统版本不低于iOS13(

iOS 13.0)的设备(iPhone6 Plus)后台运行时间约31秒;

播放无声音乐

App 进入后台后,播放无声音乐,适用于音视频类App。

笔者对逆向不了解,从iOS项目技术还债之路《一》后台下载趟坑中得知,腾讯视频、爱奇艺采用了播放无声音乐保活的方式。

后台持续定位

对于定位类App,持续定位App,可以实现App后台保活。定位类App需要后台保活,像系统的地图应用,在导航的时候切换App的时候,就需要后台保活。

后台下载资源

对于需要下载资源的App,需要后台下载资源,如我们在某App下载资源的时候,我们希望在切换App时候,或者App退出后台后,资源仍然继续下载,这样当我们打开App的时候,资源已经下载好了。

BackgroundTasks

BackgroundTasks.framework 是iOS13新增的framework,笔者认为此framework中的API可以在信息流类的App中发挥作用。

3

短时间App后台保活

系统版本低于iOS13.0的设备

系统版本低于iOS13.0的设备,在应用进入后台的时候,开始后台任务([[UIApplication sharedApplication] beginBackgroundTaskWithName:)。在应用进入前台时或后台任务快过期的回调中,终止后台任务([[UIApplication sharedApplication] endBackgroundTask:)。

示例代码如下:

– (void)applicationDidEnterBackground:(UIApplication *)application {

self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithName:kBgTaskName expirationHandler:^{
if (self.backgroundTaskIdentifier != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}
}];
}
– (void)applicationWillEnterForeground:(UIApplication *)application {

[[UIApplication sharedApplication] endBackgroundTask: self.backgroundTaskIdentifier];
}
添加相关代码后,笔者在iOS12.4的6 Plus上测试结果如下,应用在进入后台后,大概还运行了175秒。

2019-12-29 19:06:55.647288+0800 QiAppRunInBackground[1481:409744] -[AppDelegate applicationDidEnterBackground:]:应用进入后台DidEnterBackground
2019-12-29 19:06:56.256877+0800 QiAppRunInBackground[1481:409744] 定时器运行中
….
2019-12-29 19:09:50.812460+0800 QiAppRunInBackground[1481:409744] 定时器运行中
系统版本不低于iOS13.0的设备

– (void)sceneDidEnterBackground:(UIScene *)scene API_AVAILABLE(ios(13.0)){

self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithName:kBgTaskName expirationHandler:^{
if (self.backgroundTaskIdentifier != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}
}];
}
– (void)sceneWillEnterForeground:(UIScene *)scene API_AVAILABLE(ios(13.0)){

[[UIApplication sharedApplication] endBackgroundTask: self.backgroundTaskIdentifier];
}
添加相关代码后,笔者在iOS13.0的6s上测试结果如下,应用在进入后台后,大概还运行了31秒。
%title插图%num

iOS13.0+ App 进入后台

Xs·H 提到过,如果持续后台播放无声音频或是使用后台持续定位的方式实现iOS App后台保活,会浪费电量,浪费CPU,所以一般情况下,使用这种短时间延长App 后台保活的方式,应该够开发者做需要的操作了。

4

Background Modes AVAudio,AirPlay,and Picture in Picture

对于音视频类App,如果需要后台保活App,在App 进入后台后,可以考虑先使用短时间保活App的方式,如果后台保活App方式快结束后,还没处理事情,那么可以考虑使用后台播放无声音乐。相关示例代码如下。

– (AVAudioPlayer *)player {

if (!_player) {
NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@”SomethingJustLikeThis” withExtension:@”mp3″];
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
audioPlayer.numberOfLoops = NSUIntegerMax;
_player = audioPlayer;
}
return _player;
}
[self.player prepareToPlay];
系统版本低于iOS13.0的设备

– (void)applicationDidEnterBackground:(UIApplication *)application {

NSLog(@”%s:应用进入后台DidEnterBackground”, __FUNCTION__);
self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithName:kBgTaskName expirationHandler:^{

if ([QiAudioPlayer sharedInstance].needRunInBackground) {
[[QiAudioPlayer sharedInstance].player play];
}
if (self.backgroundTaskIdentifier != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}
}];
}
– (void)applicationWillEnterForeground:(UIApplication *)application {

NSLog(@”%s:应用将进入前台WillEnterForeground”, __FUNCTION__);
if ([QiAudioPlayer sharedInstance].needRunInBackground) {
[[QiAudioPlayer sharedInstance].player pause];
}
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundTaskIdentifier];
}
系统版本不低于iOS13.0的设备

– (void)sceneDidEnterBackground:(UIScene *)scene API_AVAILABLE(ios(13.0)){

NSLog(@”%s:应用已进入后台DidEnterBackground”, __FUNCTION__);

self.backgroundTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithName:kBgTaskName expirationHandler:^{
if (self.backgroundTaskIdentifier != UIBackgroundTaskInvalid) {
if ([QiAudioPlayer sharedInstance].needRunInBackground) {
[[QiAudioPlayer sharedInstance].player play];
}
NSLog(@”终止后台任务”);
[[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskIdentifier];
self.backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}
}];
}
– (void)sceneWillEnterForeground:(UIScene *)scene API_AVAILABLE(ios(13.0)){

if ([QiAudioPlayer sharedInstance].needRunInBackground) {
[[QiAudioPlayer sharedInstance].player pause];
}
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundTaskIdentifier];
NSLog(@”%s:应用将进入前台WillEnterForeground”, __FUNCTION__);
}
5

Background Modes Location updates

开启后台定位持续更新配置,添加了位置隐私申请后,在应用使用持续定位的情况下,可以实现后台保活App。

%title插图%num

添加后台获取位置及音频使用能力

%title插图%num
添加获取位置隐私申请

对于定位类App,如果需要后台保活App,在用户使用了定位功能后,App 进入后台后,App自动具备后台保活能力,部分示例代码如下。

self.locationManager = [CLLocationManager new];
self.locationManager.delegate = self;
[self.locationManager requestAlwaysAuthorization];
@try {
self.locationManager.allowsBackgroundLocationUpdates = YES;
} @catch (NSException *exception) {
NSLog(@”异常:%@”, exception);
} @finally {

}
[self.locationManager startUpdatingLocation];
如果遇到如下异常信息:

2019-12-29 19:57:46.481218+0800 QiAppRunInBackground[1218:141397] 异常:Invalid parameter not satisfying: !stayUp || CLClientIsBackgroundable(internal->fClient) || _CFMZEnabled()
检查:

Signing&Capablities 的 backgounrd Modes 中 Location updates是否勾选;

后台下载资源

当需要实现下载资源类的App在进入后台后,持续下载资源的需求时。我们可能需要使用后台如下示例示例代码。

创建指定标识的后台NSURLSessionConfiguration,配置好

NSURL *url = [NSURL URLWithString:@”https://images.pexels.com/photos/3225517/pexels-photo-3225517.jpeg”];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@”com.qishare.ios.wyw.backgroundDownloadTask”];
// 低于iOS13.0设备资源下载完后 可以得到通知 AppDelegate.m 文件中的 – (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler
// iOS13.0+的设备资源下载完后 直接在下载结束的代理方法中会有回调
sessionConfig.sessionSendsLaunchEvents = YES;
// 当传输大数据量数据的时候,建议将此属性设置为YES,这样系统可以安排对设备而言*佳的传输时间。例如,系统可能会延迟传输大文件,直到设备连接充电器并通过Wi-Fi连接到网络为止。此属性的默认值为NO。
sessionConfig.discretionary = YES;
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];
NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithURL:url];
[downloadTask resume];
6

BGTaskScheduler(iOS13.0+)

如果我们的App是信息流类App,那么我们可能会使用到BGTaskScheduler.framework中的API,实现后台保活App,帮助用户较早地获取到较新信息。

笔者尝试使用BGTaskScheduler 做了一个获取到App调度的时候。更新首页按钮颜色为随机色并且记录调度时间的Demo。

项目配置

为了App 支持 BGTaskScheduler,需要在项目中配置Background fetch,及Background Processing;

需要在Info.plist文件中添加 key 为Permitted background task scheduler identifiers,Value为数组的内容。

Value的数组填写,刷新的任务标识和清理的任务标识。

注册后台任务

在应用启动后,注册后台任务。

– (void)registerBgTask {

if (@available(iOS 13.0, *)) {
BOOL registerFlag = [[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:kRefreshTaskId usingQueue:nil launchHandler:^(__kindof BGTask * _Nonnull task) {
[self handleAppRefresh:task];
}];
if (registerFlag) {
NSLog(@”注册成功”);
} else {
NSLog(@”注册失败”);
}
} else {
// Fallback on earlier versions
}

if (@available(iOS 13.0, *)) {
[[BGTaskScheduler sharedScheduler] registerForTaskWithIdentifier:kCleanTaskId usingQueue:nil launchHandler:^(__kindof BGTask * _Nonnull task) {
[self handleAppRefresh:task];
}];
} else {
// Fallback on earlier versions
}
}
调度App 刷新

应用进入后台后,调度App 刷新。

– (void)sceneDidEnterBackground:(UIScene *)scene API_AVAILABLE(ios(13.0)){

[self scheduleAppRefresh];
}

– (void)scheduleAppRefresh {

if (@available(iOS 13.0, *)) {
BGAppRefreshTaskRequest *request = [[BGAppRefreshTaskRequest alloc] initWithIdentifier:kRefreshTaskId];
// *早15分钟后启动后台任务请求
request.earliestBeginDate = [NSDate dateWithTimeIntervalSinceNow:15.0 * 60];
NSError *error = nil;
[[BGTaskScheduler sharedScheduler] submitTaskRequest:request error:&error];
if (error) {
NSLog(@”错误信息:%@”, error);
}

} else {
// Fallback on earlier versions
}
}
得到后台任务调度的时候,调用App刷新的方法,笔者在这个方法中做了发送更新首页按钮颜色的通知,并且记录了当前更新时间的记录。

– (void)handleAppRefresh:(BGAppRefreshTask *)appRefreshTask API_AVAILABLE(ios(13.0)){

[self scheduleAppRefresh];

NSLog(@”App刷新====================================================================”);
NSOperationQueue *queue = [NSOperationQueue new];
queue.maxConcurrentOperationCount = 1;

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{

[[NSNotificationCenter defaultCenter] postNotificationName:AppViewControllerRefreshNotificationName object:nil];

NSLog(@”操作”);
NSDate *date = [NSDate date];
NSDateFormatter *dateFormatter = [NSDateFormatter new];
[dateFormatter setDateFormat:@”yyyy-MM-dd HH:mm”];
NSString *timeString = [dateFormatter stringFromDate:date];

NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@”QiLog.txt”];
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSData *data = [timeString dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:data attributes:nil];
} else {
NSData *data = [[NSData alloc] initWithContentsOfFile:filePath];
NSString *originalContent = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSString *content = [originalContent stringByAppendingString:[NSString stringWithFormat:@”\n时间:%@\n”, timeString]];
data = [content dataUsingEncoding:NSUTF8StringEncoding];
[data writeToFile:filePath atomically:YES];
}
}];

appRefreshTask.expirationHandler = ^{
[queue cancelAllOperations];
};
[queue addOperation:operation];

__weak NSBlockOperation *weakOperation = operation;
operation.completionBlock = ^{
[appRefreshTask setTaskCompletedWithSuccess:!weakOperation.isCancelled];
};
}
经过测试,发现App 在退到后台,没有手动Terminate App的情况下。苹果有调用过App调度任务的方法。现象上来看就是隔一段时间,我们再打开App 的时候可以发现,首页的按钮颜色改变了,相应的日志中追加了,调起相关方法的时间记录。

手动触发后台任务调度

Xcode运行我们的App

-> App 退到后台

-> 打开App 进入前台

-> 点击下图中蓝框中的Pause program execution,输入如下内容

后台模拟调起App

e -l objc — (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier: @”com.qishare.ios.wyw.background.refresh”]
-> 再次点击Continue program execution,就可以模拟后台启动任务,调用我们的App。

%title插图%num

Continue program execution

查看日志记录小提示

之前记得听沐灵洛提过怎么便于查看日志,正好我这里也用到了。便于我们可以直接在File App中查看写入到我们App的Documents中的文件,可以在Info.plist文件中添加key为LSSupportsOpeningDocumentsInPlace ,value为YES的键值对App 接入 iOS 11 的 Files App。

经过我们操作后,就可以打开File App -> 浏览 -> 我的iPhone -> 查看选择我们的App -> 查看我们的日志记录文件。

ios8新特性:pushkit实战总结(voip开发者必读)

Question:pushkit是什么?

Answer:ios8苹果新引入了名为pushkit的框架和一种新的push通知类型,被称作voip push.该push方式旨在提供区别于普通apns push的能力,通过这种push方式可以使app执行制定的代码(在弹出通知给用户之前);而该通知的默认行为和apns通知有所区别,它的默认行为里面是不会弹出通知的。目前来看push kit的用途还局限于voip push(根据笔者的实战经验来看,其他类型的push暂时不能够起作用,sdk也正处于演进中)。

Question: pushkit能帮我们做什么?

Answer:pushkit中的voippush,可以帮助我们提升voip应用的体验,优化voip应用的开发实现,降低voip应用的电量消耗,它需要我们重新规划和设计我们的voip应用,从而得到更好的体验(voip push可以说是准实时的,实侧延时1秒左右);苹果的目的是提供这样一种能力,可以让我们抛弃后台长连接的方案,也就是说应用程序通常不用维持和voip服务器的连接,在呼叫或者收到呼叫时,完成voip服务器的注册;当程序被杀死或者手机重启动时,都可以收到对方的来电,正常开展voip的业务。也就是说,我们当前可以利用它来优化voip的体验,增加接通率;条件成熟时我们就可以完全放弃后台的长连接,走到苹果为我们规划的道路上。
对于pushkit,除了苹果framework官方文档:https://developer.apple.com/library/prerelease/ios/documentation/NetworkingInternet/Reference/PushKit_Framework/index.html#protocols 以外,能够找到的帮助理解pushkit的莫过于wwdc的视频:712_sd_writing_energy_efficient_code_part_2。该视频也可以从苹果官网下载。

pushkit的局限:

在当前,pushkit仅支持ios8;且该功能正处于演进中,稳定性和在不同ios8小版本设备上的表现也可能有差异,在苹果开发者论坛上也有不少人反馈问题;根据经验,在下个大版本(也就是ios9)上可以期待该功能可以稳定下来。

如果需要在ios8之前的设备上支持pushkit功能,那么需要开发者付出很多额外的努力,这里不展开,有兴趣的同学可以到苹果论坛的相关板块去了解,有一些开发者在这方面走的比较远:

在下面的链接中搜索pushkit关键字,可以查找到相关内容:https://devforums.apple.com/community/ios/connected/push

    在简单介绍了pushkit和它能做的事并且了解到它的局限以后,还对pushkit感兴趣的童鞋可以往下继续看了(:)为了避免浪费大家的宝贵时间)。

 pushkit的voip功能的实现:

1.跟apns push类似,pushkit的voippush也需要申请证书(apns证书的申请流程参考:https://www.pushwoosh.com/programming-push-notification/ios/ios-configuration-guide/);voip push的证书申请步骤截图如下:

%title插图%num
%title插图%num
%title插图%num
%title插图%num
%title插图%num

2.使用该证书导出并加载到push服务器上,服务器侧无需做改动,仅替换证书相关的东西即可(具体流程和此前apns证书的加载完全类同);服务器和客户端的交互流程也基本类似。

3.客户端实现:

step1:在工程中添加pushkit;

step2:在工程设置里面的backgroundmode里面添加voip、backgroundfetch、remotenotifications的支持。

step3:保险起见,建议开发者使用*新版本的xcode和*新的sdk;也建议重新申请一个mobile provision文件用于打包。

step4:类似apns通知的客户端实现流程,voip push客户端相关的流程也类似:注册voip push通知,实现pushkit相关的代理。

贴出主要代码:

   在应用启动(appdelegate的didfinishlaunchwithoptions)后或根控制器的初始化等方法内调用如下代码:
    PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
    pushRegistry.delegate = self;
    pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
   
    UIUserNotificationSettings *userNotifySetting = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert categories:nil];
    [[UIApplication sharedApplication] registerUserNotificationSettings:userNotifySetting];
    上面的代码实现了在应用启动时对voip push的注册;

在appdelegate或框架viewcontroller类中实现voip push的代理:

@interface EPTabBarController : UITabBarController

– (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type
{
    if([credentials.token length] == 0)
    {
        NSLog(@”voip token NULL”);
        return;
    }
    
    ZeroPush * push = [[ZeroPush alloc] init];
    
   // push.apiKey = @”iosdev_1Z6JR3PKBWrAWbuHLbLQ”;
    
    push.apiKey = @”iosprod_HZDimW5ssYsRQgaSaEoE”;
    
    // iosprod_HZDimW5ssYsRQgaSaEoE
    
    [push registerDeviceToken:credentials.token channel:@”me”];
}

我们这里对接的push服务器是zeropush提供的服务;后面我们会大概介绍下该服务;上面的代理方法是设备从苹果服务器获取到了voip token,然后传递给应用程序;我们需要把这个token传递到push服务器(和apns push类似,我们也是要传递apns token到push服务器,但是这两个token的获取方式不同,分别在不同的代理方法中回调给应用,且这两个token的内容也是不同的)。

push server在获取到用户的voip token之后,在一切正常的情况下,另外一个回调会在push server下发消息到对应token的设备时被触发。

– (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type
{
 
    NSLog(@”didReceiveIncomingPushWithPayload”);
    // 此时进行voip注册

    // write your voip related codes here
    
    UIUserNotificationType theType = [UIApplication sharedApplication].currentUserNotificationSettings.types;
    if (theType == UIUserNotificationTypeNone)
    {
        UIUserNotificationSettings *userNotifySetting = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:userNotifySetting];
    }

    UILocalNotification *backgroudMsg = [[UILocalNotification alloc] init];
    backgroudMsg.alertBody= NSInternationalString(@”You receive a new call”,nil);
    [[UIApplication sharedApplication] presentLocalNotificationNow:backgroudMsg];

}
上面的回调代码里仅仅打印了日志,触发了一个本地通知;这个代理方法是收到voip push通知时触发的;如果一切正常,该通知在手机重启、应用被系统回收、手动kill程序的情况下,依然能够被触发,且可以有一段时间用来执行自己的代码(比如voip注册等)。

我们这里简单设计一个业务供大家参考,主要是为了让大家直观的认识到pushkit的能力:

1.应用的voip长连接不保持,在收到呼叫或者发起呼叫时再连接;

2.当呼叫发送到voip 服务器时,对端若不在线,通过voip 服务器连接到pushserver向对端发push通知;

3.应用收到voip push通知时,迅速完成注册;

4.呼叫方通过延时操作等逻辑(复杂一点对voip服务器进行改造,被叫连接上来以后通知到主叫侧),再次发起呼叫,通话即成功建立。

zero push的介绍:https://www.zeropush.com/

zero push的技术支持邮箱:Support@ZeroPush.com

笔者曾就一个证书相关的问题尝试给该邮箱发信,很快得到了满意的答复,非常棒!

zero push 提供了一个免费试用的服务,这让我们体验voip push非常方便;

按照它的提示,注册账号,然后创建应用,上传voip 证书,从网页上获取到它的apikey(这个key在上传token之前要用到,在上面的代理方法中)。

下面是它对voip push的介绍文章,*后面是demo工程的github链接:

https://www.zeropush.com/guide/guide-to-pushkit-and-voip

需要注意的是,该工程使用swift语言编写,如果你的证书和provision文件等都是之前申请的,只用于oc创建的工程,那么该工程在真机运行时很可能会闪退;解决办法是重新生成你的证书和provision文件,并使用到工程中,然后重新打包,该问题即可得到解决了。

iOS图标&启动图生成器(一)

前 言
一个完整的app都需要多种尺寸的图标和启动图。一般情况下,设计师需要根据开发者提供的一套规则,设计出各种尺寸的图标和启动图供开发人员使用。

但*近作者利用业余时间做了个app,因为不希望耽误设计师较多时间,希望能自己来搞定各种尺寸的图标,就只跟设计师要了*大尺寸的图标和启动图各一个。本想着找一下现成的工具,批量生成需要的的图片,但*后没有找到,只好自己使用Photoshop切出了不同尺寸的图片。

这期间还换过一次图标和启动图,作者就重复了切图工作,这花费了大量的时间。于是事后,作者开发了一个mac app——图标&启动图生成器(简称生成器)以提高工作效率。作者用两篇文章分别介绍生成器的使用和实现细节。

本篇文章介绍生成器的功能和使用方式。

01 生成器功能介绍
根据原图一键生成整套规则的图片;

支持选择所需要的平台规则;

支持选择/输入图片导出路径;

自动打开导出的图片文件夹。

02 生成器支持的平台
截止本篇文章发布,生成器v0.3版本共支持12套平台规则。

iPhone AppIcons(iPhone app 图标规则)

iPhone LaunchImages Portrait(iPhone app 竖屏启动图规则)

iPhone LaunchImages Landscape(iPhone app 横屏启动图规则)

iPad AppIcons(iPad app 图标规则)

iPad LaunchImages Portrait(iPad app 竖屏启动图规则)

iPad LaunchImages Landscape(iPad app 横屏启动图规则)

Mac AppIcons(Mac app 图标规则)

Watch AppIcons(Apple Watch app 图标规则)

CarPlay AppIcons(CarPlay app 图标规则)

Android AppIcons(Android app 常用图标规则)

Android LaunchImages Portrait(Android app 常用竖屏启动图规则)

Android LaunchImages Landscape(Android app 常用横屏启动图规则)

03 生成器界面介绍
在了解了生成器的基础功能后,来看看生成器的界面。如下图。
%title插图%num

生成器的界面比较简洁,控件元素按照从上到下、从左到右的顺序分别为:

图片框(承载源图片)

平台选择器(供选择平台规则)

路径按钮(供选择图片导出路径)

路径文本框(显示选择的路径,支持直接输入路径)

导出按钮(在目标路径中生成符合所选定的平台规则的图片,并打开路径文件夹)

04 生成器使用步骤
生成器的使用步骤非常简单,这里以此生成器app的图标生成过程为例进行介绍。

1、准备源图片
此生成器是一个mac app,需要10种尺寸的图标,如下图。

%title插图%num
其中,所需要的*大图标的尺寸为1024*1024。作者需要准备好这张*大尺寸的图片,并拖拽到图片框中作为源图片。

2 、选择平台规则
作者需要生成符合mac app图标规则的所有图标图片,所以这里选择Mac AppIcons。

3、选择导出路径
这时,点击导出按钮已经能够将源图片切成所需要的一套图片了。但在这之前,选择一个合适的图片导出路径,会便于作者管理生成的图片。另外,对文件路径规则比较熟悉的同学可以直接输入路径。

4、导出图片
点击导出按钮可以在目标路径中生成符合所选定平台规则的图片,并打开这些图片所在的文件夹以供使用。

按照以上4步,可以快速得到所需要的符合各种平台规则图标和启动图。

05 获取app资源

给天下的小白科普一下iOS和安卓的区别

IOS与安卓的区别:
1、两者运行机制不同:IOS采用的是沙盒运行机制,安卓采用的是虚拟机运行机制。
2、两者后台制度不同:IOS中任何第三方程序都不能在后台运行;安卓中任何程序都能在后台运行,直到没有内存才会关闭。
3、IOS中用于UI指令权限*高,安卓中

指令权限*高。iphone沙盒机制解释:应用程序位于文件系统的严格限制部分,程序不能直接访问其他应用程序。以杀毒软件中的

解释一下。“沙盒”技术是发现可疑行为后让程序继续运行,当发现的确是病毒时才会终止。“沙盒”技术的实践运用流程是:让疑似病毒文件的可疑行为在虚拟的“沙盒”里充分表演,“沙盒”会记下它的每一个动作;当疑似病毒充分暴露了其病毒属性后,“沙盒”就会执行“回滚”机制:将病毒的痕迹和动作抹去,恢复系统到正常状态。

机制解释:android本身不是为触摸屏打造的,所以所有的应用都是运行在一个虚拟的环境中,由底层传输数据到虚拟机中,再由虚拟机传递给用户UI,任何程序都就可以轻松访问其他程序文件。

是开源的,但是由于版本的不同意,各式各样的系统都有,界面会比IOS的好看些。软件方面:苹果的软件靠ITUNES赚钱还需要相应的许可所以相对而言质量要比较高一些。 安卓软件可以随便开发随便弄软件质量会不是很高,但是也有精品的软件。
由于安卓是开源的,软件和硬件不是一体的,所以可以刷不同的ROM,适合喜欢研究手机的人。
Android抗衡iOS还是有些力不从心,比如在移动应用开发者的收入方面,平台的整合度,操作的流畅度等。尤其在企业级市场,几乎已被iOS全面占领,新兴的企业都表示更加青睐iOS而非所谓“开放”、基于

而在另一方面你还有Android。它开源,生态环境开放,市场也开放。而把这些都融合起来还是Google,这家Android平台的开发者本身也是一家广告公司。

droid是google公司做的手机系统,ios是苹果公司做的手机系统。
droid手机系统的手机很多厂家公司在做如HTC,三星,中兴等等。。。 ios只有苹果公司的手机和数码产品才会是ios的手机系统。
droid手机系统和ios软件开发工具不同,平台不同。软件也不用,所以两个两个平台的软件不能通用,但是好的软件都会有两个系统版本,如QQ 有IOS版也是就iphoneQQ,和android版QQ。
两个系统都是现在智能手机上*火的系统,也是*有发展的系统。
安卓手机完全开源,任何软件开发商或者个人都能开发安卓的软件。苹果IOS完全封源开发
正是由于开源和各个品牌手机硬件差异*大,导致安卓手机的系统体验各有差异,软件兼容性也不如IOS。所以安卓手机总体的系统体验,流畅度,软件兼容性,明显不如系统和软件开发都对硬件有*其针对性的IOS,软件数量也不如IOS,游戏数量也不如IOS,而且很多高质量软件,特别是游戏都是先出现在IOS上。不过常用的大公司的软件,比如二楼说的QQ,不会出现上述问题。
安卓系统的软件几乎都是免费,而IOS的软件和游戏,好的基本都付费,当然苹果可以越狱,越狱后也是免费使用。
安卓手机支持FLASH,可以玩QQ农场,不过需要高端安卓机2.2以上系统才支持。IOS不支持FLASH,只支持HTML5,所以苹果上不能看FLASH,甚至苹果有时候连HTML5的视频兼容性也不好。
安卓手机使用起来上手快,下载歌曲电影等直接放到手机里就能看,IOS则需要同步到手机中,不过越狱后也能直接放到手机里看。

iOS App上架流程

一、前言:
作为一名iOS开发者,把开发出来的App上传到App Store是必须的。下面就来详细介绍一下具体流程。

二、准备:
一个已付费的开发者账号(账号类型分为个人(Individual)、公司(Company)、企业(Enterprise)、高校(University)四种类型,每年资费分别为$99、$99、$299、免费。)。
一个已经开发完成的项目。
三、检查:
你的Xcode必须是正式版的,beta版本的Xcode是不能上传项目的。
请确认你安装的Xcode是从App Store或者是开发者网站下载的,而不是从其它渠道获取的安装包安装的,因为非官方途径下载的Xcode可能带有XcodeGhost 病毒。如何检查?

检查方法

四、生成发布证书
打开苹果开发者中心:https://developer.apple.com
打开后点击:Member Center

1.苹果开发者中心

下面输入已付款过的Apple账号和密码登录(如果你的电脑已经保存了密码,会直接进入)

开发者登录账号
2.点击:Certificates, Identifiers & Profiles (专门生成证书,绑定Bundle Id,绑定device设备,生成描述文件的地方)

Member Center
3.点击Certificates生成证书

(1)选择iOS, tvOS, watchOS
(2)选择All
(3)点击右上角新添加证书

添加新证书1
(4)由于是做App上传,选择生产证书(选择App Store and Ad Hoc)

选择App Store and Ad Hoc

注意:一个开发者账号只能创建(1-2个开发(测试)证书,2-3个生产(发布)证书),如果你的App Store Ad Hoc 前面的按钮不能选择,则代表你的这个账号无法再创建新的生产证书了。
解决方法:

从共同使用这个账号的人电脑上生成.p12文件,导入自己的电脑。(尽量不要执行下面第2步)
如果你想生成的话,把现有的删除一个(建议删除时间比较靠前的)。注意:如果删除一个证书,那么正在使用这个证书的人将不能再使用了,除非重新生成,然后利用.p12重新导入自己的电脑里!
注意:如果你想删除证书,执行下面步骤,否则略过。

删除证书
然后接上上图,生产证书部分继续

生成证书2

生成证书3

生成证书4
5.上传CSR文件去获取证书(CSR文件需要我们到本机钥匙串里去创建)

(1)在Launchpad的其他里面,点击钥匙串访问弹出如下界面

其他

钥匙串访问
(2)工具栏选择钥匙串访问->证书助理->从证书颁发机构请求证书…

请求证书

证书信息
(3)将CSR文件保存到MAC磁盘的某个位置(这里我选择的是桌面,进行存储)

存储证书

点击完成

CertificateSigningRequest.certSigningRequest 文件
6.然后回到浏览器,点击choose File..

选择CSR文件
7.选择创建好的:CertificateSigningRequest.certSigningRequest 文件,点击选取

选取CSR文件

点击Generate上传证书

上传CSR证书
8.跳转到如下界面,点击 DownLoad 下载生成的证书(cer后缀的文件),然后点击Done,你创建的发布证书就会存储在帐号中。

下载生成的证书

cer后缀的文件
注意:这个证书只能下载一次。点击下载后,关闭页面后就不能再回到下载页面了。
如果不需要给别的电脑使用,则直接跳过下面附加项,跳转到第五步(绑定Bundle Identifier)

附加项:生成p12文件在其他电脑上使用这个发布证书
双击安装证书后,打开钥匙串访问,选择安装的证书右键单击

1.安装的发布证书

注意:如果没有导出,可以把这个证书删除,然后重新双击下载的证书文件安装。
导出证书

导出证书
2.存储证书

存储导出的证书

注意:存储的文件格式一定要是.p12
3.设置密码
可以为证书设置密码,也可以不设置密码;如果设置了密码,那么别人安装这个证书的时候就要输入密码,否则无法安装。这里就不设置密码了。

设置密码
4.保存导出的证书

p12发布证书

如果需要在其它电脑上也能发布App,那么就必须要安装这个发布证书。
五、创建App IDs和绑定你的App的Bundle Identifier
回到刚才的页面:https://developer.apple.com/account/ios/identifiers/bundle/bundleList.action

1.点击App IDs,进入如下界面,点击右上角的 + 号

2.点击App IDs
填写App IDs和Bundle Identifier

填写App IDs和Bundle Identifier

注意:

1.上传App使用的Bundle Identifier(不要有-,都是英文+数字)必须是固定的,不能使用占位符。
2.如果你的Bundle Identifier已经在网站上绑定了,如果你又修改了你工程里面这个Bundle Identifier的话,需要重新进入到开发者账号里面绑定。

修改工程的Bundle Identifier

下面选择App中包含的服务,默认有两项,其余的根据自己项目的需求进行选择

App Services
3.点击continue

4.点击Register

5.点击Done

六、生成描述文件(描述文件的作用就是把证书和Bundle Identifier关联起来)
1.找到Provisioning Profiles ,点击All,然后点击右上角 + 号

2.Provisioning Profiles
因为是发布,所以选择下面App Store这个描述文件,点击Continue

选择App Store
3.在App ID 这个选项栏里面找到你刚刚创建的:App IDs(Bundle Identifier) 类型的套装,点击Continue

选择发布项目的Bundle Identifier
4.选择你刚创建的发布证书(或者生成p12文件的那个发布证书),根据自己电脑上的发布证书日期来选择,点击Continue

选择创建的发布证书
5.在Profile Name栏里输入一个名字(这个是PP文件的名字,可随便输入,在这里我用工程名字,便于分别),然后点击Generate

给描述文件起个名字
6.Download生成的PP文件,然后点击Done,双击安装(闪一下就完事了,没其它效果)

Download生成的PP文件

生成的描述文件
六、在App Store开辟空间
1.回到Member Center,点击iTunes Connect

点击iTunes Connect
2.登录开发者账号(还是之前已付费的账号)

Snip20160315_68.png
3.登录成功后,点击我的App

点击我的App
4.点击左上角那个+号,点击新建(注意:我们是iOS App开发,不要选Mac App啦)

新建 App
5.依次按提示填入对应信息(SKU是公司用于做统计数据之类的id,根据公司需求填写),然后点击创建

填写App信息

注意:如果都填好以后,可能会告诉你,你的App名称已经被占用,那么不好意思,你只能改名了!(而且建议大家起名不要往比较出名的App上靠,否则审核可能会被拒*)

6.填写App其它信息

App信息
7.填写价格和销售范围(由于我的开发者账号没有签订纳税合同,所以不能上线收费应用,所以只能暂时免费)

填写价格和销售范围
8.依次把不同尺寸的App截图拉入到对应的里面
需要填写不同尺寸的手机屏幕截图(也就是拿不同尺寸的模拟器运行后,挑出至少3页*多5页进行截图然后拖到响应的区里)(在模拟器Command+S 就可以保存屏幕截图到桌面了)(注意:如果提示拖进去的图片尺寸不对,则把模拟器弄成100%然后再Command 加 S) 尺寸参照表在下面

设置不同尺寸的App截图

尺寸参照图

9.填写App简介

10.按提示依次输入

错误提示:如果上传App 图标失败,提示Alpha错误的话,看下面。
打开你的图标图片,勾掉这个

勾掉这个
11.点击分级后面的编辑,如实填写后,点击完成

分级信息
12.填写审核信息

版本发布就是:(然后*下面选择自动发布的话就是如果审核通过,就自动上传到App Store供人下载)

13.此时这个构建版本还没有生成,我们先把基本信息填写完毕,然后再进入Xcode中把项目打包发送到过来。
注意:填写完一定要点击右上角的保存。

七、在Xcode中打包工程
找到你刚刚下载的发布证书(后缀为.cer)或者p12文件,和PP文件,双击,看起来没反应,但是他们已经加入到你的钥匙串中。

1.在Xcode中选择iOS Device(这里不能选择模拟器),按照下图提示操作

选择iOS Device
2.如果你的应用不支持横屏,把这两个勾去掉

3.查看版本号和构建版本号

4.配置发布证书

注意:如果这里没有黄色叹号,代表你的配置没问题,如果有,那就是证书和描述文件不匹配,或者描述文件里刚才选的Bundle和现在的工程的Bundle Identifier不一致,去develop.apple.com 上找到你的描述文件在确认下绑定的bundle Identifier和你工程是不是一样的?

检查
5.将断点、全局断点,僵尸模式等都要去掉。

6.设置Release模式(Debug是测试的,Release是发布用的)

7.选择 Xcode下 Product 下 Archive(专门用于传项目,或者打包项目)

选择Archive

8.出现下图说明你没有添加开发者账号,点击右下脚Add… 按钮就可以添加

没有添加开发者账号
9.输入付费的开发者账号

输入开发者账号

可能会弹出下面这个界面,如果不弹出,按Command加。

10.然后回到Archive(选择已付费的账号),然后点击Choose

选择已付费的账号

然后等待

等待
11.选择Upload提交

Upload提交
12.如下就代表上传成功,如果出错,请参照iOS App上传项目遇到的问题

上传成功
13.返回ItunesConnect网站上你自己的App信息中查看一下

14.在这个构建版本这里就可以添加代码

点击+号之后选择代码版本

添加构建版本
15.提交以供审核

16.App已经从准备提交,变成正在等待审核状态

正在等待审核状态

iOS笔记–#include和#import的区别、#import和@class的区别

申明:此为本人学习笔记,若有纰漏错误之处的可留言共同探讨

#include和#import的区别:

1. #include和#import的作用都是导入头文件,

2. 只是#import是oc语言的头文件导入,它能避免重复导入,确保头文件只会被导入一次,

3. #include如果稍不警惕就会重复导入,出现相互包含的编译错误,因此开发的时候,推荐开发时使用#import。
#import和@class的区别:

1. #import是导入头文件,整个类的所有信息都会包含在内,包括实例变量和方法;

2. @class是通知编译器,声明的那个名称就是个类,不过类的内容是什么,怎么定义的暂时不需要知道。在@class之后,后面还要用到这个类的属性时,需要重新再#import一次,否则调用不出来。

3. 使用的时候,通常是在.h文件中使用@class引入新类,在.m文件中使用#import,这样做的好处在于减少编译时间。也防止了相互包含的编译错误。
————————————————

原来 Calendars 5 这么贵的吗…

听好几个朋友推荐的,然后去 App Store 看了一眼,龟龟…198 块…

我还以为是我切错成日区了,用网页打开看了眼还是 198

%title插图%num

所以说这个日历真的这么牛逼值这么多钱的吗,该不会除了我以外所有人都是限免拿的吧???

14 条回复 • 2021-03-23 21:23:26 +08:00

Procumbens 2 天前

Recent Price Changes
$29.99 since Jan 22, ’21
$6.99 for 1150 days Nov 29, ’17
$2.99 for 9 days Nov 20, ’17
$6.99 for 357 days Nov 28, ’16
$2.99 for 5 days Nov 23, ’16
$6.99 for 208 days Apr 29, ’16
Free for 8 days Apr 21, ’16
$6.99 for 117 days Dec 25, ’15
$2.99 for 4 days Dec 21, ’15
$6.99 for 136 days Aug 07, ’15
$2.99 for 3 days Aug 04, ’15
$6.99 for 179 days Feb 05, ’15
$2.99 for 9 days Jan 27, ’15
$6.99 for 32 days Dec 26, ’14
$2.99 for 11 days Dec 15, ’14
$6.99 for 15 days Dec 01, ’14
$2.99 for 4 days Nov 27, ’14
$6.99 for 116 days Aug 03, ’14
$2.99 for 3 days Jul 31, ’14
$6.99 for 64 days May 28, ’14
$2.99 for 8 days May 20, ’14
$6.99 for < 1 day May 19, '14 Free for 2 days May 17, '14 $6.99 for 26 days Apr 22, '14 $2.99 for 13 days Apr 09, '14 $6.99 for 101 days Dec 29, '13 $2.99 for 9 days Dec 20, '13 $6.99 for 18 days Dec 02, '13 $2.99 for 3 days Nov 29, '13 $6.99 before Nov 29, '13 现在这也太贵了……印象中一直是 45 块钱。。 他家东西现在是能推内购就推内购,不能的就调高价(

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速