背景
笔者昨天遇到个问题,有用户反馈在iOS 10.3.1的手机上,有个网页打开白屏。但是问题是笔者手头都没有10.x版本的手机,所以想安装模拟器来尝试复现。
然而,笔者发现电脑上的Xcode版本是12.5.1,已经不支持iOS 10.3.1的模拟器下载了。
苹果iOS是由苹果公司开发的移动操作系统。苹果公司最早于2007年1月9日的Macworld大会上公布这个系统,最初是设计给iPhone使用的,后来陆续套用到iPod touch、iPad以及Apple TV等产品上。iOS与苹果的Mac OS X操作系统一样,它也是以Darwin为基础的,因此同样属于类Unix的商业操作系统。原本这个系统名为iPhone OS,直到2010WWDC大会上宣布改名为iOS。
笔者昨天遇到个问题,有用户反馈在iOS 10.3.1的手机上,有个网页打开白屏。但是问题是笔者手头都没有10.x版本的手机,所以想安装模拟器来尝试复现。
然而,笔者发现电脑上的Xcode版本是12.5.1,已经不支持iOS 10.3.1的模拟器下载了。
Writing code for multiple Apple platforms can be tricky, this post aims to provide some guidance and explanations for ways to write code that works on different Apple platforms (iOS, macOS, watchOS, tvOS) and different versions of SDKs, and the OSes at runtime.
First, let’s take a look at how to tell apart the OS you are compiling for at build time, using the preprocessor (in case of Objective-C) or compilation conditions (Swift).
In Swift checking the OS you are building for at comile time is done using compilation conditionals and specific platform condition functions:
The os()
function can be used to check for the OS the code is compiled for, it takes one argument, which is the operating system name (not quoted). Possible operating system names at the time of writing are macOS
, iOS
, tvOS
, watchOS
and Linux
.
For example:
1 2 3 4 5 |
#if os(macOS) // This code is only compiled for macOS #elseif os(iOS) || os(tvOS) // This code is only compiled for iOS or tvOS #endif |
Of course sometimes you need to check if you are running the simualtor or not, to do that there is the targetEnvironment()
function. It takes one argument, which is the target environment name (not quoted). Possible values are simulator
and macCatalyst
(since Swift 5.1).
1 2 3 |
#if os(iOS) && targetEnvironment(simulator) // This code is only compiled for the iOS simulator #endif |
For Objective-C, C and C++ conditional compilation is handled by the preprocessor. Apple provides the TargetConditionals.h
header which contains specific defines for this.
This header has a lot of defines, I will only list the most useful ones here:
TARGET_OS_OSX
TARGET_OS_IOS
TARGET_OS_TV
TARGET_OS_WATCH
TARGET_OS_MACCATALYST
TARGET_OS_SIMULATOR
For example:
1 2 3 4 5 |
#if TARGET_OS_OSX // This code is only compiled for macOS #elif TARGET_OS_IOS || TARGET_OS_TV // This code is only compiled for iOS or tvOS #endif |
To check if compiling for the simulator, just use the TARGET_OS_SIMULATOR
define:
1 2 3 |
#if TARGET_OS_IOS && TARGET_OS_SIMULATOR // This code is only compiled for the iOS simulator #endif |
Note that there is a TARGET_OS_MAC
define, while this sounds like it will be true only for macOS, it is actually true for all Darwin OSes including iOS and tvOS. Another define which can be confusing is the TARGET_OS_IPHONE
, which is actually true for all “mobile” platforms, so iOS, tvOS, watchOS and Catalyst.
Since Clang 6.0 or Xcode 9.3 (r320734) Clang has preprocessor extensions similar to the Swift condition functions which can be used to achieve the same as with the target conditional defines above.
To check the OS code is compiled for, there is the __is_target_os()
preprocessor macro, which takes a single argument, the operating system name. Possible values for Apple OSes are macOS
, iOS
, tvOS
, watchOS
.
For example:
1 2 3 4 5 |
#if __is_target_os(macOS) // This code is only compiled for macOS #elif __is_target_os(iOS) || __is_target_os(tvOS) // This code is only compiled for iOS or tvOS #endif |
To check what environement the code is compiled for, similar to Swift there is the __is_target_environment()
preprocessor macro, which takes as argument the environment name. Possible values are simulator
and macabi
(Catalyst).
1 2 3 |
#if __is_target_os(iOS) && __is_target_environment(simulator) // This code is only compiled for the iOS simulator #endif |
Something that usually is closely related to above discussed conditional compilation is the need to handle API availability gracefully. There are various aspects to consider about API availability, one is API availability at runtime, another is API availability at compile time depending on the used SDK version.
API availability, as the name suggests, means if a specific API (function, class, method, etc.) is actually available.
macOS, iOS, tvOS and watchOS handle API availability in the same way, when a new API is introduced it is annotate with a specific macro that indicates the availability of that API. The macro expands to annotations for the API that indicate how the linker is expected to handle linking to it and can provide additional warnings or errors during compilation when using a deprecated API or trying to use a “too new” API in an application set to run on older appleOS versions that lack this API.
This sounds all very abstract and complex, so let’s have a look at this using an example, the clock_gettime()
function. If we look at the manpage for clock_gettime
we can see that it was introduced in macOS 10.12:
12 HISTORYThese functions first appeared in Mac OSX 10.12
So let’s have a look at how the header declares this function:
1 2 |
__CLOCK_AVAILABILITY int clock_gettime(clockid_t __clock_id, struct timespec *__tp); |
So these functions are annotate with __CLOCK_AVAILABILITY
, which expands to:
1 |
__OSX_AVAILABLE(10.12) __IOS_AVAILABLE(10.0) __TVOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0) |
So to be more precise than what the man page tells us, this API is available since macOS 10.12, iOS 10.0, tvOS 10.0 and watchOS 3.0, great!
Of course that still doesn’t provide the full story, to understand what exactly the availability macros do, let’s have a look at the header defining those, Availability.h
. Checking this header, we can see that these macros actually expand to (indirectly using a other macros) use of the availability attribute. I recommend reading this for all the details about how exactly this works. The most important takeaway for the purpose of this article is the following:
A declaration can typically be used even when deploying back to a platform version prior to when the declaration was introduced. When this happens, the declaration is weakly linked, as if the weak_import attribute were added to the declaration. A weakly-linked declaration may or may not be present a run-time, and a program can determine whether the declaration is present by checking whether the address of that declaration is non-NULL.
Note that in addition to the Availability.h
header, there is the AvailabilityMacros.h
header which works similar to the Availability.h
header. Depending on the Framework, it might use either the Availability.h
or the older AvailabilityMacros.h
header.
Now let’s see how we can use such a “partially” avaialble function:
1 2 3 4 5 6 7 8 9 10 |
#include <stdio.h> #include <time.h> int main(int argc, char const *argv[]) { struct timespec value; if (clock_gettime(CLOCK_REALTIME, &value) == 0) { printf("Realtime seconds: %ld\n", value.tv_sec); } } |
If we now compile this targeting macOS 10.14 like that, it just works as expected:
1 2 3 |
$ clang --target=x86_64-apple-macosx10.14 -Wunguarded-availability availability.c && ./a.out Realtime seconds: 1572996298 |
But if we were to try to compile targeting macOS 10.10, we would get a warning:
1 2 3 4 5 6 7 8 9 |
$ clang --target=x86_64-apple-macosx10.10 -Wunguarded-availability availability.c && ./a.out availability.c:7:9: warning: 'clock_gettime' is only available on macOS 10.12 or newer [-Wunguarded-availability] if (clock_gettime(CLOCK_REALTIME, &value) == 0) { ^~~~~~~~~~~~~ […]/usr/include/time.h:178:5: note: 'clock_gettime' has been marked as being introduced in macOS 10.12 here, but the deployment target is macOS 10.10.0 […] Realtime seconds: 1572996508 |
The -Wunguarded-availability
flag is what causes the compiler to emit this warning. For APIs available since macOS 10.13, iOS 11, watchOS 4 and tvOS 11 you will get these warnings even without specifying this flag, as there is a new flag, -Wunguarded-availability-new
which is enabled by default in recent Clang/Xcode versions.
As the name of the warning already gives it away, it only warns about “unguarded” availability, which implies we can “guard” such API usage. There are two ways how this can be done.
The “old” way to check if a partially available function is available would be to check its address:
1 2 3 |
if (&clock_gettime == NULL) { // clock_gettime is not available! } |
Not only is this a bit weird to read, it has some downsides:
Fortunately since some time there is a bette way to handle this! In fact, the compiler would already points this out in the partial availability warning:
note: enclose 'clock_gettime' in a __builtin_available check to silence this warning
So let’s do that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <stdio.h> #include <time.h> int main(int argc, char const *argv[]) { struct timespec value; if (__builtin_available(macOS 10.12, *)) { if (clock_gettime(CLOCK_REALTIME, &value) == 0) { printf("Realtime seconds: %ld\n", value.tv_sec); } } else { // clock_gettime not available! return 1; } } |
And now it will compile without warning again! On macOS at least, that is. We can check multiple platform versions just by listing them all:
1 2 3 |
if (__builtin_available(macOS 10.12, iOS 10.0, *)) { // Running on macOS 10.12 or iOS 10.0 or higher } |
The star at the end is mandatory and means “all other platforms”. So the previous check that just listed macOS would still compile for iOS and crash at runtime when ran on iOS versions lower than iOS 10 which lack clock_gettime
. So take care to cover all cases where the code will run in your availability check!
In Objective-C there is the @available
helper which looks a bit nicer than the longer version from C but is used in the exact same way:
1 2 3 |
if (@available(macOS 10.12, iOS 10.0, *)) { // Running on macOS 10.12 or iOS 10.0 or higher } |
In Swift there is #available
, again the usage is the same except for the different name:
1 2 3 |
if #available(macOS 10.12, iOS 10.0, *) { // Running on macOS 10.12 or iOS 10.0 or higher } |
Note that negating the check or using it together with any other condition is not supported and does not properly guards availability!
Additionally keep in mind that this is a runtime check, so using APIs inside a availability check that are missing in the current SDK version that is compiled with is still an error. To support multiple SDK versions, see the next section for how to check at compile-time!
Sometimes it is necessary to check the availability of a specific API at compile-time, for example when you want to remain compatible with multiple Apple SDKs, where some versions offer new API that you want to use and some versions lack this API.
In the previous section I already mentioned two headers, Availability.h
and AvailabilityMacros.h
. These headers define two important macros:
__<OS-VARIANT>_VERSION_MAX_ALLOWED
__<OS-VARIANT>_VERSION_MIN_REQUIRED
The <OS-VARIANT>
needs to be replaced with the OS variant we want to check for and can be MAC_OS_X
, IPHONE_OS
, TV_OS
or WATCH_OS
.
The above sounds quite abstract so lets illustrate it with a example. Suppose we have a new API introduced for macOS 10.12, so it is first present in the macOS 10.12 SDK. If we were to compile with that SDK, the __MAC_OS_X_VERSION_MAX_ALLOWED
macro is automatically set to the version of the SDK, as that is the maximum macOS version that we can use APIs from, we cannot ever use any APIs newer than the SDK we are using because those are simply not declared. So in case of the 10.12 SDK, __MAC_OS_X_VERSION_MAX_ALLOWED
will be 101200
.
If we want to stay compatible with older SDKs, we can use the following preprocessor macros:
1 2 3 4 5 6 7 8 9 10 |
#include <Availability.h> #if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101200) if (@available(macOS 10.12, *)) { // Use API available since macOS 10.12 SDK } else { // Fallback to some other API available in 10.11 and older SDKs } #else // Fallback to some other API available in 10.11 and older SDKs #endif |
Note that there are defines for the specific appleOS versions in the availability headers, like __MAC_10_12
so it is tempting to write __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_12
but this will not work because lower SDK versions, like for example the macOS 10.11 SDK will not have the define for higher macOS versions like macOS 10.12!
What is important to note is that the preprocessor checks are done at compile-time, so proper availability handling at runtime is still needed, see the previous section for details about that!
The second macro, __<OS-VARIANT>_VERSION_MIN_REQUIRED
, is useful when you have legacy code that you want to disable when targeting recent enough appleOS versions. Suppose we have function needed for macOS <= 10.11, we can easily disable that when targeting macOS 10.12 or higher by using the __MAC_OS_X_VERSION_MIN_REQUIRED
macro:
1 2 3 4 5 6 |
#include <Availability.h> #if (TARGET_OS_OSX && __MAC_OS_X_VERSION_MIN_REQUIRED <= 101100) void compat_stuff_for_1011() { // ... } #endif |
Of course a lot of other and more complex scenarios are possible with more complex checks, but I won’t cover all of the possibilities here.
Note that the AvailabilityMacros.h
header defines MAC_OS_X_VERSION_MIN_REQUIRED
without the two leading underscores, but the Availability.h
header does not define those. Both define the version with the leading underscores so to prevent confusing code I would recommend to not use the version without the leading underscores.
Note that the above only works for C/C++/Objective-C code, in Swift there is currently no way to check the SDK at compile-time.
这两个问题比较具有代表性,在群里面讨论的时候见过的次数也是最多的,因此有些人遇到的问题也许协议名称不一样,但是本质都是类似的。
从 Android5.0 行为变更 可以看到 Android 5.0 开始默认启用了 TLSv1.1 和 TLSv1.2,但是从 Android 4.1 开始 TLSv1.1 和 TLSv1.2 其实就被支持了,只是默认没有启用而已。
我们最常用到的几种协议是SSLv3、TLSv1、TLSv1.1和 TLSv1.2,解决上面两个问题要搞清楚的是这几个协议在Android 系统中被支持的情况和被启用的情况,然后我们结合 minSdkVersion 和 targetSdkVersion 来选择协议即可,不同版本的 Android 系统对上述协议的支持情况:
客户端(SSLSocket)的支持情况:
协议 | 被支持(Api级别) | 被启用(Api级别) |
---|---|---|
SSLv3 | 1–25 | 1–22 |
TLSv1 | 1+ | 1+ |
TLSv1.1 | 16+ | 20+ |
TLSv1.2 | 16+ | 20+ |
服务端(SSLServerSocket)的支持情况:
协议 | 被支持(Api级别) | 被启用(Api级别) |
---|---|---|
SSLv3 | 1–25 | 1–22 |
TLSv1 | 1+ | 1+ |
TLSv1.1 | 16+ | 16+ |
TLSv1.2 | 16+ | 16+ |
数据来源:https://developer.android.com/reference/javax/net/ssl/SSLSocket
注意:这里说的客户端和服务端不是指Android端和JavaEE端/PHP端(还有Python、.NET等等),是指的在Android开发中的客户端Socket(SSLSocket)和服务端Socket(SSLServerSocket)。
到这里其实已经知道本文开始处的问题的原因了,TLSv1.1和TLSv1.2从Android4.1(Api级别16)开始才被支持,从Android4.4 Wear(Api级别20)才被启用(手机是Android5.0,Api级别21),因此在不同版本的Android系统中会出现需要被启用和启用时报不被支持的问题。
我们可以写一个TLS协议通用的兼容类,在所有的Android中强制启用已经被支持的所有协议,总结一下就是Android8.0及以上系统可以强制启用TLSv1、TLSv1.1和TLSv1.2协议,Android4.1-Android7.1.1系统可以强制启用SSLv3、TLSv1、TLSv1.1和TLSv1.2协议,其它版本系统可以强制启用SSLv3和TLSv1协议。
综上所述,如果开发者使用SSLv3协议,那么minSdkVersion不限制,targetSdkVersion不高于25,并且需要尽快更新为TLSv1.1协议或者TLS1.2协议;如果开发者使用TLSv1.1协议或者TLSv1.2协议,那么minSdkVersion应该不低于16,targetSdkVersion不限制;如果开发者使用TLSv1协议,那么目前不受限制。
我们需要让SSLSocket启用对应的协议,代码对应的方法是:
1 |
SSLSocket#setEnabledProtocols(String[]); |
因此我们需要先在不同版本的Android系统中生成不同的协议数组:
1 2 3 4 5 6 7 8 9 10 11 |
private static final String PROTOCOL_ARRAY[]; static { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { PROTOCOL_ARRAY = new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"}; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { PROTOCOL_ARRAY = new String[]{"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"}; } else { PROTOCOL_ARRAY = new String[]{"SSLv3", "TLSv1"}; } } |
SSLSocket是由SSLSocketFactory负责生成的,我们再写一个SSLSocketFactory的包装类,主要代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
public class TLSSocketFactory extends SSLSocketFactory { private static final String PROTOCOL_ARRAY[]; static { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { PROTOCOL_ARRAY = new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"}; } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { PROTOCOL_ARRAY = new String[]{"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2"}; } else { PROTOCOL_ARRAY = new String[]{"SSLv3", "TLSv1"}; } } private SSLSocketFactory delegate; /** * 默认构造方法,直接生成启用所有协议的SSLSocket。 */ public TLSSocketFactory() { try { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[]{DEFAULT_TRUST_MANAGERS}, new SecureRandom()); delegate = sslContext.getSocketFactory(); } catch (GeneralSecurityException e) { throw new AssertionError(); // The system has no TLS. Just give up. } } /** * 包装SSLSocketFactory的构造方法,让外部生成的SSLScoket启用所有协议。 */ public TLSSocketFactory(SSLSocketFactory factory) { this.delegate = factory; } /** * 如果是SSLSocket,启用所有协议。 */ private static void setSupportProtocolAndCipherSuites(Socket socket) { if (socket instanceof SSLSocket) { ((SSLSocket) socket).setEnabledProtocols(PROTOCOL_ARRAY); } } // TODO 下面省去每一个createSocket()方法调用setSupportProtocolAndCipherSuites()方法的代码。 } |
iOS 客户端(SSLSocket)的支持情况:
TLS 1.2 从 iOS 5 开始支持(TLS 1.2 was first added to iOS in iOS 5)
另外 对于在 2020 年 9 月 1 日格林尼治标准时间/世界标准时间 00:00 或之后颁发的 TLS 服务器证书,其有效期不得超过 398 天。
苹果公司给出了一个枚举,如下:
1 2 3 4 5 6 7 8 9 |
typedef NS_ENUM (NSInteger, UIDeviceOrientation) { UIDeviceOrientationUnknown, UIDeviceOrientationPortrait, // 竖向,home键向下 UIDeviceOrientationPortraitUpsideDown, // 竖向,home键向上 UIDeviceOrientationLandscapeLeft, // 横向,home键向右 UIDeviceOrientationLandscapeRight, // 横向,home键向左 UIDeviceOrientationFaceUp, // 屏幕平放,向上 UIDeviceOrientationFaceDown // 屏幕平放,向下 } |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
typedef NS_ENUM(NSInteger, UIInterfaceOrientation) { UIInterfaceOrientationUnknown = UIDeviceOrientationUnknown, UIInterfaceOrientationPortrait = UIDeviceOrientationPortrait, UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown, UIInterfaceOrientationLandscapeLeft = UIDeviceOrientationLandscapeRight, UIInterfaceOrientationLandscapeRight = UIDeviceOrientationLandscapeLeft } |
2、对于获取手机屏幕
(1)
1 2 3 4 5 |
[[ UIDevice currentDevice ] beginGeneratingDeviceOrientationNotifications ]; dispatch_async ( dispatch_get_main_queue (), ^{ NSLog ( @"=========%zd" ,[[ UIDevice currentDevice ] orientation ]); }); [[ UIDevice currentDevice ] endGeneratingDeviceOrientationNotifications ]; |
(2)
1 |
UIInterfaceOrientation orientation = [ UIApplication sharedApplication ]. statusBarOrientation ; |
3、对于当前手机是不是横屏或者竖屏的判断
(1)判断是否是竖屏
1 2 3 |
static inline BOOL UIDeviceOrientationIsPortrait( UIDeviceOrientation orientation) { return ((orientation) == UIDeviceOrientationPortrait || (orientation) == UIDeviceOrientationPortraitUpsideDown ); } |
(2)判断是否是横屏
1 2 3 |
static inline BOOL UIDeviceOrientationIsLandscape( UIDeviceOrientation orientation) { return ((orientation) == UIDeviceOrientationLandscapeLeft || (orientation) == UIDeviceOrientationLandscapeRight ); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public class SimpleOrientationActivity extends Activity { OrientationEventListener mOrientationListener; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mOrientationListener = new OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) { @Override public void onOrientationChanged(int orientation) { Log.v(DEBUG_TAG, "Orientation changed to " + orientation); } }; if (mOrientationListener.canDetectOrientation()) { Log.v(DEBUG_TAG, "Can detect orientation"); mOrientationListener.enable(); } else { Log.v(DEBUG_TAG, "Cannot detect orientation"); mOrientationListener.disable(); } } @Override protected void onDestroy() { super.onDestroy(); mOrientationListener.disable(); } } |
判断手机方向的具体判断代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
if (OrientationEventListener.ORIENTATION_UNKNOWN == orientation) { return; //手机平放时,检测不到有效的角度 } // 我们只关心 0, 90, 180 ,270 测试发现,很多设备返回的数据是很混乱的,但是这四个度数比较稳定 if((0 == orientation % 90) && (orientation < 360) && (orientation >= 0)) { //只检测是否有四个角度的改变 if (orientation > 350 || orientation < 10) { //0度 orientation = 0; } else if (orientation > 80 && orientation < 100) { //90度 orientation = 90; } else if (orientation > 170 && orientation < 190) { //180度 orientation = 180; } else if (orientation > 260 && orientation < 280) { //270度 orientation = 270; } else { return; } Log.i("MyOrientationDetector ", "onOrientationChanged:" + orientation); } |
使用 native_device_orientation 插件完成相同的检测功能。
在iPhone 5s加入Touch ID后,指纹识别的功能在App中逐渐受到青睐,特别是对于本地安全较高的应用(如带支付的App)指纹识别是必备的功能,它既能解决在验证过程中输入密码的繁琐过程,同时指纹识的安全等级更高。那么,要想在自己开发的应用中使用指纹识别,就必须要LocalAuthentication.framework提供的API,下面将详细地介绍如何使用这个框架来实现指纹识别功能。
我们先来看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
LAContext *context = [[LAContext alloc] init]; NSError *error = nil; if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) { [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:@"输入指纹进行验证" reply:^(BOOL success, NSError * _Nullable error) { if (success) { NSLog(@"验证成功"); } else { NSLog(@"验证失败"); } }]; } else { NSLog(@"识别功能不可用"); } |
相信大家对于生物认证应该不会陌生,使用指纹登陆或者 FaceId 支付等的需求场景如今已经很普遍,所以基本上只要涉及移动端开发,不管是 Android 、iOS 或者是 RN 、Flutter 都多多少少会接触到这一业务场景。
当然,不同之处可能在于大家对于平台能力或者接口能力的熟悉程度,所以本篇主要介绍 Android 和 iOS 上使用系统的生物认证需要注意什么,具体流程是什么,给需要或者即将需要的大家出一份汇总的资料。
Xcode 9
以上 和iOS 11
以上:两者缺一不可
iOS keychain 是一个相对独立的空间,保存到keychain钥匙串中的信息不会因为卸载/重装app而丢失。相对于NSUserDefaults、plist文件保存等一般方式,keychain保存更为安全。所以我们会用keyChain保存一些私密信息,比如密码、证书、设备唯一码(把获取到用户设备的唯一ID 存到keychain 里面这样卸载或重装之后还可以获取到id,保证了一个设备一个ID)等等。keychain是用SQLite进行存储的。用苹果的话来说是一个专业的数据库,加密我们保存的数据,可以通过metadata(attributes)进行高效的搜索。keychain适合保存一些比较小的数据量的数据,如果要保存大的数据,可以考虑文件的形式存储在磁盘上,在keychain里面保存解密这个文件的密钥。
最近在macOS Big Sur(11.4)编译Hummer
1 2 3 4 5 6 7 8 9 10 11 |
//代码版本 2021-07-12 $ git clone https://github.com/didi/Hummer.git $ cd Hummer $ cd android $ bash gradlew clean build # Android Studio 打开当前目录下的项目即可 |
如果报错如下:
1 2 3 |
* What went wrong: A problem occurred configuring project ':hummer-core'. > ABIs [arm64-v8a] are not supported for platform. Supported ABIs are [armeabi-v7a, x86]. |
参考 ABIs [arm64-v8a] are not supported for platform. Supported ABIs are [armeabi-v7a, x86].
如果运行测试用例时报错如下:
1 2 3 4 5 6 |
2021-07-12 10:32:56.704 11116-11116/com.didi.hummer.demo E/log: error java.lang.ClassNotFoundException: Didn't find class "com.facebook.jni.HybridData$Destructor" on path: DexPathList[[dex file "/data/data/com.didi.hummer.demo/code_cache/.overlay/base.apk/classes2.dex", zip file "/data/app/~~ayUY1JA739Wo5kLM7hjUuQ==/com.didi.hummer.demo-7LKAxXsQNZuXWiS7L2dZwg==/base.apk"],nativeLibraryDirectories=[/data/app/~~ayUY1JA739Wo5kLM7hjUuQ==/com.didi.hummer.demo-7LKAxXsQNZuXWiS7L2dZwg==/lib/x86, /data/app/~~ayUY1JA739Wo5kLM7hjUuQ==/com.didi.hummer.demo-7LKAxXsQNZuXWiS7L2dZwg==/base.apk!/lib/x86, /system/lib, /system_ext/lib]] 2021-07-12 10:32:56.705 11116-11116/com.didi.hummer.demo A/idi.hummer.dem: java_vm_ext.cc:577] JNI DETECTED ERROR IN APPLICATION: JNI NewGlobalRef called with pending exception java.lang.ClassNotFoundException: Didn't find class "com.facebook.jni.HybridData$Destructor" on path: DexPathList[[dex file "/data/data/com.didi.hummer.demo/code_cache/.overlay/base.apk/classes2.dex", zip file "/data/app/~~ayUY1JA739Wo5kLM7hjUuQ==/com.didi.hummer.demo-7LKAxXsQNZuXWiS7L2dZwg==/base.apk"],nativeLibraryDirectories=[/data/app/~~ayUY1JA739Wo5kLM7hjUuQ==/com.didi.hummer.demo-7LKAxXsQNZuXWiS7L2dZwg==/lib/x86, /data/app/~~ayUY1JA739Wo5kLM7hjUuQ==/com.didi.hummer.demo-7LKAxXsQNZuXWiS7L2dZwg==/base.apk!/lib/x86, /system/lib, /system_ext/lib]] java_vm_ext.cc:577] (Throwable with no stack trace) java_vm_ext.cc:577] java_vm_ext.cc:577] in call to NewGlobalRef java_vm_ext.cc:577] from java.lang.String java.lang.Runtime.nativeLoad(java.lang.String, java.lang.ClassLoader, java.lang.Class) |
解决方案为在项目中增加:
1 |
implementation 'com.facebook.fbjni:fbjni:0.0.2' |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//代码版本 2021-07-12 $ git clone https://github.com/didi/Hummer.git $ cd Hummer $ cd iOS $ sudo gem install cocoapods $ pod install # 使用 XCode 12.5.1 打开当前目录下的 Example.xcworkspace |
Background
Last year, we wrote that older macOS drivers, known as System Extensions (KEXTs), would begin to be deprecated as of the macOS 10.15 Catalina release. This week, as macOS 11 Big Sur is released, KEXTs have officially been deprecated and are no longer functional.
Instead, Apple is now enforcing the requirement for devices that require third-party drivers to operate as DriverKit Extensions (DEXTs). This change affects many products across the consumer electronics industry, including Docking Stations, USB peripherals, Security/Anti-Virus software, and Networking adapters.
Luckily, most platform vendors and integrated circuit (IC) manufacturers have been prepared for this change and began working on their DEXT implementation after Apple released pertinent documentation at their WWDC (World Wide Developer Conference) in June 2019.
Unfortunately, some vendors could not begin their full transition from KEXT to DEXT until this year. Not all pertinent DriverKit documentation was released at the 2019 event as expected, but, instead, was released at WWDC 2020 in June. Specifically, for Plugable and many other brands, this applies to ASIX-based USB Ethernet Adapters like our USB3-E1000, USBC-E1000, USB2-E1000, and USB2-E100, which we wrote about here for the release of macOS 10.15 Catalina:
https://plugable.com/2019/10/04/usb-ethernet-adapter-not-working-after-macos-catalina-10-15-update-we-can-help/
Current Status for ASIX-based Ethernet Adapters [Novemeber 13, 2020]
We began removing macOS support from our most popular wired network adapters (USB3-E1000 and USBC-E1000) in October 2019. We saw the continued potential for a poor or broken user experience with ASIX-based adapters on macOS and could not in good conscience market the adapters as fully compatible in the long-term as long as ASIX was unwilling to commit to continued development and support of their ICs on macOS.
We have continued to push ASIX for a DEXT beta driver to validate and test internally in the past year. Unfortunately, ASIX has been unable to deliver this solution as requested in anticipation of this significant macOS milestone. We expect a Beta driver from ASIX by the end of November for AX88179 products like the USB3-E1000 and USBC-E1000. Once the driver is validated, we will update this blog post and product driver pages with links to download the driver.
If a wired Ethernet connection is critical to your workflow, Plugable recommends not updating to macOS 11 Big Sur from macOS 10.15 Catalina.
At this time, Plugable is unable to provide a DEXT driver for the following products:
USB3-E1000 IC: AX88179
USBC-E1000 IC: AX88179
USB2-E1000 IC: AX88178 (ASIX has discontinued the IC in this product, and they have not committed to a compatible driver for macOS 11 Big Sur)
The USB2-E100 IC: AX88772 ASIX-based product continues to function, utilizing the in-box drivers in macOS.
We understand this news can be frustrating for our customers and is a situation we have been working to avoid for over one year. We will update this blog post as new information is available to us.
While we cannot promise a functional DEXT driver at any point in the future, if you would like to be notified of that news should it occur, please sign-up on the form below, and we will email everyone as soon as it is available.