Flutter 自定义插件包

作者: 独孤流 | 来源:发表于2019-06-05 15:32 被阅读16次

    前言

    近期做个小demo,网络请求需要用到RSAAES两种方式加密解密,在flutter插件仓库pub.dev上花了一两天找了好几个库,但加密解密都和服务器不一致,而原始的iOS和Android、H5都已经匹配好了服务器的加解密,于是决定自己讲iOS和Android原始的加解密做成插件包提供给flutter使用

    参考资料:


    第一步,准备工作

    1.1 创建一个插件包
    flutter create --org com.example --template=plugin hello
    第二步,进入项目的example目录,并编译,
    1.2 cd hello/example
    1.3 flutter build ios --no-codesign
    1.4 用Xcode打开xxx/hello/example/ios/Runner.xcworkspace并运行
    运行后的效果:

    准备环境就绪
    TIPS:

    如果不进行上面的过程直接用Xcode打开xxx/hello/example/ios/Runner.xcworkspace会报
    如下的错误:[解决办法:重新执行1.2、1.3两步]

    error: xxxx/hello/example/ios/Flutter/Debug.xcconfig:1: could not find included file 'Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig' in search paths (in target 'Runner')

    或者如下错误:

    error: Multiple commands produce '/Users/xxx/Library/Developer/Xcode/DerivedData/Runner-ahuwjiirmcnmaqfoqhkenntiufto/Build/Products/Debug-iphonesimulator/Runner.app/Frameworks/Flutter.framework':

    1. Target 'Runner' has copy command from '/Users/xxx/hello/example/ios/Flutter/Flutter.framework' to '/Users/xxx/Library/Developer/Xcode/DerivedData/Runner-ahuwjiirmcnmaqfoqhkenntiufto/Build/Products/Debug-iphonesimulator/Runner.app/Frameworks/Flutter.framework'
    2. That command depends on command in Target 'Runner': script phase “[CP] Embed Pods Frameworks”

    第二步,编写业务代码

    2.1、由于我要写的这个RSA加解密需要用到第一第三方库,直接将该第三方库复制到xxx/hello/ios/Classes/GTMBase64/,结构如下如下:
    GTMBase64
    RSA
    iOS AES128加解密(NSDate+AES)

    tmp6bfcba69.png

    2.2、在xxx/hello/ios/Classes/HelloPlugin.m里编辑原生代码

    #import "HelloPlugin.h"
    #import "RSA.h"
    // AES
    #import "GTMBase64.h"
    #import "NSData+AES.h"
    
    @implementation HelloPlugin
    + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
      FlutterMethodChannel* channel = [FlutterMethodChannel
          methodChannelWithName:@"hello"
                binaryMessenger:[registrar messenger]];
      HelloPlugin* instance = [[HelloPlugin alloc] init];
      [registrar addMethodCallDelegate:instance channel:channel];
    }
    
    - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
      if ([@"getPlatformVersion" isEqualToString:call.method]) {
        result([@"iOS sadhfas " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
      }else if ([@"rsaPublicKeyEncrypt" isEqualToString:call.method]) {
          // RSA公钥加密
          NSString *originTxt = call.arguments[@"originTxt"];
          NSString *publicKey = call.arguments[@"publicKey"];
          NSString *encryptByPublicKeyString = [RSA encryptString:originTxt publicKey:publicKey];
          result(encryptByPublicKeyString);
      }else if ([@"rsaPublicKeyDecrypt" isEqualToString:call.method]) {
          // RSA公钥解密
          NSString *originTxt = call.arguments[@"originTxt"];
          NSString *publicKey = call.arguments[@"publicKey"];
          NSString *decryptByPublicKeyString = [RSA decryptString:originTxt publicKey:publicKey];
          result(decryptByPublicKeyString);
      }else if ([@"aesEncrypt" isEqualToString:call.method]) {
          // AES加密
          NSString *originTxt = call.arguments[@"originTxt"];
          NSString *aesKey = call.arguments[@"aesKey"];
          NSString *aesIV = call.arguments[@"aesIV"];
          NSData *data1 = [originTxt dataUsingEncoding:NSUTF8StringEncoding];
          NSData *data2 = [data1 AES128EncryptWithKey:aesKey iv:aesIV];
          NSData *data3 = [GTMBase64 encodeData:data2];
          NSString *encryptString = [[NSString alloc] initWithData:data3 encoding:NSUTF8StringEncoding];
          result(encryptString);
      }else if ([@"aesDecrypt" isEqualToString:call.method]) {
          // AES解密
          NSString *originTxt = call.arguments[@"originTxt"];
          NSString *aesKey = call.arguments[@"aesKey"];
          NSString *aesIV = call.arguments[@"aesIV"];
          NSData *data = [originTxt dataUsingEncoding:NSUTF8StringEncoding];
          data = [GTMBase64 decodeData:data];
          NSData *data5 = [data AES128DecryptWithKey:aesKey iv:aesIV];
          NSString *decryptString = [[NSString alloc] initWithData:data5 encoding:NSUTF8StringEncoding];
          result(decryptString);
      } else {
        result(FlutterMethodNotImplemented);
      }
    }
    
    @end
    

    NSDate+AES.h

    #import <Foundation/Foundation.h>
    
    @interface NSData (AES)
    //加密
    - (NSData *)AES128EncryptWithKey:(NSString *)key iv:(NSString *)iv;
    //解密
    - (NSData *)AES128DecryptWithKey:(NSString *)key iv:(NSString *)iv;
    @end
    

    NSDate+AES.m

    #import "NSData+AES.h"
    #import <CommonCrypto/CommonCryptor.h>
    
    @implementation NSData (AES)
    
    //加密
    - (NSData *)AES128EncryptWithKey:(NSString *)key iv:(NSString *)iv
    {
        return [self AES128operation:kCCEncrypt key:key iv:iv];
    }
    
    //解密
    - (NSData *)AES128DecryptWithKey:(NSString *)key iv:(NSString *)iv
    {
        return [self AES128operation:kCCDecrypt key:key iv:iv];
    }
    
    - (NSData *)AES128operation:(CCOperation)operation key:(NSString *)key iv:(NSString *)iv
    {
        char keyPtr[kCCKeySizeAES128 + 1];
        bzero(keyPtr, sizeof(keyPtr));
        [key getCString:keyPtr maxLength:sizeof(keyPtr) encoding:NSUTF8StringEncoding];
        
        // IV
        char ivPtr[kCCBlockSizeAES128 + 1];
        bzero(ivPtr, sizeof(ivPtr));
        [iv getCString:ivPtr maxLength:sizeof(ivPtr) encoding:NSUTF8StringEncoding];
        
        size_t bufferSize = [self length] + kCCBlockSizeAES128;
        void *buffer = malloc(bufferSize);
        size_t numBytesEncrypted = 0;
        
        
        CCCryptorStatus cryptorStatus = CCCrypt(operation, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
                                                keyPtr, kCCKeySizeAES128,
                                                ivPtr,
                                                [self bytes], [self length],
                                                buffer, bufferSize,
                                                &numBytesEncrypted);
        
        if(cryptorStatus == kCCSuccess){
            NSLog(@"Success");
            return [NSData dataWithBytesNoCopy:buffer length:numBytesEncrypted];
            
        }else{
            NSLog(@"Error");
        }
        
        free(buffer);
        return nil;
    }
    @end
    

    2.4、写flutter里的API方法xxx/hello/lib/hello.dart

    import 'dart:async';
    
    import 'package:flutter/services.dart';
    
    class Hello {
      static const MethodChannel _channel =
          const MethodChannel('hello');
    
      static Future<String> get platformVersion async {
        final String version = await _channel.invokeMethod('getPlatformVersion');
        return version;
      }
    
      /**
       * RSA公钥加密
       * originTxt: 需要加密的文本
       * publicKey: 公钥字符串
       */
      static Future<String> rsaPublicKeyEncrypt(String originTxt,String publicKey) async{
         var params = {'originTxt':originTxt,'publicKey':publicKey};
         String encryptTxt = await _channel.invokeMethod('rsaPublicKeyEncrypt',params);
        return encryptTxt;
      }
      /**
       * RSA公钥解密
       * originTxt: 需要解密的文本
       * publicKey: 公钥字符串
       */
      static Future<String> rsaPublicKeyDecrypt(String originTxt,String publicKey) async{
         var params = {'originTxt':originTxt,'publicKey':publicKey};
         String decryptTxt = await _channel.invokeMethod('rsaPublicKeyDecrypt',params);
        return decryptTxt;
      }
      /**
       * AES加密
       * originTxt: 需要解密的文本
       * aesKey AES秘钥
       * aseIV AES解密IV
       */
      static Future<String> aesEncrypt(String originTxt,String aesKey,String aesIV) async{
         var params = {'originTxt':originTxt,'aesKey':aesKey,'aesIV':aesIV};
         String encryptTxt = await _channel.invokeMethod('aesEncrypt',params);
        return encryptTxt;
      }
      /**
       * AES解密
       * originTxt: 需要解密的文本
       * aesKey AES秘钥
       * aseIV AES解密IV
       */
      static Future<String> aesDecrypt(String originTxt,String aesKey,String aesIV) async{
         var params = {'originTxt':originTxt,'aesKey':aesKey,'aesIV':aesIV};
         String decryptTxt = await _channel.invokeMethod('aesDecrypt',params);
        return decryptTxt;
      }
    }
    
    

    2.4 添加公钥和私钥文件用来测试
    /Users/xxx/hello/example/assets/keys/public_key.pem
    /Users/xxx/hello/example/assets/keys/private_key.pem

    2.5 在xxx/hello/example/lib/main.dart里写代码测试运行效果

    import 'package:flutter/material.dart';
    import 'dart:async';
    import 'package:flutter/services.dart';
    import 'package:hello/hello.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      String _platformVersion = 'Unknown';
      String _originTxt =  'Hello 中文 12345 {name:小名}';
      String _rsaEncrypt;
      String _rsaDecrypt;
      String _aesEncrypt;
      String _aesDecrypt;
    
      @override
      void initState() {
        super.initState();
        initPlatformState();
        rsaDemo();
        aesDemo();
      }
    
      // Platform messages are asynchronous, so we initialize in an async method.
      Future<void> initPlatformState() async {
        String platformVersion;
        // Platform messages may fail, so we use a try/catch PlatformException.
        try {
          platformVersion = await Hello.platformVersion;
        } on PlatformException {
          platformVersion = 'Failed to get platform version.';
        }
        // If the widget was removed from the tree while the asynchronous platform
        // message was in flight, we want to discard the reply rather than calling
        // setState to update our non-existent appearance.
        if (!mounted) return;
    
        setState(() {
          _platformVersion = platformVersion;
        });
      }
      // RSA公钥加解密
      Future<void> rsaDemo() async {
        // 获取到公钥的文本
        String publicKey = await rootBundle.loadString('assets/keys/public_key.pem');
        String rsaEncryptTxt = await Hello.rsaPublicKeyEncrypt(_originTxt, publicKey);
        String rsaDecryptTxt = await Hello.rsaPublicKeyDecrypt(rsaEncryptTxt, publicKey);
        setState(() {
          _rsaEncrypt = rsaEncryptTxt;
          _rsaDecrypt = rsaDecryptTxt;
        });
      }
    // AES加解密
      Future<void> aesDemo() async {
      
        String aesKey = 'xxxxxx';
        String aesIV = 'xxxxxx';
        String aesEncryptTxt = await Hello. aesEncrypt(_originTxt, aesKey,aesIV);
        String aesDecryptTxt = await Hello. aesDecrypt(aesEncryptTxt, ,aesKey,aesIV);
        setState(() {
          _aesEncrypt = aesEncryptTxt;
          _aesDecrypt = aesDecryptTxt;
        });
      }
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('Plugin example app'),
            ),
            body: Column(
              children: <Widget>[
                Text('Running on: $_platformVersion\n'),
                Text('originTxt on: $_originTxt\n'),
                Text('rsaEncrypt on: $_rsaEncrypt\n'),
                Text('rsaDecrypt on: $_rsaDecrypt\n'),
              ],
            )
            
          ),
        );
      }
    }
    

    第三步,将编写的插件引入到主flutter项目中路径

    详情参看【 使用 packages
    依赖: 一个Flutter应用可以依赖一个插件通过文件系统的path:依赖。路径可以是相对的,也可以是绝对的。例如,要依赖位于应用相邻目录中的插件’hello’,请使用以下语法

    dependencies:
      hello:
        path: ../hello/
    

    相关文章

      网友评论

        本文标题:Flutter 自定义插件包

        本文链接:https://www.haomeiwen.com/subject/zyajxctx.html