Repository: Assuner-Lee/dna Branch: master Commit: d910c4263645 Files: 94 Total size: 150.3 KB Directory structure: gitextract_9x1xny_o/ ├── .gitattributes ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_CN.md ├── android/ │ ├── .gitignore │ ├── build.gradle │ ├── gradle.properties │ ├── settings.gradle │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── me/ │ └── ele/ │ └── dna/ │ ├── DLog.java │ ├── DnaClient.java │ ├── DnaPlugin.java │ ├── IResultCallBack.java │ ├── exception/ │ │ ├── AbnormalConstructorException.java │ │ ├── AbnormalMethodException.java │ │ └── ArgsException.java │ ├── finder/ │ │ ├── BaseDnaFinder.java │ │ ├── ConstructorFinder.java │ │ ├── MethodFinder.java │ │ └── ProxyFinder.java │ ├── model/ │ │ ├── DnaClassInfo.java │ │ ├── DnaResult.java │ │ ├── MethodInfo.java │ │ ├── MethodTacker.java │ │ ├── ParameterInfo.java │ │ └── ResultInfo.java │ └── util/ │ ├── DnaUtils.java │ └── GsonUtils.java ├── dna.iml ├── example/ │ ├── .gitignore │ ├── .metadata │ ├── android/ │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ ├── proguard-rules.pro │ │ │ └── src/ │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── java/ │ │ │ │ │ └── me/ │ │ │ │ │ └── ele/ │ │ │ │ │ └── dna_example/ │ │ │ │ │ ├── DnaTest.java │ │ │ │ │ ├── DnaVersion.java │ │ │ │ │ └── MainActivity.java │ │ │ │ └── res/ │ │ │ │ ├── drawable/ │ │ │ │ │ └── launch_background.xml │ │ │ │ └── values/ │ │ │ │ └── styles.xml │ │ │ └── profile/ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── dna-annotations/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── me/ │ │ │ └── ele/ │ │ │ └── dna_annotations/ │ │ │ ├── DnaConstants.java │ │ │ ├── DnaMethod.java │ │ │ └── DnaParamFieldList.java │ │ ├── dna-compiler/ │ │ │ ├── .gitignore │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ └── main/ │ │ │ ├── java/ │ │ │ │ └── me/ │ │ │ │ └── ele/ │ │ │ │ └── dna_compiler/ │ │ │ │ ├── BaseDnaElement.java │ │ │ │ ├── DnaClassFinder.java │ │ │ │ ├── DnaConstructorInfo.java │ │ │ │ ├── DnaMethodInfo.java │ │ │ │ ├── DnaPackageFinder.java │ │ │ │ ├── DnaProcessor.java │ │ │ │ └── ParamInfo.java │ │ │ └── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── javax.annotation.processing.Processor │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ └── settings.gradle │ ├── ios/ │ │ ├── Flutter/ │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── Podfile │ │ ├── Runner/ │ │ │ ├── AppDelegate.h │ │ │ ├── AppDelegate.m │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ ├── Contents.json │ │ │ │ └── README.md │ │ │ ├── Base.lproj/ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ └── main.m │ │ ├── Runner.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ └── contents.xcworkspacedata │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── Runner.xcscheme │ │ └── Runner.xcworkspace/ │ │ └── contents.xcworkspacedata │ ├── lib/ │ │ └── main.dart │ ├── pubspec.yaml │ └── test/ │ └── widget_test.dart ├── ios/ │ ├── .gitignore │ ├── Assets/ │ │ └── .gitkeep │ ├── Classes/ │ │ ├── DnaPlugin.h │ │ ├── DnaPlugin.m │ │ ├── NSObject+DnaRuntime.h │ │ └── NSObject+DnaRuntime.m │ └── dna.podspec ├── lib/ │ ├── dna.dart │ ├── native_context.dart │ └── native_object.dart ├── pubspec.yaml └── test/ └── dna_test.dart ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.* linguist-language=Dart ================================================ FILE: .gitignore ================================================ .DS_Store .dart_tool/ /.idea .packages .pub/ build/ /.idea/workspace.xml ================================================ FILE: .metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b channel: stable project_type: plugin ================================================ FILE: CHANGELOG.md ================================================ ## 0.0.1 * TODO: Describe initial release. ================================================ FILE: LICENSE ================================================ TODO: Add your license here. ================================================ FILE: README.md ================================================ # dna ### [中文文档👉](./README_CN.md) ### [相关文章](https://juejin.im/post/5e5f1d41518825495b29a05b) dart native access. A lightweight dart to native super channel plugin, You can use it to invoke any native code directly in dart code. Supported Platform(Language): - iOS(Objective-C) - Android(Java) The primary scenario: - Implement some simple channels directly in dart code; - Native code that are calling using dna can also be hot-reloaded. ## Add dependency 1. Add folllowing code to the *pubspec.yaml* file in your flutter project: ``` dependencies: dna: git:git@github.com:Assuner-Lee/dna.git ``` > Reference: [https://flutter.dev/docs/development/packages-and-plugins/using-packages](https://flutter.dev/docs/development/packages-and-plugins/using-packages) 2. import header file in dart code: ``` import 'package:dna/dna.dart'; ``` 3. add gradle dependency in Android project: ``` implementation 'me.ele:dna-annotations:1.2.0' annotationProcessor 'me.ele:dna-compiler:1.2.0' ``` 4. add following conconfiguration in Android project's proguard-rules ``` -keep class **.Dna_Class_Proxy { *; } -keep class me.ele.dna_compiler.** { *; } -keep class me.ele.dna.** { *; } ``` ## Usage ### Main class - `NativeContext`: You can use it to describe *Native code* by *Dart code*, then call `context.execute()` to execute the final *Native code* on associated platform and get the returned value. - `NativeObject`: Used to identify the *native variable*. The caller `NativeObject ` can call the `invoke` method to pass in the *method name* and the *parameter array args list* in the context of the `NativeContext` to get the return value `NativeObject` object. The API of `NativeContext` is consistent. Now we will make a detailed introduction for call *ObjC* using `ObjCContext`, Then call *Java* using `JAVAContext`. ### Call ObjC using Dart `ObjCContext` is the final executor on iOS platform. #### Context call supported ##### Returned value as caller ObjC code ``` NSString *versionString = [[UIDevice currentDevice] systemVersion]; // Return versionString using fluter channel ``` Dart code ``` ObjCContext context = ObjCContext(); NativeObject UIDevice = context.classFromString('UIDevice'); NativeObject device = UIDevice.invoke(method: 'currentDevice'); NativeObject version = device.invoke(method: 'systemVersion'); context.returnVar = version; // Can be omitted, See:Quick use of instantiated objects in JSON supported // Get native execution results directly var versionString = await context.execute(); ``` ##### Returned value as parameters ObjC code ``` NSString *versionString = [[UIDevice currentDevice] systemVersion]; NSString *platform = @"iOS-"; versionString = [platform stringByAppendingString: versionString]; // Return versionString using fluter channel ``` Dart code ``` ObjCContext context = ObjCContext(); NativeClass UIDevice = context.classFromString('UIDevice'); NativeObject device = UIDevice.invoke(method: 'currentDevice'); NativeObject version = device.invoke(method: 'systemVersion'); NativeObject platform = context.classFromString("NSString").invoke(method: 'stringWithString:', args: ['iOS-']); version = platform.invoke(method: 'stringByAppendingString:', args: [version]); context.returnVar = version; // Can be omitted, See:Quick use of instantiated objects in JSON supported // Get native execution results directly var versionString = await context.execute(); ``` #### Chaining calls supported ObjC code ``` NSString *versionString = [[UIDevice currentDevice] systemVersion]; versionString = [@"iOS-" stringByAppendingString: versionString]; // Return versionString using fluter channel ``` Dart code ``` ObjCContext context = ObjCContext(); NativeObject version = context.classFromString('UIDevice').invoke(method: 'currentDevice').invoke(method: 'systemVersion'); version = context.classFromString("NSString").invoke(method: 'stringWithString:', args: ['iOS-']).invoke(method: 'stringByAppendingString:', args: [version]); context.returnVar = version; // Can be omitted, See:Quick use of instantiated objects in JSON supported // Get native execution results directly var versionString = await context.execute(); ``` > **Something about the final returned value of the `context`** > `context.returnVar` is the marker of the final returned value of `context`. > 1. When setting `context.returnVar`, you can get the *Native* variable corresponding to the `NativeObject`; > 2. Without setting `context.returnVar`, execute to the last `invoke`, if there is a return value, it will be the final returned value of `context`; if not, it will return a `null` value. > ``` > ObjCContext context = ObjCContext(); > context.classFromString('UIDevice').invoke(method: 'currentDevice').invoke(method: 'systemVersion'); > > // Get native execution results directly > var versionString = await context.execute(); > ``` #### Quick use of instantiated objects in JSON supported Sometimes, we need to directly instantiate an object with `JSON`. ObjC code ``` ClassA *objectA = [ClassA new]; objectA.a = 1; objectA.b = @"sss"; ``` Dart code One way ``` ObjCContext context = ObjCContext(); NativeObject objectA = context.classFromString('ClassA').invoke(method: 'new'); objectA.invoke(method: 'setA:', args: [1]); objectA.invoke(method: 'setB:', args: ['sss']); ``` The other way ``` ObjCContext context = ObjCContext(); NativeObject objectA = context.newNativeObjectFromJSON({'a':1,'b':'sss'}, 'ClassA'); ``` ### Call Java using Dart `JAVAContext` is the final executor on Android platform, it has all the fetures that `ObjCContext` have. - Context call supported; - Chaining calls supported; - Quick use of instantiated objects in JSON supported. In addition, it additionally supports the instantiation of an object from the constructor. #### The instantiation of an object from the constructor supported Java code ``` String platform = new String("android"); ``` Dart code ``` NativeObject version = context .newJavaObjectFromConstructor('java.lang.String', ["android "]) ``` ### Fast organization of dual platform code We provide you with a quick way to initialize and execute context: ``` static Future traversingNative(ObjCContextBuilder(ObjCContext objcContext), JAVAContextBuilder(JAVAContext javaContext)) async { NativeContext nativeContext; if (Platform.isIOS) { nativeContext = ObjCContext(); ObjCContextBuilder(nativeContext); } else if (Platform.isAndroid) { nativeContext = JAVAContext(); JAVAContextBuilder(nativeContext); } return executeNativeContext(nativeContext); } ``` So you can write the native call of two platforms quickly: ``` platformVersion = await Dna.traversingNative((ObjCContext context) { NativeObject version = context.classFromString('UIDevice').invoke(method: 'currentDevice').invoke(method: 'systemVersion'); version = context.classFromString("NSString").invoke(method: 'stringWithString:', args: ['iOS-']).invoke(method: 'stringByAppendingString:', args: [version]); context.returnVar = version; // Can be omitted }, (JAVAContext context) { NativeObject versionId = context.newJavaObjectFromConstructor('com.example.dna_example.DnaTest', null).invoke(method: 'getDnaVersion').invoke(method: 'getVersion'); NativeObject version = context.newJavaObjectFromConstructor('java.lang.String', ["android "]).invoke(method: "concat", args: [versionId]); context.returnVar = version; // Can be omitted }); ``` ## Principle introduction dna **does not involve the transformation from a dart object to a native object**, it also **does not care about the life cycle of the native object**, but **focuses on describing the `context` of native method calls**, When `context.execute()` called, a native method is called through `channel`, and the call stack is passed in the form of `JSON` for native dynamic parsing and calling. for example, Let's take a look at the previous Dart code: ``` ObjCContext context = ObjCContext(); NativeObject version = context.classFromString('UIDevice').invoke(method: 'currentDevice').invoke(method: 'systemVersion'); version = context.classFromString("NSString").invoke(method: 'stringWithString:', args: ['iOS-']).invoke(method: 'stringByAppendingString:', args: [version]); context.returnVar = version; // Can be omitted, See: Quick use of instantiated objects in JSON supported // Get native execution results directly var versionString = await context.execute(); ``` What the `execute()` method of `NativeContext` actually called is the following method: ``` static Future executeNativeContext(NativeContext context) async { return await _channel.invokeMethod('executeNativeContext', context.toJSON()); } ``` In the native executed method corresponding to the `executeNativeContext` method, the received 'JSON' is as follows: ``` { "_objectJSONWrappers": [], "returnVar": { "_objectId": "_objectId_WyWRIsLl" }, "_invocationNodes": [{ "returnVar": { "_objectId": "_objectId_KNWtiPuM" }, "object": { "_objectId": "_objectId_qyfACNGb", "clsName": "UIDevice" }, "method": "currentDevice" }, { "returnVar": { "_objectId": "_objectId_haPktBlL" }, "object": { "_objectId": "_objectId_KNWtiPuM" }, "method": "systemVersion" }, { "object": { "_objectId": "_objectId_UAUcgnOD", "clsName": "NSString" }, "method": "stringWithString:", "args": ["iOS-"], "returnVar": { "_objectId": "_objectId_UiCMaHAN" } }, { "object": { "_objectId": "_objectId_UiCMaHAN" }, "method": "stringByAppendingString:", "args": [{ "_objectId": "_objectId_haPktBlL" }], "returnVar": { "_objectId": "_objectId_WyWRIsLl" } }] } ``` Then we maintain an `objectsInContextMap` on the native side, its key is `objectId`, and the value is native object. `_invocationNodes` is the call context of the method, let's take a look at one of them. Here we will dynamically call `[UIDevice currentDevice]`, and return the object to `objectsInContextMap` with `_objectId_KNWtiPuM` stored in `returnVar` as the key. ``` { "returnVar": { "_objectId": "_objectId_KNWtiPuM" }, "object": { "_objectId": "_objectId_qyfACNGb", "clsName": "UIDevice" }, "method": "currentDevice" }, ``` Here, the object `_objectId_KNWtiPuM` is the returned value of the previous method. Take it out from the `objectsInContextMap`, continue the dynamic call, and store the new returned value with the `_objectId` of the `returnVar` as the key. ``` { "returnVar": { "_objectId": "_objectId_haPktBlL" }, "object": { "_objectId": "_objectId_KNWtiPuM" // Will find the real object in objectsInContextMap }, "method": "systemVersion" } ``` dna supports automatic package loading and unpacking when the method has parameters, such as `int<->NSNumber`, If the parameter is not one of the 15 basic types specified by `channel` but `NativeObject`, we will find the object from `objectsInContextMap` and put it into the actual parameter list. ``` { "object": { "_objectId": "_objectId_UiCMaHAN" }, "method": "stringByAppendingString:", "args": [{ "_objectId": "_objectId_haPktBlL" // Will find the real object in objectsInContextMap }], "returnVar": { "_objectId": "_objectId_WyWRIsLl" } ``` If final `returnVar` is set, The object corresponding to the `returnVar objectId` will be found from the `objectsInContextMap` and called back as the return value of the `channel `, if not, take the return value of the last `invocation`(if any). ## Author - yongguang.lyg@alibaba-inc.com - zhengguang.zzg@alibaba-inc.com - zyd178591@alibaba-inc.com ## Change log | version | note | | ------ | ------ | | 0.1.0 | alpha version | ## License dna is available under the MIT license. See the LICENSE file for more info. ## Other Tips - Code warehouse will be migrated to **eleme** in the near future; - You are welcome to star, issue and PR. ================================================ FILE: README_CN.md ================================================ # dna ### [README👉](./README.md) 一个flutter plugin. 轻量级的Dart到Native的超级通道, 可直接在dart代码中调用原生代码,目前支持安卓 JAVA 和 iOS ObjC. 主要用途: * 可以把channel中的原生代码写在dart代码中, * 让原生代码也支持热加载. # 开始 1.这里建议使用 `git` 依赖, 在flutter工程 pubspec.yaml 添加如下: ``` dependencies: dna: git:git@github.com:Assuner-Lee/dna.git ``` > [参考文档: https://flutter.dev/docs/development/packages-and-plugins/using-packages](https://flutter.dev/docs/development/packages-and-plugins/using-packages) 2.在dart代码中引入头文件 ``` import 'package:dna/dna.dart'; ``` 3.在android项目中引入依赖 ``` implementation 'me.ele:dna-annotations:1.2.0' annotationProcessor 'me.ele:dna-compiler:1.2.0' ``` 使用模块化开发时,可以在子模块中单独引入dna-annotations包,从而获得注解方法的能力 4.在android项目中加入反混淆配置 ``` -keep class **.Dna_Class_Proxy { *; } -keep class me.ele.dna_compiler.** { *; } -keep class me.ele.dna.** { *; } ``` # 使用介绍 `dna` 在`Dart代码`中: * 定义了 `NativeContext 类` ,以执行 `Dart 代码` 的方式,描述 `Native 代码` 调用上下文(调用栈);最后调用 `context.execute()` 执行对应平台的 `Native 代码` 并返回结果。 * 定义了 `NativeObject 类` ,用于标识 `Native 变量`. `调用者 NativeObject 对象` 可借助 `所在NativeContext上下文` 调用 `invoke方法` 传入 `方法名 method` 和 `参数数组 args list` ,得到 `返回值NativeObject对象` 。 `NativeContext 子类` 的API是一致的. 下面先详细介绍通过 `ObjCContext` 调用 `ObjC` ,再区别介绍 `JAVAContext` 调用 `JAVA`. ## Dart 调用 ObjC `ObjCContext` 仅在iOS平台会实际执行. ### 1. 支持上下文调用 ##### (1) 返回值作为调用者 ObjC代码 ``` NSString *versionString = [[UIDevice currentDevice] systemVersion]; // 通过channel返回versionString ``` Dart 代码 ``` ObjCContext context = ObjCContext(); NativeObject UIDevice = context.classFromString('UIDevice'); NativeObject device = UIDevice.invoke(method: 'currentDevice'); NativeObject version = device.invoke(method: 'systemVersion'); context.returnVar = version; // 可省略设定最终返回值, 参考3 // 直接获得原生执行结果 var versionString = await context.execute(); ``` ##### (2) 返回值作为参数 ObjC代码 ``` NSString *versionString = [[UIDevice currentDevice] systemVersion]; NSString *platform = @"iOS-"; versionString = [platform stringByAppendingString: versionString]; // 通过channel返回versionString ``` Dart 代码 ``` ObjCContext context = ObjCContext(); NativeClass UIDevice = context.classFromString('UIDevice'); NativeObject device = UIDevice.invoke(method: 'currentDevice'); NativeObject version = device.invoke(method: 'systemVersion'); NativeObject platform = context.classFromString("NSString").invoke(method: 'stringWithString:', args: ['iOS-']); version = platform.invoke(method: 'stringByAppendingString:', args: [version]); context.returnVar = version; // 可省略设定最终返回值, 参考3 // 直接获得原生执行结果 var versionString = await context.execute(); ``` ### 2. 支持链式调用 ObjC代码 ``` NSString *versionString = [[UIDevice currentDevice] systemVersion]; versionString = [@"iOS-" stringByAppendingString: versionString]; // 通过channel返回versionString ``` Dart 代码 ``` ObjCContext context = ObjCContext(); NativeObject version = context.classFromString('UIDevice').invoke(method: 'currentDevice').invoke(method: 'systemVersion'); version = context.classFromString("NSString").invoke(method: 'stringWithString:', args: ['iOS-']).invoke(method: 'stringByAppendingString:', args: [version]); context.returnVar = version; // 可省略设定最终返回值, 参考3 // 直接获得原生执行结果 var versionString = await context.execute(); ``` ### *关于Context的最终返回值 `context.returnVar` 是 `context` 最终执行完毕返回值的标记 1. 设定context.returnVar: 返回该NativeObject对应的Native变量 2. 不设定context.returnVar: 执行到最后一个invoke,如果有返回值,作为context的最终返回值; 无返回值则返回空值; ``` ObjCContext context = ObjCContext(); context.classFromString('UIDevice').invoke(method: 'currentDevice').invoke(method: 'systemVersion'); // 直接获得原生执行结果 var versionString = await context.execute(); ``` ### 3.支持快捷使用JSON中实例化对象 或许有些时候,我们需要用 `JSON` 直接实例化一个对象. ObjC代码 ``` ClassA *objectA = [ClassA new]; objectA.a = 1; objectA.b = @"sss"; ``` 一般时候,这样写 Dart 代码 ``` ObjCContext context = ObjCContext(); NativeObject objectA = context.classFromString('ClassA').invoke(method: 'new'); objectA.invoke(method: 'setA:', args: [1]); objectA.invoke(method: 'setB:', args: ['sss']); ``` 也可以从JSON中生成 ``` ObjCContext context = ObjCContext(); NativeObject objectA = context.newNativeObjectFromJSON({'a':1,'b':'sss'}, 'ClassA'); ``` ## Dart 调用 JAVA `JAVAContext` 仅在安卓系统中会被实际执行. `JAVAContext` 拥有上述 `ObjCContext` `Dart调ObjC` 的全部特性. * 支持上下文调用 * 支持链式调用 * 支持用JSON中实例化对象 另外,额外支持了从构造器中实例化一个对象 ### 4. 支持快捷使用构造器实例化对象 JAVA代码 ``` String platform = new String("android"); ``` Dart 代码 ``` NativeObject version = context .newJavaObjectFromConstructor('java.lang.String', ["android "]) ``` ## 快捷组织双端代码 提供了一个快捷的方法来 初始化和执行 context. ``` static Future traversingNative(ObjCContextBuilder(ObjCContext objcContext), JAVAContextBuilder(JAVAContext javaContext)) async { NativeContext nativeContext; if (Platform.isIOS) { nativeContext = ObjCContext(); ObjCContextBuilder(nativeContext); } else if (Platform.isAndroid) { nativeContext = JAVAContext(); JAVAContextBuilder(nativeContext); } return executeNativeContext(nativeContext); } ``` 可以快速书写两端的原生调用 ``` platformVersion = await Dna.traversingNative((ObjCContext context) { NativeObject version = context.classFromString('UIDevice').invoke(method: 'currentDevice').invoke(method: 'systemVersion'); version = context.classFromString("NSString").invoke(method: 'stringWithString:', args: ['iOS-']).invoke(method: 'stringByAppendingString:', args: [version]); context.returnVar = version; // 该句可省略 }, (JAVAContext context) { NativeObject versionId = context.newJavaObjectFromConstructor('com.example.dna_example.DnaTest', null).invoke(method: 'getDnaVersion').invoke(method: 'getVersion'); NativeObject version = context.newJavaObjectFromConstructor('java.lang.String', ["android "]).invoke(method: "concat", args: [versionId]); context.returnVar = version; // 该句可省略 }); ``` # 原理简介 `dna` 并不涉及` dart对象到Native对象的转换` ,也不关心 `Native对象的生命周期`,而是着重与描述原生方法调用的上下文,在 `context execute` 时通过 `channel` 调用一次原生方法,把调用栈以 `JSON` 的形式传过去供原生动态解析调用。 如前文的中 dart 代码 ``` ObjCContext context = ObjCContext(); NativeObject version = context.classFromString('UIDevice').invoke(method: 'currentDevice').invoke(method: 'systemVersion'); version = context.classFromString("NSString").invoke(method: 'stringWithString:', args: ['iOS-']).invoke(method: 'stringByAppendingString:', args: [version]); context.returnVar = version; // 可省略设定最终返回值, 参考3 // 直接获得原生执行结果 var versionString = await context.execute(); ``` `NativeContext的execute()` 方法,实际调用了 ``` static Future executeNativeContext(NativeContext context) async { return await _channel.invokeMethod('executeNativeContext', context.toJSON()); } ``` 在 `原生的 executeNativeContext` 对应执行的方法中,接收到的 `JSON` 是这样的 ``` { "_objectJSONWrappers": [], "returnVar": { "_objectId": "_objectId_WyWRIsLl" }, "_invocationNodes": [{ "returnVar": { "_objectId": "_objectId_KNWtiPuM" }, "object": { "_objectId": "_objectId_qyfACNGb", "clsName": "UIDevice" }, "method": "currentDevice" }, { "returnVar": { "_objectId": "_objectId_haPktBlL" }, "object": { "_objectId": "_objectId_KNWtiPuM" }, "method": "systemVersion" }, { "object": { "_objectId": "_objectId_UAUcgnOD", "clsName": "NSString" }, "method": "stringWithString:", "args": ["iOS-"], "returnVar": { "_objectId": "_objectId_UiCMaHAN" } }, { "object": { "_objectId": "_objectId_UiCMaHAN" }, "method": "stringByAppendingString:", "args": [{ "_objectId": "_objectId_haPktBlL" }], "returnVar": { "_objectId": "_objectId_WyWRIsLl" } }] } ``` 我们在 `Native` 维护了一个 `objectsInContextMap` , `以objectId` 为键,以 `Native对象` 为值。 `_invocationNodes` 便是方法的调用上下文, 看单个 这里会动态调用 `[UIDevice currentDevice]`, 返回对象以 `returnVar中存储的"_objectId_KNWtiPuM" ` 为键放到 `objectsInContextMap` 里 ``` { "returnVar": { "_objectId": "_objectId_KNWtiPuM" }, "object": { "_objectId": "_objectId_qyfACNGb", "clsName": "UIDevice" }, "method": "currentDevice" }, ``` 这里 `调用方法的对象的objectId` 是 `"_objectId_KNWtiPuM"` ,是上一个方法的返回值,从`objectsInContextMap` 中取出,继续动态调用,以 `returnVar的object_id为键` 存储新的返回值。 ``` { "returnVar": { "_objectId": "_objectId_haPktBlL" }, "object": { "_objectId": "_objectId_KNWtiPuM" // 会在objectsInContextMap找到中真正的对象 }, "method": "systemVersion" } ``` 方法有参数时,支持自动装包和解包的,如 `int<->NSNumber..`, 如果参数是非 `channel` 规定的15种基本类型,是`NativeObject`, 我们会把对象从 `objectsInContextMap ` 中找出,放到实际的参数列表里 ``` { "object": { "_objectId": "_objectId_UiCMaHAN" }, "method": "stringByAppendingString:", "args": [{ "_objectId": "_objectId_haPktBlL" // 会在objectsInContextMap找到中真正的对象 }], "returnVar": { "_objectId": "_objectId_WyWRIsLl" } ``` ... 如果设置了`最终的returnVar`, 将把该 `returnVar objectId` 对应的对象从 `objectsInContextMap` 中找出来,作为 `channel的返回值` 回调回去。如果没有设置,取最后一个 `invocation` 的返回值(如果有)。 ## 作者 zyd178591@alibaba-inc.com, zhengguang.zzg@alibaba-inc.com, yongguang.lyg@alibaba-inc.com ## 更新日志 | version | note | | ------ | ------ | | 0.1.0 | 能用 | ## License dna is available under the MIT license. See the LICENSE file for more info. ## 其他 * 代码仓库近期会迁移到eleme下 * 欢迎试用,建议和提交代码 ================================================ FILE: android/.gitignore ================================================ *.iml .gradle /local.properties .DS_Store /build /captures /.idea /gradle gradlew gradlew.bat .gradlew/ ================================================ FILE: android/build.gradle ================================================ group 'me.ele.dna' version '1.0-SNAPSHOT' buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.2.1' } } rootProject.allprojects { repositories { google() jcenter() } } apply plugin: 'com.android.library' android { compileSdkVersion 28 defaultConfig { minSdkVersion 16 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } lintOptions { disable 'InvalidPackage' } } dependencies { implementation "com.google.code.gson:gson:2.8.5" implementation 'me.ele:dna-annotations:1.2.0' annotationProcessor 'me.ele:dna-compiler:1.2.0' } ================================================ FILE: android/gradle.properties ================================================ org.gradle.jvmargs=-Xmx1536M ================================================ FILE: android/settings.gradle ================================================ rootProject.name = 'dna' ================================================ FILE: android/src/main/AndroidManifest.xml ================================================ ================================================ FILE: android/src/main/java/me/ele/dna/DLog.java ================================================ package me.ele.dna; import android.util.Log; /** * Author: Zhiqing.Zhang * FileName: DLog * Description: dna log utls */ public class DLog { public static final String DNA = "dna"; public static void e(String msg) { Log.e(DNA, msg); } public static void i(String msg) { Log.i(DNA, msg); } } ================================================ FILE: android/src/main/java/me/ele/dna/DnaClient.java ================================================ package me.ele.dna; import android.text.TextUtils; import me.ele.dna.exception.AbnormalConstructorException; import me.ele.dna.exception.AbnormalMethodException; import me.ele.dna.exception.ArgsException; import me.ele.dna.finder.ConstructorFinder; import me.ele.dna.finder.BaseDnaFinder; import me.ele.dna.finder.MethodFinder; import me.ele.dna.finder.ProxyFinder; import me.ele.dna.model.MethodInfo; import me.ele.dna.model.MethodTacker; import me.ele.dna.model.ParameterInfo; import me.ele.dna.model.ResultInfo; import me.ele.dna.util.DnaUtils; import me.ele.dna_annotations.DnaConstants; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Author: Zhiqing.Zhang * FileName: DnaClient * Description: */ public class DnaClient { private IResultCallBack iResultCallBack; private static Map> classCahe = new HashMap<>(); private static Map> classConstructiorCahe = new HashMap<>(); private static Map> methodCache = new HashMap<>(); private static DnaClient mInstance; public static DnaClient getClient() { if (mInstance == null) { synchronized (DnaClient.class) { if (mInstance == null) { mInstance = new DnaClient(); } } } return mInstance; } public DnaClient() { } public void setiResultCallBack(IResultCallBack iResultCallBack) { this.iResultCallBack = iResultCallBack; } public IResultCallBack getiResultCallBack() { return iResultCallBack; } /** * 调用构造方法 * * @param className * @param param * @return * @throws ClassNotFoundException * @throws IllegalAccessException */ public ResultInfo invokeConstructorMethod(String className, List param) throws ClassNotFoundException, ArgsException, AbnormalMethodException, IllegalAccessException, InstantiationException, AbnormalConstructorException, InvocationTargetException { boolean isProxy = false; if (!TextUtils.isEmpty(className) && (className.startsWith("android.") || className.startsWith("java."))) { try { Class.forName(className); } catch (ClassNotFoundException e) { isProxy = true; } } else { isProxy = true; } if (isProxy) { String methodName = DnaConstants.PROXYCONSTRUCTOR.concat(className.substring(className.lastIndexOf(".") + 1)); return invokeMethod(true, className, null, methodName, param); } return invokeRawConstructorMethod(className, param); } public ResultInfo invokeRawConstructorMethod(String className, List param) throws ClassNotFoundException, InstantiationException, IllegalAccessException, AbnormalConstructorException, InvocationTargetException { Class constructorClass = classConstructiorCahe.get(className); if (constructorClass == null) { constructorClass = Class.forName(className); classConstructiorCahe.put(className, constructorClass); } if (DnaUtils.isEmpty(param)) { return new ResultInfo(constructorClass.newInstance(), className); } return new ResultInfo(constructorOwner(constructorClass, param), className); } /** * 调用方法 * * @param className * @param owner * @param methodName * @param param * @return * @throws ArgsException * @throws ClassNotFoundException * @throws AbnormalMethodException */ public ResultInfo invokeMethod(boolean isConstruct, String className, Object owner, String methodName, List param) throws ArgsException, ClassNotFoundException, AbnormalMethodException { Map methods = methodCache.get(className); MethodInfo methodImp = null; if (methods == null) { methods = new HashMap<>(); } if (!methods.isEmpty()) { methodImp = methods.get(methodName); } if (methodImp == null || !methodImp.checkParam(param)) { methodImp = getReflectMethod(isConstruct, className, methodName, param); methods.put(methodName, methodImp); } if (methodImp == null) { throw new AbnormalMethodException("method exception"); } MethodTacker methodTacker = new MethodTacker(methodImp); List args = methodTacker.getArgs(param, owner); Object returnResult = reflectMethod(methodImp.getMethod(), methodImp.isProxy() ? null : owner, args != null ? args.toArray() : null); return new ResultInfo(returnResult, methodImp.getReturnType()); } private MethodInfo getReflectMethod(boolean isConstruct, String className, String methodName, List param) throws ClassNotFoundException { boolean isProxy = false; Class invokeClass = classCahe.get(className); if (invokeClass == null) { if (!TextUtils.isEmpty(className) && (className.startsWith("android.") || className.startsWith("java."))) { try { invokeClass = Class.forName(className); } catch (ClassNotFoundException e) { isProxy = true; } } else { isProxy = true; } if (isProxy) { methodName = isConstruct ? methodName : className.substring(className.lastIndexOf(".") + 1) + "_" + methodName; className = className.substring(0, className.lastIndexOf(".") + 1) + DnaConstants.PROXYCLASS; invokeClass = Class.forName(className); } classCahe.put(className, invokeClass); } BaseDnaFinder finder = isProxy ? ProxyFinder.build(invokeClass, methodName, param, isConstruct) : MethodFinder.build(invokeClass, methodName, param, isConstruct); return finder.getReflectMethodFromClazz(); } /** * 构造函数 * * @param param */ private Object constructorOwner(Class owner, List param) throws InstantiationException, IllegalAccessException, AbnormalConstructorException, IllegalArgumentException, InvocationTargetException { Constructor[] cons = owner.getConstructors(); if (cons == null || cons.length == 0) { return null; } Object ownerInstance; ConstructorFinder constructorFinder = new ConstructorFinder(owner, param); Constructor con = constructorFinder.getConstructor(); if (con == null) { throw new AbnormalConstructorException("invalid constructor"); } else { ownerInstance = con.newInstance(getParamContent(param).toArray()); } return ownerInstance; } private Object reflectMethod(Method method, Object owner, Object... args) { try { return method.invoke(owner, (args == null || args.length == 0) ? null : args); } catch (InvocationTargetException e) { DLog.i(e.getMessage()); } catch (IllegalAccessException e) { throw new IllegalStateException("Unexpected exception", e); } return null; } private List getParamContent(List parameterInfos) { if (DnaUtils.isEmpty(parameterInfos)) { return null; } List list = new ArrayList<>(); for (ParameterInfo info : parameterInfos) { list.add(info.getContent()); } return list; } } ================================================ FILE: android/src/main/java/me/ele/dna/DnaPlugin.java ================================================ package me.ele.dna; import me.ele.dna.model.DnaClassInfo; import me.ele.dna.model.ParameterInfo; import me.ele.dna.model.ResultInfo; import me.ele.dna.util.DnaUtils; import me.ele.dna.util.GsonUtils; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.MethodCallHandler; import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry.Registrar; /** * DnaPlugin */ public class DnaPlugin implements MethodCallHandler { public static final String EXECUTE_NATIVE_CONTEXT = "executeNativeContext"; public static final String INVOCATION_NODES = "_invocationNodes"; public static final String OBJECT_JSON_WRAPPERS = "_objectJSONWrappers"; public static final String RETURN_VAR = "returnVar"; public static final String OBJECT_ID = "_objectId"; public static final String DNA_JSON = "json"; public static final String DNA_CLS = "cls"; public static final String DNA_OBJECT = "object"; public static final String DNA_CLS_NAME = "clsName"; public static final String DNA_METHOD = "method"; public static final String DNA_ARGS = "args"; public static final String DNA_CONSTRUCT_CLS = "constructCls"; /** * Plugin registration. */ public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), "dna"); channel.setMethodCallHandler(new DnaPlugin()); } @Override public void onMethodCall(MethodCall call, Result result) { if (call.method.equals("getPlatformVersion")) { result.success("Android " + android.os.Build.VERSION.RELEASE); } else if (call.method.equals(EXECUTE_NATIVE_CONTEXT)) { try { excuteNativeMethod(call, result); } catch (Exception e) { DLog.e(e.getMessage()); if (DnaClient.getClient().getiResultCallBack() != null) { DnaClient.getClient().getiResultCallBack().onException(e); } } } else { result.notImplemented(); } } private void excuteNativeMethod(MethodCall call, Result result) throws Exception { List> invocationNodes = call.argument(INVOCATION_NODES); List> objectJSONs = call.argument(OBJECT_JSON_WRAPPERS); Map valueMap = new HashMap<>();// 用于映射id和返回值 if (DnaUtils.isEmpty(invocationNodes)) { return; } Map returnVar = call.argument(RETURN_VAR); String finalReturnVarId = getReturnId(returnVar); if (!DnaUtils.isEmpty(objectJSONs)) { for (Map objectJson : objectJSONs) { String objectId = String.valueOf(objectJson.get(OBJECT_ID)); String content = GsonUtils.toJson(objectJson.get(DNA_JSON)); String classType = String.valueOf(objectJson.get(DNA_CLS)); valueMap.put(objectId, new ParameterInfo(content, classType)); } } Map idMap; String clsName; String constructName; String nodeId; String methodName; List argsMap; List parameterInfos; Object currentObject = null; for (Map node : invocationNodes) { idMap = (Map) node.get(DNA_OBJECT); nodeId = (String) idMap.get(OBJECT_ID); if (idMap.containsKey(DNA_CLS_NAME)) { clsName = (String) idMap.get(DNA_CLS_NAME); valueMap.put(nodeId, new DnaClassInfo(clsName, false)); } else if (!valueMap.containsKey(nodeId) && idMap.containsKey(DNA_CONSTRUCT_CLS)) { constructName = (String) idMap.get(DNA_CONSTRUCT_CLS); valueMap.put(nodeId, new DnaClassInfo(constructName, true)); } methodName = String.valueOf(node.get(DNA_METHOD)); argsMap = (List) node.get(DNA_ARGS); parameterInfos = getParameters(valueMap, argsMap); String returnId = getReturnId((Map) node.get(RETURN_VAR)); Object ownerObject = valueMap.get(nodeId); if (ownerObject == null) { return; } else if (ownerObject instanceof DnaClassInfo) { currentObject = ((DnaClassInfo) ownerObject).isConstrcut() ? DnaClient.getClient().invokeConstructorMethod(((DnaClassInfo) ownerObject).getClassName(), parameterInfos) : DnaClient.getClient().invokeMethod(false, ((DnaClassInfo) ownerObject).getClassName(), null, methodName, parameterInfos); } else if (ownerObject instanceof ResultInfo) { currentObject = DnaClient.getClient().invokeMethod(false, ((ResultInfo) ownerObject).getReturnType(), ((ResultInfo) ownerObject).getObject(), methodName, parameterInfos); } else { throw new Exception("Abnormal Error"); } valueMap.put(returnId, currentObject); if (returnId != null && returnId.equals(finalReturnVarId)) { Object o = valueMap.get(returnId); result.success(o instanceof ResultInfo ? ((ResultInfo) o).getObject() : o); } } result.success(currentObject instanceof ResultInfo ? ((ResultInfo) currentObject).getObject() : currentObject); } /** * 获取下一个节点的id * * @param returnVar * @return */ private String getReturnId(Map returnVar) { String returnVarId = null; if (returnVar != null && !returnVar.isEmpty()) { returnVarId = returnVar.get(OBJECT_ID); } return returnVarId; } /** * 获取参数信息 * * @param valueMap * @param argsMap * @return */ private List getParameters(Map valueMap, List argsMap) { String paraId; Object paraContent; List parameters = new ArrayList<>(); if (!DnaUtils.isEmpty(argsMap)) { for (Object o : argsMap) { if (o instanceof Map) { paraId = String.valueOf(((Map) o).get(OBJECT_ID)); paraContent = valueMap.get(paraId); if (paraContent instanceof ParameterInfo) { parameters.add((ParameterInfo) paraContent); } else if (paraContent instanceof ResultInfo) { parameters.add(new ParameterInfo(getExcuteParameter(((ResultInfo) paraContent).getObject()), ((ResultInfo) paraContent).getReturnType())); } else if (paraContent != null) { parameters.add(new ParameterInfo(getExcuteParameter(paraContent), paraContent.getClass().getName())); } } else if (o != null) { parameters.add(new ParameterInfo(String.valueOf(o), o.getClass().getName())); } else { parameters.add(new ParameterInfo(null, null)); } } } return parameters; } private String getExcuteParameter(Object result) { return DnaUtils.isPrimitiveClass(result.getClass()) ? String.valueOf(result) : GsonUtils.toJson(result); } } ================================================ FILE: android/src/main/java/me/ele/dna/IResultCallBack.java ================================================ package me.ele.dna; public interface IResultCallBack { void onException(Exception e); } ================================================ FILE: android/src/main/java/me/ele/dna/exception/AbnormalConstructorException.java ================================================ package me.ele.dna.exception; public class AbnormalConstructorException extends Exception { public AbnormalConstructorException(String message) { super(message); } } ================================================ FILE: android/src/main/java/me/ele/dna/exception/AbnormalMethodException.java ================================================ package me.ele.dna.exception; /** * Author: Zhiqing.Zhang * Description: */ public class AbnormalMethodException extends Exception { public AbnormalMethodException(String message) { super(message); } } ================================================ FILE: android/src/main/java/me/ele/dna/exception/ArgsException.java ================================================ package me.ele.dna.exception; /** * Author: Zhiqing.Zhang * FileName: ArgsException * Description: */ public class ArgsException extends Exception { public ArgsException(String message) { super(message); } } ================================================ FILE: android/src/main/java/me/ele/dna/finder/BaseDnaFinder.java ================================================ package me.ele.dna.finder; import android.text.TextUtils; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import me.ele.dna.model.MethodInfo; import me.ele.dna.util.DnaUtils; public abstract class BaseDnaFinder { protected static final int BRIDGE = 0x40; protected static final int SYNTHETIC = 0x1000; protected static final int MODIFIERS_UN = Modifier.ABSTRACT | BRIDGE | SYNTHETIC; protected Class invokeClass; protected String methodName; protected List paramType; protected boolean isConstruct; public BaseDnaFinder(Class invokeClass, String methodName, List paramType, boolean isConstruct) { this.invokeClass = invokeClass; this.methodName = methodName; this.paramType = paramType; this.isConstruct = isConstruct; } protected abstract MethodInfo getExactMethod(List methods); public MethodInfo getReflectMethodFromClazz() { List tempMethodList = new ArrayList<>(); Method[] methods = invokeClass.getMethods(); if (methods == null) { return null; } for (Method method : methods) { int modifier = method.getModifiers(); if ((modifier & Modifier.PUBLIC) != 0 && (modifier & MODIFIERS_UN) == 0) { if (methodName.equals(method.getName())) { tempMethodList.add(method); } } } return getExactMethod(tempMethodList); } protected MethodInfo createMethod(Method method, String returnType) { MethodInfo methodInfo = null; if (method != null) { Class[] parameterTypes = method.getParameterTypes(); methodInfo = new MethodInfo(method, Arrays.asList(parameterTypes), returnType, this instanceof ProxyFinder, isConstruct); } return methodInfo; } /** * java.lang.Boolean#TYPE * java.lang.Character#TYPE * java.lang.Byte#TYPE * java.lang.Short#TYPE * java.lang.Integer#TYPE * java.lang.Long#TYPE * java.lang.Float#TYPE * java.lang.Double#TYPE * java.lang.Void#TYPE * * @param name * @return */ protected String wrapper(String name) { switch (name) { case "boolean": return Boolean.class.getName(); case "char": return Character.class.getName(); case "byte": return Byte.class.getName(); case "short": return Short.class.getName(); case "int": return Integer.class.getName(); case "long": return Long.class.getName(); case "float": return Float.class.getName(); case "double": return Double.class.getName(); case "void": return Void.class.getName(); default: return null; } } protected boolean isEqualType(String dartType, String javaType) { if (TextUtils.isEmpty(dartType) && TextUtils.isEmpty(javaType)) { return true; } if (TextUtils.isEmpty(dartType) || TextUtils.isEmpty(javaType)) { return false; } if (javaType.equals(Float.class.getName()) && dartType.equals(Double.class.getName())) { return true; } if (javaType.equals(Long.class.getName()) && dartType.equals(Integer.class.getName())) { return true; } if (javaType.equals(Short.class.getName()) && dartType.equals(Integer.class.getName())) { return true; } return javaType.equals(dartType); } } ================================================ FILE: android/src/main/java/me/ele/dna/finder/ConstructorFinder.java ================================================ package me.ele.dna.finder; import me.ele.dna.model.ParameterInfo; import me.ele.dna.util.DnaUtils; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.List; /** * Author: Zhiqing.Zhang * Description: */ public class ConstructorFinder { Class owner; List param; public ConstructorFinder(Class owner, List param) { this.owner = owner; this.param = new ArrayList<>(); if (!DnaUtils.isEmpty(param)) { for (ParameterInfo info : param) { this.param.add(info.getType()); } } } public Constructor getConstructor() { if (owner == null) { return null; } Constructor[] cons = owner.getConstructors(); if (cons == null || cons.length == 0) { return null; } for (Constructor con : cons) { if (checkConstructor(con, param)) { return con; } } return null; } private boolean checkConstructor(Constructor con, List param) { Class[] parameterTypes = con.getParameterTypes(); if (parameterTypes.length != param.size()) { return false; } if (parameterTypes.length == 0) { return true; } for (int i = 0; i < parameterTypes.length; i++) { if (parameterTypes[i].getName() != param.get(i) && param.get(i) != null) { return false; } } return true; } } ================================================ FILE: android/src/main/java/me/ele/dna/finder/MethodFinder.java ================================================ package me.ele.dna.finder; import me.ele.dna.model.MethodInfo; import me.ele.dna.model.ParameterInfo; import me.ele.dna.util.DnaUtils; import java.lang.reflect.Method; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; /** * Author: Zhiqing.Zhang * Description: */ public class MethodFinder extends BaseDnaFinder { public static MethodFinder build(Class invokeClass, String methodName, List paramInfoList, boolean isConstruct) { List paramType = new ArrayList<>(); if (!DnaUtils.isEmpty(paramInfoList)) { for (ParameterInfo info : paramInfoList) { paramType.add(info.getType()); } } return new MethodFinder(invokeClass, methodName, paramType, isConstruct); } public MethodFinder(Class invokeClass, String methodName, List paramType, boolean isConstruct) { super(invokeClass, methodName, paramType, isConstruct); } @Override protected MethodInfo getExactMethod(List methods) { if (DnaUtils.isEmpty(methods)) { return null; } boolean isExactMethod; Method curMethod = null; for (Method method : methods) { Type[] types = method.getGenericParameterTypes(); if (types == null && paramType == null) { curMethod = method; break; } if (types == null || paramType == null || types.length != paramType.size()) { continue; } if (types.length == 0) { curMethod = method; break; } isExactMethod = true; for (int i = 0; i < types.length; i++) { if (!(types[i] instanceof Class)) { return null; } if (paramType.get(i) != null) { String typeName = ((Class) types[i]).isPrimitive() ? wrapper(((Class) types[i]).getName()) : ((Class) types[i]).getName(); if (!(isEqualType(paramType.get(i), typeName))) { isExactMethod = false; break; } } } if (isExactMethod) { curMethod = method; break; } } return curMethod != null ? createMethod(curMethod, curMethod.getReturnType().getName()) : null; } } ================================================ FILE: android/src/main/java/me/ele/dna/finder/ProxyFinder.java ================================================ package me.ele.dna.finder; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import me.ele.dna.model.MethodInfo; import me.ele.dna.model.ParameterInfo; import me.ele.dna.util.DnaUtils; import me.ele.dna_annotations.DnaParamFieldList; public class ProxyFinder extends BaseDnaFinder { public static ProxyFinder build(Class invokeClass, String methodName, List paramInfoList, boolean isConstruct) { List paramType = new ArrayList<>(); if (!DnaUtils.isEmpty(paramInfoList)) { for (ParameterInfo info : paramInfoList) { paramType.add(info.getType()); } } return new ProxyFinder(invokeClass, methodName, paramType, isConstruct); } public ProxyFinder(Class invokeClass, String methodName, List paramType, boolean isConstruct) { super(invokeClass, methodName, paramType, isConstruct); } @Override protected MethodInfo getExactMethod(List methods) { if (DnaUtils.isEmpty(methods)) { return null; } boolean isExactMethod; String[] params; String returnType; for (Method method : methods) { DnaParamFieldList fieldList = method.getAnnotation(DnaParamFieldList.class); if (fieldList == null) { continue; } params = fieldList.params(); returnType = fieldList.returnType(); if (paramType == null || params.length != paramType.size()) { continue; } if (params.length == 0) { return createMethod(method, returnType); } isExactMethod = true; for (int i = 0; i < params.length; i++) { if (paramType.get(i) != null) { String typeName = wrapper(params[i]) != null ? wrapper(params[i]) : params[i]; if (!isEqualType(paramType.get(i), typeName)) { isExactMethod = false; break; } } } if (isExactMethod) { return createMethod(method, returnType); } } return null; } } ================================================ FILE: android/src/main/java/me/ele/dna/model/DnaClassInfo.java ================================================ package me.ele.dna.model; import java.util.List; /** * Author: ZhiQing.Zhang **/ public class DnaClassInfo { String className; boolean isConstrcut; List args; public DnaClassInfo(String className, boolean isConstrcut) { this.className = className; this.isConstrcut = isConstrcut; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public boolean isConstrcut() { return isConstrcut; } public void setConstrcut(boolean constrcut) { isConstrcut = constrcut; } public List getArgs() { return args; } public void setArgs(List args) { this.args = args; } } ================================================ FILE: android/src/main/java/me/ele/dna/model/DnaResult.java ================================================ package me.ele.dna.model; public class DnaResult { Object content; String varId; public DnaResult(Object content, String varId) { this.content = content; this.varId = varId; } public Object getContent() { return content; } public void setContent(Object content) { this.content = content; } public String getVarId() { return varId; } public void setVarId(String varId) { this.varId = varId; } } ================================================ FILE: android/src/main/java/me/ele/dna/model/MethodInfo.java ================================================ package me.ele.dna.model; import java.lang.reflect.Method; import java.util.List; /** * Author: Zhiqing.Zhang * FileName: MethodInfo * Description: */ public class MethodInfo { private Method method; private List> args; private String returnType; private boolean isProxy; private boolean isConstruct; public MethodInfo(Method method, List> args, String returnType, boolean isProxy, boolean isConstruct) { this.method = method; this.args = args; this.returnType = returnType; this.isProxy = isProxy; this.isConstruct = isConstruct; } public String getReturnType() { return returnType; } public Method getMethod() { return method; } public void setMethod(Method method) { this.method = method; } public List> getArgs() { return args; } public boolean isProxy() { return isProxy; } public boolean isConstruct() { return isConstruct; } public void setArgs(List> args) { this.args = args; } public boolean checkParam(List parameterInfos) { if (args == null && parameterInfos == null) { return true; } if (args == null || parameterInfos == null || args.size() != parameterInfos.size()) { return false; } for (int i = 0; i < args.size(); i++) { if (args.get(i).getName() != parameterInfos.get(i).getType()) { return false; } } return true; } } ================================================ FILE: android/src/main/java/me/ele/dna/model/MethodTacker.java ================================================ package me.ele.dna.model; import me.ele.dna.util.GsonUtils; import me.ele.dna.exception.ArgsException; import java.util.ArrayList; import java.util.List; import me.ele.dna.exception.ArgsException; /** * Author: Zhiqing.Zhang * FileName: MethodTacker * Description: */ public class MethodTacker { MethodInfo info; public MethodTacker(MethodInfo info) { this.info = info; } public List getArgs(List args, Object owner) throws ArgsException { if (info == null) { return null; } List> reflectArgs = info.getArgs(); if (reflectArgs == null || args == null) { return null; } if (info.isProxy() && !info.isConstruct() && reflectArgs.size() != args.size() + 1) { throw new ArgsException("proxy class Args size error"); } if (!info.isProxy() && reflectArgs.size() != args.size()) { throw new ArgsException("Args size error"); } List argsElements = new ArrayList<>(); if (info.isProxy() && !info.isConstruct()) { argsElements.add(0, owner); for (int i = 1; i < reflectArgs.size(); i++) { argsElements.add(isString(reflectArgs.get(i)) ? args.get(i - 1).getContent() : GsonUtils.fromJson(args.get(i - 1).getContent(), reflectArgs.get(i))); } } else { for (int i = 0; i < reflectArgs.size(); i++) { argsElements.add(isString(reflectArgs.get(i)) ? args.get(i).getContent() : GsonUtils.fromJson(args.get(i).getContent(), reflectArgs.get(i))); } } return argsElements; } public static boolean isString(Class clz) { if (clz.getName().equals(String.class.getName())) { return true; } return false; } } ================================================ FILE: android/src/main/java/me/ele/dna/model/ParameterInfo.java ================================================ package me.ele.dna.model; /** * Author: Zhiqing.Zhang * Description: */ public class ParameterInfo { String content; String type; public ParameterInfo(String content, String type) { this.content = content; this.type = type; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getType() { return type; } public void setType(String type) { this.type = type; } } ================================================ FILE: android/src/main/java/me/ele/dna/model/ResultInfo.java ================================================ package me.ele.dna.model; public class ResultInfo { Object object; String returnType; public ResultInfo(Object object, String returnType) { this.object = object; this.returnType = returnType; } public Object getObject() { return object; } public String getReturnType() { return returnType; } } ================================================ FILE: android/src/main/java/me/ele/dna/util/DnaUtils.java ================================================ package me.ele.dna.util; import java.util.Collection; /** * Author: Zhiqing.Zhang * Description: */ public class DnaUtils { public static boolean isEmpty(Collection collection) { return collection == null || collection.isEmpty(); } public static boolean isPrimitiveClass(Class clz) { if (clz.getName() == String.class.getName()) { return true; } return clz.isPrimitive() || isWrapClass(clz); } public static boolean isWrapClass(Class clz) { try { return ((Class) clz.getField("TYPE").get(null)).isPrimitive(); } catch (Exception e) { return false; } } } ================================================ FILE: android/src/main/java/me/ele/dna/util/GsonUtils.java ================================================ package me.ele.dna.util; import com.google.gson.Gson; import java.lang.reflect.Type; /** * Author: Zhiqing.Zhang * FileName: GsonUtils * Description: */ public class GsonUtils { private static final Gson GSON = new Gson(); private GsonUtils() { } public static String toJson(Object obj) { return null == obj ? "" : GSON.toJson(obj); } public static Object fromJson(String json, Type classType) { return null == json ? null : GSON.fromJson(json, classType); } } ================================================ FILE: dna.iml ================================================ ================================================ FILE: example/.gitignore ================================================ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # Visual Studio Code related .vscode/ # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .packages .pub-cache/ .pub/ /build/ # Android related **/android/**/gradle-wrapper.jar **/android/.gradle **/android/captures/ **/android/gradlew **/android/gradlew.bat **/android/local.properties **/android/**/GeneratedPluginRegistrant.java # iOS/XCode related **/ios/**/*.mode1v3 **/ios/**/*.mode2v3 **/ios/**/*.moved-aside **/ios/**/*.pbxuser **/ios/**/*.perspectivev3 **/ios/**/*sync/ **/ios/**/.sconsign.dblite **/ios/**/.tags* **/ios/**/.vagrant/ **/ios/**/DerivedData/ **/ios/**/Icon? **/ios/**/Pods/ **/ios/**/.symlinks/ **/ios/**/profile **/ios/**/xcuserdata **/ios/.generated/ **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip **/ios/Flutter/flutter_assets/ **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: example/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b channel: stable project_type: app ================================================ FILE: example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 28 lintOptions { disable 'InvalidPackage' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "me.ele.dna_example" minSdkVersion 16 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } flutter { source '../..' } dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' implementation 'com.google.code.gson:gson:2.8.5' implementation 'me.ele:dna-annotations:1.2.0' annotationProcessor 'me.ele:dna-compiler:1.2.0' } ================================================ FILE: example/android/app/proguard-rules.pro ================================================ -ignorewarnings -keep class **.Dna_Class_Proxy { *; } -keep class io.flutter.app.** { *; } -keep class io.flutter.plugin.** { *; } -keep class io.flutter.util.** { *; } -keep class io.flutter.view.** { *; } -keep class io.flutter.** { *; } -keep class io.flutter.plugins.** { *; } -keep class me.ele.dna_compiler.** { *; } -keep class me.ele.dna.** { *; } ================================================ FILE: example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: example/android/app/src/main/java/me/ele/dna_example/DnaTest.java ================================================ package me.ele.dna_example; import me.ele.dna_annotations.DnaMethod; public class DnaTest { @DnaMethod public DnaTest() { } @DnaMethod public DnaVersion getDnaVersion() { return new DnaVersion(); } } ================================================ FILE: example/android/app/src/main/java/me/ele/dna_example/DnaVersion.java ================================================ package me.ele.dna_example; import me.ele.dna_annotations.DnaMethod; public class DnaVersion { @DnaMethod public DnaVersion() { } @DnaMethod public String getVersion() { return android.os.Build.VERSION.RELEASE; } } ================================================ FILE: example/android/app/src/main/java/me/ele/dna_example/MainActivity.java ================================================ package me.ele.dna_example; import android.os.Bundle; import android.util.Log; import io.flutter.app.FlutterActivity; import io.flutter.plugins.GeneratedPluginRegistrant; import me.ele.dna.DnaClient; public class MainActivity extends FlutterActivity { public static String TAG = "DNA"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); GeneratedPluginRegistrant.registerWith(this); DnaClient.getClient().setiResultCallBack(e -> { Log.i(TAG, "error msg:" + e.getMessage()); }); } } ================================================ FILE: example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: example/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: example/android/build.gradle ================================================ buildscript { repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.2.1' } } allprojects { repositories { google() jcenter() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: example/android/dna-annotations/.gitignore ================================================ /build ================================================ FILE: example/android/dna-annotations/build.gradle ================================================ apply plugin: 'java-library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) } sourceCompatibility = "1.8" targetCompatibility = "1.8" ================================================ FILE: example/android/dna-annotations/src/main/java/me/ele/dna_annotations/DnaConstants.java ================================================ package me.ele.dna_annotations; public class DnaConstants { public static final String PROXYCLASS = "Dna_Class_Proxy"; public static final String PROXYCONSTRUCTOR = "Dna_Constructor_Proxy"; public static final String PROXYMETHOD = "Dna_Method_Proxy"; } ================================================ FILE: example/android/dna-annotations/src/main/java/me/ele/dna_annotations/DnaMethod.java ================================================ package me.ele.dna_annotations; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.CONSTRUCTOR; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.CLASS; @Target({METHOD, CONSTRUCTOR}) @Retention(CLASS) public @interface DnaMethod { } ================================================ FILE: example/android/dna-annotations/src/main/java/me/ele/dna_annotations/DnaParamFieldList.java ================================================ package me.ele.dna_annotations; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Target(METHOD) @Retention(RUNTIME) public @interface DnaParamFieldList { String owner(); String[] params(); String returnType(); } ================================================ FILE: example/android/dna-compiler/.gitignore ================================================ /build ================================================ FILE: example/android/dna-compiler/build.gradle ================================================ apply plugin: 'java-library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.squareup:javapoet:1.12.1' implementation 'me.ele:dna-annotations:1.2.0' } sourceCompatibility = "1.8" targetCompatibility = "1.8" ================================================ FILE: example/android/dna-compiler/src/main/java/me/ele/dna_compiler/BaseDnaElement.java ================================================ package me.ele.dna_compiler; import com.squareup.javapoet.MethodSpec; import java.util.List; import javax.lang.model.element.TypeElement; public abstract class BaseDnaElement { protected List paramterType; protected TypeElement enclosingElement; protected String methodName; protected String returnType; public BaseDnaElement(List paramterType, TypeElement enclosingElement, String methodName, String returnType) { this.paramterType = paramterType; this.enclosingElement = enclosingElement; this.methodName = methodName; this.returnType = returnType; } public List getParamterType() { return paramterType; } public TypeElement getEnclosingElement() { return enclosingElement; } public String getMethodName() { return methodName; } public abstract MethodSpec createMethod(); } ================================================ FILE: example/android/dna-compiler/src/main/java/me/ele/dna_compiler/DnaClassFinder.java ================================================ package me.ele.dna_compiler; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.TypeSpec; import java.util.ArrayList; import java.util.List; import static javax.lang.model.element.Modifier.PUBLIC; import static me.ele.dna_annotations.DnaConstants.PROXYCLASS; public class DnaClassFinder { private List methodInfos = new ArrayList<>(); private String packageName; public DnaClassFinder(String packageName) { this.packageName = packageName; } public void addMethodInfo(BaseDnaElement info) { if (methodInfos == null) { methodInfos = new ArrayList<>(); } methodInfos.add(info); } public String getPackageName() { return packageName; } public JavaFile createJavaFile() { TypeSpec.Builder classBuilder = TypeSpec.classBuilder(PROXYCLASS).addModifiers(PUBLIC); for (BaseDnaElement info : methodInfos) { classBuilder.addMethod(info.createMethod()); } return JavaFile.builder(packageName, classBuilder.build()) .addFileComment("Generated code from DNA Do not modify!") .build(); } } ================================================ FILE: example/android/dna-compiler/src/main/java/me/ele/dna_compiler/DnaConstructorInfo.java ================================================ package me.ele.dna_compiler; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import java.util.List; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import me.ele.dna_annotations.DnaParamFieldList; public class DnaConstructorInfo extends BaseDnaElement { public DnaConstructorInfo(List paramterType, TypeElement enclosingElement, String methodName, String returnType) { super(paramterType, enclosingElement, methodName, returnType); } @Override public MethodSpec createMethod() { MethodSpec.Builder mehthodBuidler; mehthodBuidler = MethodSpec.methodBuilder(methodName). returns(TypeName.get(enclosingElement.asType())).addModifiers(Modifier.PUBLIC, Modifier.STATIC); String parament = ""; String annotionSpc = "{"; String[] annotionList = new String[]{}; int paramenterSize = 0; if (paramterType != null && paramterType.size() > 0) { paramenterSize = paramterType.size(); annotionList = new String[paramenterSize]; for (int i = 0; i < paramenterSize; i++) { mehthodBuidler.addParameter(paramterType.get(i).getTypeName(), "var" + i); annotionList[i] = paramterType.get(i).getClassName(); if (i == paramterType.size() - 1) { parament += "$N"; annotionSpc += "$S"; } else { parament += "$N,"; annotionSpc += "$S,"; } } } parament += ")"; annotionSpc += "}"; String stateMement = "return new $N(" + parament; Object[] objects = new Object[paramenterSize + 1]; objects[0] = enclosingElement.getQualifiedName(); for (int i = 1; i < objects.length; i++) { objects[i] = "var" + (i - 1); } AnnotationSpec spec = AnnotationSpec.builder(DnaParamFieldList.class).addMember("params", annotionSpc, annotionList) .addMember("owner", "$S", "") .addMember("returnType", "$S", enclosingElement.getQualifiedName().toString()) .build(); mehthodBuidler.addStatement(stateMement, objects).addAnnotation(spec); return mehthodBuidler.build(); } } ================================================ FILE: example/android/dna-compiler/src/main/java/me/ele/dna_compiler/DnaMethodInfo.java ================================================ package me.ele.dna_compiler; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import java.util.List; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import me.ele.dna_annotations.DnaParamFieldList; public class DnaMethodInfo extends BaseDnaElement { private boolean isReturn; public DnaMethodInfo(List paramterType, TypeElement enclosingElement, String methodName, boolean isReturn, String returnType) { super(paramterType, enclosingElement, methodName, returnType); this.isReturn = isReturn; } public String getClassName() { if (enclosingElement != null) { return enclosingElement.getSimpleName().toString(); } return ""; } public boolean isReturn() { return isReturn; } @Override public MethodSpec createMethod() { MethodSpec.Builder mehthodBuidler; mehthodBuidler = MethodSpec.methodBuilder(getClassName() + "_" + methodName).addModifiers(Modifier.PUBLIC, Modifier.STATIC). returns(isReturn ? Object.class : void.class); String parament = ""; String annotionSpc = "{"; String[] annotionList = new String[]{}; mehthodBuidler.addParameter( TypeName.get(enclosingElement.asType()), "owner"); int paramenterSize = 0; if (paramterType != null && paramterType.size() > 0) { paramenterSize = paramterType.size(); annotionList = new String[paramenterSize]; for (int i = 0; i < paramenterSize; i++) { mehthodBuidler.addParameter(paramterType.get(i).getTypeName(), "var" + i); annotionList[i] = paramterType.get(i).getClassName(); if (i == paramterType.size() - 1) { parament += "$N"; annotionSpc += "$S"; } else { parament += "$N,"; annotionSpc += "$S,"; } } } parament += ")"; annotionSpc += "}"; String stateMement; if (isReturn) { stateMement = "return $N.$N(" + parament; } else { stateMement = "$N.$N(" + parament; } Object[] objects = new Object[paramenterSize + 2]; objects[0] = "owner"; objects[1] = methodName; for (int i = 2; i < objects.length; i++) { objects[i] = "var" + (i - 2); } AnnotationSpec spec = AnnotationSpec.builder(DnaParamFieldList.class).addMember("params", annotionSpc, annotionList) .addMember("owner", "$S", enclosingElement.getQualifiedName().toString()) .addMember("returnType", "$S", returnType) .build(); mehthodBuidler.addStatement(stateMement, objects).addAnnotation(spec); return mehthodBuidler.build(); } } ================================================ FILE: example/android/dna-compiler/src/main/java/me/ele/dna_compiler/DnaPackageFinder.java ================================================ package me.ele.dna_compiler; import java.util.ArrayList; import java.util.List; public class DnaPackageFinder { private List infoList; public DnaPackageFinder() { infoList = new ArrayList<>(); } public void addMethodInfo(String packgeName, BaseDnaElement methodInfo) { DnaClassFinder temProxy; if (infoList == null) { infoList = new ArrayList<>(); } if (!infoList.isEmpty()) { for (DnaClassFinder packageProxy : infoList) { if (packageProxy.getPackageName() != null && packageProxy.getPackageName().equals(packgeName)) { packageProxy.addMethodInfo(methodInfo); return; } } } temProxy = new DnaClassFinder(packgeName); temProxy.addMethodInfo(methodInfo); infoList.add(temProxy); return; } public List getInfoList() { return infoList; } } ================================================ FILE: example/android/dna-compiler/src/main/java/me/ele/dna_compiler/DnaProcessor.java ================================================ package me.ele.dna_compiler; import com.squareup.javapoet.TypeName; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import me.ele.dna_annotations.DnaConstants; import me.ele.dna_annotations.DnaMethod; import static javax.lang.model.element.ElementKind.CLASS; import static javax.lang.model.element.ElementKind.CONSTRUCTOR; import static javax.lang.model.element.ElementKind.METHOD; import static javax.lang.model.element.Modifier.PRIVATE; public class DnaProcessor extends AbstractProcessor { private Messager messager; private Elements elementUtils; private Filer filer; private Types typeUtils; /** * ` * 初始化操作 * * @param processingEnvironment */ @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); typeUtils = processingEnvironment.getTypeUtils(); elementUtils = processingEnvironment.getElementUtils(); filer = processingEnvironment.getFiler(); messager = processingEnvironment.getMessager(); } @Override public Set getSupportedOptions() { return super.getSupportedOptions(); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public Set getSupportedAnnotationTypes() { Set set = new LinkedHashSet<>(); set.add(DnaMethod.class.getCanonicalName()); return set; } @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { List classFinders = getMethodInfos(roundEnv); if (classFinders != null && !classFinders.isEmpty()) { for (DnaClassFinder finder : classFinders) { try { finder.createJavaFile().writeTo(filer); } catch (Exception e) { e.printStackTrace(); } } } return false; } private List getMethodInfos(RoundEnvironment roundEnv) { DnaPackageFinder finder = new DnaPackageFinder(); String packageName; Set annotatedElements = roundEnv.getElementsAnnotatedWith(DnaMethod.class); BaseDnaElement dnaElement; for (Element element : annotatedElements) { if (!(element instanceof ExecutableElement) || (element.getKind() != METHOD && element.getKind() != CONSTRUCTOR)) { throw new IllegalStateException("DnaMethod annotation must be on a method."); } boolean isReturn; ExecutableElement executableElement = (ExecutableElement) element; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); String methodName; if (!isAccessible(element)) { throw new IllegalStateException(" annotated method can't access."); } List parameters = executableElement.getParameters(); TypeMirror methodParameterType; packageName = elementUtils.getPackageOf(element).getQualifiedName().toString(); List paramterType = new ArrayList<>(); if (parameters != null && parameters.size() != 0) { for (VariableElement variableElement : parameters) { methodParameterType = variableElement.asType(); if (methodParameterType instanceof TypeVariable) { TypeVariable typeVariable = (TypeVariable) methodParameterType; methodParameterType = typeVariable.getUpperBound(); } paramterType.add(new ParamInfo(methodParameterType.toString(), TypeName.get(methodParameterType))); } } TypeMirror returnType = executableElement.getReturnType(); String reutrnName = returnType.toString(); if (element.getKind() == CONSTRUCTOR) { methodName = enclosingElement.getSimpleName().toString(); dnaElement = new DnaConstructorInfo(paramterType, enclosingElement, DnaConstants.PROXYCONSTRUCTOR.concat(methodName), reutrnName); } else { methodName = executableElement.getSimpleName().toString(); isReturn = returnType != null && returnType.getKind() != TypeKind.VOID; dnaElement = new DnaMethodInfo(paramterType, enclosingElement, methodName, isReturn, reutrnName); } finder.addMethodInfo(packageName, dnaElement); } return finder.getInfoList(); } private boolean isAccessible(Element element) { Set modifiers = element.getModifiers(); TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); if (modifiers.contains(PRIVATE)) { return false; } if (enclosingElement.getKind() != CLASS || enclosingElement.getModifiers().contains(PRIVATE)) { return false; } return true; } } ================================================ FILE: example/android/dna-compiler/src/main/java/me/ele/dna_compiler/ParamInfo.java ================================================ package me.ele.dna_compiler; import com.squareup.javapoet.TypeName; public class ParamInfo { String className; TypeName typeName; public ParamInfo(String className, TypeName typeName) { this.className = className; this.typeName = typeName; } public String getClassName() { return className; } public TypeName getTypeName() { return typeName; } } ================================================ FILE: example/android/dna-compiler/src/main/resources/META-INF/services/javax.annotation.processing.Processor ================================================ me.ele.dna_compiler.DnaProcessor ================================================ FILE: example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip ================================================ FILE: example/android/gradle.properties ================================================ ================================================ FILE: example/android/settings.gradle ================================================ include ':app', ':dna-compiler', ':dna-annotations' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 8.0 ================================================ FILE: example/ios/Flutter/Debug.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" ================================================ FILE: example/ios/Flutter/Release.xcconfig ================================================ #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" ================================================ FILE: example/ios/Podfile ================================================ # Uncomment this line to define a global platform for your project platform :ios, '9.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' project 'Runner', { 'Debug' => :debug, 'Profile' => :release, 'Release' => :release, } def parse_KV_file(file, separator='=') file_abs_path = File.expand_path(file) if !File.exists? file_abs_path return []; end pods_ary = [] skip_line_start_symbols = ["#", "/"] File.foreach(file_abs_path) { |line| next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } plugin = line.split(pattern=separator) if plugin.length == 2 podname = plugin[0].strip() path = plugin[1].strip() podpath = File.expand_path("#{path}", file_abs_path) pods_ary.push({:name => podname, :path => podpath}); else puts "Invalid plugin specification: #{line}" end } return pods_ary end target 'Runner' do # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock # referring to absolute paths on developers' machines. system('rm -rf .symlinks') system('mkdir -p .symlinks/plugins') # Flutter Pods generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') if generated_xcode_build_settings.empty? puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." end generated_xcode_build_settings.map { |p| if p[:name] == 'FLUTTER_FRAMEWORK_DIR' symlink = File.join('.symlinks', 'flutter') File.symlink(File.dirname(p[:path]), symlink) pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) end } # Plugin Pods plugin_pods = parse_KV_file('../.flutter-plugins') plugin_pods.map { |p| symlink = File.join('.symlinks', 'plugins', p[:name]) File.symlink(p[:path], symlink) pod p[:name], :path => File.join(symlink, 'ios') } end post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['ENABLE_BITCODE'] = 'NO' end end end ================================================ FILE: example/ios/Runner/AppDelegate.h ================================================ #import #import @interface AppDelegate : FlutterAppDelegate @end ================================================ FILE: example/ios/Runner/AppDelegate.m ================================================ #include "AppDelegate.h" #include "GeneratedPluginRegistrant.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [GeneratedPluginRegistrant registerWithRegistry:self]; // Override point for customization after application launch. return [super application:application didFinishLaunchingWithOptions:launchOptions]; } @end ================================================ FILE: example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "Icon-App-20x20@3x.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "Icon-App-29x29@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "Icon-App-40x40@3x.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@2x.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "Icon-App-60x60@3x.png", "scale" : "3x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@1x.png", "scale" : "1x" }, { "size" : "20x20", "idiom" : "ipad", "filename" : "Icon-App-20x20@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@1x.png", "scale" : "1x" }, { "size" : "29x29", "idiom" : "ipad", "filename" : "Icon-App-29x29@2x.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@1x.png", "scale" : "1x" }, { "size" : "40x40", "idiom" : "ipad", "filename" : "Icon-App-40x40@2x.png", "scale" : "2x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@1x.png", "scale" : "1x" }, { "size" : "76x76", "idiom" : "ipad", "filename" : "Icon-App-76x76@2x.png", "scale" : "2x" }, { "size" : "83.5x83.5", "idiom" : "ipad", "filename" : "Icon-App-83.5x83.5@2x.png", "scale" : "2x" }, { "size" : "1024x1024", "idiom" : "ios-marketing", "filename" : "Icon-App-1024x1024@1x.png", "scale" : "1x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "LaunchImage.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "LaunchImage@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "LaunchImage@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # Launch Screen Assets You can customize the launch screen with your own desired assets by replacing the image files in this directory. You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. ================================================ FILE: example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName dna_example CFBundlePackageType APPL CFBundleShortVersionString $(MARKETING_VERSION) CFBundleSignature ???? CFBundleVersion $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance ================================================ FILE: example/ios/Runner/main.m ================================================ #import #import #import "AppDelegate.h" int main(int argc, char* argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } ================================================ FILE: example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; E3CFC2FBA3EFAA3A631F71C1 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0A65BEB49930F6DF43C86C42 /* libPods-Runner.a */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 0A65BEB49930F6DF43C86C42 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, E3CFC2FBA3EFAA3A631F71C1 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, A6616C248645BB21C226DCDD /* Pods */, F6D482DBDF21B5F3B566A03E /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( 97C146F21CF9000F007C117D /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; A6616C248645BB21C226DCDD /* Pods */ = { isa = PBXGroup; children = ( ); name = Pods; sourceTree = ""; }; F6D482DBDF21B5F3B566A03E /* Frameworks */ = { isa = PBXGroup; children = ( 0A65BEB49930F6DF43C86C42 /* libPods-Runner.a */, ); name = Frameworks; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( F38B7F5F662F7823958ED7F4 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 3B6AFBCB775D17CA43AF09CB /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 0910; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( English, en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; }; 3B6AFBCB775D17CA43AF09CB /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( ); outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; F38B7F5F662F7823958ED7F4 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( "${PODS_PODFILE_DIR_PATH}/Podfile.lock", "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( ); outputPaths = ( "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Profile; }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = 100; DEVELOPMENT_TEAM = S8QB4VV633; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.dnaExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = 100; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.dnaExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = 100; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = com.example.dnaExample; PRODUCT_NAME = "$(TARGET_NAME)"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example/lib/main.dart ================================================ import 'package:flutter/material.dart'; import 'dart:async'; import 'package:flutter/services.dart'; import 'package:dna/dna.dart'; void main() => runApp(MyApp()); class MyApp extends StatefulWidget { @override _MyAppState createState() => _MyAppState(); } class _MyAppState extends State { String _platformVersion = 'Unknown'; @override void initState() { super.initState(); initPlatformState(); } // Platform messages are asynchronous, so we initialize in an async method. Future initPlatformState() async { String platformVersion; // Platform messages may fail, so we use a try/catch PlatformException. try { platformVersion = await Dna.traversingNative((ObjCContext context) { NativeObject version = context.classFromString('UIDevice').invoke(method: 'currentDevice').invoke(method: 'systemVersion'); version = context.classFromString("NSString").invoke(method: 'stringWithString:', args: ['iOS-']).invoke(method: 'stringByAppendingString:', args: [version]); context.returnVar = version; // 该句可省略 }, (JAVAContext context) { NativeObject versionId = context.newJavaObjectFromConstructor('me.ele.dna_example.DnaTest', null).invoke(method: 'getDnaVersion').invoke(method: 'getVersion'); NativeObject version = context.newJavaObjectFromConstructor('java.lang.String', ["android "]).invoke(method: "concat", args: [versionId]); context.returnVar = version; // 该句可省略 }); // ObjCContext context = ObjCContext(); // NativeObject version = context.classFromString('UIDevice').invoke(method: 'currentDevice').invoke(method: 'systemVersion'); // context.classFromString('NSString').invoke(method: 'stringWithString:', args: ['IOS-']).invoke(method:'stringByAppendingString:',args: [version]); // NativeObject objectA = context.newNativeObjectFromJSON({'a':1, 'b':2}, 'ClassA'); // NativeObject objectB = context.classFromString('ClassB').invoke(method: 'new'); // objectB.invoke(method: 'setC:',args: [3]); // objectB.invoke(method: 'sum:',args: [objectA]); // int x = await context.execute(); // platformVersion = await context.execute(); // android 测试代码 /* NativeObject objectA = context .classFromString("com.example.dna_example.DnaTest") .invoke(method: "getDna") .invoke(method: "HelloDna", args: ["Hello dna"]); NativeObject objectC = context.newNativeObjectFromJSON( {'a': 1, 'b': 2}, 'com.example.dna_example.TestModel'); NativeObject objectB = context .classFromString('com.example.dna_example.DnaComTest') .invoke(method: 'printlin', args: [objectA]).invoke( method: 'printlin', args: [objectC]);*/ } 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; }); } @override Widget build(BuildContext context) { initPlatformState(); return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text('Plugin example app'), ), body: Center( child: Text('Running on: $_platformVersion\n'), ), ), ); } } ================================================ FILE: example/pubspec.yaml ================================================ name: dna_example description: Demonstrates how to use the dna plugin. publish_to: 'none' environment: sdk: ">=2.1.0 <3.0.0" dependencies: flutter: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^0.1.2 dev_dependencies: flutter_test: sdk: flutter dna: path: ../ # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec # The following section is specific to Flutter. flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: example/test/widget_test.dart ================================================ // This is a basic Flutter widget test. // // To perform an interaction with a widget in your test, use the WidgetTester // utility that Flutter provides. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:dna_example/main.dart'; void main() { testWidgets('Verify Platform version', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(MyApp()); // Verify that platform version is retrieved. expect( find.byWidgetPredicate( (Widget widget) => widget is Text && widget.data.startsWith('Running on:'), ), findsOneWidget, ); }); } ================================================ FILE: ios/.gitignore ================================================ .idea/ .vagrant/ .sconsign.dblite .svn/ .DS_Store *.swp profile DerivedData/ build/ GeneratedPluginRegistrant.h GeneratedPluginRegistrant.m .generated/ *.pbxuser *.mode1v3 *.mode2v3 *.perspectivev3 !default.pbxuser !default.mode1v3 !default.mode2v3 !default.perspectivev3 xcuserdata *.moved-aside *.pyc *sync/ Icon? .tags* /Flutter/Generated.xcconfig ================================================ FILE: ios/Assets/.gitkeep ================================================ ================================================ FILE: ios/Classes/DnaPlugin.h ================================================ #import @interface DnaPlugin : NSObject @end ================================================ FILE: ios/Classes/DnaPlugin.m ================================================ #import "DnaPlugin.h" #import #import //@interface ClassA : NSObject //@property (nonatomic) NSInteger a; //@property (nonatomic) NSInteger b; //@end //@implementation ClassA //@end // //@interface ClassB : NSObject //@property (nonatomic) NSInteger c; //@end //@implementation ClassB // //- (NSInteger)sum:(ClassA *)objectA { // return self.c + objectA.a + objectA.b; //} //@end @implementation DnaPlugin + (void)registerWithRegistrar:(NSObject*)registrar { FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"dna" binaryMessenger:[registrar messenger]]; DnaPlugin* instance = [[DnaPlugin alloc] init]; [registrar addMethodCallDelegate:instance channel:channel]; } - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { if ([@"getPlatformVersion" isEqualToString:call.method]) { result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); } else if ([@"executeNativeContext" isEqualToString:call.method]) { [self executeNativeContext:call.arguments result:result]; } else { result(FlutterMethodNotImplemented); } } NSString * const dna_objectId = @"_objectId"; NSString * const dna_clsName = @"clsName"; NSString * const dna_objectJSONWrappers = @"_objectJSONWrappers"; NSString * const dna_json = @"json"; NSString * const dna_cls = @"cls"; NSString * const dna_invocationNodes = @"_invocationNodes"; NSString * const dna_object = @"object"; NSString * const dna_method = @"method"; NSString * const dna_args = @"args"; NSString * const dna_returnVar = @"returnVar"; - (void)executeNativeContext:(NSDictionary *)context result:(FlutterResult)result { // key : _objectId, value: object NSMutableDictionary *objectsInContextMap = [NSMutableDictionary dictionary]; // 直接根据objectJSONWrapper生成对象,以_objectId为键,加入到objectsInContextMap中 NSArray *_objectJSONWrappers = context[dna_objectJSONWrappers]; for (NSDictionary *objectJSONWrapper in _objectJSONWrappers) { NSDictionary *json = objectJSONWrapper[dna_json]; NSString *cls = objectJSONWrapper[dna_cls]; id object = [NSClassFromString(cls) yy_modelWithJSON:json]; if (object) { NSString *objectId = dna_getObjectId(objectJSONWrapper); objectsInContextMap[objectId] = object; } } // 得到context returnVar对应的_objectId NSDictionary *contextReturnVar = context[dna_returnVar]; BOOL hasContextReturnVarFlag = dna_isAvailable(contextReturnVar); NSString *contextReturnVarObjectId = nil; if (hasContextReturnVarFlag) { contextReturnVarObjectId = dna_getObjectId(contextReturnVar); } // 陆续调用所有Invocation NSArray *_invocationNodes = context[dna_invocationNodes]; for (NSUInteger i = 0; i < _invocationNodes.count; i++) { NSDictionary *invocation = _invocationNodes[i]; // 得到当前调用方法的对象,类或实例; NSObject *object = nil; NSDictionary *objectInfo = invocation[dna_object]; if (dna_isAvailable(dna_getObjectId(objectInfo))) { // 根据_objectId得到,可能是其他invocation的返回值 / 从objectJSONWrapper生成 object = objectsInContextMap[dna_getObjectId(objectInfo)]; if (!object && dna_isAvailable(objectInfo[dna_clsName])) { // 根据类名获取到类对象,并加入到变量表里 object = (id)NSClassFromString(objectInfo[dna_clsName]); if (object) { objectsInContextMap[dna_getObjectId(objectInfo)] = object; } } } // 获取当前selector SEL sel = NSSelectorFromString(invocation[dna_method]); // 处理获得当前所有参数 NSArray *invocationArgs = invocation[dna_args]; NSMutableArray *absoluteArgs = [NSMutableArray array]; if (dna_isAvailable(invocationArgs)) { for (id arg in invocationArgs) { if ([arg isKindOfClass:NSDictionary.class] && dna_getObjectId(arg)) { // 如果含有_objectId, 根据_objectId得到实例 id argInContext = objectsInContextMap[dna_getObjectId(arg)]; if (argInContext) { [absoluteArgs addObject:argInContext]; } } else { // channel 约定的基本类型,直接添加 [absoluteArgs addObject:arg]; } } } // 当前Invocation的返回值_objectId; NSString *invocationReturnVarObjectId = dna_getObjectId(invocation[dna_returnVar]); // 执行Invocation; id invocationReturnVar = [object dna_performSelector:sel withObjects:absoluteArgs]; if (invocationReturnVar) { // 当前invocation有返回值,把返回值 返回值_objectId 加入到objectsInContextMap中 objectsInContextMap[invocationReturnVarObjectId] = invocationReturnVar; if (!hasContextReturnVarFlag && (i == _invocationNodes.count - 1)) { // 如果context没有设置返回值_objectId,以最后一个invocation的返回值_objectId作为context 返回值_objectId contextReturnVarObjectId = invocationReturnVarObjectId; } } } id contextReturnValue = nil; if (contextReturnVarObjectId) { // 取得context的返回值 contextReturnValue = objectsInContextMap[contextReturnVarObjectId]; } if (result) { // 回调给dart返回值 result(contextReturnValue); } } NS_INLINE BOOL dna_isAvailable(id arg) { return arg && ![arg isKindOfClass:NSNull.class]; } NS_INLINE NSString *dna_getObjectId(NSDictionary *nativeVarJSON) { return nativeVarJSON[dna_objectId]; } @end ================================================ FILE: ios/Classes/NSObject+DnaRuntime.h ================================================ // // NSObject+DnaRuntime.h // LPDAdditionsKit // // Created by Assuner on 2018/4/13. // #import @interface NSObject (DnaRuntime) + (id)dna_objectWithBuffer:(void *)valueLoc type:(const char *)typeStr; - (void)dna_getValue:(void *)valueLoc type:(const char *)argType; // id / NSValue Type - (id)dna_performSelector:(SEL)aSelector withObjects:(NSArray *)objects; // id / NSValue Type @end @interface NSInvocation (DnaObjectParams) - (id)dna_getArgumentObjectAtIndex:(NSInteger)idx; - (void)dna_setArgumentObject:(id)arguementObject atIndex:(NSInteger)idx; @end ================================================ FILE: ios/Classes/NSObject+DnaRuntime.m ================================================ // // NSObject+DnaRuntime.m // LPDAdditionsKit // // Created by Assuner on 2018/4/13. // #import "NSObject+DnaRuntime.h" @implementation NSObject (DnaRuntime) + (id)dna_objectWithBuffer:(void *)valueLoc type:(const char *)argType { #define RETURN_WRAPPERED_OBJECT(type) \ do { \ type val = 0; \ val = *((type *) valueLoc); \ return @(val); \ } while(0); if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0 || strcmp(argType, @encode(void(^)(void))) == 0) { return *((__autoreleasing id *)valueLoc); } else if (strcmp(argType, @encode(char)) == 0) { RETURN_WRAPPERED_OBJECT(char); } else if (strcmp(argType, @encode(int)) == 0) { RETURN_WRAPPERED_OBJECT(int); } else if (strcmp(argType, @encode(short)) == 0) { RETURN_WRAPPERED_OBJECT(short); } else if (strcmp(argType, @encode(long)) == 0) { RETURN_WRAPPERED_OBJECT(long); } else if (strcmp(argType, @encode(long long)) == 0) { RETURN_WRAPPERED_OBJECT(long long); } else if (strcmp(argType, @encode(unsigned char)) == 0) { RETURN_WRAPPERED_OBJECT(unsigned char); } else if (strcmp(argType, @encode(unsigned int)) == 0) { RETURN_WRAPPERED_OBJECT(unsigned int); } else if (strcmp(argType, @encode(unsigned short)) == 0) { RETURN_WRAPPERED_OBJECT(unsigned short); } else if (strcmp(argType, @encode(unsigned long)) == 0) { RETURN_WRAPPERED_OBJECT(unsigned long); } else if (strcmp(argType, @encode(unsigned long long)) == 0) { RETURN_WRAPPERED_OBJECT(unsigned long long); } else if (strcmp(argType, @encode(float)) == 0) { RETURN_WRAPPERED_OBJECT(float); } else if (strcmp(argType, @encode(double)) == 0) { RETURN_WRAPPERED_OBJECT(double); } else if (strcmp(argType, @encode(BOOL)) == 0) { RETURN_WRAPPERED_OBJECT(BOOL); } else if (strcmp(argType, @encode(char *)) == 0) { RETURN_WRAPPERED_OBJECT(const char *); } else { return [NSValue valueWithBytes:valueLoc objCType:argType]; } } - (void)dna_getValue:(void *)valueLoc type:(const char *)argType { #define UNWRAPPER_AND_SET(type, selector) \ do { \ *((type *) valueLoc) = [(id)self selector];\ } while (0) if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0 || strcmp(argType, @encode(void(^)(void))) == 0) { *((__autoreleasing id *)valueLoc) = self; } else if (strcmp(argType, @encode(char)) == 0) { UNWRAPPER_AND_SET(char, charValue); } else if (strcmp(argType, @encode(int)) == 0) { UNWRAPPER_AND_SET(int, intValue); } else if (strcmp(argType, @encode(short)) == 0) { UNWRAPPER_AND_SET(short, shortValue); } else if (strcmp(argType, @encode(long)) == 0) { UNWRAPPER_AND_SET(long, longValue); } else if (strcmp(argType, @encode(long long)) == 0) { UNWRAPPER_AND_SET(long long, longLongValue); } else if (strcmp(argType, @encode(unsigned char)) == 0) { UNWRAPPER_AND_SET(unsigned char, unsignedCharValue); } else if (strcmp(argType, @encode(unsigned int)) == 0) { UNWRAPPER_AND_SET(unsigned int, unsignedIntValue); } else if (strcmp(argType, @encode(unsigned short)) == 0) { UNWRAPPER_AND_SET(unsigned short, unsignedShortValue); } else if (strcmp(argType, @encode(unsigned long)) == 0) { UNWRAPPER_AND_SET(unsigned long, unsignedLongValue); } else if (strcmp(argType, @encode(unsigned long long)) == 0) { UNWRAPPER_AND_SET(unsigned long long, unsignedLongLongValue); } else if (strcmp(argType, @encode(float)) == 0) { UNWRAPPER_AND_SET(float, floatValue); } else if (strcmp(argType, @encode(double)) == 0) { UNWRAPPER_AND_SET(double, doubleValue); } else if (strcmp(argType, @encode(BOOL)) == 0) { UNWRAPPER_AND_SET(BOOL, boolValue); } else if (strcmp(argType, @encode(char *)) == 0) { *((char **) valueLoc) = (char *)[(id)self UTF8String]; } else { [(NSValue *)self getValue:valueLoc]; } } - (id)dna_performSelector:(SEL)aSelector withObjects:(NSArray *)objects { NSMethodSignature *signature = [self methodSignatureForSelector:aSelector]; if (!signature || objects.count != signature.numberOfArguments - 2) { return nil; } NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; for (NSInteger i = 0; i < objects.count; i ++) { [invocation dna_setArgumentObject:objects[i] atIndex:i+2]; } invocation.selector = aSelector; invocation.target = self; [invocation retainArguments]; [invocation invoke]; if (!signature.methodReturnLength) { return nil; } else { void *valueLoc = alloca(signature.methodReturnLength); [invocation getReturnValue:valueLoc]; return [NSObject dna_objectWithBuffer:valueLoc type:signature.methodReturnType]; } } @end @implementation NSInvocation (DnaObjectParams) - (id)dna_getArgumentObjectAtIndex:(NSInteger)idx { #define WRAP_AND_RETURN(type) \ do { \ type val = 0; \ [self getArgument:&val atIndex:idx]; \ return @(val); \ } while (0) const char *argType = [self.methodSignature getArgumentTypeAtIndex:idx]; if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) { __autoreleasing id returnObj; [self getArgument:&returnObj atIndex:idx]; return returnObj; } else if (strcmp(argType, @encode(char)) == 0) { WRAP_AND_RETURN(char); } else if (strcmp(argType, @encode(int)) == 0) { WRAP_AND_RETURN(int); } else if (strcmp(argType, @encode(short)) == 0) { WRAP_AND_RETURN(short); } else if (strcmp(argType, @encode(long)) == 0) { WRAP_AND_RETURN(long); } else if (strcmp(argType, @encode(long long)) == 0) { WRAP_AND_RETURN(long long); } else if (strcmp(argType, @encode(unsigned char)) == 0) { WRAP_AND_RETURN(unsigned char); } else if (strcmp(argType, @encode(unsigned int)) == 0) { WRAP_AND_RETURN(unsigned int); } else if (strcmp(argType, @encode(unsigned short)) == 0) { WRAP_AND_RETURN(unsigned short); } else if (strcmp(argType, @encode(unsigned long)) == 0) { WRAP_AND_RETURN(unsigned long); } else if (strcmp(argType, @encode(unsigned long long)) == 0) { WRAP_AND_RETURN(unsigned long long); } else if (strcmp(argType, @encode(float)) == 0) { WRAP_AND_RETURN(float); } else if (strcmp(argType, @encode(double)) == 0) { WRAP_AND_RETURN(double); } else if (strcmp(argType, @encode(BOOL)) == 0) { WRAP_AND_RETURN(BOOL); } else if (strcmp(argType, @encode(char *)) == 0) { WRAP_AND_RETURN(const char *); } else if (strcmp(argType, @encode(void (^)(void))) == 0) { __unsafe_unretained id block = nil; [self getArgument:&block atIndex:idx]; return [block copy]; } else { NSUInteger valueSize = 0; NSGetSizeAndAlignment(argType, &valueSize, NULL); unsigned char valueBytes[valueSize]; [self getArgument:valueBytes atIndex:idx]; return [NSValue valueWithBytes:valueBytes objCType:argType]; } } - (void)dna_setArgumentObject:(id)arguementObject atIndex:(NSInteger)idx { #define PULL_AND_SET(type, selector) \ do { \ type val = [arguementObject selector]; \ [self setArgument:&val atIndex:idx]; \ } while (0) if ([arguementObject isKindOfClass:NSNull.class]) { arguementObject = nil; } const char *argType = [self.methodSignature getArgumentTypeAtIndex:idx]; if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) { [self setArgument:&arguementObject atIndex:idx]; } else if (strcmp(argType, @encode(char)) == 0) { PULL_AND_SET(char, charValue); } else if (strcmp(argType, @encode(int)) == 0) { PULL_AND_SET(int, intValue); } else if (strcmp(argType, @encode(short)) == 0) { PULL_AND_SET(short, shortValue); } else if (strcmp(argType, @encode(long)) == 0) { PULL_AND_SET(long, longValue); } else if (strcmp(argType, @encode(long long)) == 0) { PULL_AND_SET(long long, longLongValue); } else if (strcmp(argType, @encode(unsigned char)) == 0) { PULL_AND_SET(unsigned char, unsignedCharValue); } else if (strcmp(argType, @encode(unsigned int)) == 0) { PULL_AND_SET(unsigned int, unsignedIntValue); } else if (strcmp(argType, @encode(unsigned short)) == 0) { PULL_AND_SET(unsigned short, unsignedShortValue); } else if (strcmp(argType, @encode(unsigned long)) == 0) { PULL_AND_SET(unsigned long, unsignedLongValue); } else if (strcmp(argType, @encode(unsigned long long)) == 0) { PULL_AND_SET(unsigned long long, unsignedLongLongValue); } else if (strcmp(argType, @encode(float)) == 0) { PULL_AND_SET(float, floatValue); } else if (strcmp(argType, @encode(double)) == 0) { PULL_AND_SET(double, doubleValue); } else if (strcmp(argType, @encode(BOOL)) == 0) { PULL_AND_SET(BOOL, boolValue); } else if (strcmp(argType, @encode(char *)) == 0) { const char *cString = [arguementObject UTF8String]; [self setArgument:&cString atIndex:idx]; [self retainArguments]; } else if (strcmp(argType, @encode(void (^)(void))) == 0) { [self setArgument:&arguementObject atIndex:idx]; } else { NSCParameterAssert([arguementObject isKindOfClass:NSValue.class]); NSUInteger valueSize = 0; NSGetSizeAndAlignment([arguementObject objCType], &valueSize, NULL); unsigned char valueBytes[valueSize]; [arguementObject getValue:valueBytes]; [self setArgument:valueBytes atIndex:idx]; } } @end ================================================ FILE: ios/dna.podspec ================================================ # # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| s.name = 'dna' s.version = '0.0.1' s.summary = 'A new flutter plugin project.' s.description = <<-DESC A new flutter plugin project. DESC s.homepage = 'http://example.com' s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' s.dependency 'YYModel' s.ios.deployment_target = '8.0' end ================================================ FILE: lib/dna.dart ================================================ import 'dart:async'; import 'package:flutter/services.dart'; import 'dart:io'; import 'native_context.dart'; export 'native_context.dart' show NativeContext, ObjCContext, JAVAContext; export 'native_object.dart' show NativeObject, NativeClass; class Dna { static const MethodChannel _channel = const MethodChannel('dna'); static Future executeNativeContext(NativeContext context) async { return await _channel.invokeMethod('executeNativeContext', context.toJSON()); } static Future traversingNative( ObjCContextBuilder(ObjCContext objcContext), JAVAContextBuilder(JAVAContext javaContext)) async { NativeContext nativeContext; if (Platform.isIOS) { nativeContext = ObjCContext(); ObjCContextBuilder(nativeContext); } else if (Platform.isAndroid) { nativeContext = JAVAContext(); JAVAContextBuilder(nativeContext); } return executeNativeContext(nativeContext); } } ================================================ FILE: lib/native_context.dart ================================================ import 'dart:io'; import 'dna.dart'; import 'native_object.dart'; class NativeInvocation { final NativeObject object; final String method; final List args; final NativeObject returnVar; NativeInvocation(this.object, this.method, this.args, this.returnVar); Map toJSON() { Map json = Map(); if (object != null) { json['object'] = object.toJSON(); } if (method != null) { json['method'] = method; } if (args != null) { List argsJSON = List(); for (var arg in args) { if (arg is NativeObject) { argsJSON.add(arg.toJSON()); } else { argsJSON.add(arg); } } json['args'] = argsJSON; } if (returnVar != null) { json['returnVar'] = returnVar.toJSON(); } return json; } } //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// class NativeContext { final List _invocationNodes = List(); final List _objectJSONWrappers = List(); NativeObject returnVar; void invoke({NativeObject object, String method, List args, NativeObject returnVar}) { NativeInvocation invocation = NativeInvocation(object, method, args, returnVar); _invocationNodes.add(invocation); } NativeObject classFromString(String clsName) { NativeClass cls = NativeClass(this, clsName); return cls; } NativeObject newNativeObject() { NativeObject object = NativeObject(this); return object; } NativeObject newNativeObjectFromJSON(Map json, String cls) { NativeObject object = NativeObjectJSONWrapper(this, json, cls); _objectJSONWrappers.add(object); return object; } Map toJSON() { List invocationNodesJSON = List(); for (var invocation in _invocationNodes) { invocationNodesJSON.add(invocation.toJSON()); } List objectJSONWrappersJSON = List(); for (var jsonVar in _objectJSONWrappers) { objectJSONWrappersJSON.add(jsonVar.toJSON()); } Map json = Map(); json['_invocationNodes'] = invocationNodesJSON; json['_objectJSONWrappers'] = objectJSONWrappersJSON; if (returnVar != null) { json['returnVar'] = returnVar.toJSON(); } return json; } bool canExecute() { return false; } Future execute() async { if (this.canExecute()) { return await Dna.executeNativeContext(this); } else { return null; } } } ////////////////// class ObjCContext extends NativeContext { bool canExecute() { return Platform.isIOS; } } ////////////////// class JAVAContext extends NativeContext { bool canExecute() { return Platform.isAndroid; } NativeObject newJavaObjectFromConstructor(String clsName, List args) { NativeObject orignVar = JavaObjectConstructor(this, clsName, args); return orignVar; } } ================================================ FILE: lib/native_object.dart ================================================ import 'dart:math'; import 'native_context.dart'; class NativeObject extends Object { final NativeContext context; static String _alphabet = 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM'; static int _strlenght = 8; String _objectId; static String _randomString() { String randomString = ''; for (var i = 0; i < _strlenght; i++) { randomString = randomString + _alphabet[Random().nextInt(_alphabet.length)]; } return randomString; } NativeObject(this.context) { _objectId = '_objectId_' + _randomString(); } Map toJSON () { Map json = Map(); if (_objectId != null) { json['_objectId'] = _objectId; } return json; } NativeObject invoke({String method, List args}) { NativeObject returnValue = NativeObject(this.context); context.invoke(object: this, method: method, args: args, returnVar: returnValue); return returnValue; } } ////////////////// class NativeClass extends NativeObject { final String clsName; NativeClass(NativeContext context, this.clsName) : super(context); Map toJSON () { Map json = super.toJSON(); if (clsName != null) { json['clsName'] = clsName; } return json; } } ////////////////// class NativeObjectJSONWrapper extends NativeObject { final Map json; final String cls; NativeObjectJSONWrapper(NativeContext context, this.json, this.cls) : super(context); Map toJSON () { Map json = super.toJSON(); if (this.json != null) { json['json'] = this.json; } if (cls != null) { json['cls'] = cls; } return json; } } //////////////////// class JavaObjectConstructor extends NativeObject { final String cls; JavaObjectConstructor(NativeContext context, this.cls, List args) : super(context) { context.invoke(object: this, method: null, args: args, returnVar: this); } Map toJSON() { Map json = super.toJSON(); json['constructCls'] = cls; return json; } } ================================================ FILE: pubspec.yaml ================================================ name: dna description: A new flutter plugin project. version: 0.0.1 author: homepage: environment: sdk: ">=2.1.0 <3.0.0" dependencies: flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec # The following section is specific to Flutter. flutter: # This section identifies this Flutter project as a plugin project. # The androidPackage and pluginClass identifiers should not ordinarily # be modified. They are used by the tooling to maintain consistency when # adding or updating assets for this project. plugin: androidPackage: me.ele.dna pluginClass: DnaPlugin # To add assets to your plugin package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg # # For details regarding assets in packages, see # https://flutter.dev/assets-and-images/#from-packages # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. # To add custom fonts to your plugin package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For # example: # fonts: # - family: Schyler # fonts: # - asset: fonts/Schyler-Regular.ttf # - asset: fonts/Schyler-Italic.ttf # style: italic # - family: Trajan Pro # fonts: # - asset: fonts/TrajanPro.ttf # - asset: fonts/TrajanPro_Bold.ttf # weight: 700 # # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages ================================================ FILE: test/dna_test.dart ================================================ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:dna/dna.dart'; void main() { const MethodChannel channel = MethodChannel('dna'); setUp(() { channel.setMockMethodCallHandler((MethodCall methodCall) async { return '42'; }); }); tearDown(() { channel.setMockMethodCallHandler(null); }); test('getPlatformVersion', () async { }); }