├── .gitattributes ├── .gitignore ├── LICENSE ├── README-zh.md ├── README.md ├── build.gradle ├── freeline-databinding-cli ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── antfortune │ └── freeline │ └── databinding │ ├── CliMain.java │ ├── DataBindingHelper.java │ ├── ExportDataBindingInfo.java │ └── ProcessLayouts.java ├── freeline-docs ├── README.md ├── en.md └── zh_cn.md ├── freeline-gradle-plugin ├── .gitignore ├── bintray.gradle ├── build.gradle └── src │ └── main │ ├── groovy │ └── com │ │ └── antfortune │ │ └── freeline │ │ ├── Constants.groovy │ │ ├── FreelineAnnotationCollector.groovy │ │ ├── FreelineClassVisitor.groovy │ │ ├── FreelineCompat.groovy │ │ ├── FreelineConfigGenerateTask.groovy │ │ ├── FreelineDownloader.groovy │ │ ├── FreelineExtension.groovy │ │ ├── FreelineGenerator.groovy │ │ ├── FreelineInitializer.groovy │ │ ├── FreelineInjector.groovy │ │ ├── FreelineParser.groovy │ │ ├── FreelinePlugin.groovy │ │ ├── FreelineUtils.groovy │ │ └── versions │ │ ├── StaticVersionComparator.groovy │ │ ├── Version.groovy │ │ └── VersionParser.groovy │ └── resources │ └── META-INF │ └── gradle-plugins │ └── com.antfortune.freeline.properties ├── freeline-runtime-no-op ├── .gitignore ├── bintray.gradle ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── antfortune │ │ └── android │ │ └── freeline │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── antfortune │ │ └── freeline │ │ └── FreelineCore.java │ └── test │ └── java │ └── com │ └── antfortune │ └── android │ └── freeline │ └── ExampleUnitTest.java ├── freeline-runtime ├── .gitignore ├── bintray.gradle ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── antfortune │ │ └── android │ │ └── freeline │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── apktime │ │ └── hackload.dex │ ├── java │ │ └── com │ │ │ └── antfortune │ │ │ └── freeline │ │ │ ├── FreelineApplication.java │ │ │ ├── FreelineCore.java │ │ │ ├── FreelineReceiver.java │ │ │ ├── FreelineService.java │ │ │ ├── IDynamic.java │ │ │ ├── MiddlewareActivity.java │ │ │ ├── gradle │ │ │ └── GradleDynamic.java │ │ │ ├── resources │ │ │ └── MonkeyPatcher.java │ │ │ ├── router │ │ │ ├── ISchemaAction.java │ │ │ ├── Router.java │ │ │ └── schema │ │ │ │ ├── CheckResourceSchema.java │ │ │ │ ├── CheckSyncSchema.java │ │ │ │ ├── CloseLonglinkSchema.java │ │ │ │ ├── GetSyncTicketSchema.java │ │ │ │ ├── PushDexSchema.java │ │ │ │ ├── PushFullResourcePackSchema.java │ │ │ │ ├── PushNativeSchema.java │ │ │ │ ├── PushResourceSchema.java │ │ │ │ └── RestartSchema.java │ │ │ ├── server │ │ │ ├── EmbedHttpServer.java │ │ │ └── LongLinkServer.java │ │ │ └── util │ │ │ ├── ActivityManager.java │ │ │ ├── AppUtils.java │ │ │ ├── DexUtils.java │ │ │ ├── FileUtils.java │ │ │ ├── NativeUtils.java │ │ │ ├── ReflectUtil.java │ │ │ └── Restarter.java │ └── res │ │ └── values │ │ └── values.xml │ └── test │ └── java │ └── com │ └── antfortune │ └── android │ └── freeline │ └── ExampleUnitTest.java ├── freeline-sample ├── .gitignore ├── PureJavaLib │ ├── .gitignore │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── antfortune │ │ └── freeline │ │ └── sample │ │ └── pure_java │ │ └── JavaTools.java ├── ThirdParty │ ├── payment │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── antfortune │ │ │ │ └── freeline │ │ │ │ └── sample │ │ │ │ └── payment │ │ │ │ └── PaymentUtils.java │ │ │ └── res │ │ │ └── values │ │ │ └── strings.xml │ └── share │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── proguard-rules.pro │ │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── res │ │ ├── drawable-hdpi │ │ └── ic_settings.png │ │ └── values │ │ └── strings.xml ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── antfortune │ │ │ └── freeline │ │ │ └── sample │ │ │ ├── App.java │ │ │ └── MainActivity.java │ │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml ├── common │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── antfortune │ │ │ └── freeline │ │ │ └── sample │ │ │ └── common │ │ │ └── CommonTools.java │ │ └── res │ │ └── values │ │ └── strings.xml ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties └── resources │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── resource_assets_file │ ├── java │ └── com │ │ └── antfortune │ │ └── freeline │ │ └── sample │ │ └── resources │ │ └── ResourcesTools.java │ ├── res │ ├── drawable-hdpi │ │ ├── bg_test_1.png │ │ ├── bg_test_2.png │ │ └── bg_test_3.png │ └── values │ │ └── strings.xml │ └── res_emoji │ └── values │ └── strings.xml ├── freeline-studio-plugin ├── .gitignore ├── README.md ├── build.gradle └── src │ └── main │ ├── java │ └── com │ │ └── antfortune │ │ └── freeline │ │ └── idea │ │ ├── actions │ │ ├── BaseAction.java │ │ ├── CrashReporterAction.java │ │ ├── DebugRunAction.java │ │ ├── FeedbackAction.java │ │ ├── ForceRunAction.java │ │ ├── FreelineRunAction.java │ │ ├── OnRequestCallback.java │ │ ├── SendFeedbackDialog.form │ │ ├── SendFeedbackDialog.java │ │ ├── UpdateAction.java │ │ └── UsingReportAction.java │ │ ├── configuration │ │ ├── FreeConfigurationFactory.java │ │ ├── FreeConfigurationType.java │ │ ├── FreeRunConfiguration.java │ │ ├── FreeSettingEditor.form │ │ └── FreeSettingEditor.java │ │ ├── icons │ │ └── PluginIcons.java │ │ ├── models │ │ ├── ArtifactDependencyModelWrapper.java │ │ ├── Constant.java │ │ ├── FreelineConfiguration.java │ │ ├── FreelineStatus.java │ │ ├── GetServerCallback.java │ │ ├── GradleDependencyEntity.java │ │ └── GradleSyncHandler.java │ │ ├── utils │ │ ├── DialogUtil.java │ │ ├── DocumentUtil.java │ │ ├── FreeUIManager.java │ │ ├── FreelineUtil.java │ │ ├── GradleSyncUtil.java │ │ ├── GradleUtil.java │ │ ├── GroovyFileUil.java │ │ ├── LogUtil.java │ │ ├── NotificationUtils.java │ │ ├── StreamUtil.java │ │ └── Utils.java │ │ └── views │ │ ├── CheckUpdateDialog.form │ │ ├── CheckUpdateDialog.java │ │ ├── FreelineTerminal.java │ │ ├── FreelineToolWindowFactory.java │ │ ├── ImageJPanel.java │ │ ├── JTitle.java │ │ └── MulLabel.java │ └── resources │ ├── META-INF │ └── plugin.xml │ └── icons │ ├── OpenTerminal.png │ ├── OpenTerminal@2x.png │ ├── bg_update.png │ ├── debug.png │ ├── debug@2x.png │ ├── gradlesync.png │ ├── gradlesync@2x.png │ ├── icon.png │ ├── icon@2x.png │ ├── play2.png │ └── play2@2x.png ├── freeline.py ├── freeline_core ├── __init__.py ├── android_tools.py ├── build_commands.py ├── builder.py ├── command.py ├── cursor.py ├── dispatcher.py ├── exceptions.py ├── freeline_build.py ├── gradle_clean_build.py ├── gradle_inc_build.py ├── gradle_tools.py ├── hackwindows.py ├── init.py ├── logger.py ├── sync_client.py ├── task.py ├── task_engine.py ├── terminal.py ├── tracing.py ├── utils.py └── version.py ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── release-tools ├── DexMerge.jar ├── FreelineAapt ├── FreelineAapt.exe ├── FreelineAapt_ ├── classrelated.jar ├── databinding-cli.jar ├── databinding-cli8.jar ├── desugar_deploy.jar ├── dexmerger ├── dx ├── dx.bat ├── dx.jar ├── jasmin ├── jasmin.jar ├── lib64 │ └── libc++.so ├── mainDexClasses ├── mainDexClasses.bat ├── mainDexClasses.rules └── manifest.txt └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | *.groovy linguist-language=Java -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | local.properties 4 | .DS_Store 5 | build/ 6 | captures/ 7 | .idea/ 8 | *.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Alibaba 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of freeline nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | # Freeline 2 | 3 | ![Freeline](http://ww4.sinaimg.cn/large/006tNc79gw1f6ooza8pkuj30h804gjrk.jpg) 4 | 5 | [![Release Version](https://img.shields.io/badge/release-0.8.8-red.svg)](https://github.com/alibaba/freeline/releases) [![BSD3 License](https://img.shields.io/badge/license-BSD3-blue.svg)](https://github.com/alibaba/freeline/blob/master/LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/alibaba/freeline/pulls) 6 | 7 | Freeline 是 Android 平台上的秒级编译方案,Instant Run 的替代品,也可以从 [Freeline 官方主页](https://www.freelinebuild.com/)来获取更多的信息。 8 | 9 | Freeline 由[蚂蚁聚宝](https://www.antfortune.com/) Android 团队开发,它可以充分利用缓存文件,在几秒钟内迅速地对代码的改动进行编译并部署到设备上,有效地减少了日常开发中的大量重新编译与安装的耗时。 10 | 11 | Freeline能够为Android开发者节省很多喝杯咖啡的时间 : ) 12 | 13 | ## Freeline Insights 14 | Freeline 将整个编译过程拆分成多个小模块,根据模块间的依赖关系并发进行编译任务。它可以对所有编译缓存文件做到充分地利用,真正地做到了“增量”编译。同时,它也提取了 buck 的部分组件(dx 与 DexMerger),来加速构建流程。Freeline 会在 app 运行时启动一条独立进程来运行 Socket Server 以进行开发机与 app 的通信,同时也能确保即使在 app 出现 crash 之后,仍然能够使增量编译持续生效。 15 | 16 | Freeline 是通过 multi-dex 的方案来实现 Java 代码的增量更新的。与此同时,我们也专门对 aapt 工具做了深度优化,给出了独立的 FreelineAapt 资源打包工具用来生成增量的资源包以及加快资源包的打包速度。运行期的资源替换,Freeline 中使用了 Instant Run 的方案。除此之外,Freeline 也支持运行期动态更新 so 文件。 17 | 18 | Freeline 会根据代码文件的变更情况,自动在全量编译与增量编译中进行切换。 19 | 20 | 实际上,Freeline 可以在经过简单的改造之后,作为线上热修复方案,通过动态下发增量资源,来对应用进行修复或者资源替换。经过压缩后的增量资源通常都会在 100kb 以下,用户可以在移动网络的环境下直接接收到热补丁。蚂蚁聚宝已经基于 Freeline 打造了完整的热修复框架,应用于线上问题修复以及 A/B Test。 21 | 22 | [中文原理说明](https://yq.aliyun.com/articles/59122?spm=5176.8091938.0.0.1Bw3mU) 23 | 24 | ## Features 25 | - 支持标准的多模块 Gradle 工程的增量构建 26 | - 并发执行增量编译任务 27 | - 进程级别异常隔离机制 28 | - 支持 so 动态更新 29 | - 支持 resource.arsc 缓存 30 | - 支持 retrolambda 31 | - 支持 DataBinding 32 | - 支持各类主流注解库 33 | - 支持 Windows,Linux,Mac 平台 34 | 35 | 以下列表为 Freeline 支持的热更新情况: 36 | 37 | || Java | drawable, layout, etc. | res/values | native so| 38 | |:-----:|:----:|:----:|:----:|:----:| 39 | | add | √ | √ |√ | √ | 40 | | change | √ | √ |√ | √ | 41 | | remove | √ | √ |x| - | 42 | 43 | Freeline 已经分别在 API 17,19,22,23 的 Android 模拟器、Android 6.0 Nexus 6P 以及 Android 4.4 锤子手机上经过测试。如果想要充分体验 Freeline 增量编译的速度的话,最好使用 Android 5.0+ 的设备。 44 | 45 | ## Download 46 | 配置 project-level 的 build.gradle,加入 freeline-gradle 的依赖: 47 | 48 | ````Gradle 49 | buildscript { 50 | repositories { 51 | jcenter() 52 | } 53 | dependencies { 54 | classpath 'com.antfortune.freeline:gradle:0.8.8' 55 | } 56 | } 57 | ```` 58 | 然后,在你的主 module 的 build.gradle 中,应用 freeline 插件的依赖: 59 | 60 | ````Gradle 61 | apply plugin: 'com.antfortune.freeline' 62 | 63 | android { 64 | ... 65 | } 66 | ```` 67 | 68 | 最后,在命令行执行以下命令来下载 freeline 的 python 和二进制依赖。 69 | 70 | - Windows[CMD]: gradlew initFreeline 71 | - Linux/Mac: ./gradlew initFreeline 72 | 73 | 对于国内的用户来说,如果你的下载的时候速度很慢,你也可以加上参数,执行`gradlew initFreeline -Pmirror`,这样就会从国内镜像地址来下载。 74 | 75 | 你也可以使用参数`-PfreelineVersion={your-specific-version}`来下载特定版本的 freeline 依赖。 76 | 77 | 如果你的工程结构较为复杂,在第一次使用 freeline 编译的时候报错了的话,你可以添加一些 freeline 提供的配置项,来适配你的工程。具体可以看 [Freeline DSL References](https://github.com/alibaba/freeline/wiki/Freeline-DSL-References)。 78 | 79 | ## Usage 80 | Freeline 最快捷的使用方法就是直接安装 Android Studio 插件。 81 | 82 | 在 Android Studio 中,通过以下路径: 83 | 84 | - MacOS 85 | > Android Studio → Preferences... → Plugins → Browse repositories... 86 | 87 | - Windows 和 Linux 88 | > File → Settings... → Plugins → Browse repositories... 89 | 90 | 并搜索“freeline”,并安装。 91 | 92 | ![](http://ww4.sinaimg.cn/large/65e4f1e6gw1f82eknaeudj20tk01omxe.jpg) 93 | 94 | 直接点击 `Run Freeline` 的按钮,就可以享受 Freeline 带来的开发效率的提升啦(当然,你可能会先需要一个较为耗时的全量编译过程)。 95 | 96 | 插件也会提示你 Freeline 最新的版本是多少,你也可以通过插件来对 Freeline 进行更新。 97 | 98 | 非常感谢 [@pengwei1024](https://github.com/pengwei1024) 和 [@act262](https://github.com/act262) 帮助我们开发了这个非常棒的 Freeline 插件。 99 | 100 | 除此之外,你也可以通过在命令行执行 Python 脚本的方式来使用 Freeline,具体可以看我们的相应的链接 [Freeline CLI Usage](https://github.com/alibaba/freeline/wiki/Freeline-CLI-Usage)。 101 | 102 | ## Sample Usage 103 | ```` 104 | git clone git@github.com:alibaba/freeline.git 105 | cd freeline/sample 106 | ./gradlew initFreeline 107 | python freeline.py 108 | ```` 109 | 110 | ## TODO 111 | - 兼容性与稳定性的提升 112 | - 多设备同时连接 PC 的支持 113 | 114 | ## Limitations 115 | - 第一次增量资源编译的时候可能会有点慢 116 | - 不支持删除带 id 的资源,否则可能导致 aapt 编译出错 117 | - 暂不支持抽象类的增量编译 118 | - 不支持开启 Jack 编译 119 | - 不支持 Kotlin/Groovy/Scala 120 | 121 | ## Contributing 122 | 不论是小的修改,还是大的feature更新,我们都非常欢迎大家为 Freeline 贡献代码。 123 | 124 | ## Troubleshooting 125 | See [wiki](https://github.com/alibaba/freeline/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98). 126 | 127 | ## Thanks 128 | - [Instant Run](https://developer.android.com/studio/run/index.html#instant-run) 129 | - [Buck](https://github.com/facebook/buck) 130 | - [LayoutCast](https://github.com/mmin18/LayoutCast) 131 | 132 | ## License 133 | BSD3 License 134 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | mavenLocal() 6 | google() 7 | jcenter() 8 | maven { url 'http://dl.bintray.com/jetbrains/intellij-plugin-service' } 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.0.0' 12 | classpath "com.antfortune.freeline:gradle:$FREELINE_RELEASE_VERSION" 13 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' 14 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0' 15 | classpath 'me.tatarka:gradle-retrolambda:3.6.1' 16 | // NOTE: Do not place your application dependencies here; they belong 17 | // in the individual module build.gradle files 18 | } 19 | } 20 | 21 | allprojects { 22 | repositories { 23 | mavenLocal() 24 | google() 25 | jcenter() 26 | } 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | 33 | def supportLibVersion = '27.0.0' 34 | 35 | ext { 36 | freelineDevVersion = FREELINE_RELEASE_VERSION 37 | freelineReleaseVersion = FREELINE_RELEASE_VERSION 38 | minSdkVersion = 14 39 | targetSdkVersion = 25 40 | compileSdkVersion = 27 41 | buildToolsVersion = '27.0.0' 42 | } 43 | 44 | def deps = [ 45 | appcompatV7 : "com.android.support:appcompat-v7:$supportLibVersion", 46 | supportV4 : "com.android.support:support-v4:$supportLibVersion", 47 | recyclerview : "com.android.support:recyclerview-v7:$supportLibVersion", 48 | design : "com.android.support:design:$supportLibVersion", 49 | 50 | freelineRuntime : "com.antfortune.freeline:runtime:$freelineReleaseVersion", 51 | freelineRuntimeNoOp: "com.antfortune.freeline:runtime-no-op:$freelineReleaseVersion" 52 | ] 53 | 54 | ext.deps = deps -------------------------------------------------------------------------------- /freeline-databinding-cli/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.antfortune.freeline' 2 | version '1.0.2' 3 | 4 | apply plugin: 'java' 5 | 6 | sourceCompatibility = 1.7 7 | 8 | def compilerVersion = '2.1.3' 9 | def cliJarName = 'databinding-cli.jar' 10 | 11 | def isBuildForJdk8 = hasProperty("jdk8") 12 | if (isBuildForJdk8) { 13 | compilerVersion = '2.2.0' 14 | cliJarName = 'databinding-cli8.jar' 15 | } 16 | 17 | println "build with compiler version $compilerVersion" 18 | println "output jar name: $cliJarName" 19 | 20 | dependencies { 21 | compile fileTree(dir: 'libs', include: ['*.jar']) 22 | compile "com.android.databinding:compilerCommon:$compilerVersion" 23 | compile "commons-cli:commons-cli:1.3.1" 24 | } 25 | 26 | 27 | jar { 28 | manifest { 29 | attributes 'Main-Class': 'com.antfortune.freeline.databinding.CliMain' 30 | attributes 'Manifest-Version': version 31 | } 32 | from { 33 | configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } 34 | } 35 | archiveName cliJarName 36 | } 37 | -------------------------------------------------------------------------------- /freeline-databinding-cli/src/main/java/com/antfortune/freeline/databinding/DataBindingHelper.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.databinding; 2 | 3 | import android.databinding.tool.DataBindingBuilder; 4 | import android.databinding.tool.LayoutXmlProcessor; 5 | 6 | import java.io.File; 7 | 8 | /** 9 | * Created by huangyong on 16/10/21. 10 | */ 11 | public class DataBindingHelper { 12 | 13 | private static LayoutXmlProcessor sLayoutXmlProcessor; 14 | 15 | public static void init(String packageName, int minSdkVersion, String classOutputPath, boolean isLibrary) { 16 | if (sLayoutXmlProcessor == null) { 17 | sLayoutXmlProcessor = new LayoutXmlProcessor( 18 | packageName, 19 | new DataBindingBuilder().createJavaFileWriter(new File(classOutputPath)), 20 | minSdkVersion, 21 | isLibrary, 22 | new LayoutXmlProcessor.OriginalFileLookup() { 23 | @Override 24 | public File getOriginalFileFor(File file) { 25 | return file; 26 | } 27 | } 28 | ); 29 | } 30 | } 31 | 32 | public static LayoutXmlProcessor getLayoutXmlProcessor() { 33 | return sLayoutXmlProcessor; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /freeline-databinding-cli/src/main/java/com/antfortune/freeline/databinding/ExportDataBindingInfo.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.databinding; 2 | 3 | import java.io.File; 4 | 5 | /** 6 | * Created by huangyong on 16/10/21. 7 | */ 8 | public class ExportDataBindingInfo { 9 | 10 | public static void run(File sdkDirectory, File outputDirectory) { 11 | // dataBindingExportBuildInfo 12 | // TODO: exportClassListTo 13 | DataBindingHelper.getLayoutXmlProcessor().writeInfoClass(sdkDirectory, outputDirectory, null, true, true); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /freeline-databinding-cli/src/main/java/com/antfortune/freeline/databinding/ProcessLayouts.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.databinding; 2 | 3 | import android.databinding.tool.LayoutXmlProcessor; 4 | 5 | import java.io.File; 6 | import java.util.List; 7 | 8 | /** 9 | * Created by huangyong on 16/10/21. 10 | */ 11 | public class ProcessLayouts { 12 | 13 | public static void run(boolean isIncremental, File inputDirectory, File outputDirectory, File layoutInfoDirectory, List changedFiles) throws Exception { 14 | LayoutXmlProcessor.ResourceInput resourceInput = 15 | new LayoutXmlProcessor.ResourceInput(isIncremental, inputDirectory, outputDirectory); 16 | if (isIncremental && changedFiles != null) { 17 | for (String path : changedFiles) { 18 | resourceInput.changed(new File(path)); 19 | } 20 | } 21 | 22 | // dataBindingProcessLayouts 23 | DataBindingHelper.getLayoutXmlProcessor().processResources(resourceInput); 24 | DataBindingHelper.getLayoutXmlProcessor().writeLayoutInfoFiles(layoutInfoDirectory); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /freeline-docs/README.md: -------------------------------------------------------------------------------- 1 | ## Freeline Documents 2 | 3 | Online Release: [Freeline Documents Site](https://www.freelinebuild.com/docs/) 4 | 5 | - [English](en.md) 6 | - [中文文档](zh_cn.md) 7 | -------------------------------------------------------------------------------- /freeline-docs/en.md: -------------------------------------------------------------------------------- 1 | ## What's Freeline? 2 | 3 | Freeline is a super fast build tool for Android, an alternative to Instant Run. 4 | 5 | ## Getting Started 6 | 7 | We are working for this... -------------------------------------------------------------------------------- /freeline-gradle-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/ 5 | .DS_Store 6 | /build 7 | */build 8 | /captures 9 | 10 | /.freeline_cache 11 | *.py 12 | *.pyc 13 | -------------------------------------------------------------------------------- /freeline-gradle-plugin/bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | apply plugin: 'maven' 3 | apply plugin: 'maven-publish' 4 | 5 | def mavenDesc = 'Freeline is an incremental build tool for Android platform.' 6 | def siteUrl = 'https://github.com/alibaba/freeline' 7 | def gitUrl = 'https://github.com/alibaba/freeline' 8 | def issueUrl = 'https://github.com/alibaba/freeline/issues' 9 | 10 | def licenseIds = ['BSD'] 11 | 12 | def developerId = 'yong.hy' 13 | def developerName = 'yong.hy' 14 | def developerEmail = 'yong.hy@alipay.com' 15 | 16 | def publishedGroupId = 'com.antfortune.freeline' 17 | def artifact = 'gradle' 18 | def libraryName = 'freeline-gradle' 19 | def publishVersion = FREELINE_ARTIFACT_VERSION 20 | 21 | install { 22 | repositories { 23 | mavenInstaller { 24 | pom.project { 25 | name libraryName 26 | description mavenDesc 27 | url siteUrl 28 | 29 | groupId publishedGroupId 30 | artifactId artifact 31 | version publishVersion 32 | 33 | developers { 34 | developer { 35 | id developerId 36 | name developerName 37 | email developerEmail 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | task sourcesJar(type: Jar) { 46 | from sourceSets.main.allGroovy 47 | classifier = 'sources' 48 | } 49 | 50 | 51 | task javadocJar(type: Jar, dependsOn: groovydoc) { 52 | from groovydoc.getDestinationDir() 53 | classifier = 'javadoc' 54 | } 55 | 56 | artifacts { 57 | archives javadocJar 58 | archives sourcesJar 59 | } 60 | 61 | // Bintray 62 | Properties properties = new Properties() 63 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 64 | 65 | bintray { 66 | user = properties.getProperty("bintray.user") 67 | key = properties.getProperty("bintray.apikey") 68 | 69 | configurations = ['archives'] 70 | pkg { 71 | repo = 'maven' 72 | name = libraryName 73 | desc = mavenDesc 74 | websiteUrl = siteUrl 75 | issueTrackerUrl = issueUrl 76 | vcsUrl = gitUrl 77 | labels = ['android', 'incremental build', 'freeline', 'hotpatch'] 78 | licenses = licenseIds 79 | publish = true 80 | publicDownloadNumbers = true 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /freeline-gradle-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | apply plugin: 'java' 3 | apply from: 'bintray.gradle' 4 | 5 | group 'com.antfortune.freeline' 6 | version FREELINE_ARTIFACT_VERSION 7 | 8 | sourceCompatibility = 1.6 9 | targetCompatibility = 1.6 10 | 11 | repositories { 12 | google() 13 | } 14 | dependencies { 15 | compile gradleApi() 16 | compile 'com.squareup:javapoet:1.7.0' 17 | compile 'com.github.javaparser:javaparser-core:2.4.0' 18 | compile 'com.android.tools.build:gradle:3.0.0' 19 | } 20 | 21 | jar { 22 | from { 23 | configurations.compile.findResults { 24 | it.absolutePath.contains('javapoet') || it.absolutePath.contains('javaparser-core') ? zipTree(it) : null 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /freeline-gradle-plugin/src/main/groovy/com/antfortune/freeline/Constants.groovy: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline 2 | 3 | /** 4 | * Created by huangyong on 16/7/20. 5 | */ 6 | class Constants { 7 | 8 | public static final String FREELINE_BUILD_CACHE_DIR = "freeline" 9 | 10 | public static final String FREELINE_PRO_DESC_FILE_NAME = "freeline_project_description.json" 11 | 12 | } 13 | -------------------------------------------------------------------------------- /freeline-gradle-plugin/src/main/groovy/com/antfortune/freeline/FreelineAnnotationCollector.groovy: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline 2 | 3 | import groovy.json.JsonBuilder 4 | import org.gradle.api.Project; 5 | 6 | /** 7 | * Created by huangyong on 16/11/3. 8 | */ 9 | class FreelineAnnotationCollector { 10 | 11 | public static final def ANNOTATION_CLASSES = [ 12 | "Landroid/databinding/BindingAdapter;", 13 | "Landroid/databinding/BindingConversion;", 14 | "Landroid/databinding/Bindable;", 15 | "Ldagger/Component;", 16 | "Ldagger/Module;", 17 | "Ljavax/inject/Inject;" 18 | ] 19 | 20 | public static final def ANNOTATION_TARGETS = [ 21 | "Landroid/databinding/BindingAdapter;": "BindingAdapter", 22 | "Landroid/databinding/BindingConversion;": "BindingConversion", 23 | "Landroid/databinding/Bindable;": "Bindable", 24 | "Ldagger/Component;": "DaggerComponent", 25 | "Ldagger/Module;": "DaggerModule", 26 | "Ljavax/inject/Inject;": "Inject" 27 | ] 28 | 29 | private static def sAnnotationCollection = [:] 30 | 31 | public static void addNewAnno(String anno, String path, String className, String entry, boolean isJar) { 32 | String key = ANNOTATION_TARGETS[anno] 33 | if (!sAnnotationCollection.containsKey(key)) { 34 | sAnnotationCollection[key] = [] 35 | } 36 | 37 | sAnnotationCollection[key].add(['path': path, 'className': className, 'entry': entry, 'isJar': isJar]) 38 | } 39 | 40 | public static void saveCollections(Project project, String buildCacheDirPath, Map modules) { 41 | def description = FreelineUtils.readProjectDescription(project) 42 | sAnnotationCollection.keySet().each { key -> 43 | sAnnotationCollection[key].each { value -> 44 | if (value['isJar']) { 45 | modules.each { m, mappers -> 46 | for (String mapper : mappers) { 47 | if (value['path'].contains(mapper)) { 48 | value['module'] = m 49 | value['java_path'] = findJavaPath(description, m as String, value['className'] as String) 50 | return false 51 | } 52 | } 53 | } 54 | } else { 55 | value['module'] = project.name 56 | value['java_path'] = findJavaPath(description, project.name, value['className'] as String) 57 | } 58 | } 59 | } 60 | 61 | def json = new JsonBuilder(sAnnotationCollection).toPrettyString() 62 | println json 63 | FreelineUtils.saveJson(json, FreelineUtils.joinPath(buildCacheDirPath, "freeline_annotation_info.json"), true) 64 | 65 | sAnnotationCollection.clear() 66 | } 67 | 68 | private static String findJavaPath(def description, String module, String className) { 69 | if (description != null) { 70 | if (description['project_source_sets'].containsKey(module)) { 71 | def relatedPath = className.replace("/", File.separator).replace(".class", ".java") 72 | if (!relatedPath.endsWith(".java")) { 73 | relatedPath = relatedPath + ".java" 74 | } 75 | 76 | def javaPath = null 77 | description['project_source_sets'][module]['main_src_directory'].each { path -> 78 | File file = new File(FreelineUtils.joinPath(path as String, relatedPath)) 79 | if (file.exists()) { 80 | javaPath = file.absolutePath 81 | return false 82 | } 83 | } 84 | 85 | if (javaPath != null) { 86 | return javaPath 87 | } 88 | } 89 | } 90 | return null 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /freeline-gradle-plugin/src/main/groovy/com/antfortune/freeline/FreelineCompat.groovy: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline 2 | 3 | /** 4 | * Created by huangyong on 17/1/12. 5 | */ 6 | class FreelineCompat { 7 | 8 | private static final def IGNORE_RESOURCE_IDS = ["avd_hide_password_1", "avd_hide_password_2", 9 | "avd_hide_password_3", "avd_show_password_1", 10 | "avd_show_password_2", "avd_show_password_3"] 11 | 12 | public static List compatIgnoreResourceIds(List ignoreResourceIds) { 13 | for (String id : IGNORE_RESOURCE_IDS) { 14 | if (!ignoreResourceIds.contains(id)) { 15 | ignoreResourceIds.add(id) 16 | } 17 | } 18 | return ignoreResourceIds 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /freeline-gradle-plugin/src/main/groovy/com/antfortune/freeline/FreelineConfigGenerateTask.groovy: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline 2 | 3 | import org.apache.commons.io.FileUtils 4 | import org.gradle.api.DefaultTask 5 | import org.gradle.api.tasks.Input 6 | import org.gradle.api.tasks.OutputDirectory 7 | import org.gradle.api.tasks.TaskAction 8 | 9 | /** 10 | * Created by huangyong on 16/9/13. 11 | */ 12 | class FreelineConfigGenerateTask extends DefaultTask { 13 | 14 | @Input 15 | public String applicationClass; 16 | 17 | @Input 18 | public String packageName; 19 | 20 | @OutputDirectory 21 | public File outputDir; 22 | 23 | @TaskAction 24 | public void action() { 25 | FileUtils.deleteDirectory(outputDir) 26 | 27 | Map configMap = new HashMap(); 28 | configMap.put("packageName", packageName); 29 | configMap.put("applicationClass", applicationClass) 30 | FreelineGenerator.generateFreelineConfig(configMap, outputDir) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /freeline-gradle-plugin/src/main/groovy/com/antfortune/freeline/FreelineExtension.groovy: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline 2 | 3 | import org.gradle.api.Project 4 | 5 | /** 6 | * Created by huangyong on 16/5/19. 7 | */ 8 | class FreelineExtension { 9 | 10 | String productFlavor = "" 11 | 12 | boolean hack = false 13 | 14 | String buildScript = "" 15 | 16 | String apkPath = "" 17 | 18 | String buildScriptWorkDirectory = "" 19 | 20 | String packageName = "" 21 | 22 | String launcher = "" 23 | 24 | List extraResourceDependencyPaths = [] 25 | 26 | List excludeResourceDependencyPaths = [] 27 | 28 | List excludeHackClasses = [] 29 | 30 | List ignoreResourceIds = [] 31 | 32 | List checkSourcesMd5 = [] 33 | 34 | boolean foceLowerVersion = false 35 | 36 | boolean applicationProxy = true 37 | 38 | boolean autoDependency = true 39 | 40 | boolean aptEnabled = true 41 | 42 | boolean retrolambdaEnabled = true 43 | 44 | boolean useSystemGradle = false 45 | 46 | boolean forceVersionName = false 47 | 48 | FreelineExtension(Project project) { 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /freeline-gradle-plugin/src/main/groovy/com/antfortune/freeline/FreelineGenerator.groovy: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline 2 | 3 | import com.squareup.javapoet.FieldSpec 4 | import com.squareup.javapoet.JavaFile 5 | import com.squareup.javapoet.TypeSpec 6 | import org.apache.tools.ant.taskdefs.condition.Os 7 | 8 | import java.nio.charset.Charset 9 | import java.security.MessageDigest 10 | 11 | import static javax.lang.model.element.Modifier.FINAL; 12 | import static javax.lang.model.element.Modifier.PUBLIC; 13 | import static javax.lang.model.element.Modifier.STATIC; 14 | 15 | /** 16 | * Created by huangyong on 16/8/1. 17 | */ 18 | class FreelineGenerator { 19 | 20 | public static boolean isNormalProductFlavor(String productFlavor) { 21 | return productFlavor.equalsIgnoreCase("") || productFlavor.equalsIgnoreCase("debug") 22 | } 23 | 24 | public static String generateBuildScript(boolean isRootModuleTheMainModule, String mainModule, String productFlavor) { 25 | def params = [] 26 | if (Os.isFamily(Os.FAMILY_WINDOWS)) { 27 | params.add("gradlew.bat") 28 | } else { 29 | params.add("./gradlew") 30 | } 31 | 32 | if (isRootModuleTheMainModule) { 33 | params.add('assembleDebug') 34 | } else if (isNormalProductFlavor(productFlavor)) { 35 | params.add("assembleDebug") 36 | } else { 37 | params.add("assemble${productFlavor.capitalize()}Debug") 38 | } 39 | 40 | return params.join(" ") 41 | } 42 | 43 | public static String generateApkPath(String buildDir, String projectName, String productFlavor) { 44 | if (isNormalProductFlavor(productFlavor)) { 45 | return FreelineUtils.joinPath(buildDir, "outputs", "apk", "$projectName-debug.apk") 46 | } else { 47 | return FreelineUtils.joinPath(buildDir, "outputs", "apk", "$projectName-$productFlavor-debug.apk") 48 | } 49 | } 50 | 51 | public static String generateMainRPath(String buildDir, String productFlavor, String packageName) { 52 | String suffix = FreelineUtils.joinPath(packageName.replace(".", File.separator), "R.java") 53 | if (isNormalProductFlavor(productFlavor)) { 54 | return FreelineUtils.joinPath(buildDir, "generated", "source", "r", "debug", suffix) 55 | } else { 56 | return FreelineUtils.joinPath(buildDir, "generated", "source", "r", productFlavor, "debug", suffix) 57 | } 58 | } 59 | 60 | public static String generateProjectBuildAssetsPath(String buildDir, String productFlavor) { 61 | if (isNormalProductFlavor(productFlavor)) { 62 | return FreelineUtils.joinPath(buildDir, "intermediates", "assets", "debug") 63 | } else { 64 | return FreelineUtils.joinPath(buildDir, "intermediates", "assets", productFlavor, "debug") 65 | } 66 | } 67 | 68 | public static String generateProjectBuildJniFolderPath(String buildDir, String productFlavor) { 69 | if (isNormalProductFlavor(productFlavor)) { 70 | return FreelineUtils.joinPath(buildDir, "intermediates", "jniLibs", "debug") 71 | } else { 72 | return FreelineUtils.joinPath(buildDir, "intermediates", "jniLibs", productFlavor, "debug") 73 | } 74 | } 75 | 76 | public static String generateStringMD5(String input) { 77 | return MessageDigest.getInstance("MD5").digest(input.getBytes(Charset.forName("UTF-8"))).encodeHex().toString() 78 | } 79 | 80 | public static void generateFreelineConfig(Map config, File outputDirectory) { 81 | TypeSpec.Builder result = TypeSpec.classBuilder("FreelineConfig").addModifiers(PUBLIC).addModifiers(FINAL); 82 | 83 | config.each { key, value -> 84 | FieldSpec.Builder fieldSpecBuilder = FieldSpec.builder(String.class, key) 85 | .addModifiers(PUBLIC, STATIC, FINAL) 86 | .initializer('$S', value); 87 | result.addField(fieldSpecBuilder.build()) 88 | } 89 | 90 | JavaFile configFile = JavaFile.builder("com.antfortune.freeline", result.build()) 91 | .addFileComment("Generated by freeline.") 92 | .build() 93 | configFile.writeTo(outputDirectory) 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /freeline-gradle-plugin/src/main/groovy/com/antfortune/freeline/FreelineParser.groovy: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline 2 | /** 3 | * Created by huangyong on 16/7/19. 4 | */ 5 | class FreelineParser { 6 | 7 | public static String getPackageName(String applicationId, String manifestPath) { 8 | if (!FreelineUtils.isEmpty(applicationId) && !"null".equals(applicationId)) { 9 | return applicationId 10 | } 11 | return getPackage(manifestPath) 12 | } 13 | 14 | public static String getPackage(String manifestPath) { 15 | def packageName = "" 16 | def manifestFile = new File(manifestPath) 17 | if (manifestFile.exists() && manifestFile.isFile()) { 18 | def manifest = new XmlSlurper(false, false).parse(manifestFile) 19 | packageName = manifest."@package".text() 20 | } 21 | return packageName 22 | } 23 | 24 | public static String getApplication(String manifestPath, String packageName) { 25 | def application = "" 26 | packageName = fetchRealPackageName(manifestPath, packageName) 27 | def manifestFile = new File(manifestPath) 28 | if (manifestFile.exists() && manifestFile.isFile()) { 29 | def manifest = new XmlSlurper(false, false).parse(manifestFile) 30 | application = manifest.application."@android:name".text() 31 | if (application != null && application.startsWith(".")) { 32 | application = packageName + application 33 | } 34 | } 35 | return application 36 | } 37 | 38 | public static String getLauncher(String manifestPath, String packageName) { 39 | def launcher = "" 40 | packageName = fetchRealPackageName(manifestPath, packageName) 41 | def manifestFile = new File(manifestPath) 42 | if (manifestFile.exists() && manifestFile.isFile()) { 43 | def manifest = new XmlSlurper(false, false).parse(manifestFile) 44 | def launcherCandidates = [] 45 | manifest.application.activity.each { node -> 46 | def candidate = [name: "", isDefault: false] 47 | if (node."intent-filter".category.size() > 0) { 48 | node."intent-filter".category.each { category -> 49 | if (category."@android:name" == "android.intent.category.LAUNCHER") { 50 | candidate.name = node."@android:name".text() 51 | } else if (category."@android:name" == "android.intent.category.DEFAULT") { 52 | candidate.isDefault = true 53 | } 54 | } 55 | 56 | if (candidate.name != "") { 57 | launcherCandidates.add(candidate) 58 | } 59 | } 60 | } 61 | 62 | if (launcherCandidates.size() == 0) { 63 | // throw exception 64 | } else if (launcherCandidates.size() == 1) { 65 | launcher = launcherCandidates[0].name 66 | } else { 67 | launcherCandidates.each { 68 | if (it.isDefault) { 69 | launcher = it.name 70 | return true 71 | } 72 | } 73 | } 74 | 75 | if (launcher.startsWith(".")) { 76 | launcher = packageName + launcher 77 | } 78 | } 79 | return launcher 80 | } 81 | 82 | public static int getMinSdkVersion(String manifestPath) { 83 | def minSdkVersion = 0 84 | def manifestFile = new File(manifestPath) 85 | if (manifestFile.exists() && manifestFile.isFile()) { 86 | def manifest = new XmlSlurper(false, false).parse(manifestFile) 87 | minSdkVersion = manifest."uses-sdk"."@android:minSdkVersion".text() 88 | } 89 | return Integer.valueOf(minSdkVersion) 90 | } 91 | 92 | private static String fetchRealPackageName(String manifestPath, String packageName) { 93 | def manifestPackageName = getPackage(manifestPath) 94 | if (manifestPackageName != null && !manifestPackageName.equals(packageName)) { 95 | packageName = manifestPackageName 96 | } 97 | return packageName 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /freeline-gradle-plugin/src/main/groovy/com/antfortune/freeline/versions/StaticVersionComparator.groovy: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.versions 2 | 3 | import com.google.common.collect.ImmutableMap 4 | 5 | /** 6 | * ref @{org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.StaticVersionComparator} 7 | * in gradle-2.4 8 | */ 9 | public class StaticVersionComparator implements Comparator { 10 | 11 | private static final Map SPECIAL_MEANINGS = 12 | ImmutableMap.of("dev", new Integer(-1), "rc", new Integer(1), "final", new Integer(2)); 13 | 14 | /** 15 | * Compares 2 versions. Algorithm is inspired by PHP version_compare one. 16 | */ 17 | public int compare(Version version1, Version version2) { 18 | if (version1.equals(version2)) { 19 | return 0; 20 | } 21 | 22 | String[] parts1 = version1.getParts(); 23 | String[] parts2 = version2.getParts(); 24 | 25 | int i = 0; 26 | for (; i < parts1.length && i < parts2.length; i++) { 27 | if (parts1[i].equals(parts2[i])) { 28 | continue; 29 | } 30 | boolean is1Number = isNumber(parts1[i]); 31 | boolean is2Number = isNumber(parts2[i]); 32 | if (is1Number && !is2Number) { 33 | return 1; 34 | } 35 | if (is2Number && !is1Number) { 36 | return -1; 37 | } 38 | if (is1Number && is2Number) { 39 | return Long.valueOf(parts1[i]).compareTo(Long.valueOf(parts2[i])); 40 | } 41 | // both are strings, we compare them taking into account special meaning 42 | Integer sm1 = SPECIAL_MEANINGS.get(parts1[i].toLowerCase(Locale.US)); 43 | Integer sm2 = SPECIAL_MEANINGS.get(parts2[i].toLowerCase(Locale.US)); 44 | if (sm1 != null) { 45 | sm2 = sm2 == null ? 0 : sm2; 46 | return sm1 - sm2; 47 | } 48 | if (sm2 != null) { 49 | return -sm2; 50 | } 51 | return parts1[i].compareTo(parts2[i]); 52 | } 53 | if (i < parts1.length) { 54 | return isNumber(parts1[i]) ? 1 : -1; 55 | } 56 | if (i < parts2.length) { 57 | return isNumber(parts2[i]) ? -1 : 1; 58 | } 59 | 60 | return 0; 61 | } 62 | 63 | private boolean isNumber(String str) { 64 | return str.matches("\\d+"); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /freeline-gradle-plugin/src/main/groovy/com/antfortune/freeline/versions/Version.groovy: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.versions 2 | 3 | /** 4 | * ref @{org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionParser#DefaultVersion} 5 | * in gradle-2.4 6 | */ 7 | public class Version { 8 | private final String source; 9 | private final String[] parts; 10 | private final Version baseVersion; 11 | 12 | public Version(String source, List parts, Version baseVersion) { 13 | this.source = source; 14 | this.parts = parts.toArray(new String[parts.size()]); 15 | this.baseVersion = baseVersion == null ? this : baseVersion; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return source; 21 | } 22 | 23 | @Override 24 | public boolean equals(Object obj) { 25 | if (obj == null || obj.getClass() != getClass()) { 26 | return false; 27 | } 28 | Version other = (Version) obj; 29 | return source.equals(other.source); 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return source.hashCode(); 35 | } 36 | 37 | public String[] getParts() { 38 | return parts; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /freeline-gradle-plugin/src/main/groovy/com/antfortune/freeline/versions/VersionParser.groovy: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.versions 2 | 3 | /** 4 | * ref @{org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionParser} 5 | * in gradle-2.4 6 | */ 7 | public class VersionParser { 8 | 9 | public Version transform(String original) { 10 | List parts = new ArrayList(); 11 | boolean digit = false; 12 | int startPart = 0; 13 | int pos = 0; 14 | int endBase = 0; 15 | int endBaseStr = 0; 16 | for (; pos < original.length(); pos++) { 17 | char ch = original.charAt(pos); 18 | if (ch == '.' || ch == '_' || ch == '-' || ch == '+') { 19 | parts.add(original.substring(startPart, pos)); 20 | startPart = pos + 1; 21 | digit = false; 22 | if (ch != '.' && endBaseStr == 0) { 23 | endBase = parts.size(); 24 | endBaseStr = pos; 25 | } 26 | } else if (ch >= '0' && ch <= '9') { 27 | if (!digit && pos > startPart) { 28 | if (endBaseStr == 0) { 29 | endBase = parts.size() + 1; 30 | endBaseStr = pos; 31 | } 32 | parts.add(original.substring(startPart, pos)); 33 | startPart = pos; 34 | } 35 | digit = true; 36 | } else { 37 | if (digit) { 38 | if (endBaseStr == 0) { 39 | endBase = parts.size() + 1; 40 | endBaseStr = pos; 41 | } 42 | parts.add(original.substring(startPart, pos)); 43 | startPart = pos; 44 | } 45 | digit = false; 46 | } 47 | } 48 | if (pos > startPart) { 49 | parts.add(original.substring(startPart, pos)); 50 | } 51 | Version base = null; 52 | if (endBaseStr > 0) { 53 | base = new Version(original.substring(0, endBaseStr), parts.subList(0, endBase), null); 54 | } 55 | return new Version(original, parts, base); 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /freeline-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.antfortune.freeline.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.antfortune.freeline.FreelinePlugin -------------------------------------------------------------------------------- /freeline-runtime-no-op/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /freeline-runtime-no-op/bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | apply plugin: 'maven-publish' 4 | 5 | def mavenDesc = 'Freeline is an incremental build tool for Android platform.' 6 | def siteUrl = 'https://github.com/alibaba/freeline' 7 | def gitUrl = 'https://github.com/alibaba/freeline' 8 | def issueUrl = 'https://github.com/alibaba/freeline/issues' 9 | 10 | def licenseIds = ['BSD'] 11 | 12 | def developerId = 'yong.hy' 13 | def developerName = 'yong.hy' 14 | def developerEmail = 'yong.hy@alipay.com' 15 | 16 | def publishedGroupId = 'com.antfortune.freeline' 17 | def artifact = 'runtime-no-op' 18 | def libraryName = 'freeline-runtime-no-op' 19 | def publishVersion = FREELINE_ARTIFACT_VERSION 20 | 21 | install { 22 | repositories { 23 | mavenInstaller { 24 | pom.project { 25 | name libraryName 26 | description mavenDesc 27 | url siteUrl 28 | 29 | groupId publishedGroupId 30 | artifactId artifact 31 | version publishVersion 32 | 33 | developers { 34 | developer { 35 | id developerId 36 | name developerName 37 | email developerEmail 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | task sourcesJar(type: Jar) { 46 | from android.sourceSets.main.java.srcDirs 47 | classifier = 'sources' 48 | } 49 | 50 | artifacts { 51 | //archives javadocJar 52 | archives sourcesJar 53 | } 54 | 55 | // Bintray 56 | Properties properties = new Properties() 57 | try { 58 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 59 | } catch(Exception e) { 60 | // nothing 61 | } 62 | 63 | bintray { 64 | user = properties.getProperty("bintray.user") 65 | key = properties.getProperty("bintray.apikey") 66 | 67 | configurations = ['archives'] 68 | pkg { 69 | repo = 'maven' 70 | name = libraryName 71 | desc = mavenDesc 72 | websiteUrl = siteUrl 73 | issueTrackerUrl = issueUrl 74 | vcsUrl = gitUrl 75 | labels = ['android', 'incremental build', 'freeline', 'hotpatch'] 76 | licenses = licenseIds 77 | publish = true 78 | publicDownloadNumbers = true 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /freeline-runtime-no-op/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply from: 'bintray.gradle' 3 | 4 | group 'com.antfortune.freeline' 5 | version FREELINE_ARTIFACT_VERSION 6 | 7 | android { 8 | compileSdkVersion rootProject.ext.compileSdkVersion 9 | buildToolsVersion rootProject.ext.buildToolsVersion 10 | 11 | defaultConfig { 12 | minSdkVersion rootProject.ext.minSdkVersion 13 | targetSdkVersion rootProject.ext.targetSdkVersion 14 | versionCode 10 15 | versionName "0.7.0" 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(dir: 'libs', include: ['*.jar']) 27 | } 28 | 29 | -------------------------------------------------------------------------------- /freeline-runtime-no-op/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/huangyong/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /freeline-runtime-no-op/src/androidTest/java/com/antfortune/android/freeline/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.android.freeline; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /freeline-runtime-no-op/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /freeline-runtime-no-op/src/main/java/com/antfortune/freeline/FreelineCore.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | /** 7 | * Created by huangyong on 16/9/1. 8 | */ 9 | public class FreelineCore { 10 | 11 | private static final String TAG = "Freeline"; 12 | 13 | public static void init(Application app, Application realApplication) { 14 | markFreeline(); 15 | } 16 | 17 | public static void init(Application app) { 18 | markFreeline(); 19 | } 20 | 21 | private static void markFreeline() { 22 | Log.i(TAG, "Freeline with runtime-no-op loaded!"); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /freeline-runtime-no-op/src/test/java/com/antfortune/android/freeline/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.android.freeline; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /freeline-runtime/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /freeline-runtime/bintray.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | apply plugin: 'maven-publish' 4 | 5 | def mavenDesc = 'Freeline is an incremental build tool for Android platform.' 6 | def siteUrl = 'https://github.com/alibaba/freeline' 7 | def gitUrl = 'https://github.com/alibaba/freeline' 8 | def issueUrl = 'https://github.com/alibaba/freeline/issues' 9 | 10 | def licenseIds = ['BSD'] 11 | 12 | def developerId = 'yong.hy' 13 | def developerName = 'yong.hy' 14 | def developerEmail = 'yong.hy@alipay.com' 15 | 16 | def publishedGroupId = 'com.antfortune.freeline' 17 | def artifact = 'runtime' 18 | def libraryName = 'freeline-runtime' 19 | def publishVersion = FREELINE_ARTIFACT_VERSION 20 | 21 | install { 22 | repositories { 23 | mavenInstaller { 24 | pom.project { 25 | name libraryName 26 | description mavenDesc 27 | url siteUrl 28 | 29 | groupId publishedGroupId 30 | artifactId artifact 31 | version publishVersion 32 | 33 | developers { 34 | developer { 35 | id developerId 36 | name developerName 37 | email developerEmail 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | task sourcesJar(type: Jar) { 46 | from android.sourceSets.main.java.srcDirs 47 | classifier = 'sources' 48 | } 49 | 50 | artifacts { 51 | //archives javadocJar 52 | archives sourcesJar 53 | } 54 | 55 | // Bintray 56 | Properties properties = new Properties() 57 | try { 58 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 59 | } catch(Exception e) { 60 | // nothing 61 | } 62 | 63 | bintray { 64 | user = properties.getProperty("bintray.user") 65 | key = properties.getProperty("bintray.apikey") 66 | 67 | configurations = ['archives'] 68 | pkg { 69 | repo = 'maven' 70 | name = libraryName 71 | desc = mavenDesc 72 | websiteUrl = siteUrl 73 | issueTrackerUrl = issueUrl 74 | vcsUrl = gitUrl 75 | labels = ['android', 'incremental build', 'freeline', 'hotpatch'] 76 | licenses = licenseIds 77 | publish = true 78 | publicDownloadNumbers = true 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /freeline-runtime/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply from: 'bintray.gradle' 3 | 4 | group 'com.antfortune.freeline' 5 | version FREELINE_ARTIFACT_VERSION 6 | 7 | android { 8 | compileSdkVersion rootProject.ext.compileSdkVersion 9 | buildToolsVersion rootProject.ext.buildToolsVersion 10 | 11 | defaultConfig { 12 | minSdkVersion rootProject.ext.minSdkVersion 13 | targetSdkVersion rootProject.ext.targetSdkVersion 14 | versionCode 10 15 | versionName version 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | } 24 | 25 | dependencies { 26 | compile fileTree(dir: 'libs', include: ['*.jar']) 27 | } 28 | 29 | -------------------------------------------------------------------------------- /freeline-runtime/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/huangyong/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /freeline-runtime/src/androidTest/java/com/antfortune/android/freeline/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.android.freeline; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /freeline-runtime/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 8 | 9 | 11 | 12 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/assets/apktime: -------------------------------------------------------------------------------- 1 | ${placeholder} -------------------------------------------------------------------------------- /freeline-runtime/src/main/assets/hackload.dex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-runtime/src/main/assets/hackload.dex -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/FreelineApplication.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.pm.PackageManager; 6 | import android.text.TextUtils; 7 | import android.util.Log; 8 | 9 | import com.antfortune.freeline.util.ReflectUtil; 10 | 11 | import java.lang.reflect.Constructor; 12 | 13 | /** 14 | * Created by huangyong on 16/9/14. 15 | */ 16 | 17 | public class FreelineApplication extends Application { 18 | 19 | protected static final String TAG = "FreelineApplication"; 20 | 21 | private Class freelineConfigClazz; 22 | 23 | private Application realApplication; 24 | 25 | @Override 26 | public void onCreate() { 27 | super.onCreate(); 28 | Log.d(TAG, "FreelineApplication#onCreate()"); 29 | initFreelineConfig(); 30 | createRealApplication(); 31 | FreelineCore.init(this, realApplication); 32 | startRealApplication(); 33 | } 34 | 35 | @Override 36 | public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException { 37 | Context c = realApplication.createPackageContext(packageName, flags); 38 | return c == null ? realApplication : c; 39 | } 40 | 41 | private void startRealApplication() { 42 | if (realApplication != null) { 43 | try { 44 | ReflectUtil.invokeMethod(Application.class, realApplication, "attach", new Class[]{Context.class}, new Object[]{getBaseContext()}); 45 | Log.d(TAG, "realApplication#attach(Context)"); 46 | } catch (Exception e) { 47 | FreelineCore.printStackTrace(e); 48 | Log.e(TAG, "attach with realApplication error"); 49 | } 50 | 51 | realApplication.onCreate(); 52 | Log.d(TAG, "realApplication#onCreate()"); 53 | } 54 | } 55 | 56 | private void initFreelineConfig() { 57 | try { 58 | freelineConfigClazz = Class.forName("com.antfortune.freeline.FreelineConfig"); 59 | } catch (Exception e) { 60 | FreelineCore.printStackTrace(e); 61 | Log.e(TAG, "initFreelineConfig error"); 62 | } 63 | } 64 | 65 | private String getConfigValue(String fieldName) { 66 | try { 67 | return ReflectUtil.getStaticFieldValue(freelineConfigClazz, fieldName).toString(); 68 | } catch (Exception e) { 69 | FreelineCore.printStackTrace(e); 70 | Log.e(TAG, "get config value error"); 71 | return ""; 72 | } 73 | } 74 | 75 | private void createRealApplication() { 76 | String applicationClass = getConfigValue("applicationClass"); 77 | if (TextUtils.isEmpty(applicationClass)) { 78 | realApplication = new Application(); 79 | Log.d(TAG, "create empty application."); 80 | } else { 81 | try { 82 | Class realClass = Class.forName(applicationClass); 83 | Constructor constructor = realClass.getConstructor(); 84 | this.realApplication = constructor.newInstance(); 85 | Log.d(TAG, "create application: " + applicationClass); 86 | } catch (Exception e) { 87 | FreelineCore.printStackTrace(e); 88 | Log.e(TAG, "create real application error"); 89 | } 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/FreelineReceiver.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.text.TextUtils; 7 | import android.util.Log; 8 | 9 | import com.antfortune.freeline.util.ActivityManager; 10 | 11 | import java.util.HashMap; 12 | 13 | /** 14 | * Created by huangyong on 16/7/31. 15 | */ 16 | public class FreelineReceiver extends BroadcastReceiver { 17 | 18 | private static final String TAG = "Freeline.Receiver"; 19 | 20 | public static final String ACTION_KEY = "action"; 21 | 22 | public static final String UUID = "uuid"; 23 | 24 | public static final String SP_KEY = "sp_key"; 25 | public static final String SP_VALUE = "sp_value"; 26 | public static final String DEX_VALUE = "dex_path"; 27 | public static final String OPT_VALUE = "opt_path"; 28 | 29 | public static final int ACTION_UPDATE_ACTIVITY = 1; 30 | public static final int ACTION_RESTART_APPLICATION = 2; 31 | public static final int ACTION_SAVE_DYNAMIC_INFO = 3; 32 | 33 | @Override 34 | public void onReceive(Context context, Intent intent) { 35 | String uuid = intent.getStringExtra(UUID); 36 | if (FreelineCore.getUuid().equalsIgnoreCase(uuid)) { 37 | int type = intent.getIntExtra(ACTION_KEY, -1); 38 | Log.i(TAG, "receive action type: " + type); 39 | if (type == ACTION_UPDATE_ACTIVITY) { 40 | saveDynamicResInfo(intent); 41 | FreelineCore.updateDynamicTime(); 42 | ActivityManager.restartForegroundActivity(); 43 | } else if (type == ACTION_RESTART_APPLICATION) { 44 | saveDynamicResInfo(intent); 45 | applyDynamicDex(intent); 46 | FreelineCore.updateDynamicTime(); 47 | ActivityManager.restart(FreelineCore.getApplication(), true); 48 | } 49 | } 50 | } 51 | 52 | private void saveDynamicResInfo(Intent intent) { 53 | String key = intent.getStringExtra(SP_KEY); 54 | String value = intent.getStringExtra(SP_VALUE); 55 | if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value)) { 56 | HashMap res = new HashMap(); 57 | Log.d(TAG, "destPath :" + value); 58 | res.put(key, value); 59 | FreelineCore.saveDynamicResInfo(res); 60 | } 61 | } 62 | 63 | private void applyDynamicDex(Intent intent) { 64 | String dex = intent.getStringExtra(DEX_VALUE); 65 | String opt = intent.getStringExtra(OPT_VALUE); 66 | if (!TextUtils.isEmpty(dex) && !TextUtils.isEmpty(opt)) { 67 | FreelineCore.applyDynamicDex(dex, opt); 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/FreelineService.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline; 2 | 3 | import android.app.AlarmManager; 4 | import android.app.Notification; 5 | import android.app.PendingIntent; 6 | import android.app.Service; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.os.Build; 10 | import android.os.IBinder; 11 | import android.text.TextUtils; 12 | import android.util.Log; 13 | 14 | import com.antfortune.freeline.router.Router; 15 | import com.antfortune.freeline.server.LongLinkServer; 16 | 17 | import java.util.concurrent.TimeUnit; 18 | 19 | /** 20 | * Created by huangyong on 16/7/31. 21 | */ 22 | public class FreelineService extends Service { 23 | 24 | private static final String LOG_TAG = "Freeline.Service"; 25 | private static final int SERVICE_NOTIFICATION_ID = 8861; 26 | private static final String ACTION_KEEP_LIVE = ".Notification_RTC_WAKEUP_PUSH"; 27 | 28 | private AlarmManager am = null; 29 | private PendingIntent mCheckSender = null; 30 | 31 | @Override 32 | public void onCreate() { 33 | super.onCreate(); 34 | this.am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); 35 | } 36 | 37 | @Override 38 | public int onStartCommand(Intent intent, int flags, int startId) { 39 | Log.i(LOG_TAG, "onStartCommand Received start id " + startId + ", intent: " + intent); 40 | LongLinkServer.start(this.getApplication(), Router.getInstance()); 41 | 42 | String marker = intent == null ? null : intent.getStringExtra("wakeup"); 43 | if (TextUtils.isEmpty(marker)) { 44 | try { 45 | setForegroundService(); 46 | } catch (Exception e) { 47 | Log.e(LOG_TAG, "setForegroundService fail", e); 48 | } 49 | } 50 | return START_STICKY; 51 | } 52 | 53 | @Override 54 | public void onDestroy() { 55 | super.onDestroy(); 56 | try { 57 | stopForeground(true); 58 | startAlarmTimer(TimeUnit.SECONDS.toMillis(5)); 59 | } catch (Exception e) { 60 | Log.e(LOG_TAG, "stopForeground fail", e); 61 | } 62 | } 63 | 64 | @Override 65 | public IBinder onBind(Intent intent) { 66 | return null; 67 | } 68 | 69 | protected void startAlarmTimer(long nextTime) { 70 | Log.i(LOG_TAG, "startAlarmTimer ELAPSED_REALTIME_WAKEUP! nextTime=" + nextTime); 71 | 72 | Intent intent = new Intent(); 73 | intent.setAction(this.getPackageName() + ACTION_KEEP_LIVE); 74 | this.mCheckSender = PendingIntent.getBroadcast(this, 100, intent, 0); 75 | 76 | try { 77 | if (am != null && mCheckSender != null) { 78 | am.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + nextTime, mCheckSender); 79 | } 80 | } catch (Exception e) { 81 | Log.e(LOG_TAG, "startAlarmTimer fail", e); 82 | } 83 | } 84 | 85 | private void setForegroundService() { 86 | if (Build.VERSION.SDK_INT < 18) { 87 | startForeground(SERVICE_NOTIFICATION_ID, new Notification()); 88 | } else { 89 | startForeground(SERVICE_NOTIFICATION_ID, new Notification()); 90 | 91 | Intent innerIntent = new Intent(this, InnerService.class); 92 | startService(innerIntent); 93 | } 94 | } 95 | 96 | public static class InnerService extends Service { 97 | private static final String LOG_TAG_INNER = LOG_TAG + "$Inner"; 98 | 99 | @Override 100 | public IBinder onBind(Intent intent) { 101 | return null; 102 | } 103 | 104 | @Override 105 | public int onStartCommand(Intent intent, int flags, int startId) { 106 | Log.i(LOG_TAG_INNER, "onStartCommand Received start id " + startId + ", intent: " + intent); 107 | try{ 108 | startForeground(SERVICE_NOTIFICATION_ID, new Notification()); 109 | stopForeground(true); 110 | stopSelf(); 111 | } catch (Exception e) { 112 | Log.e(LOG_TAG_INNER, "startForeground, stopForeground, stopSelf fail", e); 113 | } 114 | return super.onStartCommand(intent, flags, startId); 115 | } 116 | 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/IDynamic.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * Created by xianying on 16/3/16. 7 | */ 8 | public interface IDynamic { 9 | /*** 10 | * packagid + newResPath 11 | * @param dynamicRes 12 | */ 13 | boolean applyDynamicRes(HashMap dynamicRes); 14 | 15 | String getOriginResPath(String packageId); 16 | 17 | void clearResourcesCache(); 18 | } 19 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/MiddlewareActivity.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline; 2 | 3 | import android.app.Activity; 4 | import android.app.AlarmManager; 5 | import android.app.PendingIntent; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.os.Bundle; 9 | import android.os.Handler; 10 | import android.os.Looper; 11 | import android.os.Process; 12 | import android.os.SystemClock; 13 | import android.util.Log; 14 | import android.widget.TextView; 15 | 16 | public class MiddlewareActivity extends Activity { 17 | private static final String TAG = "Freeline.MiddlewareAct"; 18 | private static final Handler HANDLER = new Handler(Looper.getMainLooper()); 19 | private static final long RESET_WAIT = 1000L; 20 | private static final int DELAY_TIME = 200; 21 | private long createTime; 22 | private boolean ready; 23 | private int back; 24 | private final Runnable reset = new Runnable() { 25 | public void run() { 26 | 27 | //通过定时器启动提高重新启动应用的成功率 28 | Context context = MiddlewareActivity.this; 29 | 30 | Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName()); 31 | intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); 32 | 33 | PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); 34 | 35 | AlarmManager mgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE); 36 | mgr.set(AlarmManager.RTC, System.currentTimeMillis() + DELAY_TIME, pendingIntent); 37 | 38 | Log.d(TAG, "kill process: " + Process.myPid()); 39 | Process.killProcess(Process.myPid()); 40 | } 41 | 42 | }; 43 | 44 | protected void onCreate(Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | TextView tv = new TextView(this); 47 | tv.setGravity(17); 48 | tv.setText("building increment app..."); 49 | setContentView(tv); 50 | this.createTime = SystemClock.uptimeMillis(); 51 | 52 | this.ready = getIntent().getBooleanExtra("reset", false); 53 | if (this.ready) { 54 | reset(); 55 | } 56 | } 57 | 58 | 59 | protected void onDestroy() { 60 | HANDLER.removeCallbacks(this.reset); 61 | super.onDestroy(); 62 | } 63 | 64 | 65 | public void reset() { 66 | this.ready = true; 67 | HANDLER.removeCallbacks(this.reset); 68 | long d = SystemClock.uptimeMillis() - this.createTime; 69 | if (d > RESET_WAIT) { 70 | HANDLER.postDelayed(this.reset, 100L); 71 | } else { 72 | HANDLER.postDelayed(this.reset, RESET_WAIT - d); 73 | } 74 | } 75 | 76 | 77 | public void onBackPressed() { 78 | if (this.back++ > 0) { 79 | if (this.ready) { 80 | this.reset.run(); 81 | } 82 | super.onBackPressed(); 83 | } 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/gradle/GradleDynamic.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.gradle; 2 | 3 | import android.app.Application; 4 | import android.text.TextUtils; 5 | import android.util.Log; 6 | 7 | import com.antfortune.freeline.resources.MonkeyPatcher; 8 | import com.antfortune.freeline.FreelineCore; 9 | import com.antfortune.freeline.IDynamic; 10 | import com.antfortune.freeline.util.ActivityManager; 11 | 12 | import java.io.File; 13 | import java.util.Arrays; 14 | import java.util.HashMap; 15 | 16 | public class GradleDynamic implements IDynamic { 17 | 18 | private static final String TAG = "Freeline.GradleDynamic"; 19 | 20 | private Application app; 21 | 22 | public GradleDynamic(Application context) { 23 | this.app = context; 24 | } 25 | 26 | @Override 27 | public boolean applyDynamicRes(HashMap dynamicRes) { 28 | String dynamicResPath = dynamicRes.get(FreelineCore.DEFAULT_PACKAGE_ID); 29 | Log.i(TAG, "dynamicResPath: " + dynamicResPath); 30 | if (!TextUtils.isEmpty(dynamicResPath)) { 31 | Application realApplication = FreelineCore.getRealApplication(); 32 | MonkeyPatcher.monkeyPatchApplication(app, app, realApplication, dynamicResPath); 33 | MonkeyPatcher.monkeyPatchExistingResources(app, dynamicResPath, Arrays.asList(ActivityManager.getAllActivities())); 34 | Log.i(TAG, "GradleDynamic apply dynamic resource successfully"); 35 | } 36 | return true; 37 | } 38 | 39 | @Override 40 | public String getOriginResPath(String packageId) { 41 | File baseResFile = new File(FreelineCore.getDynamicInfoTempDir(), "full-res-pack.so"); 42 | return baseResFile.getAbsolutePath(); 43 | } 44 | 45 | @Override 46 | public void clearResourcesCache() { 47 | 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/router/ISchemaAction.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.router; 2 | 3 | import com.antfortune.freeline.server.EmbedHttpServer; 4 | 5 | import java.io.InputStream; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public interface ISchemaAction { 10 | 11 | String DESCRIPTION = "description"; 12 | 13 | String getDescription(); 14 | 15 | void handle(String method, 16 | String path, 17 | HashMap headers, 18 | Map queries, 19 | InputStream input, 20 | EmbedHttpServer.ResponseOutputStream response) throws Exception; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/router/Router.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.router; 2 | 3 | import com.antfortune.freeline.server.EmbedHttpServer; 4 | 5 | import java.io.InputStream; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | public class Router { 10 | 11 | private static Router sInstance; 12 | 13 | private Map mSchemaMap = new HashMap<>(); 14 | 15 | private Router() { 16 | 17 | } 18 | 19 | public static Router getInstance() { 20 | synchronized (Router.class) { 21 | if (sInstance == null) { 22 | sInstance = new Router(); 23 | } 24 | return sInstance; 25 | } 26 | } 27 | 28 | public void registerSchema(ISchemaAction schemaAction) { 29 | if (schemaAction != null) { 30 | mSchemaMap.put(schemaAction.getDescription(), schemaAction); 31 | } 32 | } 33 | 34 | public boolean dispatch( 35 | String method, 36 | String path, 37 | HashMap headers, 38 | Map queries, 39 | InputStream input, 40 | EmbedHttpServer.ResponseOutputStream response) throws Exception { 41 | if (queries == null || queries.size() == 0) { 42 | return false; 43 | } 44 | 45 | String description = queries.get(ISchemaAction.DESCRIPTION); 46 | for (String name : mSchemaMap.keySet()) { 47 | if (name.equals(description)) { 48 | mSchemaMap.get(name).handle(method, path, headers, queries, input, response); 49 | return true; 50 | } 51 | } 52 | 53 | return false; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/router/schema/CheckResourceSchema.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.router.schema; 2 | 3 | import android.text.TextUtils; 4 | import android.util.Log; 5 | 6 | import com.antfortune.freeline.FreelineCore; 7 | import com.antfortune.freeline.router.ISchemaAction; 8 | import com.antfortune.freeline.server.EmbedHttpServer; 9 | 10 | import java.io.File; 11 | import java.io.InputStream; 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | /** 16 | * Created by huangyong on 16/7/28. 17 | */ 18 | public class CheckResourceSchema implements ISchemaAction { 19 | 20 | private static final String TAG = "Freeline.CheckResource"; 21 | 22 | @Override 23 | public String getDescription() { 24 | return "checkResource"; 25 | } 26 | 27 | @Override 28 | public void handle(String method, String path, HashMap headers, Map queries, InputStream input, EmbedHttpServer.ResponseOutputStream response) throws Exception { 29 | String dst = FreelineCore.getBundleFilePathByPackageId("base-res"); 30 | if (TextUtils.isEmpty(dst)) { 31 | response.write("get base res path error.".getBytes("utf-8")); 32 | Log.e(TAG, "base resource path not found"); 33 | response.setStatusCode(500); 34 | } else { 35 | File dstFile = new File(dst); 36 | String result = dstFile.exists() ? "1" : "0"; 37 | response.write(result.getBytes("utf-8")); 38 | response.setStatusCode(200); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/router/schema/CheckSyncSchema.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.router.schema; 2 | 3 | import android.text.TextUtils; 4 | import android.util.Log; 5 | 6 | import com.antfortune.freeline.FreelineCore; 7 | import com.antfortune.freeline.router.ISchemaAction; 8 | import com.antfortune.freeline.server.EmbedHttpServer; 9 | 10 | import java.io.InputStream; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | public class CheckSyncSchema implements ISchemaAction { 15 | 16 | private static final String TAG = "Freeline.CheckSync"; 17 | 18 | @Override 19 | public String getDescription() { 20 | return "checkSync"; 21 | } 22 | 23 | @Override 24 | public void handle(String method, String path, HashMap headers, Map queries, InputStream input, EmbedHttpServer.ResponseOutputStream response) throws Exception { 25 | long clientSync = Long.parseLong(queries.get("sync")); 26 | String uuid = queries.get("uuid"); 27 | 28 | long apkBuildFlag = FreelineCore.getApkBuildFlag(); 29 | long lastSync = FreelineCore.getLastDynamicSyncId() + apkBuildFlag; 30 | 31 | String devUuid = FreelineCore.getUuid(); 32 | Log.i(TAG, "devUuid: " + devUuid); 33 | 34 | int result = clientSync == lastSync ? 1 : 0; 35 | Log.i(TAG, "dev apkflag:" + apkBuildFlag + ", last sync is:" + lastSync + ", current sync is:" + clientSync); 36 | 37 | if (!TextUtils.isEmpty(uuid) && !uuid.equals(devUuid)) { 38 | result = -1; 39 | Log.i(TAG, "check uuid failed, skip check sync."); 40 | } 41 | 42 | response.setContentTypeText(); 43 | response.write(String.valueOf(result).getBytes("utf-8")); 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/router/schema/CloseLonglinkSchema.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.router.schema; 2 | 3 | import android.util.Log; 4 | 5 | import com.antfortune.freeline.FreelineCore; 6 | import com.antfortune.freeline.router.ISchemaAction; 7 | import com.antfortune.freeline.server.EmbedHttpServer; 8 | import com.antfortune.freeline.server.LongLinkServer; 9 | 10 | import java.io.InputStream; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | /** 15 | * Created by huangyong on 16/7/28. 16 | */ 17 | public class CloseLonglinkSchema implements ISchemaAction { 18 | 19 | private static final String TAG = "Freeline.CloseLongLink"; 20 | 21 | @Override 22 | public String getDescription() { 23 | return "closeLongLink"; 24 | } 25 | 26 | @Override 27 | public void handle(String method, String path, HashMap headers, Map queries, InputStream input, EmbedHttpServer.ResponseOutputStream response) throws Exception { 28 | String lastSync = queries.get("lastSync"); 29 | FreelineCore.saveLastDynamicSyncId(Long.parseLong(lastSync)); 30 | Log.i(TAG, "save last sync value: " + lastSync); 31 | boolean forceRestart = queries.containsKey("restart"); 32 | if (forceRestart) { 33 | Log.i(TAG, "find restart marker, appliacation will restart."); 34 | } 35 | if (LongLinkServer.isDexChanged() 36 | || LongLinkServer.isResourcesChanged() 37 | || LongLinkServer.isNativeChanged()) { 38 | if (LongLinkServer.isDexChanged() 39 | || LongLinkServer.isNativeChanged() 40 | || forceRestart) { 41 | if (LongLinkServer.isDexChanged()) { 42 | Log.i(TAG, "with dex changes, need to restart the process (activity stack will be reserved)"); 43 | } else if (LongLinkServer.isNativeChanged()) { 44 | Log.i(TAG, "with .so files changed, need to restart the process (activity stack will be reserved)"); 45 | } 46 | FreelineCore.restartApplication(LongLinkServer.getBundleName(), LongLinkServer.getDstPath(), LongLinkServer.getDynamicDexPath(), LongLinkServer.getOptDirPath()); 47 | LongLinkServer.resetDexChangedFlag(); 48 | LongLinkServer.resetResourcesChangedFlag(); 49 | LongLinkServer.resetNativeChangedFlag(); 50 | } else if (LongLinkServer.isResourcesChanged()) { 51 | FreelineCore.clearResourcesCache(); 52 | FreelineCore.updateActivity(LongLinkServer.getBundleName(), LongLinkServer.getDstPath()); 53 | LongLinkServer.resetResourcesChangedFlag(); 54 | Log.i(TAG, "with only res changes, just recreate the running activity."); 55 | } 56 | response.setStatusCode(200); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/router/schema/GetSyncTicketSchema.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.router.schema; 2 | 3 | import android.text.TextUtils; 4 | import android.util.Log; 5 | 6 | import com.antfortune.freeline.FreelineCore; 7 | import com.antfortune.freeline.router.ISchemaAction; 8 | import com.antfortune.freeline.server.EmbedHttpServer; 9 | 10 | import java.io.InputStream; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | /** 15 | * Created by hejie on 17/4/17 16 | */ 17 | public class GetSyncTicketSchema implements ISchemaAction { 18 | 19 | private static final String TAG = "Freeline.GetSyncTicket"; 20 | 21 | @Override 22 | public String getDescription() { 23 | return "getSyncTicket"; 24 | } 25 | 26 | @Override 27 | public void handle(String method, String path, HashMap headers, Map queries, InputStream input, EmbedHttpServer.ResponseOutputStream response) throws Exception { 28 | long apkBuildFlag = FreelineCore.getApkBuildFlag(); 29 | long lastSync = FreelineCore.getLastDynamicSyncId() + apkBuildFlag; 30 | Log.i(TAG, "apkBuildFlag: " + apkBuildFlag); 31 | Log.i(TAG, "lastSync: " + lastSync); 32 | 33 | String result = "{'apkBuildFlag':"+apkBuildFlag+",'lastSync':"+lastSync+"}"; 34 | 35 | response.setContentTypeText(); 36 | response.write(result.getBytes("utf-8")); 37 | } 38 | 39 | 40 | } 41 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/router/schema/PushDexSchema.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.router.schema; 2 | 3 | import android.util.Log; 4 | 5 | import com.antfortune.freeline.FreelineCore; 6 | import com.antfortune.freeline.router.ISchemaAction; 7 | import com.antfortune.freeline.server.EmbedHttpServer; 8 | import com.antfortune.freeline.server.LongLinkServer; 9 | 10 | import java.io.File; 11 | import java.io.FileOutputStream; 12 | import java.io.InputStream; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * Created by huangyong on 16/7/28. 18 | */ 19 | public class PushDexSchema implements ISchemaAction { 20 | 21 | private static final String TAG = "Freeline.PushDex"; 22 | 23 | @Override 24 | public String getDescription() { 25 | return "pushDex"; 26 | } 27 | 28 | @Override 29 | public void handle(String method, String path, HashMap headers, Map queries, InputStream input, EmbedHttpServer.ResponseOutputStream response) throws Exception { 30 | String dexName = queries.get("dexName"); 31 | File dexDir = new File(FreelineCore.getDynamicDexDir()); 32 | File file = new File(dexDir, dexName + ".pending"); 33 | File dir = new File(FreelineCore.getDynamicInfoTempDir()); 34 | File optDir = new File(dir, "opt"); 35 | if (!optDir.exists()) { 36 | optDir.mkdirs(); 37 | } 38 | String vmVersion = System.getProperty("java.vm.version"); 39 | File finalFile = null; 40 | if (vmVersion != null && vmVersion.startsWith("2")) { 41 | finalFile = new File(dexDir, dexName + ".apk"); 42 | } else { 43 | finalFile = new File(dexDir, dexName + ".dex"); 44 | } 45 | FileOutputStream fos = new FileOutputStream(file); 46 | byte[] buf = new byte[4096]; 47 | int l; 48 | while ((l = input.read(buf)) != -1) { 49 | fos.write(buf, 0, l); 50 | } 51 | fos.close(); 52 | LongLinkServer.markDexChanged(); 53 | response.setStatusCode(201); 54 | boolean rst = file.renameTo(finalFile); 55 | //FreelineCore.applyDynamicDex(finalFile.getAbsolutePath(), optDir.getAbsolutePath()); 56 | LongLinkServer.setDynamicDexPath(dexDir.getAbsolutePath()); 57 | LongLinkServer.setOptDirPath(optDir.getAbsolutePath()); 58 | FreelineCore.applyDynamicDex(dexDir.getAbsolutePath(), optDir.getAbsolutePath()); 59 | Log.d(TAG, "dex file received (" + finalFile.length() + " bytes), rename result :" + rst + ", save to " + finalFile.getAbsolutePath()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/router/schema/PushFullResourcePackSchema.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.router.schema; 2 | 3 | import android.util.Log; 4 | 5 | import com.antfortune.freeline.FreelineCore; 6 | import com.antfortune.freeline.router.ISchemaAction; 7 | import com.antfortune.freeline.server.EmbedHttpServer; 8 | import com.antfortune.freeline.util.FileUtils; 9 | 10 | import java.io.File; 11 | import java.io.FileOutputStream; 12 | import java.io.InputStream; 13 | import java.util.HashMap; 14 | import java.util.Map; 15 | 16 | /** 17 | * Created by huangyong on 16/7/28. 18 | */ 19 | public class PushFullResourcePackSchema implements ISchemaAction { 20 | 21 | private static final String TAG = "Freeline.PushFullRes"; 22 | 23 | @Override 24 | public String getDescription() { 25 | return "pushFullResourcePack"; 26 | } 27 | 28 | @Override 29 | public void handle(String method, String path, HashMap headers, Map queries, InputStream input, EmbedHttpServer.ResponseOutputStream response) throws Exception { 30 | Log.i(TAG, "receive full res pack: " + path); 31 | String dst = FreelineCore.getBundleFilePathByPackageId("base-res"); 32 | File dstFile = new File(dst); 33 | Log.i(TAG, "dst path: " + dstFile.getAbsolutePath()); 34 | File pending = new File(dst + ".bak"); 35 | try { 36 | if (!pending.exists()) { 37 | pending.createNewFile(); 38 | } 39 | FileOutputStream fos = new FileOutputStream(pending); 40 | byte[] buf = new byte[4096]; 41 | int l; 42 | while ((l = input.read(buf)) != -1) { 43 | fos.write(buf, 0, l); 44 | } 45 | fos.close(); 46 | } catch (Exception e) { 47 | Log.e(TAG, "read full resource failed"); 48 | Log.d(TAG, e.getStackTrace().toString()); 49 | response.setStatusCode(500); 50 | return; 51 | } 52 | if (dstFile.exists()) { 53 | FileUtils.rm(dstFile); 54 | } 55 | pending.renameTo(dstFile); 56 | Log.i(TAG, "receive full res pack successfully"); 57 | response.setStatusCode(201); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/router/schema/PushNativeSchema.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.router.schema; 2 | 3 | import android.util.Log; 4 | 5 | import com.antfortune.freeline.FreelineCore; 6 | import com.antfortune.freeline.router.ISchemaAction; 7 | import com.antfortune.freeline.server.EmbedHttpServer; 8 | import com.antfortune.freeline.server.LongLinkServer; 9 | 10 | import java.io.BufferedInputStream; 11 | import java.io.ByteArrayOutputStream; 12 | import java.io.File; 13 | import java.io.FileOutputStream; 14 | import java.io.InputStream; 15 | import java.util.HashMap; 16 | import java.util.Map; 17 | import java.util.zip.ZipEntry; 18 | import java.util.zip.ZipInputStream; 19 | 20 | /** 21 | * Created by huangyong on 16/7/28. 22 | */ 23 | public class PushNativeSchema implements ISchemaAction { 24 | 25 | private static final String TAG = "Freeline.pushNative"; 26 | 27 | @Override 28 | public String getDescription() { 29 | return "pushNative"; 30 | } 31 | 32 | @Override 33 | public void handle(String method, String path, HashMap headers, Map queries, InputStream input, EmbedHttpServer.ResponseOutputStream response) throws Exception { 34 | Log.i(TAG, "method " + method + ", " + path + headers + queries); 35 | ZipInputStream zis = new ZipInputStream(new BufferedInputStream(input)); 36 | try { 37 | ZipEntry ze; 38 | while ((ze = zis.getNextEntry()) != null) { 39 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 40 | byte[] buffer = new byte[1024]; 41 | int count; 42 | while ((count = zis.read(buffer)) != -1) { 43 | baos.write(buffer, 0, count); 44 | } 45 | String filename = ze.getName().substring(ze.getName().lastIndexOf(File.separator) + 1); 46 | byte[] bytes = baos.toByteArray(); 47 | File dir = new File(FreelineCore.getDynamicNativeDir()); 48 | File file = new File(dir, filename); 49 | if (file.exists()) file.delete(); 50 | file.createNewFile(); 51 | FileOutputStream fileOutputStream = new FileOutputStream(file); 52 | fileOutputStream.write(bytes); 53 | fileOutputStream.close(); 54 | Log.d(TAG, new StringBuilder().append("native file ").append(filename).append("received (").append(file.length()).append(" bytes)").toString()); 55 | } 56 | } catch (Exception ex) { 57 | ex.printStackTrace(); 58 | } finally { 59 | zis.close(); 60 | } 61 | LongLinkServer.markNativeChanged(); 62 | response.setStatusCode(201); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/router/schema/RestartSchema.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.router.schema; 2 | 3 | import com.antfortune.freeline.FreelineCore; 4 | import com.antfortune.freeline.router.ISchemaAction; 5 | import com.antfortune.freeline.server.EmbedHttpServer; 6 | 7 | import java.io.InputStream; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * Created by huangyong on 16/7/28. 13 | */ 14 | public class RestartSchema implements ISchemaAction { 15 | 16 | @Override 17 | public String getDescription() { 18 | return "restart"; 19 | } 20 | 21 | @Override 22 | public void handle(String method, String path, HashMap headers, Map queries, InputStream input, EmbedHttpServer.ResponseOutputStream response) throws Exception { 23 | FreelineCore.restartApplication(null, null, null, null); 24 | response.setStatusCode(200); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/util/AppUtils.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.util; 2 | 3 | import android.app.ActivityManager; 4 | import android.content.Context; 5 | import android.content.pm.ApplicationInfo; 6 | import android.text.TextUtils; 7 | import android.util.Log; 8 | 9 | import java.lang.reflect.InvocationTargetException; 10 | import java.lang.reflect.Method; 11 | import java.util.List; 12 | 13 | /** 14 | * Created by huangyong on 16/7/30. 15 | */ 16 | public class AppUtils { 17 | 18 | private static final String TAG = "Freeline.AppUtils"; 19 | 20 | public static boolean isApkDebugable(Context context) { 21 | try { 22 | ApplicationInfo info = context.getApplicationInfo(); 23 | return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; 24 | } catch (Exception e) { 25 | 26 | } 27 | return false; 28 | } 29 | 30 | public static String getCurProcessName(Context context) { 31 | String strRet = null; 32 | try { 33 | Class clazz = Class.forName("android.ddm.DdmHandleAppName"); 34 | Method method = clazz.getDeclaredMethod("getAppName"); 35 | strRet = (String) method.invoke(clazz); 36 | } catch (Exception e) { 37 | Log.w(TAG, e); 38 | } 39 | if (TextUtils.isEmpty(strRet)) { 40 | final int pid = android.os.Process.myPid(); 41 | android.app.ActivityManager activityManager = (android.app.ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 42 | final List runningAppProcesses = activityManager.getRunningAppProcesses(); 43 | for (ActivityManager.RunningAppProcessInfo appProcess : runningAppProcesses) { 44 | if (appProcess.pid == pid) { 45 | strRet = appProcess.processName; 46 | break; 47 | } 48 | } 49 | } 50 | return strRet; 51 | } 52 | 53 | public static boolean isMainProcess(Context context) { 54 | String packageName = context.getPackageName(); 55 | String processName = getCurProcessName(context); 56 | return packageName.equalsIgnoreCase(processName); 57 | } 58 | 59 | public static boolean isFreelineProcess(Context context) { 60 | String processName = getCurProcessName(context); 61 | return processName.endsWith(":freeline"); 62 | } 63 | 64 | public static String findJniLibrary(Context context, String libName) { 65 | String result = null; 66 | ClassLoader classLoader = (context.getClassLoader()); 67 | if (classLoader != null) { 68 | try { 69 | Method findLibraryMethod = classLoader.getClass().getMethod("findLibrary", new Class[] { String.class }); 70 | if (findLibraryMethod != null) { 71 | Object objPath = findLibraryMethod.invoke(classLoader, new Object[] { libName }); 72 | if (objPath != null && objPath instanceof String) { 73 | result = (String) objPath; 74 | } 75 | } 76 | } catch (Exception e) { 77 | Log.e(TAG, e.toString()); 78 | } 79 | } 80 | 81 | return result; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/util/DexUtils.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.util; 2 | 3 | import android.os.Build.VERSION; 4 | import android.util.Log; 5 | 6 | import dalvik.system.DexClassLoader; 7 | import dalvik.system.DexFile; 8 | import dalvik.system.PathClassLoader; 9 | 10 | import java.io.File; 11 | import java.lang.reflect.Array; 12 | import java.lang.reflect.Field; 13 | 14 | public class DexUtils { 15 | 16 | private static final String TAG = "Freeline.hackDex"; 17 | 18 | public static boolean inject(PathClassLoader classLoader, File dex, File opt) { 19 | Log.i(TAG, dex.getAbsolutePath() + " dex length: " + dex.length()); 20 | Log.i(TAG, opt.getAbsolutePath() + " opt length: " + opt.length()); 21 | 22 | DexFile[] dexFiles = null; 23 | Field pathListField = null; 24 | Field fDexElements = null; 25 | Object dstObject = null; 26 | 27 | try { 28 | Object newDexElements; 29 | int dexLength; 30 | if (VERSION.SDK_INT >= 14) { 31 | pathListField = ReflectUtil.fieldGetOrg(classLoader, Class.forName("dalvik.system.BaseDexClassLoader"), "pathList"); 32 | fDexElements = ReflectUtil.fieldGetOrg(pathListField.get(classLoader), "dexElements"); 33 | Object e = fDexElements.get(pathListField.get(classLoader)); 34 | dstObject = e; 35 | dexFiles = new DexFile[Array.getLength(e)]; 36 | for (int i = 0; i < Array.getLength(e); ++i) { 37 | newDexElements = Array.get(e, i); 38 | dexFiles[i] = (DexFile) ReflectUtil.fieldGet(newDexElements, "dexFile"); 39 | } 40 | } else { 41 | pathListField = ReflectUtil.fieldGetOrg(classLoader, "mDexs"); 42 | dstObject = pathListField.get(classLoader); 43 | dexFiles = new DexFile[Array.getLength(dstObject)]; 44 | for (dexLength = 0; dexLength < Array.getLength(dstObject); ++dexLength) { 45 | dexFiles[dexLength] = (DexFile) Array.get(dstObject, dexLength); 46 | } 47 | } 48 | dexLength = Array.getLength(dstObject) + 1; 49 | newDexElements = Array.newInstance(fDexElements.getType().getComponentType(), dexLength); 50 | 51 | DexClassLoader dynamicDex = new DexClassLoader(dex.getAbsolutePath(), opt.getAbsolutePath(), null, classLoader.getParent()); 52 | Log.i(TAG, "after opt, dex len:" + dex.length() + "; opt len:" + opt.length()); 53 | Object pathList = pathListField.get(dynamicDex); 54 | Object dexElements = fDexElements.get(pathList); 55 | Object firstDexElement = Array.get(dexElements, 0); 56 | Array.set(newDexElements, 0, firstDexElement); 57 | 58 | for (int i = 0; i < dexLength - 1; ++i) { 59 | Object element = Array.get(dstObject, i); 60 | Array.set(newDexElements, i + 1, element); 61 | } 62 | 63 | if (VERSION.SDK_INT >= 14) { 64 | fDexElements.set(pathListField.get(classLoader), newDexElements); 65 | } else { 66 | pathListField.set(classLoader, newDexElements); 67 | } 68 | return true; 69 | } catch (Exception e) { 70 | Log.e(TAG, "fail to override classloader " + classLoader + " with " + dex.getAbsolutePath(), e); 71 | return false; 72 | } 73 | } 74 | 75 | 76 | } 77 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/java/com/antfortune/freeline/util/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.util; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.File; 5 | import java.io.FileInputStream; 6 | import java.io.FileNotFoundException; 7 | import java.io.FileOutputStream; 8 | import java.io.IOException; 9 | import java.util.zip.ZipEntry; 10 | import java.util.zip.ZipInputStream; 11 | 12 | /** 13 | * Created by huangyong on 16/7/30. 14 | */ 15 | public class FileUtils { 16 | 17 | public static void rm(File f) { 18 | if (f.isDirectory()) { 19 | for (File ff : f.listFiles()) { 20 | rm(ff); 21 | } 22 | f.delete(); 23 | } else if (f.isFile()) { 24 | f.delete(); 25 | } 26 | } 27 | 28 | public static void unzip(File zipFile, File targetDirectory) throws IOException { 29 | ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(zipFile))); 30 | try { 31 | ZipEntry ze; 32 | int count; 33 | byte[] buffer = new byte[8192]; 34 | while ((ze = zis.getNextEntry()) != null) { 35 | File file = new File(targetDirectory, ze.getName()); 36 | File dir = ze.isDirectory() ? file : file.getParentFile(); 37 | if (!dir.isDirectory() && !dir.mkdirs()) 38 | throw new FileNotFoundException("Failed to ensure directory: " + dir.getAbsolutePath()); 39 | if (ze.isDirectory()) 40 | continue; 41 | FileOutputStream fout = new FileOutputStream(file); 42 | try { 43 | while ((count = zis.read(buffer)) != -1) 44 | fout.write(buffer, 0, count); 45 | } finally { 46 | fout.close(); 47 | } 48 | } 49 | } finally { 50 | zis.close(); 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /freeline-runtime/src/main/res/values/values.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #fff 5 | #fff 6 | #fff 7 | #fff 8 | #fff 9 | #fff 10 | -------------------------------------------------------------------------------- /freeline-runtime/src/test/java/com/antfortune/android/freeline/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.android.freeline; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } -------------------------------------------------------------------------------- /freeline-sample/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .idea 10 | freeline/ 11 | freeline_core/ 12 | freeline.py 13 | -------------------------------------------------------------------------------- /freeline-sample/PureJavaLib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /freeline-sample/PureJavaLib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | targetCompatibility = '1.7' 4 | sourceCompatibility = '1.7' 5 | 6 | dependencies { 7 | implementation fileTree(dir: 'libs', include: ['*.jar']) 8 | } -------------------------------------------------------------------------------- /freeline-sample/PureJavaLib/src/main/java/com/antfortune/freeline/sample/pure_java/JavaTools.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.sample.pure_java; 2 | 3 | public class JavaTools { 4 | } 5 | -------------------------------------------------------------------------------- /freeline-sample/ThirdParty/payment/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /freeline-sample/ThirdParty/payment/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | buildToolsVersion rootProject.ext.buildToolsVersion 6 | 7 | defaultConfig { 8 | minSdkVersion rootProject.ext.minSdkVersion 9 | targetSdkVersion rootProject.ext.targetSdkVersion 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation deps.appcompatV7 24 | } 25 | -------------------------------------------------------------------------------- /freeline-sample/ThirdParty/payment/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/huangyong/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /freeline-sample/ThirdParty/payment/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /freeline-sample/ThirdParty/payment/src/main/java/com/antfortune/freeline/sample/payment/PaymentUtils.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.sample.payment; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | 6 | /** 7 | * Created by huangyong on 16/8/14. 8 | */ 9 | public class PaymentUtils { 10 | 11 | public static void showMessage(Context context) { 12 | Toast.makeText(context, "toast from payment library again", Toast.LENGTH_SHORT).show(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /freeline-sample/ThirdParty/payment/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Payment 3 | 4 | -------------------------------------------------------------------------------- /freeline-sample/ThirdParty/share/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /freeline-sample/ThirdParty/share/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | buildToolsVersion rootProject.ext.buildToolsVersion 6 | 7 | defaultConfig { 8 | minSdkVersion rootProject.ext.minSdkVersion 9 | targetSdkVersion rootProject.ext.targetSdkVersion 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation deps.appcompatV7 24 | } 25 | -------------------------------------------------------------------------------- /freeline-sample/ThirdParty/share/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/huangyong/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /freeline-sample/ThirdParty/share/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /freeline-sample/ThirdParty/share/src/main/res/drawable-hdpi/ic_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-sample/ThirdParty/share/src/main/res/drawable-hdpi/ic_settings.png -------------------------------------------------------------------------------- /freeline-sample/ThirdParty/share/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Share 3 | 4 | -------------------------------------------------------------------------------- /freeline-sample/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /freeline-sample/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.antfortune.freeline' 3 | 4 | freeline { 5 | hack true 6 | productFlavor "develop" 7 | autoDependency false 8 | } 9 | 10 | android { 11 | compileSdkVersion rootProject.ext.compileSdkVersion 12 | buildToolsVersion rootProject.ext.buildToolsVersion 13 | 14 | defaultConfig { 15 | applicationId "com.antfortune.freeline.sample" 16 | minSdkVersion rootProject.ext.minSdkVersion 17 | targetSdkVersion rootProject.ext.targetSdkVersion 18 | versionCode 1 19 | versionName "1.0" 20 | multiDexEnabled true 21 | manifestPlaceholders = [ 22 | SUPPORT_RTL: false 23 | ] 24 | } 25 | 26 | buildTypes { 27 | debug { 28 | applicationIdSuffix ".dev" 29 | applicationVariants.all { variant -> 30 | variant.outputs.all { 31 | outputFileName = "${variant.name}-${variant.versionName}.apk" 32 | } 33 | } 34 | } 35 | release { 36 | minifyEnabled false 37 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 38 | } 39 | monkey { 40 | matchingFallbacks = ['debug', 'release'] 41 | } 42 | } 43 | 44 | flavorDimensions "env" 45 | productFlavors { 46 | normal { 47 | } 48 | 49 | develop { 50 | } 51 | } 52 | 53 | useLibrary 'org.apache.http.legacy' 54 | 55 | compileOptions { 56 | sourceCompatibility JavaVersion.VERSION_1_8 57 | targetCompatibility JavaVersion.VERSION_1_8 58 | } 59 | } 60 | 61 | 62 | dependencies { 63 | // testCompile 'junit:junit:4.12' 64 | implementation deps.appcompatV7 65 | 66 | implementation project(":freeline-sample:common") 67 | implementation project(":freeline-sample:PureJavaLib") 68 | implementation project(":freeline-sample:ThirdParty:share") 69 | implementation project(":freeline-sample:ThirdParty:payment") 70 | 71 | releaseImplementation deps.freelineRuntimeNoOp 72 | debugImplementation deps.freelineRuntime 73 | 74 | implementation 'com.jakewharton:butterknife:8.4.0' 75 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.4.0' 76 | 77 | implementation "org.androidannotations:androidannotations-api:4.1.0" 78 | annotationProcessor "org.androidannotations:androidannotations:4.1.0" 79 | } 80 | -------------------------------------------------------------------------------- /freeline-sample/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/huangyong/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /freeline-sample/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /freeline-sample/app/src/main/java/com/antfortune/freeline/sample/App.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.sample; 2 | 3 | import android.app.Application; 4 | 5 | import com.antfortune.freeline.FreelineCore; 6 | 7 | /** 8 | * Created by huangyong on 16/8/5. 9 | */ 10 | public class App extends Application { 11 | 12 | @Override 13 | public void onCreate() { 14 | super.onCreate(); 15 | FreelineCore.init(this); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /freeline-sample/app/src/main/java/com/antfortune/freeline/sample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.sample; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.widget.TextView; 6 | import android.widget.Toast; 7 | import com.antfortune.freeline.sample.payment.PaymentUtils; 8 | 9 | public class MainActivity extends AppCompatActivity { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | setContentView(R.layout.activity_main); 15 | PaymentUtils.showMessage(this); 16 | 17 | TextView textView = findViewById(R.id.text); 18 | 19 | // test lambda method 20 | textView.setOnClickListener(view -> 21 | Toast.makeText(MainActivity.this, "click from lambda", Toast.LENGTH_SHORT).show() 22 | ); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /freeline-sample/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /freeline-sample/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-sample/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /freeline-sample/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-sample/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /freeline-sample/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-sample/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /freeline-sample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-sample/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /freeline-sample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-sample/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /freeline-sample/app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /freeline-sample/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /freeline-sample/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /freeline-sample/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Freeline Sample 会被自动ignore的字符 3 | Freeline Release Content 4 | 5 | -------------------------------------------------------------------------------- /freeline-sample/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /freeline-sample/common/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /freeline-sample/common/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | buildToolsVersion rootProject.ext.buildToolsVersion 6 | 7 | defaultConfig { 8 | minSdkVersion rootProject.ext.minSdkVersion 9 | targetSdkVersion rootProject.ext.targetSdkVersion 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | api deps.appcompatV7 24 | api project(":freeline-sample:resources") 25 | } 26 | -------------------------------------------------------------------------------- /freeline-sample/common/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/huangyong/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /freeline-sample/common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /freeline-sample/common/src/main/java/com/antfortune/freeline/sample/common/CommonTools.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.sample.common; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | 6 | /** 7 | * Created by huangyong on 16/8/23. 8 | */ 9 | public class CommonTools { 10 | 11 | public static void toast(Context context) { 12 | Toast.makeText(context, R.string.resources_library_string, Toast.LENGTH_SHORT).show(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /freeline-sample/common/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Common 3 | 4 | -------------------------------------------------------------------------------- /freeline-sample/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-sample/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /freeline-sample/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Aug 31 15:33:14 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /freeline-sample/resources/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /freeline-sample/resources/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion rootProject.ext.compileSdkVersion 5 | buildToolsVersion rootProject.ext.buildToolsVersion 6 | 7 | defaultConfig { 8 | minSdkVersion rootProject.ext.minSdkVersion 9 | targetSdkVersion rootProject.ext.targetSdkVersion 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | sourceSets { 20 | main.res.srcDirs = ['src/main/res', 'src/main/res_emoji'] 21 | } 22 | } 23 | 24 | dependencies { 25 | implementation fileTree(dir: 'libs', include: ['*.jar']) 26 | implementation deps.appcompatV7 27 | } 28 | -------------------------------------------------------------------------------- /freeline-sample/resources/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/huangyong/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /freeline-sample/resources/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /freeline-sample/resources/src/main/assets/resource_assets_file: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-sample/resources/src/main/assets/resource_assets_file -------------------------------------------------------------------------------- /freeline-sample/resources/src/main/java/com/antfortune/freeline/sample/resources/ResourcesTools.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.sample.resources; 2 | 3 | /** 4 | * Created by huangyong on 16/8/23. 5 | */ 6 | public class ResourcesTools { 7 | public static void test() { 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /freeline-sample/resources/src/main/res/drawable-hdpi/bg_test_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-sample/resources/src/main/res/drawable-hdpi/bg_test_1.png -------------------------------------------------------------------------------- /freeline-sample/resources/src/main/res/drawable-hdpi/bg_test_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-sample/resources/src/main/res/drawable-hdpi/bg_test_2.png -------------------------------------------------------------------------------- /freeline-sample/resources/src/main/res/drawable-hdpi/bg_test_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-sample/resources/src/main/res/drawable-hdpi/bg_test_3.png -------------------------------------------------------------------------------- /freeline-sample/resources/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Resources 3 | resource library string 4 | 特殊的标签测试标签测试 5 | 6 | -------------------------------------------------------------------------------- /freeline-sample/resources/src/main/res_emoji/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | resource emoji 3 | 4 | -------------------------------------------------------------------------------- /freeline-studio-plugin/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .git 3 | *.iml 4 | out 5 | *.jar -------------------------------------------------------------------------------- /freeline-studio-plugin/README.md: -------------------------------------------------------------------------------- 1 | # Freeline Plugin 2 | #### Installation 3 | ![](http://qiniu.apkfuns.com/D4B34086-FCE2-4D6F-ADCE-7B03374ED9E9.png) 4 | #### Add Run Configurations 5 | ![](http://qiniu.apkfuns.com/3C37279F-230E-4508-BF98-63AE60B91393.png) 6 | #### Run Action 7 | ![](http://qiniu.apkfuns.com/54F6776E-B1A4-4693-8858-53E030DD9312.png) 8 | #### Check Update 9 | ![](http://qiniu.apkfuns.com/8A1FA6B7-83EE-4749-9C40-311DD0182638.png) 10 | 11 | 12 | ### Note 13 | - Intellij IDEA 16 won't support JDK 1.7 -------------------------------------------------------------------------------- /freeline-studio-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "org.jetbrains.intellij" version "0.2.4" 3 | } 4 | 5 | apply plugin: 'org.jetbrains.intellij' 6 | apply plugin: 'java' 7 | 8 | compileJava { 9 | sourceCompatibility = 1.6 10 | targetCompatibility = 1.6 11 | } 12 | 13 | intellij { 14 | version 'IC-2016.2.5' 15 | pluginName 'Freeline Plugin' 16 | plugins = ['android', 'gradle', 'Groovy', 'terminal'] 17 | 18 | // Uncomment to test against Android Studio 19 | // intellij.alternativeIdePath = '/Applications/Android Studio.app' 20 | } -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/actions/BaseAction.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.actionSystem.DataKeys; 6 | import com.intellij.openapi.application.ApplicationManager; 7 | import com.intellij.openapi.project.Project; 8 | import com.antfortune.freeline.idea.utils.DocumentUtil; 9 | 10 | import javax.swing.*; 11 | import java.io.File; 12 | 13 | /** 14 | * Created by pengwei on 16/9/11. 15 | */ 16 | public abstract class BaseAction extends AnAction { 17 | 18 | protected Project currentProject; 19 | protected File projectDir; 20 | protected AnActionEvent anActionEvent; 21 | 22 | public BaseAction(Icon icon) { 23 | super(icon); 24 | } 25 | 26 | @Override 27 | public final void actionPerformed(AnActionEvent anActionEvent) { 28 | DocumentUtil.saveDocument(); 29 | this.anActionEvent = anActionEvent; 30 | this.currentProject = DataKeys.PROJECT.getData(anActionEvent.getDataContext()); 31 | this.projectDir = new File(currentProject.getBasePath()); 32 | actionPerformed(); 33 | } 34 | 35 | public abstract void actionPerformed(); 36 | 37 | 38 | /** 39 | * 异步执行 40 | * 41 | * @param runnable 42 | */ 43 | protected void asyncTask(Runnable runnable) { 44 | ApplicationManager.getApplication().executeOnPooledThread(runnable); 45 | } 46 | 47 | protected void invokeLater(Runnable runnable) { 48 | ApplicationManager.getApplication().invokeLater(runnable); 49 | } 50 | 51 | 52 | } 53 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/actions/CrashReporterAction.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.actions; 2 | 3 | import com.antfortune.freeline.idea.models.FreelineConfiguration; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.actionSystem.ToggleAction; 6 | 7 | /** 8 | * Created by huangyong on 17/2/14. 9 | */ 10 | public class CrashReporterAction extends ToggleAction { 11 | 12 | private FreelineConfiguration mConfigurationStorage; 13 | 14 | public CrashReporterAction() { 15 | mConfigurationStorage = FreelineConfiguration.getInstance(); 16 | } 17 | 18 | @Override 19 | public boolean isSelected(AnActionEvent anActionEvent) { 20 | return mConfigurationStorage.DISABLE_CRASH_REPORTER; 21 | } 22 | 23 | @Override 24 | public void setSelected(AnActionEvent anActionEvent, boolean b) { 25 | mConfigurationStorage.DISABLE_CRASH_REPORTER = b; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/actions/DebugRunAction.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.actions; 2 | 3 | import com.antfortune.freeline.idea.icons.PluginIcons; 4 | 5 | /** 6 | * Created by pengwei on 16/9/11. 7 | */ 8 | public class DebugRunAction extends FreelineRunAction { 9 | 10 | public DebugRunAction() { 11 | super(PluginIcons.StartDebugger); 12 | } 13 | 14 | @Override 15 | protected String getArgs() { 16 | return "-d"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/actions/FeedbackAction.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.actions; 2 | 3 | import com.antfortune.freeline.idea.icons.PluginIcons; 4 | import com.intellij.openapi.actionSystem.AnAction; 5 | import com.intellij.openapi.actionSystem.AnActionEvent; 6 | 7 | /** 8 | * Created by huangyong on 17/2/14. 9 | */ 10 | public class FeedbackAction extends AnAction { 11 | 12 | public FeedbackAction() { 13 | super(PluginIcons.EditConfig); 14 | } 15 | 16 | @Override 17 | public void actionPerformed(AnActionEvent anActionEvent) { 18 | SendFeedbackDialog dialog = new SendFeedbackDialog(); 19 | dialog.showDialog(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/actions/ForceRunAction.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.actions; 2 | 3 | import com.antfortune.freeline.idea.icons.PluginIcons; 4 | 5 | /** 6 | * Created by pengwei on 16/9/11. 7 | */ 8 | public class ForceRunAction extends FreelineRunAction { 9 | 10 | public ForceRunAction() { 11 | super(PluginIcons.QuickfixBulb); 12 | } 13 | 14 | @Override 15 | protected String getArgs() { 16 | return "-f"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/actions/FreelineRunAction.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.actions; 2 | 3 | import com.antfortune.freeline.idea.icons.PluginIcons; 4 | import com.antfortune.freeline.idea.utils.FreelineUtil; 5 | import com.antfortune.freeline.idea.utils.NotificationUtils; 6 | import com.antfortune.freeline.idea.utils.Utils; 7 | import com.antfortune.freeline.idea.views.FreelineTerminal; 8 | 9 | import javax.swing.*; 10 | 11 | /** 12 | * Created by pengwei on 16/9/11. 13 | */ 14 | public class FreelineRunAction extends BaseAction { 15 | 16 | public FreelineRunAction() { 17 | super(PluginIcons.FreelineIcon); 18 | } 19 | 20 | public FreelineRunAction(Icon icon) { 21 | super(icon); 22 | } 23 | 24 | @Override 25 | public void actionPerformed() { 26 | if (FreelineUtil.checkInstall(currentProject)) { 27 | String python = Utils.getPythonLocation(); 28 | if (python == null) { 29 | NotificationUtils.pythonNotFound(); 30 | } else { 31 | FreelineTerminal.getInstance(currentProject).initAndExecute(new String[]{ 32 | python, "freeline.py", getArgs()}); 33 | } 34 | } 35 | } 36 | 37 | /** 38 | * 设置参数 39 | * 40 | * @return 41 | */ 42 | protected String getArgs() { 43 | return null; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/actions/OnRequestCallback.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.actions; 2 | 3 | /** 4 | * Created by huangyong on 17/2/15. 5 | */ 6 | public interface OnRequestCallback { 7 | 8 | void onSuccess(); 9 | 10 | void onFailure(Exception e); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/configuration/FreeConfigurationFactory.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.configuration; 2 | 3 | import com.intellij.execution.configurations.ConfigurationFactory; 4 | import com.intellij.execution.configurations.ConfigurationType; 5 | import com.intellij.execution.configurations.RunConfiguration; 6 | import com.intellij.openapi.project.Project; 7 | import org.jetbrains.annotations.NotNull; 8 | import com.antfortune.freeline.idea.utils.FreelineUtil; 9 | 10 | /** 11 | * Freeline Configuration Factory 12 | * 13 | * @author act262@gmail.com 14 | */ 15 | class FreeConfigurationFactory extends ConfigurationFactory { 16 | 17 | FreeConfigurationFactory(@NotNull ConfigurationType type) { 18 | super(type); 19 | } 20 | 21 | @NotNull 22 | @Override 23 | public RunConfiguration createTemplateConfiguration(@NotNull Project project) { 24 | return new FreeRunConfiguration(project, this, "FreelineFactory"); 25 | } 26 | 27 | @Override 28 | public boolean isApplicable(@NotNull Project project) { 29 | // enabled only had init project 30 | return FreelineUtil.hadInitFreeline(project); 31 | } 32 | 33 | @Override 34 | public boolean isConfigurationSingletonByDefault() { 35 | return true; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/configuration/FreeConfigurationType.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.configuration; 2 | 3 | import com.intellij.execution.configurations.ConfigurationTypeBase; 4 | import com.antfortune.freeline.idea.icons.PluginIcons; 5 | 6 | import javax.swing.*; 7 | 8 | /** 9 | * Freeline run configuration type 10 | * 11 | * @author act262@gmail.com 12 | */ 13 | public class FreeConfigurationType extends ConfigurationTypeBase { 14 | 15 | private final static String ID = "com.antfortune.freeline.run"; 16 | private final static String DISPLAY_NAME = "Freeline Run"; 17 | private final static String DESC = "Freeline Run Configuration"; 18 | private final static Icon ICON = PluginIcons.ICON_ACTION_RUN; 19 | 20 | protected FreeConfigurationType() { 21 | super(ID, DISPLAY_NAME, DESC, ICON); 22 | this.addFactory(new FreeConfigurationFactory(this)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/configuration/FreeRunConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.configuration; 2 | 3 | import com.intellij.execution.CantRunException; 4 | import com.intellij.execution.ExecutionException; 5 | import com.intellij.execution.Executor; 6 | import com.intellij.execution.configurations.*; 7 | import com.intellij.execution.process.OSProcessHandler; 8 | import com.intellij.execution.process.ProcessHandler; 9 | import com.intellij.execution.runners.ExecutionEnvironment; 10 | import com.intellij.execution.ui.ConsoleView; 11 | import com.intellij.openapi.options.SettingsEditor; 12 | import com.intellij.openapi.project.Project; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | import com.antfortune.freeline.idea.utils.FreelineUtil; 16 | 17 | /** 18 | * Freeline run configuration implementation 19 | * 20 | * @author act262@gmail.com 21 | */ 22 | class FreeRunConfiguration extends RunConfigurationBase { 23 | 24 | FreeRunConfiguration(@NotNull Project project, @NotNull ConfigurationFactory factory, String name) { 25 | super(project, factory, name); 26 | } 27 | 28 | @NotNull 29 | @Override 30 | public SettingsEditor getConfigurationEditor() { 31 | // setting editor ui 32 | return new FreeSettingEditor(); 33 | } 34 | 35 | @Override 36 | public void checkConfiguration() throws RuntimeConfigurationException { 37 | if (!FreelineUtil.hadInitFreeline(getProject())) { 38 | throw new RuntimeConfigurationException("Not yet initialize freeline code", "Warning"); 39 | } 40 | } 41 | 42 | @Nullable 43 | @Override 44 | public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment executionEnvironment) throws ExecutionException { 45 | return new FreeRunState(executionEnvironment); 46 | } 47 | 48 | 49 | /** 50 | * RunState 51 | */ 52 | private class FreeRunState extends CommandLineState { 53 | 54 | FreeRunState(ExecutionEnvironment environment) { 55 | super(environment); 56 | } 57 | 58 | @NotNull 59 | @Override 60 | protected ProcessHandler startProcess() throws ExecutionException { 61 | if (!FreelineUtil.hadInitFreeline(getProject())) { 62 | throw new CantRunException("Not yet initialized freeline code"); 63 | } 64 | // here just run one command: python freeline.py 65 | GeneralCommandLine commandLine = new GeneralCommandLine(); 66 | ExecutionEnvironment environment = getEnvironment(); 67 | commandLine.setWorkDirectory(environment.getProject().getBasePath()); 68 | commandLine.setExePath("python"); 69 | commandLine.addParameters("freeline.py"); 70 | return new OSProcessHandler(commandLine); 71 | } 72 | 73 | @Nullable 74 | @Override 75 | protected ConsoleView createConsole(@NotNull Executor executor) throws ExecutionException { 76 | ConsoleView console = super.createConsole(executor); 77 | // before run new task,clean log 78 | if (console != null) { 79 | console.clear(); 80 | } 81 | return console; 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/configuration/FreeSettingEditor.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/configuration/FreeSettingEditor.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.configuration; 2 | 3 | import com.intellij.openapi.options.ConfigurationException; 4 | import com.intellij.openapi.options.SettingsEditor; 5 | import org.jetbrains.annotations.NotNull; 6 | 7 | import javax.swing.*; 8 | 9 | /** 10 | * Run configuration setting editor 11 | *

12 | * TODO: not implementation ui 13 | * 14 | * @author act262@gmail.com 15 | */ 16 | public class FreeSettingEditor extends SettingsEditor { 17 | private JPanel panel; 18 | 19 | @Override 20 | protected void resetEditorFrom(@NotNull FreeRunConfiguration freeRunConfiguration) { 21 | } 22 | 23 | @Override 24 | protected void applyEditorTo(@NotNull FreeRunConfiguration freeRunConfiguration) throws ConfigurationException { 25 | } 26 | 27 | @NotNull 28 | @Override 29 | protected JComponent createEditor() { 30 | return panel; 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/icons/PluginIcons.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.icons; 2 | 3 | import com.intellij.icons.AllIcons; 4 | import com.intellij.openapi.util.IconLoader; 5 | import icons.AndroidIcons; 6 | 7 | import javax.swing.*; 8 | 9 | /** 10 | * Created by pengwei on 16/9/15. 11 | */ 12 | public class PluginIcons { 13 | 14 | public static final Icon FreelineIcon = load("/icons/icon.png"); 15 | public static final Icon OpenTerminal = load("/icons/OpenTerminal.png"); 16 | public static final Icon Execute = intellijLoad("/actions/execute.png"); 17 | public static final Icon Suspend = intellijLoad("/actions/suspend.png"); 18 | public static final Icon StartDebugger = intellijLoad("/actions/startDebugger.png"); 19 | public static final Icon QuickfixBulb = intellijLoad("/actions/quickfixBulb.png"); 20 | public static final Icon GC = intellijLoad("/actions/gc.png"); 21 | public static final Icon GradleSync = load("/icons/gradlesync.png"); 22 | public static final Icon EditConfig = intellijLoad("/actions/edit.png"); 23 | 24 | /* Run action icon */ 25 | public static final Icon ICON_ACTION_RUN = FreelineIcon; 26 | /* Tool window icon */ 27 | public static final Icon ICON_TOOL_WINDOW = OpenTerminal; 28 | 29 | private static Icon load(String path) { 30 | try { 31 | return IconLoader.getIcon(path, PluginIcons.class); 32 | } catch (IllegalStateException e) { 33 | return null; 34 | } 35 | } 36 | 37 | private static Icon androidLoad(String path) { 38 | return IconLoader.getIcon(path, AndroidIcons.class); 39 | } 40 | 41 | private static Icon intellijLoad(String path) { 42 | return IconLoader.getIcon(path, AllIcons.class); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/models/ArtifactDependencyModelWrapper.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.models; 2 | 3 | import com.android.tools.idea.gradle.dsl.model.dependencies.ArtifactDependencyModel; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import java.lang.reflect.Method; 8 | 9 | /** 10 | * Created by pengwei on 16/9/14. 11 | * 兼容android studio不同版本 12 | * 2.1.2之前版本name()方法返回string, 之后返回GradleNullableValue 13 | */ 14 | public class ArtifactDependencyModelWrapper { 15 | 16 | private ArtifactDependencyModel model; 17 | 18 | public ArtifactDependencyModelWrapper(ArtifactDependencyModel model) { 19 | this.model = model; 20 | } 21 | 22 | @NotNull 23 | public String name() { 24 | Object value = getArtifactDependencyModelMethod("name"); 25 | if (value != null) { 26 | return getGradleNotNullValue(value); 27 | } 28 | return ""; 29 | } 30 | 31 | @Nullable 32 | public String group() { 33 | Object value = getArtifactDependencyModelMethod("group"); 34 | if (value != null) { 35 | return getGradleNullableValue(value); 36 | } 37 | return ""; 38 | } 39 | 40 | @Nullable 41 | public String version() { 42 | Object value = getArtifactDependencyModelMethod("version"); 43 | if (value != null) { 44 | return getGradleNullableValue(value); 45 | } 46 | return ""; 47 | } 48 | 49 | public String configurationName() { 50 | return model.configurationName(); 51 | } 52 | 53 | /** 54 | * 反射调用ArtifactDependencyModel方法 55 | * 56 | * @param methodName 57 | * @return 58 | */ 59 | private Object getArtifactDependencyModelMethod(String methodName) { 60 | try { 61 | Class ArtifactDependencyModelClass = Class.forName("com.android.tools.idea.gradle.dsl.model.dependencies.ArtifactDependencyModel"); 62 | Method groupMethod = ArtifactDependencyModelClass.getMethod(methodName); 63 | Object value = groupMethod.invoke(model); 64 | return value; 65 | } catch (Exception e) { 66 | return null; 67 | } 68 | } 69 | 70 | private String getGradleNullableValue(Object value) { 71 | return getGradleValue("GradleNullableValue", value); 72 | } 73 | 74 | private String getGradleNotNullValue(Object value) { 75 | return getGradleValue("GradleNotNullValue", value); 76 | } 77 | 78 | private String getGradleValue(String className, Object value) { 79 | if (value instanceof String) { 80 | return (String) value; 81 | } 82 | try { 83 | Class classType = Class.forName("com.android.tools.idea.gradle.dsl.model.values." + className); 84 | if (value.getClass().equals(classType)) { 85 | Method valueMethod = classType.getMethod("value"); 86 | value = valueMethod.invoke(value); 87 | if (value instanceof String) { 88 | return (String) value; 89 | } 90 | } 91 | } catch (Exception e) { 92 | } 93 | return ""; 94 | } 95 | 96 | 97 | } 98 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/models/Constant.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.models; 2 | 3 | /** 4 | * Created by pengwei on 2016/10/22. 5 | */ 6 | public interface Constant { 7 | 8 | boolean DEBUG_MODE = true; 9 | 10 | // groupId 11 | String FREELINE_CLASSPATH_GROUP = "com.antfortune.freeline"; 12 | // artifactId 13 | String FREELINE_CLASSPATH_ARTIFACT = "gradle"; 14 | // plugin name 15 | String FREELINE_PLUGIN_ID = "com.antfortune.freeline"; 16 | 17 | // root folder 18 | String FREELINE_ROOT_FOLDER = "freeline"; 19 | String FREELINE_ROOT_FOLDER_CORE = "freeline_core"; 20 | String FREELINE_PYTHON = "freeline.py"; 21 | 22 | // gradle tool 23 | String ANDROID_GRADLE_TOOL_GROUP_NAME = "com.android.tools.build"; 24 | } 25 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/models/FreelineConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.models; 2 | 3 | import com.intellij.openapi.components.*; 4 | import com.intellij.util.xmlb.XmlSerializerUtil; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | /** 8 | * Created by huangyong on 17/2/14. 9 | */ 10 | @State( 11 | name = "FreelineConfigurationStorage", 12 | storages = @Storage(file = "freeline-configuration.xml", roamingType = RoamingType.DISABLED) 13 | ) 14 | public class FreelineConfiguration implements PersistentStateComponent { 15 | 16 | public boolean DISABLE_CRASH_REPORTER = false; 17 | 18 | @Nullable 19 | @Override 20 | public FreelineConfiguration getState() { 21 | return this; 22 | } 23 | 24 | @Override 25 | public void loadState(FreelineConfiguration freelineConfiguration) { 26 | XmlSerializerUtil.copyBean(freelineConfiguration, this); 27 | } 28 | 29 | public static FreelineConfiguration getInstance() { 30 | return ServiceManager.getService(FreelineConfiguration.class); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/models/FreelineStatus.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.models; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VirtualFile; 5 | import com.antfortune.freeline.idea.utils.FreelineUtil; 6 | import com.antfortune.freeline.idea.utils.LogUtil; 7 | 8 | import java.io.File; 9 | import java.util.Collection; 10 | import java.util.Timer; 11 | import java.util.TimerTask; 12 | 13 | /** 14 | * Created by pengwei on 2016/10/31. 15 | */ 16 | public class FreelineStatus { 17 | Collection gradleBuildFiles; 18 | boolean existClasspath = false; 19 | private VirtualFile classpathFile; 20 | boolean existPlugin = false; 21 | private VirtualFile pluginFile; 22 | // mac下只有freeline文件夹 + freeline.py , win下有freeline.py + freeline/ + freeline_core/ 23 | boolean existFreelineCore = false; 24 | 25 | public FreelineStatus setClasspathFile(VirtualFile classpathFile) { 26 | this.classpathFile = classpathFile; 27 | if (classpathFile != null) { 28 | existClasspath = true; 29 | } 30 | return this; 31 | } 32 | 33 | public FreelineStatus setPluginFile(VirtualFile pluginFile) { 34 | this.pluginFile = pluginFile; 35 | if (pluginFile != null) { 36 | existPlugin = true; 37 | } 38 | return this; 39 | } 40 | 41 | public FreelineStatus setExistFreelineCore(boolean existFreelineCore) { 42 | this.existFreelineCore = existFreelineCore; 43 | return this; 44 | } 45 | 46 | public boolean isExistClasspath() { 47 | return existClasspath; 48 | } 49 | 50 | public VirtualFile getClasspathFile() { 51 | return classpathFile; 52 | } 53 | 54 | public boolean isExistPlugin() { 55 | return existPlugin; 56 | } 57 | 58 | public VirtualFile getPluginFile() { 59 | return pluginFile; 60 | } 61 | 62 | public boolean isExistFreelineCore() { 63 | return existFreelineCore; 64 | } 65 | 66 | /** 67 | * 是否初始化Freeline 68 | * 满足一下三个条件 69 | * 1. 存在classpath 'com.antfortune.freeline:gradle:*' 70 | * 2. 存在apply plugin: 'com.antfortune.freeline' 71 | * 3. 存在freeline文件夹、freeline.py 72 | * 73 | * @return 74 | */ 75 | public boolean hasInitFreeline() { 76 | LogUtil.d("existClasspath=%s,existPlugin=%s,existFreelineCore=%s", existClasspath, existPlugin, existFreelineCore); 77 | return existClasspath && existPlugin && existFreelineCore; 78 | } 79 | 80 | public Collection getGradleBuildFiles() { 81 | return gradleBuildFiles; 82 | } 83 | 84 | public FreelineStatus setGradleBuildFiles(Collection gradleBuildFiles) { 85 | this.gradleBuildFiles = gradleBuildFiles; 86 | return this; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/models/GetServerCallback.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.models; 2 | 3 | /** 4 | * Created by pengwei on 16/9/14. 5 | */ 6 | public interface GetServerCallback { 7 | void onSuccess(GradleDependencyEntity entity); 8 | void onFailure(String errMsg); 9 | } 10 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/models/GradleSyncHandler.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.models; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.antfortune.freeline.idea.utils.LogUtil; 5 | 6 | import java.lang.reflect.InvocationHandler; 7 | import java.lang.reflect.Method; 8 | 9 | /** 10 | * Created by pengwei on 2016/11/25. 11 | */ 12 | public class GradleSyncHandler implements InvocationHandler { 13 | 14 | @Override 15 | public final Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 16 | LogUtil.d("method=" + method.toString() + ";proxy=" + proxy); 17 | if (method.toString().equals("syncSucceeded")) { 18 | syncSucceeded(null); 19 | } else if (method.toString().equals("syncFailed")) { 20 | syncFailed(null, null); 21 | } 22 | return null; 23 | } 24 | 25 | public void syncSucceeded(Project project) { 26 | 27 | } 28 | 29 | public void syncFailed(Project project, String msg) { 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/utils/DialogUtil.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.utils; 2 | 3 | import com.intellij.openapi.ui.DialogBuilder; 4 | import com.intellij.openapi.ui.Messages; 5 | 6 | import javax.swing.*; 7 | 8 | /** 9 | * Created by pengwei on 2016/11/2. 10 | */ 11 | public final class DialogUtil { 12 | 13 | /** 14 | * 创建普通对话框 15 | * @param message 16 | * @param okText 17 | * @param cancelText 18 | * @return 19 | */ 20 | public static boolean createDialog(String message, String okText, String cancelText) { 21 | DialogBuilder builder = new DialogBuilder(); 22 | builder.setTitle("Dialog Message"); 23 | builder.resizable(false); 24 | builder.setCenterPanel(new JLabel(message, Messages.getInformationIcon(), SwingConstants.CENTER)); 25 | builder.addOkAction().setText(okText); 26 | builder.addCancelAction().setText(cancelText); 27 | builder.setButtonsAlignment(SwingConstants.CENTER); 28 | return builder.show() == 0; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/utils/DocumentUtil.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.utils; 2 | 3 | import com.intellij.codeInsight.actions.OptimizeImportsProcessor; 4 | import com.intellij.codeInsight.actions.ReformatCodeAction; 5 | import com.intellij.codeInsight.actions.ReformatCodeProcessor; 6 | import com.intellij.openapi.application.ApplicationManager; 7 | import com.intellij.openapi.fileEditor.FileDocumentManager; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.openapi.vfs.VirtualFile; 10 | import com.intellij.psi.PsiFile; 11 | 12 | /** 13 | * Created by pengwei on 2016/11/1. 14 | */ 15 | public class DocumentUtil { 16 | 17 | /** 18 | * 保存文档和设置 19 | */ 20 | public static void saveDocument() { 21 | FileDocumentManager.getInstance().saveAllDocuments(); 22 | ApplicationManager.getApplication().saveSettings(); 23 | } 24 | 25 | /** 26 | * 格式化代码 27 | * 28 | * @param project 29 | * @param virtualFiles 30 | */ 31 | public static void reformatCode(final Project project, final VirtualFile virtualFiles) { 32 | ApplicationManager.getApplication().runWriteAction(new Runnable() { 33 | @Override 34 | public void run() { 35 | PsiFile[] psiFiles = ReformatCodeAction.convertToPsiFiles(new VirtualFile[]{virtualFiles}, project); 36 | if (psiFiles != null && psiFiles.length == 1 && psiFiles[0] != null) { 37 | new ReformatCodeProcessor(project, psiFiles[0], null, false).run(); 38 | } 39 | } 40 | }); 41 | } 42 | 43 | /** 44 | * 优化导入 45 | * 46 | * @param project 47 | * @param virtualFiles 48 | */ 49 | public static void optimizeImports(Project project, VirtualFile... virtualFiles) { 50 | new OptimizeImportsProcessor( 51 | project, ReformatCodeAction.convertToPsiFiles(virtualFiles, project), null).run(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/utils/FreeUIManager.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.utils; 2 | 3 | import com.intellij.execution.filters.TextConsoleBuilderFactory; 4 | import com.intellij.execution.ui.ConsoleView; 5 | import com.intellij.openapi.components.ServiceManager; 6 | import com.intellij.openapi.project.Project; 7 | 8 | /** 9 | * UI manager service 10 | */ 11 | public class FreeUIManager { 12 | 13 | public static FreeUIManager getInstance(Project project) { 14 | return ServiceManager.getService(project, FreeUIManager.class); 15 | } 16 | 17 | private ConsoleView mFreeConsole; 18 | 19 | public ConsoleView getConsoleView(Project project) { 20 | if (mFreeConsole == null) { 21 | mFreeConsole = TextConsoleBuilderFactory.getInstance().createBuilder(project).getConsole(); 22 | } 23 | return mFreeConsole; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/utils/GroovyFileUil.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.utils; 2 | 3 | import com.google.common.base.Predicate; 4 | import com.google.common.collect.Iterables; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; 8 | import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; 9 | import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrMethodCall; 10 | import org.jetbrains.plugins.groovy.lang.psi.api.util.GrStatementOwner; 11 | 12 | import java.util.Arrays; 13 | import java.util.Iterator; 14 | 15 | /** 16 | * Created by pengwei on 2016/10/31. 17 | */ 18 | public class GroovyFileUil { 19 | 20 | /** 21 | * 获取最后一个插件的表达式 22 | * 23 | * @param buildScript 24 | * @return 25 | */ 26 | public static GrExpression getLastPlugin(GroovyFile buildScript) { 27 | Iterator var2 = getMethodCalls(buildScript, "apply").iterator(); 28 | GrExpression expression = null; 29 | while (var2.hasNext()) { 30 | GrMethodCall methodCall = (GrMethodCall) var2.next(); 31 | expression = methodCall.getInvokedExpression(); 32 | } 33 | return expression; 34 | } 35 | 36 | public static Iterable getMethodCalls(@NotNull GrStatementOwner parent) { 37 | return Iterables.filter(Arrays.asList(parent.getStatements()), GrMethodCall.class); 38 | } 39 | 40 | public static Iterable getMethodCalls(@NotNull GrStatementOwner parent, @NotNull final String methodName) { 41 | return Iterables.filter(getMethodCalls(parent), new Predicate() { 42 | public boolean apply(@Nullable GrMethodCall input) { 43 | return input != null && methodName.equals(getMethodCallName(input)); 44 | } 45 | }); 46 | } 47 | 48 | public static String getMethodCallName(@NotNull GrMethodCall gmc) { 49 | GrExpression expression = gmc.getInvokedExpression(); 50 | return expression.getText() != null ? expression.getText() : ""; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/utils/LogUtil.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.utils; 2 | 3 | import com.antfortune.freeline.idea.models.Constant; 4 | 5 | /** 6 | * Created by pengwei on 2016/11/2. 7 | */ 8 | public class LogUtil { 9 | 10 | public static void d(String info) { 11 | if (!Constant.DEBUG_MODE) { 12 | return; 13 | } 14 | System.out.println(info); 15 | } 16 | 17 | public static void d(String info, Object...args) { 18 | if (!Constant.DEBUG_MODE) { 19 | return; 20 | } 21 | System.out.println(String.format(info, args)); 22 | } 23 | 24 | public static void d(Object info) { 25 | if (!Constant.DEBUG_MODE) { 26 | return; 27 | } 28 | System.out.println(info); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/utils/NotificationUtils.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.utils; 2 | 3 | import com.intellij.notification.Notification; 4 | import com.intellij.notification.NotificationGroup; 5 | import com.intellij.notification.NotificationType; 6 | import com.intellij.notification.Notifications; 7 | import com.intellij.openapi.application.ApplicationManager; 8 | import com.intellij.openapi.ui.Messages; 9 | import com.intellij.openapi.util.SystemInfo; 10 | 11 | public class NotificationUtils { 12 | 13 | private static final String TITLE = "Freeline Plugin"; 14 | private static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.balloonGroup(TITLE); 15 | 16 | /** 17 | * show a Notification 18 | * 19 | * @param message 20 | * @param type 21 | */ 22 | public static void showNotification(final String message, 23 | final NotificationType type) { 24 | ApplicationManager.getApplication().invokeLater(new Runnable() { 25 | @Override 26 | public void run() { 27 | Notification notification = 28 | NOTIFICATION_GROUP.createNotification(TITLE, message, type, null); 29 | Notifications.Bus.notify(notification); 30 | } 31 | }); 32 | } 33 | 34 | /** 35 | * show a error Notification 36 | * 37 | * @param message 38 | */ 39 | public static void errorNotification(final String message) { 40 | showNotification(message, NotificationType.ERROR); 41 | } 42 | 43 | /** 44 | * error message dialog 45 | * @param message 46 | */ 47 | public static void errorMsgDialog(String message) { 48 | Messages.showMessageDialog(message, "Error", Messages.getInformationIcon()); 49 | } 50 | 51 | /** 52 | * show a info Notification 53 | * 54 | * @param message 55 | */ 56 | public static void infoNotification(final String message) { 57 | showNotification(message, NotificationType.INFORMATION); 58 | } 59 | 60 | /** 61 | * python提示 62 | */ 63 | public static void pythonNotFound() { 64 | String tip = "command 'python' not found. "; 65 | if (SystemInfo.isWindows) { 66 | tip += " if first installation python or Set Environment variable, Please restart your computer"; 67 | } 68 | errorNotification(tip); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/utils/StreamUtil.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.utils; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | 8 | /** 9 | * Created by pengwei on 16/9/11. 10 | */ 11 | public class StreamUtil { 12 | 13 | /** 14 | * string 转 InputStream 15 | * @param text 16 | * @return 17 | */ 18 | public static final InputStream string2InputStream(String text) { 19 | return new ByteArrayInputStream(text.getBytes()); 20 | } 21 | 22 | /** 23 | * InputStream 转 byte[] 24 | * @param inStream 25 | * @return 26 | * @throws IOException 27 | */ 28 | public static final byte[] inputStream2byte(InputStream inStream) throws IOException { 29 | ByteArrayOutputStream swapStream = new ByteArrayOutputStream(); 30 | byte[] buff = new byte[100]; 31 | int rc = 0; 32 | while ((rc = inStream.read(buff, 0, 100)) > 0) { 33 | swapStream.write(buff, 0, rc); 34 | } 35 | byte[] in2b = swapStream.toByteArray(); 36 | return in2b; 37 | } 38 | 39 | /** 40 | * InputStream 转 string 41 | * @param inStream 42 | * @return 43 | * @throws IOException 44 | */ 45 | public static final String inputStream2String(InputStream inStream) throws IOException { 46 | return new String(inputStream2byte(inStream)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/utils/Utils.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.utils; 2 | 3 | import com.intellij.openapi.util.SystemInfo; 4 | 5 | import java.awt.*; 6 | import java.awt.event.KeyEvent; 7 | import java.io.IOException; 8 | import java.net.URI; 9 | import java.net.URISyntaxException; 10 | 11 | /** 12 | * Created by pengwei on 16/9/11. 13 | */ 14 | public final class Utils { 15 | 16 | public static final String BREAK_LINE = System.getProperty("line.separator"); 17 | 18 | /** 19 | * 获取python安装目录 20 | * 21 | * @return 22 | */ 23 | public static String getPythonLocation() { 24 | Process process = null; 25 | try { 26 | process = Runtime.getRuntime().exec(new String[]{"python", "--version"}); 27 | if (process.waitFor() == 0) { 28 | return "python"; 29 | } 30 | } catch (Exception e) { 31 | } 32 | try { 33 | if (!SystemInfo.isWindows) { 34 | process = Runtime.getRuntime().exec(new String[]{"whereis", "python"}); 35 | if (process != null && process.getInputStream() != null) { 36 | String result = StreamUtil.inputStream2String(process.getInputStream()); 37 | if (notEmpty(result)) { 38 | return result; 39 | } 40 | } 41 | } 42 | } catch (IOException e) { 43 | } 44 | return null; 45 | } 46 | 47 | public static boolean notEmpty(String text) { 48 | return (text != null && text.trim().length() != 0); 49 | } 50 | 51 | /** 52 | * 打开浏览器 53 | * 54 | * @param url 55 | */ 56 | public static void openUrl(String url) { 57 | if (SystemInfo.isWindows) { 58 | try { 59 | Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url); 60 | } catch (IOException e) { 61 | e.printStackTrace(); 62 | } 63 | } else { 64 | try { 65 | URI uri = new URI(url); 66 | Desktop desktop = null; 67 | if (Desktop.isDesktopSupported()) { 68 | desktop = Desktop.getDesktop(); 69 | } 70 | if (desktop != null) 71 | desktop.browse(uri); 72 | } catch (IOException ioe) { 73 | ioe.printStackTrace(); 74 | } catch (URISyntaxException e) { 75 | e.printStackTrace(); 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * 模拟键盘输入 82 | * @param r 83 | * @param key 84 | */ 85 | public static void keyPressWithCtrl(Robot r, int key) { 86 | if (r == null) { 87 | return; 88 | } 89 | r.keyPress(KeyEvent.VK_CONTROL); 90 | r.keyPress(key); 91 | r.keyRelease(key); 92 | r.keyRelease(KeyEvent.VK_CONTROL); 93 | r.delay(100); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/views/CheckUpdateDialog.form: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/views/FreelineToolWindowFactory.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.views; 2 | 3 | import com.intellij.openapi.project.DumbAware; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.wm.ToolWindow; 6 | import com.intellij.openapi.wm.ToolWindowFactory; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | /** 10 | * Created by pengwei on 16/9/16. 11 | */ 12 | public class FreelineToolWindowFactory implements ToolWindowFactory, DumbAware { 13 | 14 | public static final String TOOL_WINDOW_ID = "Freeline"; 15 | 16 | public FreelineToolWindowFactory() { 17 | } 18 | 19 | @Override 20 | public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { 21 | FreelineTerminal.getInstance(project).initTerminal(toolWindow); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/views/ImageJPanel.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.views; 2 | 3 | 4 | import javax.imageio.ImageIO; 5 | import javax.swing.*; 6 | import java.awt.*; 7 | import java.awt.image.BufferedImage; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | 11 | /** 12 | * Created by pengwei on 16/9/14. 13 | */ 14 | public class ImageJPanel extends JPanel { 15 | 16 | private BufferedImage image; 17 | 18 | public void setImagePath(String path) { 19 | try { 20 | InputStream is = this.getClass().getClassLoader().getResourceAsStream(path); 21 | if (is != null) { 22 | image = ImageIO.read(is); 23 | } 24 | } catch (IOException e) { 25 | 26 | } 27 | } 28 | 29 | @Override 30 | public void paint(Graphics g) { 31 | super.paint(g); 32 | g.drawImage(image, 0, 0, getWidth(), getHeight(), null, null); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/views/JTitle.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.views; 2 | 3 | import javax.swing.*; 4 | import java.awt.*; 5 | 6 | /** 7 | * Created by pengwei on 16/9/14. 8 | */ 9 | public class JTitle extends JLabel { 10 | 11 | public JTitle(String text) { 12 | super(text); 13 | setFont(new Font("微软雅黑", Font.BOLD, 15)); 14 | setBorder(BorderFactory.createEmptyBorder(10,0,0,0)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/java/com/antfortune/freeline/idea/views/MulLabel.java: -------------------------------------------------------------------------------- 1 | package com.antfortune.freeline.idea.views; 2 | 3 | import javax.swing.*; 4 | 5 | /** 6 | * Created by pengwei on 16/9/14. 7 | */ 8 | public class MulLabel extends JLabel { 9 | 10 | public MulLabel() { 11 | this(""); 12 | } 13 | 14 | public MulLabel(String text) { 15 | this(text, false); 16 | } 17 | 18 | public MulLabel(String text, boolean html) { 19 | super(text); 20 | if (html) { 21 | setHtml(text); 22 | } 23 | setBorder(BorderFactory.createEmptyBorder(3,0,3,0)); 24 | } 25 | 26 | public void setHtml(String text) { 27 | setText("" + text + ""); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/resources/icons/OpenTerminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-studio-plugin/src/main/resources/icons/OpenTerminal.png -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/resources/icons/OpenTerminal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-studio-plugin/src/main/resources/icons/OpenTerminal@2x.png -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/resources/icons/bg_update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-studio-plugin/src/main/resources/icons/bg_update.png -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/resources/icons/debug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-studio-plugin/src/main/resources/icons/debug.png -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/resources/icons/debug@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-studio-plugin/src/main/resources/icons/debug@2x.png -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/resources/icons/gradlesync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-studio-plugin/src/main/resources/icons/gradlesync.png -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/resources/icons/gradlesync@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-studio-plugin/src/main/resources/icons/gradlesync@2x.png -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/resources/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-studio-plugin/src/main/resources/icons/icon.png -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/resources/icons/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-studio-plugin/src/main/resources/icons/icon@2x.png -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/resources/icons/play2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-studio-plugin/src/main/resources/icons/play2.png -------------------------------------------------------------------------------- /freeline-studio-plugin/src/main/resources/icons/play2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/freeline-studio-plugin/src/main/resources/icons/play2@2x.png -------------------------------------------------------------------------------- /freeline.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | from __future__ import print_function 4 | import sys 5 | from argparse import ArgumentParser 6 | 7 | from freeline_core.dispatcher import Dispatcher 8 | from freeline_core.init import init 9 | 10 | 11 | class Freeline(object): 12 | def __init__(self): 13 | self.dispatcher = Dispatcher() 14 | 15 | def call(self, args=None): 16 | if 'init' in args and args.init: 17 | print('init freeline project...') 18 | init() 19 | exit() 20 | 21 | self.dispatcher.call_command(args) 22 | 23 | 24 | def get_parser(): 25 | parser = ArgumentParser() 26 | parser.add_argument('-v', '--version', action='store_true', help='show version') 27 | parser.add_argument('-f', '--cleanBuild', action='store_true', help='force to execute a clean build') 28 | parser.add_argument('-w', '--wait', action='store_true', help='make application wait for debugger') 29 | parser.add_argument('-a', '--all', action='store_true', 30 | help="together with '-f', freeline will force to clean build all projects.") 31 | parser.add_argument('-c', '--clean', action='store_true', help='clean cache directory and workspace') 32 | parser.add_argument('-d', '--debug', action='store_true', help='show freeline debug output (NOT DEBUG APPLICATION)') 33 | # parser.add_argument('-i', '--init', action='store_true', help='init freeline project') 34 | parser.parse_args() 35 | return parser 36 | 37 | 38 | def main(): 39 | if sys.version_info > (3, 0): 40 | print('Freeline only support Python 2.7+ now. Please use the correct version of Python for freeline.') 41 | exit() 42 | 43 | parser = get_parser() 44 | args = parser.parse_args() 45 | freeline = Freeline() 46 | freeline.call(args=args) 47 | 48 | 49 | if __name__ == '__main__': 50 | main() 51 | -------------------------------------------------------------------------------- /freeline_core/__init__.py: -------------------------------------------------------------------------------- 1 | import build_commands 2 | import builder 3 | import command 4 | import dispatcher 5 | import exceptions 6 | import task 7 | import tracing 8 | import utils 9 | -------------------------------------------------------------------------------- /freeline_core/build_commands.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf8 -*- 2 | from command import AbstractBuildCommand, MacroCommand 3 | 4 | 5 | class CleanBuildCommand(AbstractBuildCommand): 6 | def __init__(self, builder): 7 | AbstractBuildCommand.__init__(self, builder, command_name='clean_build') 8 | self._setup() 9 | 10 | def execute(self): 11 | map(lambda command: command.execute(), self.command_list) 12 | 13 | def _setup(self): 14 | self.add_command(CheckBulidEnvironmentCommand(self._builder)) 15 | self.add_command(FindDependenciesOfTasksCommand(self._builder)) 16 | self.add_command(GenerateSortedBuildTasksCommand(self._builder)) 17 | self.add_command(UpdateApkCreatedTimeCommand(self._builder)) 18 | self.add_command(ExecuteCleanBuildCommand(self._builder)) 19 | 20 | 21 | class IncrementalBuildCommand(AbstractBuildCommand): 22 | def __init__(self, builder): 23 | AbstractBuildCommand.__init__(self, builder, command_name='incremental_build') 24 | self._setup() 25 | 26 | def execute(self): 27 | map(lambda command: command.execute(), self.command_list) 28 | 29 | def _setup(self): 30 | self.add_command(CheckBulidEnvironmentCommand(self._builder)) 31 | self.add_command(GenerateSortedBuildTasksCommand(self._builder)) 32 | self.add_command(ExecuteIncrementalBuildCommand(self._builder)) 33 | 34 | 35 | class CheckBulidEnvironmentCommand(AbstractBuildCommand): 36 | def __init__(self, builder): 37 | AbstractBuildCommand.__init__(self, builder, command_name='check_build_environment') 38 | 39 | def execute(self): 40 | self._builder.check_build_environment() 41 | 42 | 43 | class FindDependenciesOfTasksCommand(AbstractBuildCommand): 44 | def __init__(self, builder): 45 | AbstractBuildCommand.__init__(self, builder, command_name='find_dependencies_of_tasks') 46 | 47 | def execute(self): 48 | self._builder.find_dependencies() 49 | 50 | 51 | class GenerateSortedBuildTasksCommand(AbstractBuildCommand): 52 | def __init__(self, builder): 53 | AbstractBuildCommand.__init__(self, builder, command_name='generate_build_tasks') 54 | 55 | def execute(self): 56 | self._builder.generate_sorted_build_tasks() 57 | 58 | 59 | class UpdateApkCreatedTimeCommand(AbstractBuildCommand): 60 | def __init__(self, builder): 61 | AbstractBuildCommand.__init__(self, builder, command_name='update_apk_created_time') 62 | 63 | def execute(self): 64 | self._builder.update_apk_created_time() 65 | 66 | 67 | class ExecuteCleanBuildCommand(AbstractBuildCommand): 68 | def __init__(self, builder): 69 | AbstractBuildCommand.__init__(self, builder, command_name='execute_clean_build') 70 | 71 | def execute(self): 72 | self._builder.clean_build() 73 | 74 | 75 | class ExecuteIncrementalBuildCommand(AbstractBuildCommand): 76 | def __init__(self, builder): 77 | AbstractBuildCommand.__init__(self, builder, command_name='execute_incremental_build') 78 | 79 | def execute(self): 80 | self._builder.incremental_build() 81 | 82 | 83 | class CompileCommand(MacroCommand): 84 | def __init__(self, name, invoker): 85 | MacroCommand.__init__(self, name) 86 | self._invoker = invoker 87 | self._setup() 88 | 89 | def _setup(self): 90 | self.add_command(IncAaptCommand(self.command_name, self._invoker)) 91 | self.add_command(IncJavacCommand(self.command_name, self._invoker)) 92 | self.add_command(IncDexCommand(self.command_name, self._invoker)) 93 | 94 | 95 | class IncAaptCommand(MacroCommand): 96 | def __init__(self, pro, invoker): 97 | MacroCommand.__init__(self, '{}_inc_res_compile'.format(pro)) 98 | self._invoker = invoker 99 | 100 | def execute(self): 101 | self._invoker.run_aapt_task() 102 | 103 | 104 | class IncJavacCommand(MacroCommand): 105 | def __init__(self, pro, invoker): 106 | MacroCommand.__init__(self, '{}_inc_javac_compile'.format(pro)) 107 | self._invoker = invoker 108 | 109 | def execute(self): 110 | self._invoker.run_javac_task() 111 | 112 | 113 | class IncDexCommand(MacroCommand): 114 | def __init__(self, pro, invoker): 115 | MacroCommand.__init__(self, '{}_inc_dex_compile'.format(pro)) 116 | self._invoker = invoker 117 | 118 | def execute(self): 119 | self._invoker.run_dex_task() 120 | 121 | 122 | class SyncCommand(MacroCommand): 123 | def __init__(self, sync_client, command_name): 124 | MacroCommand.__init__(self, command_name=command_name) 125 | self._sync_client = sync_client 126 | -------------------------------------------------------------------------------- /freeline_core/command.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf8 -*- 2 | from __future__ import print_function 3 | from utils import generate_random_string 4 | from logger import Logger 5 | 6 | 7 | class AbstractCommand(object): 8 | TPL_COMMAND_DEBUG_MSG = '[{}] {}' 9 | 10 | def __init__(self, command_name=None): 11 | self.command_name = generate_random_string() if not command_name else command_name 12 | 13 | def execute(self): 14 | raise NotImplementedError 15 | 16 | def debug(self, message): 17 | Logger.debug(AbstractBuildCommand.TPL_COMMAND_DEBUG_MSG.format(self.command_name, message)) 18 | 19 | def __repr__(self): 20 | return self.command_name 21 | 22 | __str__ = __repr__ 23 | 24 | 25 | class MacroCommand(AbstractCommand): 26 | def __init__(self, command_name=None): 27 | AbstractCommand.__init__(self, command_name=command_name) 28 | self.command_list = [] 29 | 30 | def add_command(self, command): 31 | if isinstance(command, AbstractCommand): 32 | self.command_list.append(command) 33 | 34 | def remove_command(self, command): 35 | self.command_list.remove(command) 36 | 37 | def execute(self): 38 | map(lambda command: command.execute(), self.command_list) 39 | 40 | 41 | class AbstractBuildCommand(MacroCommand): 42 | def __init__(self, builder, command_name=None): 43 | MacroCommand.__init__(self, command_name=command_name) 44 | self._builder = builder 45 | 46 | 47 | class AbstractCleanBuildCommand(AbstractBuildCommand): 48 | def __init__(self, builder, command_name=None): 49 | AbstractBuildCommand.__init__(self, builder, command_name=command_name) 50 | 51 | def execute(self): 52 | raise NotImplementedError 53 | 54 | 55 | class AbstractIncrementalBuildCommand(AbstractBuildCommand): 56 | def __init__(self, builder, command_name=None): 57 | AbstractBuildCommand.__init__(self, builder, command_name=command_name) 58 | 59 | def execute(self): 60 | raise NotImplementedError 61 | -------------------------------------------------------------------------------- /freeline_core/cursor.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf8 -*- 2 | 3 | from terminal import Terminal 4 | 5 | 6 | class Cursor(object): 7 | def __init__(self, term=None): 8 | self.term = Terminal() if term is None else term 9 | self._stream = self.term.stream 10 | self._saved = False 11 | 12 | def write(self, s): 13 | self._stream.write(s) 14 | 15 | def save(self): 16 | self.write(self.term.save) 17 | self._saved = True 18 | 19 | def restore(self): 20 | if self._saved: 21 | self.write(self.term.restore) 22 | 23 | def flush(self): 24 | self._stream.flush() 25 | 26 | def newline(self): 27 | self.write(self.term.move_down) 28 | self.write(self.term.clear_bol) 29 | 30 | def clear_lines(self, num_lines=0): 31 | for i in range(num_lines): 32 | self.write(self.term.clear_eol) 33 | self.write(self.term.move_down) 34 | for i in range(num_lines): 35 | self.write(self.term.move_up) 36 | -------------------------------------------------------------------------------- /freeline_core/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf8 -*- 2 | 3 | 4 | class NoConfigFoundException(Exception): 5 | def __init__(self, path): 6 | Exception.__init__(self, '{} not found, please execute gradlew checkBeforeCleanBuild first.'.format(path)) 7 | 8 | 9 | class EnvironmentException(Exception): 10 | def __init__(self, message): 11 | Exception.__init__(self, message) 12 | 13 | 14 | class FreelineException(Exception): 15 | def __init__(self, message, cause): 16 | Exception.__init__(self, message) 17 | self.cause = cause 18 | 19 | 20 | class NoDeviceFoundException(FreelineException): 21 | def __init__(self, message, cause): 22 | FreelineException.__init__(self, message, cause) 23 | 24 | 25 | class CheckSyncStateException(FreelineException): 26 | def __init__(self, message, cause): 27 | FreelineException.__init__(self, message, cause) 28 | 29 | 30 | class NoInstallationException(FreelineException): 31 | def __init__(self, message, cause): 32 | FreelineException.__init__(self, message, cause) 33 | 34 | 35 | class FileMissedException(FreelineException): 36 | def __init__(self, message, cause): 37 | FreelineException.__init__(self, message, cause) 38 | 39 | 40 | class UsbConnectionException(FreelineException): 41 | def __init__(self, message, cause): 42 | FreelineException.__init__(self, message, cause) 43 | -------------------------------------------------------------------------------- /freeline_core/freeline_build.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf8 -*- 2 | from __future__ import print_function 3 | from command import AbstractCommand 4 | from exceptions import NoConfigFoundException 5 | from logger import Logger 6 | 7 | 8 | class FreelineBuildCommand(AbstractCommand): 9 | def __init__(self, config, task_engine=None): 10 | AbstractCommand.__init__(self, command_name='freeline_build_command') 11 | self._config = config 12 | self._task_engine = task_engine 13 | self._project_type = None 14 | self._dispatch_policy = None 15 | self._builder = None 16 | self._scan_command = None 17 | self._build_command = None 18 | self._setup() 19 | 20 | def execute(self): 21 | file_changed_dict = self._scan_command.execute() 22 | 23 | if self._dispatch_policy.is_need_clean_build(self._config, file_changed_dict): 24 | self._setup_clean_builder(file_changed_dict) 25 | from build_commands import CleanBuildCommand 26 | self._build_command = CleanBuildCommand(self._builder) 27 | else: 28 | # only flush changed list when your project need a incremental build. 29 | Logger.debug('file changed list:') 30 | Logger.debug(file_changed_dict) 31 | self._setup_inc_builder(file_changed_dict) 32 | from build_commands import IncrementalBuildCommand 33 | self._build_command = IncrementalBuildCommand(self._builder) 34 | 35 | self._build_command.execute() 36 | 37 | def _setup(self): 38 | if not self._config: 39 | raise NoConfigFoundException 40 | 41 | if 'project_type' in self._config: 42 | self._project_type = self._config['project_type'] 43 | if self._project_type == 'gradle': 44 | from gradle_tools import GradleScanChangedFilesCommand, GradleDispatchPolicy 45 | self._scan_command = GradleScanChangedFilesCommand(self._config) 46 | self._dispatch_policy = GradleDispatchPolicy() 47 | 48 | def _setup_clean_builder(self, file_changed_dict): 49 | if self._project_type == 'gradle': 50 | from gradle_clean_build import GradleCleanBuilder 51 | project_info = self._scan_command.project_info 52 | self._builder = GradleCleanBuilder(self._config, self._task_engine, project_info=project_info) 53 | 54 | def _setup_inc_builder(self, file_changed_dict): 55 | if self._project_type == 'gradle': 56 | project_info = self._scan_command.project_info 57 | from gradle_inc_build import GradleIncBuilder 58 | self._builder = GradleIncBuilder(file_changed_dict, self._config, self._task_engine, 59 | project_info=project_info) 60 | 61 | 62 | class DispatchPolicy(object): 63 | """ 64 | file_changed_dict: 65 | 66 | 'projects': { 67 | bundle1: { 68 | 'js': [], 69 | 'assets': [], 70 | 'res': [], 71 | 'src': [], 72 | 'manifest': [], 73 | 'pom': [] 74 | }, 75 | bundle2: { 76 | 'js': [], 77 | 'assets': [], 78 | 'res': [], 79 | 'src': [], 80 | 'manifest': [], 81 | 'pom': [] 82 | }, 83 | ... 84 | }, 85 | 86 | 'build_info': { 87 | 'last_clean_build_time': int, 88 | 'root_pom_changed': bool 89 | } 90 | """ 91 | 92 | def is_need_clean_build(self, config, file_changed_dict): 93 | raise NotImplementedError 94 | 95 | 96 | class ScanChangedFilesCommand(AbstractCommand): 97 | def __init__(self): 98 | AbstractCommand.__init__(self, command_name='scan_changed_files_command') 99 | 100 | def execute(self): 101 | raise NotImplementedError 102 | -------------------------------------------------------------------------------- /freeline_core/hackwindows.py: -------------------------------------------------------------------------------- 1 | TIOCGWINSZ = 1074295912 2 | 3 | 4 | def fcntl(fd, op, arg=0): 5 | return 0 6 | 7 | 8 | def ioctl(fd, op, arg=0, mutable_flag=True): 9 | if mutable_flag: 10 | return 0 11 | else: 12 | return "" 13 | 14 | 15 | def flock(fd, op): 16 | return 17 | 18 | 19 | def lockf(fd, operation, length=0, start=0, whence=0): 20 | return 21 | -------------------------------------------------------------------------------- /freeline_core/init.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf8 -*- 2 | from __future__ import print_function 3 | import os 4 | 5 | from exceptions import FreelineException 6 | from utils import is_windows_system, cexec, copy, get_file_content 7 | 8 | is_windows = is_windows_system() 9 | 10 | 11 | def init(): 12 | project_dir = os.getcwd() 13 | symlink('freeline', project_dir, 'freeline.py') 14 | 15 | if is_windows: 16 | symlink('freeline', project_dir, 'freeline_core') 17 | 18 | from gradle_tools import get_all_modules 19 | modules = get_all_modules(project_dir) 20 | for m in modules: 21 | if is_main_project(m['path']): 22 | main_module = m 23 | break 24 | 25 | if not main_module: 26 | raise FreelineException('main module not found', 'set main module first') 27 | 28 | print('find main module: ' + main_module['name']) 29 | args = [] 30 | if is_windows: 31 | args.append('gradlew.bat') 32 | else: 33 | args.append('./gradlew') 34 | args.append(':{}:checkBeforeCleanBuild'.format(main_module['name'])) 35 | print('freeline is reading project info, please wait a moment...') 36 | output, err, code = cexec(args, cwd=project_dir) 37 | if code != 0: 38 | raise FreelineException('freeline failed when read project info with script: {}'.format(args), 39 | '{}\n{}'.format(output, err)) 40 | print('freeline init success') 41 | 42 | 43 | def is_main_project(module): 44 | config_path = os.path.join(module, 'build.gradle') 45 | if os.path.exists(config_path): 46 | content = get_file_content(config_path) 47 | if "apply plugin: 'com.antfortune.freeline'" in content: 48 | return True 49 | return False 50 | 51 | 52 | def symlink(base_dir, target_dir, fn): 53 | base_path = os.path.join(base_dir, fn) 54 | target_path = os.path.join(target_dir, fn) 55 | 56 | if not os.path.exists(base_path): 57 | raise FreelineException('file missing: {}'.format(base_path), ' Maybe you should sync freeline repo') 58 | 59 | if os.path.exists(target_path): 60 | os.remove(target_path) 61 | 62 | if is_windows_system(): 63 | copy(base_path, target_path) 64 | else: 65 | os.symlink(base_path, target_path) 66 | -------------------------------------------------------------------------------- /freeline_core/tracing.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from logger import Logger 4 | 5 | 6 | class Tracing(object): 7 | def __init__(self, description): 8 | self.__name = "tracing" 9 | self.__description = description 10 | self.__start_time = 0 11 | 12 | def __enter__(self): 13 | self.__start_time = time.time() 14 | 15 | def __exit__(self, exc_type, exc_val, exc_tb): 16 | if exc_tb: 17 | return False 18 | else: 19 | self.debug(time.time() - self.__start_time) 20 | return True 21 | 22 | def debug(self, execute_time): 23 | Logger.debug('[{}] {}: {}ms'.format(self.__name, self.__description, execute_time * 1000)) 24 | -------------------------------------------------------------------------------- /freeline_core/version.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf8 -*- 2 | import subprocess 3 | import os 4 | 5 | VERSION_FORMATTER = '{}({})' 6 | FREELINE_VERSION = 'v0.8.8' 7 | 8 | 9 | def get_freeline_version(): 10 | if is_git_dir(): 11 | return VERSION_FORMATTER.format(FREELINE_VERSION, get_git_short_version()) 12 | else: 13 | return FREELINE_VERSION 14 | 15 | 16 | def get_git_short_version(): 17 | # note: get git version 18 | # http://stackoverflow.com/questions/14989858/get-the-current-git-hash-in-a-python-script 19 | return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']) 20 | 21 | 22 | def is_git_dir(): 23 | project_root_path = os.path.abspath(os.path.join(os.path.realpath(__file__), os.pardir)) 24 | git_dir_path = os.path.join(project_root_path, '.git') 25 | if os.path.exists(git_dir_path) and os.path.isdir(git_dir_path): 26 | return True 27 | return False 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # release version or dependencies 21 | FREELINE_ARTIFACT_VERSION=0.8.9-SNAPSHOT 22 | 23 | FREELINE_RELEASE_VERSION=0.8.9-SNAPSHOT 24 | 25 | # develop with idea 26 | android.injected.build.model.only.versioned=3 27 | # disable aapt2 28 | android.enableAapt2=false 29 | 30 | # debug gradle 31 | #org.gradle.debug=true 32 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Aug 31 15:33:14 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /release-tools/DexMerge.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/release-tools/DexMerge.jar -------------------------------------------------------------------------------- /release-tools/FreelineAapt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/release-tools/FreelineAapt -------------------------------------------------------------------------------- /release-tools/FreelineAapt.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/release-tools/FreelineAapt.exe -------------------------------------------------------------------------------- /release-tools/FreelineAapt_: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/release-tools/FreelineAapt_ -------------------------------------------------------------------------------- /release-tools/classrelated.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/release-tools/classrelated.jar -------------------------------------------------------------------------------- /release-tools/databinding-cli.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/release-tools/databinding-cli.jar -------------------------------------------------------------------------------- /release-tools/databinding-cli8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/release-tools/databinding-cli8.jar -------------------------------------------------------------------------------- /release-tools/desugar_deploy.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/release-tools/desugar_deploy.jar -------------------------------------------------------------------------------- /release-tools/dexmerger: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (C) 2012 The Android Open Source Project 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Set up prog to be the path of this script, including following symlinks, 18 | # and set up progdir to be the fully-qualified pathname of its directory. 19 | prog="$0" 20 | while [ -h "${prog}" ]; do 21 | newProg=`/bin/ls -ld "${prog}"` 22 | newProg=`expr "${newProg}" : ".* -> \(.*\)$"` 23 | if expr "x${newProg}" : 'x/' >/dev/null; then 24 | prog="${newProg}" 25 | else 26 | progdir=`dirname "${prog}"` 27 | prog="${progdir}/${newProg}" 28 | fi 29 | done 30 | oldwd=`pwd` 31 | progdir=`dirname "${prog}"` 32 | cd "${progdir}" 33 | progdir=`pwd` 34 | prog="${progdir}"/`basename "${prog}"` 35 | cd "${oldwd}" 36 | 37 | jarfile=dx.jar 38 | libdir="$progdir" 39 | 40 | if [ ! -r "$libdir/$jarfile" ]; then 41 | # set dx.jar location for the SDK case 42 | libdir=`dirname "$progdir"`/platform-tools/lib 43 | fi 44 | 45 | 46 | if [ ! -r "$libdir/$jarfile" ]; then 47 | # set dx.jar location for the Android tree case 48 | libdir=`dirname "$progdir"`/framework 49 | fi 50 | 51 | if [ ! -r "$libdir/$jarfile" ]; then 52 | echo `basename "$prog"`": can't find $jarfile" 53 | exit 1 54 | fi 55 | 56 | # By default, give dexmerger a max heap size of 1 gig. This can be overridden 57 | # by using a "-J" option (see below). 58 | defaultMx="-Xmx1024M" 59 | 60 | # The following will extract any initial parameters of the form 61 | # "-J" from the command line and pass them to the Java 62 | # invocation (instead of to dexmerger). This makes it possible for you to add 63 | # a command-line parameter such as "-JXmx256M" in your scripts, for 64 | # example. "java" (with no args) and "java -X" give a summary of 65 | # available options. 66 | 67 | javaOpts="" 68 | 69 | while expr "x$1" : 'x-J' >/dev/null; do 70 | opt=`expr "x$1" : 'x-J\(.*\)'` 71 | javaOpts="${javaOpts} -${opt}" 72 | if expr "x${opt}" : "xXmx[0-9]" >/dev/null; then 73 | defaultMx="no" 74 | fi 75 | shift 76 | done 77 | 78 | if [ "${defaultMx}" != "no" ]; then 79 | javaOpts="${javaOpts} ${defaultMx}" 80 | fi 81 | 82 | if [ "$OSTYPE" = "cygwin" ]; then 83 | # For Cygwin, convert the jarfile path into native Windows style. 84 | jarpath=`cygpath -w "$libdir/$jarfile"` 85 | else 86 | jarpath="$libdir/$jarfile" 87 | fi 88 | 89 | exec java $javaOpts -cp "$jarpath" com.android.dx.merge.DexMerger "$@" 90 | -------------------------------------------------------------------------------- /release-tools/dx: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (C) 2007 The Android Open Source Project 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Set up prog to be the path of this script, including following symlinks, 18 | # and set up progdir to be the fully-qualified pathname of its directory. 19 | prog="$0" 20 | while [ -h "${prog}" ]; do 21 | newProg=`/bin/ls -ld "${prog}"` 22 | newProg=`expr "${newProg}" : ".* -> \(.*\)$"` 23 | if expr "x${newProg}" : 'x/' >/dev/null; then 24 | prog="${newProg}" 25 | else 26 | progdir=`dirname "${prog}"` 27 | prog="${progdir}/${newProg}" 28 | fi 29 | done 30 | oldwd=`pwd` 31 | progdir=`dirname "${prog}"` 32 | cd "${progdir}" 33 | progdir=`pwd` 34 | prog="${progdir}"/`basename "${prog}"` 35 | cd "${oldwd}" 36 | 37 | jarfile=dx.jar 38 | libdir="freeline/release-tools" 39 | 40 | if [ ! -r "$libdir/$jarfile" ]; then 41 | # set dx.jar location for the SDK case 42 | libdir="$libdir/lib" 43 | fi 44 | 45 | 46 | if [ ! -r "$libdir/$jarfile" ]; then 47 | # set dx.jar location for the Android tree case 48 | libdir=`dirname "$progdir"`/framework 49 | fi 50 | 51 | if [ ! -r "$libdir/$jarfile" ]; then 52 | echo `basename "$prog"`": can't find $jarfile" 53 | exit 1 54 | fi 55 | 56 | # By default, give dx a max heap size of 1 gig. This can be overridden 57 | # by using a "-J" option (see below). 58 | defaultMx="-Xmx1024M" 59 | 60 | # The following will extract any initial parameters of the form 61 | # "-J" from the command line and pass them to the Java 62 | # invocation (instead of to dx). This makes it possible for you to add 63 | # a command-line parameter such as "-JXmx256M" in your scripts, for 64 | # example. "java" (with no args) and "java -X" give a summary of 65 | # available options. 66 | 67 | javaOpts="" 68 | 69 | while expr "x$1" : 'x-J' >/dev/null; do 70 | opt=`expr "x$1" : 'x-J\(.*\)'` 71 | javaOpts="${javaOpts} -${opt}" 72 | if expr "x${opt}" : "xXmx[0-9]" >/dev/null; then 73 | defaultMx="no" 74 | fi 75 | shift 76 | done 77 | 78 | if [ "${defaultMx}" != "no" ]; then 79 | javaOpts="${javaOpts} ${defaultMx}" 80 | fi 81 | 82 | if [ "$OSTYPE" = "cygwin" ]; then 83 | # For Cygwin, convert the jarfile path into native Windows style. 84 | jarpath=`cygpath -w "$libdir/$jarfile"` 85 | else 86 | jarpath="$libdir/$jarfile" 87 | fi 88 | 89 | exec java $javaOpts -jar "$jarpath" "$@" 90 | -------------------------------------------------------------------------------- /release-tools/dx.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Copyright (C) 2007 The Android Open Source Project 3 | REM 4 | REM Licensed under the Apache License, Version 2.0 (the "License"); 5 | REM you may not use this file except in compliance with the License. 6 | REM You may obtain a copy of the License at 7 | REM 8 | REM http://www.apache.org/licenses/LICENSE-2.0 9 | REM 10 | REM Unless required by applicable law or agreed to in writing, software 11 | REM distributed under the License is distributed on an "AS IS" BASIS, 12 | REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | REM See the License for the specific language governing permissions and 14 | REM limitations under the License. 15 | 16 | REM don't modify the caller's environment 17 | setlocal 18 | 19 | REM Locate dx.jar in the directory where dx.bat was found and start it. 20 | 21 | REM Set up prog to be the path of this script, including following symlinks, 22 | REM and set up progdir to be the fully-qualified pathname of its directory. 23 | set prog=%~f0 24 | 25 | rem Check we have a valid Java.exe in the path. 26 | set java_exe= 27 | if exist "%~dp0..\tools\lib\find_java.bat" call "%~dp0..\tools\lib\find_java.bat" 28 | if exist "%~dp0..\..\tools\lib\find_java.bat" call "%~dp0..\..\tools\lib\find_java.bat" 29 | if not defined java_exe goto :EOF 30 | 31 | set jarfile=dx.jar 32 | set "frameworkdir=%~dp0" 33 | rem frameworkdir must not end with a dir sep. 34 | set "frameworkdir=%frameworkdir:~0,-1%" 35 | 36 | if exist "%frameworkdir%\%jarfile%" goto JarFileOk 37 | set "frameworkdir=%~dp0lib" 38 | 39 | if exist "%frameworkdir%\%jarfile%" goto JarFileOk 40 | set "frameworkdir=%~dp0..\framework" 41 | 42 | :JarFileOk 43 | 44 | set "jarpath=%frameworkdir%\%jarfile%" 45 | 46 | set javaOpts= 47 | set args= 48 | 49 | REM By default, give dx a max heap size of 1 gig and a stack size of 1meg. 50 | rem This can be overridden by using "-JXmx..." and "-JXss..." options below. 51 | set defaultXmx=-Xmx1024M 52 | set defaultXss=-Xss1m 53 | 54 | REM Capture all arguments that are not -J options. 55 | REM Note that when reading the input arguments with %1, the cmd.exe 56 | REM automagically converts --name=value arguments into 2 arguments "--name" 57 | REM followed by "value". Dx has been changed to know how to deal with that. 58 | set params= 59 | 60 | :firstArg 61 | if [%1]==[] goto endArgs 62 | set a=%~1 63 | 64 | if [%defaultXmx%]==[] goto notXmx 65 | if %a:~0,5% NEQ -JXmx goto notXmx 66 | set defaultXmx= 67 | :notXmx 68 | 69 | if [%defaultXss%]==[] goto notXss 70 | if %a:~0,5% NEQ -JXss goto notXss 71 | set defaultXss= 72 | :notXss 73 | 74 | if %a:~0,2% NEQ -J goto notJ 75 | set javaOpts=%javaOpts% -%a:~2% 76 | shift /1 77 | goto firstArg 78 | 79 | :notJ 80 | set params=%params% %1 81 | shift /1 82 | goto firstArg 83 | 84 | :endArgs 85 | 86 | set javaOpts=%javaOpts% %defaultXmx% %defaultXss% 87 | call "%java_exe%" %javaOpts% -Djava.ext.dirs="%frameworkdir%" -jar "%jarpath%" %params% 88 | 89 | -------------------------------------------------------------------------------- /release-tools/dx.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/release-tools/dx.jar -------------------------------------------------------------------------------- /release-tools/jasmin: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright (C) 2007 The Android Open Source Project 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # Set up prog to be the path of this script, including following symlinks, 18 | # and set up progdir to be the fully-qualified pathname of its directory. 19 | prog="$0" 20 | while [ -h "${prog}" ]; do 21 | newProg=`/bin/ls -ld "${prog}"` 22 | newProg=`expr "${newProg}" : ".* -> \(.*\)$"` 23 | if expr "x${newProg}" : 'x/' >/dev/null; then 24 | prog="${newProg}" 25 | else 26 | progdir=`dirname "${prog}"` 27 | prog="${progdir}/${newProg}" 28 | fi 29 | done 30 | oldwd=`pwd` 31 | progdir=`dirname "${prog}"` 32 | cd "${progdir}" 33 | progdir=`pwd` 34 | prog="${progdir}"/`basename "${prog}"` 35 | cd "${oldwd}" 36 | 37 | libdir=`dirname $progdir`/etc 38 | 39 | exec java -jar $libdir/jasmin.jar "$@" 40 | -------------------------------------------------------------------------------- /release-tools/jasmin.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/release-tools/jasmin.jar -------------------------------------------------------------------------------- /release-tools/lib64/libc++.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alibaba/freeline/818e5de64f630a4c506499d7208b761061cb9d1f/release-tools/lib64/libc++.so -------------------------------------------------------------------------------- /release-tools/mainDexClasses.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | REM Copyright (C) 2013 The Android Open Source Project 3 | REM 4 | REM Licensed under the Apache License, Version 2.0 (the "License"); 5 | REM you may not use this file except in compliance with the License. 6 | REM You may obtain a copy of the License at 7 | REM 8 | REM http://www.apache.org/licenses/LICENSE-2.0 9 | REM 10 | REM Unless required by applicable law or agreed to in writing, software 11 | REM distributed under the License is distributed on an "AS IS" BASIS, 12 | REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | REM See the License for the specific language governing permissions and 14 | REM limitations under the License. 15 | 16 | REM don't modify the caller's environment 17 | setlocal 18 | 19 | rem Check we have a valid Java.exe in the path. 20 | set java_exe= 21 | if exist "%~dp0..\tools\lib\find_java.bat" call "%~dp0..\tools\lib\find_java.bat" 22 | if exist "%~dp0..\..\tools\lib\find_java.bat" call "%~dp0..\..\tools\lib\find_java.bat" 23 | if not defined java_exe goto :EOF 24 | 25 | set baserules="%~dp0\mainDexClasses.rules" 26 | 27 | REM Locate dx.jar in the directory where dx.bat was found. 28 | set jarfile=dx.jar 29 | set "frameworkdir=%~dp0" 30 | rem frameworkdir must not end with a dir sep. 31 | set "frameworkdir=%frameworkdir:~0,-1%" 32 | if exist "%frameworkdir%\%jarfile%" goto JarFileOk 33 | set "frameworkdir=%~dp0lib" 34 | 35 | if exist "%frameworkdir%\%jarfile%" goto JarFileOk 36 | set "frameworkdir=%~dp0..\framework" 37 | :JarFileOk 38 | 39 | set "jarpath=%frameworkdir%\%jarfile%" 40 | 41 | set "shrinkedAndroidJar=%SHRINKED_ANDROID_JAR% 42 | if exist "%shrinkedAndroidJar%" goto shrinkedAndroidOk 43 | set "shrinkedAndroidJar=shrinkedAndroid.jar" 44 | 45 | if exist "%shrinkedAndroidJar%" goto shrinkedAndroidOk 46 | set "shrinkedAndroidJar=%frameworkdir%\%shrinkedAndroidJar%" 47 | 48 | :shrinkedAndroidOk 49 | set "proguardExec=proguard.bat" 50 | set "proguard=%PROGUARD_HOME%\bin\%proguardExec%" 51 | 52 | if exist "%proguard%" goto proguardOk 53 | REM set proguard location for the SDK case 54 | set "PROGUARD_HOME=%~dp0\..\..\tools\proguard" 55 | set "proguard=%PROGUARD_HOME%\bin\%proguardExec%" 56 | 57 | if exist "%proguard%" goto proguardOk 58 | REM set proguard location for the Android tree case 59 | set "PROGUARD_HOME=%~dp0\..\..\..\..\external\proguard" 60 | set "proguard=%PROGUARD_HOME%\bin\%proguardExec%" 61 | 62 | :proguardOk 63 | REM Capture all arguments. 64 | REM Note that when reading the input arguments with %1, the cmd.exe 65 | REM automagically converts --name=value arguments into 2 arguments "--name" 66 | REM followed by "value". Dx has been changed to know how to deal with that. 67 | set params= 68 | 69 | set output= 70 | 71 | :firstArg 72 | if [%1]==[] goto endArgs 73 | 74 | if %1 NEQ --output goto notOut 75 | set "output=%2" 76 | shift 77 | shift 78 | goto firstArg 79 | 80 | :notOut 81 | if defined params goto usage 82 | set params=%1 83 | shift 84 | goto firstArg 85 | 86 | :endArgs 87 | if defined params ( goto makeTmpJar ) else ( goto usage ) 88 | 89 | :makeTmpJar 90 | set "tmpJar=%TMP%\mainDexClasses-%RANDOM%.tmp.jar" 91 | if exist "%tmpJar%" goto makeTmpJar 92 | echo "" > "%tmpJar%" 93 | set "exitStatus=0" 94 | 95 | 96 | call "%proguard%" -injars %params% -dontwarn -forceprocessing -outjars "%tmpJar%" -libraryjars "%shrinkedAndroidJar%" -dontoptimize -dontobfuscate -dontpreverify -include "%baserules%" 1>nul 97 | 98 | if DEFINED output goto redirect 99 | call "%java_exe%" -Djava.ext.dirs="%frameworkdir%" com.android.multidex.ClassReferenceListBuilder "%tmpJar%" "%params%" 100 | goto afterClassReferenceListBuilder 101 | :redirect 102 | call "%java_exe%" -Djava.ext.dirs="%frameworkdir%" com.android.multidex.ClassReferenceListBuilder "%tmpJar%" "%params%" 1>"%output%" 103 | :afterClassReferenceListBuilder 104 | 105 | del %tmpJar% 106 | exit /b 107 | 108 | :usage 109 | echo "Usage : %0 [--output ] " 110 | exit /b 1 111 | -------------------------------------------------------------------------------- /release-tools/mainDexClasses.rules: -------------------------------------------------------------------------------- 1 | -keep public class * extends android.app.Instrumentation { 2 | (); 3 | } 4 | -keep public class * extends android.app.Application { 5 | (); 6 | } 7 | -keep public class * extends android.app.Activity { 8 | (); 9 | } 10 | -keep public class * extends android.app.Service { 11 | (); 12 | } 13 | -keep public class * extends android.content.ContentProvider { 14 | (); 15 | } 16 | -keep public class * extends android.content.BroadcastReceiver { 17 | (); 18 | } 19 | -keep public class * extends android.app.backup.BackupAgent { 20 | (); 21 | } 22 | -keep class android.support.multidex.** { 23 | *; 24 | } -------------------------------------------------------------------------------- /release-tools/manifest.txt: -------------------------------------------------------------------------------- 1 | Main-Class: com.android.dx.command.Main 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | // freeline sample 2 | include ':freeline-sample:app' 3 | include ':freeline-sample:common' 4 | include ':freeline-sample:resources' 5 | include ':freeline-sample:ThirdParty:share' 6 | include ':freeline-sample:ThirdParty:payment' 7 | include ':freeline-sample:PureJavaLib' 8 | 9 | // freeline runtime 10 | include ':runtime' 11 | project(':runtime').projectDir = new File('freeline-runtime') 12 | include ':runtime-no-op' 13 | project(':runtime-no-op').projectDir = new File('freeline-runtime-no-op') 14 | 15 | // freeline databinding-cli 16 | include ':freeline-databinding-cli' 17 | 18 | // freeline gradle plugin 19 | include ':gradle' 20 | project(':gradle').projectDir = new File('freeline-gradle-plugin') 21 | 22 | // freeline studio plugin 23 | //include ':freeline-studio-plugin' --------------------------------------------------------------------------------