分类: IOS技术

IOS技术

iOS开发笔记–keyboard相关

*近一个项目有键盘相关的需求:自定义键盘与系统键盘切换。就将键盘相关的知识点顺了一遍。

一、UITextInputTraits 协议

该协议定义了一些与键盘输入相关的属性。所有支持键盘输入的对象都必须接受这个协议,目的是为了与文本输入管理系统正确地交互。

UITextField 和 UITextView ,UISearchBar都支持该协议。

@protocol UITextInputTraits <NSObject>

@optional

@property(nonatomic) UITextAutocapitalizationType autocapitalizationType;
// 定义输入文本的自动大写类型 default is UITextAutocapitalizationTypeSentences
@property(nonatomic) UITextAutocorrectionType autocorrectionType;
// 定义输入文本的自动更正类型 default is UITextAutocorrectionTypeDefault
@property(nonatomic) UITextSpellCheckingType spellCheckingType NS_AVAILABLE_IOS(5_0);
// 定义输入文本的拼写检查类型 default is UITextSpellCheckingTypeDefault;
@property(nonatomic) UIKeyboardType keyboardType;
// 定义键盘类型 default is UIKeyboardTypeDefault
@property(nonatomic) UIKeyboardAppearance keyboardAppearance;
// 定义键盘外貌类型 default is UIKeyboardAppearanceDefault
@property(nonatomic) UIReturnKeyType returnKeyType;

// 定义键盘returnKey的类型 default is UIReturnKeyDefault (See note under UIReturnKeyType enum)
@property(nonatomic) BOOL enablesReturnKeyAutomatically;
// default is NO (when YES, will automatically disable return key when text widget has zero-length contents, and will automatically enable when text widget has non-zero-length contents)
@property(nonatomic,getter=isSecureTextEntry) BOOL secureTextEntry;
// 输入文本是否加密 default is NO

@end

typedef NS_ENUM(NSInteger, UIKeyboardType) {
UIKeyboardTypeDefault, // Default type for the current input method.
UIKeyboardTypeASCIICapable,
// 字母键盘 Displays a keyboard which can enter ASCII characters, non-ASCII keyboards remain active
UIKeyboardTypeNumbersAndPunctuation, // Numbers and assorted punctuation.
UIKeyboardTypeURL, // A type optimized for URL entry (shows . / .com prominently).
UIKeyboardTypeNumberPad, // A number pad (0-9). Suitable for PIN entry.
UIKeyboardTypePhonePad, // A phone pad (1-9, *, 0, #, with letters under the numbers).
UIKeyboardTypeNamePhonePad, // A type optimized for entering a person’s name or phone number.
UIKeyboardTypeEmailAddress, // A type optimized for multiple email address entry (shows space @ . prominently).
#if __IPHONE_4_1 <= __IPHONE_OS_VERSION_MAX_ALLOWED
UIKeyboardTypeDecimalPad, // A number pad with a decimal point.
#endif
#if __IPHONE_5_0 <= __IPHONE_OS_VERSION_MAX_ALLOWED
UIKeyboardTypeTwitter, // A type optimized for twitter text entry (easy access to @ #)
#endif

UIKeyboardTypeAlphabet = UIKeyboardTypeASCIICapable, // Deprecated

};

这个属性决定了在输入文本中,是否支持拼写检查。

二、定制键盘

@interface UIResponder (UIResponderInputViewAdditions)

// Called and presented when object becomes first responder. Goes up the responder chain.
@property (readonly, retain) UIView *inputView NS_AVAILABLE_IOS(3_2);
//键盘视图,定制的键盘视图要赋值给该属性
@property (readonly, retain) UIView *inputAccessoryView NS_AVAILABLE_IOS(3_2);
//键盘辅助视图,即位于键盘视图上面一些额外的辅助性视图,可在上添加辅助功能键

// If called while object is first responder, reloads inputView and inputAccessoryView. Otherwise ignored.
– (void)reloadInputViews NS_AVAILABLE_IOS(3_2);

@end

UITextField 和 UITextView 都提供了以上方法。

%title插图%num
如上图所示,绿色视图为 inputAccessoryView。下面的是定制的键盘视图

示例代码如下

– (void)viewDidLoad
{
[super viewDidLoad];

self.numberTextField = [[UITextField alloc] initWithFrame:CGRectMake(20, 120, 280, 80)];
self.numberTextField.backgroundColor = [UIColor darkGrayColor];
self.numberTextField.delegate = self;
self.numberTextField.keyboardType = UIKeyboardTypeNamePhonePad;

self.numberTextField.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.numberTextField.autocorrectionType = UITextAutocorrectionTypeNo;
self.numberTextField.spellCheckingType = UITextSpellCheckingTypeNo;
self.numberTextField.returnKeyType = UIReturnKeySearch;

_inputView = [[AZStockKeyboardView alloc] initWithFrame:CGRectMake(0, 0, rect.size.width, 216)];
_inputView.delegate = self;
self.numberTextField.inputView = _inputView;
UIView *accessoryView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
accessoryView.backgroundColor = [UIColor greenColor];
self.numberTextField.inputAccessoryView = accessoryView;
[self.view addSubview:self.numberTextField];
}

切换系统键盘

– (void)customedKeyboardDidChange
{
self.numberTextField.inputView = nil;
[self.numberTextField reloadInputViews];
}

系统键盘切换定制键盘

– (void)systemKeyboardDidChange
{
self.numberTextField.inputView = _inputView;
[self.numberTextField reloadInputViews];
}

给定制的键盘添加系统键盘按键声音

1.定制键盘视图要继承UIView,接受 UIInputViewAudioFeedback 协议,并实现协议方法

@interface AZStockKeyboardView : UIView<UIInputViewAudioFeedback>
{

}

#pragma mark UIInputViewAudioFeedback protocol methods

– (BOOL)enableInputClicksWhenVisible
{
return YES;
}

2.在定制的键盘的按键响应的方法中,调用 [[UIDevice currentDevice] playInputClick]

– (void)numberButtonClicked:(id)sender
{
[[UIDevice currentDevice] playInputClick];
}

三、改造系统键盘

由于需要定制键盘和系统键盘互相切换,就需要将系统键盘的切换键盘的按键响应我们自己的切换键盘方法。

Apple官方并没有提供这种方法,目前可行的做法是将该按键用我们自己的创建的按键将其覆盖。

UITextEffectsWindow       //键盘所在window

UIPeripheralHostView      //键盘视图所在的父视图  定制的键盘视图,辅助视图都放在这个视图上面

UIKeyboardAutomatic      //键盘视图

UIKeyboardImpl               //自己按英文的意思理解的,键盘的实现视图

UIKeyboardLayoutStar     //自己按英文的意思理解的,键盘的布局视图

UIKBKeyplaneView          //

UIKBKeyView                   //根据description判断,是键盘的功能性按键的视图(除字母,数字之外,类似删除键,空格键等)

以上视图都有层级关系,从上到下,上面的视图是下面视图的父视图

完成覆盖按键的任务,需要获取 UIKeyboardAutomatic 视图,然后把定制的切换按键添加到该视图上

获取系统键盘视图
– (UIView *)getSystemKeyboardView
{
UIView *returnView = nil;

UIWindow *keyboardWindow = nil;
for (UIWindow *window in [[UIApplication sharedApplication] windows])
{
if (![NSStringFromClass([window class]) isEqualToString:NSStringFromClass([UIWindow class])])
{
keyboardWindow = window;
break;
}
}
if (keyboardWindow == nil)
return nil;

for (UIView *firstView in [keyboardWindow subviews])
{
if ([[firstView description] hasPrefix:@”<UIPeripheralHostView”])
{
for (UIView *secondView in [firstView subviews])
{
if ([[secondView description] hasPrefix:@”<UIKeyboardAutomatic”])
{
returnView = secondView;
}
}
}
}

return returnView;
}

监听键盘事件

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];

向键盘视图上覆盖按键

– (void)keyboardDidShow:(id)notification
{
_keyboardDefaultView = [self getSystemKeyboardView];
if (_keyboardDefaultView)
{
_switchNumButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_switchNumButton setTitle:@”123″ forState:UIControlStateNormal];
[_switchNumButton setBackgroundImage:[UIImage imageNamed:@”num.png”] forState:UIControlStateNormal];
_switchNumButton.frame = CGRectMake(1, 173, 78, 42);
[_switchNumButton addTarget:self action:@selector(changeCutomeButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[_keyboardDefaultView addSubview:_switchNumButton];
}
}

效果如下:

%title插图%num

注意:由于所有系统键盘都是所有程序共享的,所以在当前界面消失前,或者其他类型的输入视图调用键盘前,要将我们自己定制的按键移除。

否则,从其他应用调用该类型键盘,都可以看到我们定制的按键。

同时也应该在调用键盘前,即键盘弹出时,在用我们定制的按键覆盖之前加限定条件

– (void)keyboardDidShow:(id)notification
{
_keyboardDefaultView = [self getSystemKeyboardView];
if (_keyboardDefaultView && [_numberTextField isFirstResponder])
{
_switchNumButton = [UIButton buttonWithType:UIButtonTypeCustom];
[_switchNumButton setTitle:@”123″ forState:UIControlStateNormal];
[_switchNumButton setBackgroundImage:[UIImage imageNamed:@”num.png”] forState:UIControlStateNormal];
_switchNumButton.frame = CGRectMake(1, 173, 78, 42);
[_switchNumButton addTarget:self action:@selector(changeCutomeButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[_keyboardDefaultView addSubview:_switchNumButton];
}
else
{
if (_switchNumButton) {
[_switchNumButton removeFromSuperview];
}
}
}

iOS开发 — 获取网络状态

之前用的根据状态栏获取的,iOS 13貌似不行了。。。

#import <Foundation/Foundation.h>
typedef enum {
NETWORK_TYPE_NONE = 0,
NETWORK_TYPE_WIFI,
NETWORK_TYPE_2G,
NETWORK_TYPE_3G,
NETWORK_TYPE_4G,
NETWORK_TYPE_5G, // 5G目前为猜测结果

} NETWORK_TYPE;

@interface BaseTooles : NSObject

+ (NETWORK_TYPE)SDKgetNetworkTypeFromStatusBar;
/*!
* 获取当前网络类型
* 通过statusBar的网络subview获取具体类型
*/
+ (NETWORK_TYPE)SDKgetNetworkTypeFromStatusBar{

NSArray *subviews;

if([[[UIApplication sharedApplication] valueForKeyPath:@”_statusBar”] isKindOfClass:NSClassFromString(@”UIStatusBar_Modern”)]) {
// iPhone X
subviews = [[[[[UIApplication sharedApplication] valueForKeyPath:@”_statusBar”] valueForKeyPath:@”_statusBar”] valueForKeyPath:@”foregroundView”] subviews];
} else{
subviews = [[[[UIApplication sharedApplication] valueForKeyPath:@”_statusBar”] valueForKeyPath:@”foregroundView”] subviews];
}

// NSArray *subviews = [[[[UIApplication sharedApplication] valueForKey:@”statusBar”]valueForKey:@”foregroundView”] subviews];
NSNumber *dataNetworkItemView = nil;
for (id subview in subviews) {
if ([subview isKindOfClass:[NSClassFromString(@”UIStatusBarDataNetworkItemView”) class]]) {
dataNetworkItemView = subview;
break;
}
}
NETWORK_TYPE nettype = NETWORK_TYPE_NONE;
NSNumber *num = [dataNetworkItemView valueForKey:@”dataNetworkType”];
switch ([num intValue]) {
case 0:
nettype = NETWORK_TYPE_NONE;

break;
case 1:
nettype = NETWORK_TYPE_2G;

break;
case 2:
nettype = NETWORK_TYPE_3G;

break;
case 3:
nettype = NETWORK_TYPE_4G;

break;

default:
nettype = NETWORK_TYPE_WIFI;
break;
}

return nettype;
}

iphone手机上的抓包流程和原理概述

在ios应用开发过程中,为了进行端到端的分析,很多时候我们需要对手机的抓包进行分析。

对手机的抓包我们要在手机上安装抓包工具:tcpdump

前提:手机越狱;安装tcpdump    这样一部iphone就具备了抓包能力。

方法1:手机直接抓包(在手机上安装terminal用于输入命令)
不受网络条件制约,但由于是在终端上操作抓包,对ios设备来讲,MobileTerminal切至后台就不再工作,因此该抓包方法可行性较低,尽管如此,MobileTerminal对终端测试人员来说依然是一款很实用的工具;命令控推荐此方法。

方法2:通过pc/mac连接到手机(iPhoneTunnel软件,有mac和pc版本),通过在pc/mac下输入抓包命令,在手机上抓包。

pc下:用USB线缆连接终端与PC,确保手机终端正确,启动iPhoneTunnel,绿灯表示Tunnel Status正常,此时点击Launch Terminal按钮即可SSH进入终端,输入root密码,然后执行命令抓包

mac下:用USB线缆连接终端与mac,确保手机终端正确,启动iPhoneTunnel并开启后,点击tools->ssh(root) 菜单调出一个命令行界面,此时有可能报一个连接错误,输入ssh-keygen -R [127.0.0.1]:2222清除下里面的一个配置,即可正常连接。连接以后执行命令抓包,同pc。(tip:mac下终端输入密码时没有占位符,需要保证正确,注意)
WiFi下抓包tcpdump -i en0 -X -s0 -w data.pcap

2G/3G下抓包 tcpdump -i pdp_ip0 -X -s0 -w data.pcap 或 tcpdump -X -s0 -w data.pcap  (两个都可以试下,我一般用前者)
抓包结束命令:ctl+c
敲此命令抓包的默认路径在var->root文件夹内。
总结:两个方法本质一样,都是利用tcpdump在手机上抓包,方法二通过ssh连接到手机去执行抓包命令(此时iphone充当一个server角色,pc/mac作为client连接到该server进行远程控制),因为抓到的包一般需要在pc/mac下用wireshark进行分析,所以pc/mac基本是必须的,推荐方法2。

颠覆认知的ios代码,真机实测!

记录一段神奇的代码。关键代码:
[self performSelector:@selector(recreateCommonWebView) withObject:nil afterDelay:0.1];
注释掉以后,日志执行顺序正常;否则,异步变同步。

(WKWebView *)dequeueCommonWebView
{
[self.commonSet addObject:self.preparedCommonWebView];

// 避免影响本次的加载。 5秒已经足够长了。

[self performSelector:@selector(recreateCommonWebView) withObject:nil afterDelay:0.1];

//@weakify_self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//@strongify_self;
NSLog(@“async setup!”);
//self.preparedCommonWebView = nil;
});

NSLog(@“sync logging!”);

return nil;

// NSLog(@“current count in common = %@”, @(self.commonSet.count));
//
// if (self.preparedCommonWebView == nil)
// {
// NSLog(@“error, prepared nil!”);
// }
//
// return self.preparedCommonWebView;
}

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的技术支持邮箱:[email protected]

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

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则需要同步到手机中,不过越狱后也能直接放到手机里看。

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