From f33a7075f59f571b5dbc7e3fcb75cd8b06d16986 Mon Sep 17 00:00:00 2001 From: qiyi Date: Sat, 9 Dec 2017 14:12:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Activity/Fragment=E7=94=9F?= =?UTF-8?q?=E5=91=BD=E5=91=A8=E6=9C=9F=E5=85=B3=E8=81=94=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 201 ++++++++++++++++++ README.md | 145 ++++++++----- build.gradle | 2 +- cc-settings.gradle | 2 +- cc/build.gradle | 3 +- .../java/com/billy/cc/core/component/CC.java | 82 ++++++- .../billy/cc/core/component/CCMonitor.java | 60 +++++- component_protect_demo/build.gradle | 2 +- demo/src/main/AndroidManifest.xml | 1 + .../java/com/billy/cc/demo/MainActivity.java | 6 + .../cc/demo/lifecycle/LifecycleActivity.java | 86 ++++++++ .../cc/demo/lifecycle/LifecycleFragment.java | 96 +++++++++ demo/src/main/res/anim/enter_from_right.xml | 8 + demo/src/main/res/anim/exit_to_left.xml | 8 + .../main/res/layout/activity_lifecycle.xml | 50 +++++ demo/src/main/res/layout/activity_main.xml | 10 +- image/CC.gif | Bin 289707 -> 662229 bytes 17 files changed, 689 insertions(+), 73 deletions(-) create mode 100644 LICENSE create mode 100644 demo/src/main/java/com/billy/cc/demo/lifecycle/LifecycleActivity.java create mode 100644 demo/src/main/java/com/billy/cc/demo/lifecycle/LifecycleFragment.java create mode 100644 demo/src/main/res/anim/enter_from_right.xml create mode 100644 demo/src/main/res/anim/exit_to_left.xml create mode 100644 demo/src/main/res/layout/activity_lifecycle.xml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 4fdb3a5..c7ca63f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# CC : ComponentCaller +# CC : ComponentCaller (可关联生命周期的组件化开发框架) ##### [![Join the chat at https://gitter.im/billy_home/CC](https://badges.gitter.im/billy_home/CC.svg)](https://gitter.im/billy_home/CC?utm_source=share-link&utm_medium=link&utm_campaign=share-link) [![Download](https://api.bintray.com/packages/hellobilly/android/cc/images/download.svg)](https://bintray.com/hellobilly/android/cc/_latestVersion) @@ -32,17 +32,18 @@ 1. 支持组件间相互调用(不只是Activity跳转,支持任意指令的调用/回调) - 2. 支持app间跨进程的组件调用(组件开发/调试时可单独作为app运行) - 3. 支持app间调用的开关及权限设置(满足不同级别的安全需求,默认打开状态且不需要权限) - 4. 支持同步/异步方式调用 - 5. 支持同步/异步方式实现组件 - 6. 调用方式不受实现方式的限制(例如:可以同步调用另一个组件的异步实现功能。注:不要在主线程同步调用耗时操作) - 7. 支持添加自定义拦截器(按添加的先后顺序执行) - 8. 支持超时设置 - 9. 支持手动取消 - 10. 编译时自动注册组件(IComponent),无需手动维护组件注册表(使用ASM修改字节码的方式实现) - 11. 支持动态注册/反注册组件(IDynamicComponent) - 12. 支持组件间传递Fragment等非基础类型的对象(组件在同一个app内时支持、跨app传递非基础类型的对象暂不支持) + 2. 支持组件调用与Activity、Fragment的生命周期关联 + 3. 支持app间跨进程的组件调用(组件开发/调试时可单独作为app运行) + 4. 支持app间调用的开关及权限设置(满足不同级别的安全需求,默认打开状态且不需要权限) + 5. 支持同步/异步方式调用 + 6. 支持同步/异步方式实现组件 + 7. 调用方式不受实现方式的限制(例如:可以同步调用另一个组件的异步实现功能。注:不要在主线程同步调用耗时操作) + 8. 支持添加自定义拦截器(按添加的先后顺序执行) + 9. 支持超时设置 + 10. 支持手动取消 + 11. 编译时自动注册组件(IComponent),无需手动维护组件注册表(使用ASM修改字节码的方式实现) + 12. 支持动态注册/反注册组件(IDynamicComponent) + 13. 支持组件间传递Fragment等非基础类型的对象(组件在同一个app内时支持、跨app传递非基础类型的对象暂不支持) - 执行过程全程监控 @@ -136,58 +137,85 @@ public class ComponentA implements IComponent { //同步调用,直接返回结果 CCResult result = CC.obtainBuilder("ComponentA").build().call(); //或 异步调用,不需要回调结果 -CC.obtainBuilder("ComponentA").build().callAsync(); +String callId = CC.obtainBuilder("ComponentA").build().callAsync(); //或 异步调用,在子线程执行回调 -CC.obtainBuilder("ComponentA").build().callAsync(new IComponentCallback(){...}); +String callId = CC.obtainBuilder("ComponentA").build().callAsync(new IComponentCallback(){...}); //或 异步调用,在主线程执行回调 -CC.obtainBuilder("ComponentA").build().callAsyncCallbackOnMainThread(new IComponentCallback(){...}); +String callId = CC.obtainBuilder("ComponentA").build().callAsyncCallbackOnMainThread(new IComponentCallback(){...}); ``` #### 3. 进阶使用 -- 设置Context信息 -```java -builder.setContext(activity) -``` -- 超时时间设置 -```java -builder.setTimeout(1000) -``` -- 参数传递 -```java -builder.addParam("name", "billy").addParam("id", 12345) -``` -- 设置返回信息 ```java +//开启/关闭debug日志打印 +CC.enableDebug(trueOrFalse); +//开启/关闭组件调用详细日志打印 +CC.enableVerboseLog(trueOrFalse); +//开启/关闭跨app调用 +CC.enableRemoteCC(trueOrFalse) +//中止指定callId的组件调用任务 +CC.cancel(callId) +//设置Context信息 +CC.obtainBuilder("ComponentA")...setContext(context)...build().callAsync() +//关联Activity的生命周期,在onDestroy方法调用后自动执行cancel +CC.obtainBuilder("ComponentA")...cancelOnDestroyWith(activity)...build().callAsync() +//关联v4包Fragment的生命周期,在onDestroy方法调用后自动执行cancel +CC.obtainBuilder("ComponentA")...cancelOnDestroyWith(fragment)...build().callAsync() +//设置ActionName +CC.obtainBuilder("ComponentA")...setActionName(actionName)...build().callAsync() +//超时时间设置 +CC.obtainBuilder("ComponentA")...setTimeout(1000)...build().callAsync() +//参数传递 +CC.obtainBuilder("ComponentA")...addParam("name", "billy").addParam("id", 12345)...build().callAsync() + + +//设置成功的返回信息 CCResult.success(key1, value1).addData(key2, value2) -``` -- 发送结果给调用方 -```java -CC.sendCCResult(cc.getCallId(), ccResult) -``` -- 读取调用状态 -```java +//设置失败的返回信息 +CCResult.error(message).addData(key, value) +//发送结果给调用方 +CC.sendCCResult(cc.getCallId(), ccResult) +//读取调用成功与否 ccResult.isSuccess() -``` -- 读取调用错误信息 -```java -ccResult.getErrorMessage() -``` -- 读取返回信息 -```java -ccResult.getDataMap().get(key1) -``` -- 开启/关闭跨app调用 -```java -CC.enableRemoteCC(trueOrFalse) -``` -- 开启/关闭debug日志打印 -```java -CC.enableDebug(trueOrFalse); -``` -- 开启/关闭组件调用详细日志打印 -```java -CC.enableVerboseLog(trueOrFalse); +//读取调用状态码(0:success, <0: 组件调用失败, 1:调用到了组件,业务执行判断失败,如调用登录组件最终登录失败) +ccResult.getCode() +//读取调用错误信息 +ccResult.getErrorMessage() +//读取返回的附加信息 +Map data = ccResult.getDataMap(); +if (data != null) { + Object value = data.get(key) +} ``` +状态码清单: +```java +// CC调用成功 +public static final int CODE_SUCCESS = 0; +// 组件调用成功,但业务逻辑判定为失败 +public static final int CODE_ERROR_BUSINESS = 1; +// 保留状态码:默认的请求错误code +public static final int CODE_ERROR_DEFAULT = -1; +// 没有指定组件名称 +public static final int CODE_ERROR_COMPONENT_NAME_EMPTY = -2; +/** + * result不该为null + * 例如:组件回调时使用 CC.sendCCResult(callId, null) 或 interceptor返回null + */ +public static final int CODE_ERROR_NULL_RESULT = -3; +// 调用过程中出现exception +public static final int CODE_ERROR_EXCEPTION_RESULT = -4; +// 没有找到组件能处理此次调用请求 +public static final int CODE_ERROR_NO_COMPONENT_FOUND = -5; +// context 为null,通过反射获取application失败 +public static final int CODE_ERROR_CONTEXT_NULL = -6; +// 跨app调用组件时,LocalSocket连接出错 +public static final int CODE_ERROR_CONNECT_FAILED = -7; +// 取消 +public static final int CODE_ERROR_CANCELED = -8; +// 超时 +public static final int CODE_ERROR_TIMEOUT = -9; +// 未调用CC.sendCCResult(callId, ccResult)方法 +public static final int CODE_ERROR_CALLBACK_NOT_INVOKED = -10; +``` - 自定义拦截器 1. 实现ICCInterceptor接口( 只有一个方法: intercept(Chain chain) ) @@ -219,7 +247,7 @@ CC.enableVerboseLog(trueOrFalse); - 给跨app组件的调用添加自定义权限限制 - 新建一个module - - 在该module的build.gradle中添加依赖: `compile 'com.billy.android:cc:0.2.0'` + - 在该module的build.gradle中添加依赖: `compile 'com.billy.android:cc:0.3.0'` - 在该module的src/main/AndroidManifest.xml中设置权限及权限的级别,参考[component_protect_demo](https://github.com/luckybilly/CC/blob/master/component_protect_demo/src/main/AndroidManifest.xml) - 其它每个module都额外依赖此module,或自定义一个全局的cc-settings.gradle,参考[cc-settings-demo-b.gradle](https://github.com/luckybilly/CC/blob/master/cc-settings-demo-b.gradle) @@ -283,6 +311,11 @@ CC.enableVerboseLog(trueOrFalse); # 更新日志 +- 2017.12.09 V0.3.0版 + + + 添加Activity及Fragment生命周期关联的功能并添加对应的demo + - 2017.11.29 V0.2.0版 diff --git a/build.gradle b/build.gradle index a4b6bc0..3f6d1b7 100644 --- a/build.gradle +++ b/build.gradle @@ -30,7 +30,7 @@ ext { buildVersion = '25.0.0' minVersion = 8 - supportVersion = '25.1.0' + supportVersion = '25.3.1' } task clean(type: Delete) { delete rootProject.buildDir diff --git a/cc-settings.gradle b/cc-settings.gradle index 40374b6..9e0d4ee 100644 --- a/cc-settings.gradle +++ b/cc-settings.gradle @@ -47,7 +47,7 @@ android { } } dependencies { - compile "com.billy.android:cc:0.2.0" + compile "com.billy.android:cc:0.3.0" // compile project(":cc") } diff --git a/cc/build.gradle b/cc/build.gradle index afee7fc..9ea7ba8 100644 --- a/cc/build.gradle +++ b/cc/build.gradle @@ -25,6 +25,7 @@ android { dependencies { compile "com.billy.android:pools:0.0.5" + provided "com.android.support:appcompat-v7:${rootProject.supportVersion}" } sourceCompatibility = 1.7 @@ -43,7 +44,7 @@ ext { siteUrl = 'https://github.com/luckybilly/CC' gitUrl = 'git@github.com:luckybilly/CC.git' - libraryVersion = '0.2.0' + libraryVersion = '0.3.0' developerId = 'billy' developerName = 'billy' diff --git a/cc/src/main/java/com/billy/cc/core/component/CC.java b/cc/src/main/java/com/billy/cc/core/component/CC.java index feb9ef7..2db2be4 100644 --- a/cc/src/main/java/com/billy/cc/core/component/CC.java +++ b/cc/src/main/java/com/billy/cc/core/component/CC.java @@ -1,9 +1,13 @@ package com.billy.cc.core.component; import android.annotation.SuppressLint; +import android.app.Activity; import android.app.Application; import android.content.Context; +import android.os.Build; import android.os.Looper; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; import android.text.TextUtils; import android.util.Log; @@ -52,11 +56,17 @@ public class CC { private static Application application; + WeakReference cancelOnDestroyActivity; + + WeakReference cancelOnDestroyFragment; + + static { try { //通过反射的方式来获取当前进程的application对象 - application = (Application) Class.forName("android.app.ActivityThread") + Application application = (Application) Class.forName("android.app.ActivityThread") .getMethod("currentApplication").invoke(null); + init(application); } catch(Exception e) { e.printStackTrace(); } @@ -67,8 +77,13 @@ public class CC { * 在Application.onCreate(...)中调用 * @param app 为了防止反射获取application对象失败,预留此初始化功能 */ - public static void init(Application app) { - application = app; + public static synchronized void init(Application app) { + if (application == null && app != null) { + application = app; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + application.registerActivityLifecycleCallbacks(new CCMonitor.ActivityMonitor()); + } + } } private static final ObjPool BUILDER_POOL = new ObjPool() { @@ -90,9 +105,8 @@ protected Builder newInstance(String componentName) { private final Map params = new HashMap<>(); /** * 回调对象 - * 采用弱引用防止内存泄露 */ - private WeakReference callback; + private IComponentCallback callback; /** * 是否异步执行 */ @@ -234,6 +248,31 @@ public Builder addInterceptor(ICCInterceptor interceptor) { } return this; } + /** + * 设置activity.onDestroy时自动cancel + * @param activity 监控此activity的生命周期,在onDestroy方法被调用后若cc未执行完则自动cancel + * @return Builder自身 + */ + public Builder cancelOnDestroyWith(Activity activity) { + if (activity != null) { + cr.cancelOnDestroyActivity = new WeakReference<>(activity); + } + return this; + } + + /** + * 设置fragment.onDestroy时自动cancel + * @param fragment 监控此fragment的生命周期,在onDestroy方法被调用后若cc未执行完则自动cancel + * @return Builder自身 + */ + public Builder cancelOnDestroyWith(Fragment fragment) { + if (fragment != null) { + cr.cancelOnDestroyFragment = new WeakReference<>(fragment); + } + return this; + } + + /** * 构建CC对象 @@ -380,10 +419,35 @@ void wait4Result() { } IComponentCallback getCallback() { - if (callback != null) { - return callback.get(); + return callback; + } + + /** + * 在onDestroy后,自动cancel + */ + void cancelOnDestroy(Object reason) { + if (!isFinished()) { + if (VERBOSE_LOG) { + verboseLog(callId, "call cancel on " + reason + " destroyed"); + } + cancel(); + } + } + + void addCancelOnFragmentDestroyIfSet() { + if (cancelOnDestroyFragment == null) { + return; + } + Fragment fragment = cancelOnDestroyFragment.get(); + if (fragment == null) { + return; + } + FragmentManager manager = fragment.getFragmentManager(); + if (manager != null) { + manager.registerFragmentLifecycleCallbacks( + new CCMonitor.FragmentMonitor(this) + , false); } - return null; } String getComponentName() { @@ -422,7 +486,7 @@ public String callAsyncCallbackOnMainThread(IComponentCallback callback) { private String processCallAsync(IComponentCallback callback) { if (callback != null) { - this.callback = new WeakReference<>(callback); + this.callback = callback; } this.async = true; //调用方未设置超时时间,默认为无超时时间 diff --git a/cc/src/main/java/com/billy/cc/core/component/CCMonitor.java b/cc/src/main/java/com/billy/cc/core/component/CCMonitor.java index 8d2f9b1..70db479 100644 --- a/cc/src/main/java/com/billy/cc/core/component/CCMonitor.java +++ b/cc/src/main/java/com/billy/cc/core/component/CCMonitor.java @@ -1,5 +1,15 @@ package com.billy.cc.core.component; +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.Application; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; + +import java.lang.ref.WeakReference; +import java.util.Collection; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; @@ -7,7 +17,6 @@ * CC监控系统 * 每个CC对象有自己的超时时间点, * 维护一个待监控的CC列表,当列表不为空时,启动一个线程进行监控 - * 每10ms循环遍历一次,若列表为空,则退出线程,下次添加监控的时候再启动一个新线程 * @author billy.qi */ class CCMonitor { @@ -21,6 +30,7 @@ class CCMonitor { static void addMonitorFor(CC cc) { if (cc != null) { CC_MAP.put(cc.getCallId(), cc); + cc.addCancelOnFragmentDestroyIfSet(); long timeoutAt = cc.timeoutAt; if (timeoutAt > 0) { if (minTimeoutAt > timeoutAt) { @@ -89,4 +99,50 @@ public void run() { } } -} + /** + * activity lifecycle monitor + * + */ + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + static class ActivityMonitor implements Application.ActivityLifecycleCallbacks { + @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { } + @Override public void onActivityStarted(Activity activity) { } + @Override public void onActivityResumed(Activity activity) { } + @Override public void onActivityPaused(Activity activity) { } + @Override public void onActivityStopped(Activity activity) { } + @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { } + @Override public void onActivityDestroyed(Activity activity) { + Collection values = CC_MAP.values(); + for (CC cc : values) { + if (!cc.isFinished() && cc.cancelOnDestroyActivity != null + && cc.cancelOnDestroyActivity.get() == activity) { + cc.cancelOnDestroy(activity); + } + } + } + } + + static class FragmentMonitor extends FragmentManager.FragmentLifecycleCallbacks { + WeakReference reference; + + FragmentMonitor(CC cc) { + this.reference = new WeakReference<>(cc); + } + + @Override + public void onFragmentDestroyed(FragmentManager fm, Fragment f) { + if (reference != null) { + CC cc = reference.get(); + if (cc != null && !cc.isFinished()) { + WeakReference fragReference = cc.cancelOnDestroyFragment; + if (fragReference != null) { + Fragment fragment = fragReference.get(); + if (f == fragment) { + cc.cancelOnDestroy(f); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/component_protect_demo/build.gradle b/component_protect_demo/build.gradle index 686c1c5..d6a2854 100644 --- a/component_protect_demo/build.gradle +++ b/component_protect_demo/build.gradle @@ -22,6 +22,6 @@ android { } dependencies { - compile "com.billy.android:cc:0.2.0" + compile "com.billy.android:cc:0.3.0" // compile project(":cc") } diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml index 79189be..fa6b460 100644 --- a/demo/src/main/AndroidManifest.xml +++ b/demo/src/main/AndroidManifest.xml @@ -16,6 +16,7 @@ + diff --git a/demo/src/main/java/com/billy/cc/demo/MainActivity.java b/demo/src/main/java/com/billy/cc/demo/MainActivity.java index 63c9dc2..ca233bb 100644 --- a/demo/src/main/java/com/billy/cc/demo/MainActivity.java +++ b/demo/src/main/java/com/billy/cc/demo/MainActivity.java @@ -1,5 +1,6 @@ package com.billy.cc.demo; +import android.content.Intent; import android.os.Bundle; import android.support.annotation.IdRes; import android.support.v7.app.AppCompatActivity; @@ -10,6 +11,7 @@ import com.billy.cc.core.component.CC; import com.billy.cc.core.component.CCResult; import com.billy.cc.core.component.IComponentCallback; +import com.billy.cc.demo.lifecycle.LifecycleActivity; /** * @author billy @@ -24,6 +26,7 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); textView = (TextView) findViewById(R.id.console); addOnClickListeners(R.id.componentAOpenActivity + , R.id.test_lifecycle , R.id.componentAAsyncOpenActivity , R.id.componentAGetData , R.id.componentAAsyncGetData @@ -47,6 +50,9 @@ public void onClick(View v) { textView.setText(""); CCResult result = null; switch (v.getId()) { + case R.id.test_lifecycle: + startActivity(new Intent(this, LifecycleActivity.class)); + break; case R.id.componentAOpenActivity: result = CC.obtainBuilder("ComponentA") .setActionName("showActivityA") diff --git a/demo/src/main/java/com/billy/cc/demo/lifecycle/LifecycleActivity.java b/demo/src/main/java/com/billy/cc/demo/lifecycle/LifecycleActivity.java new file mode 100644 index 0000000..e01af98 --- /dev/null +++ b/demo/src/main/java/com/billy/cc/demo/lifecycle/LifecycleActivity.java @@ -0,0 +1,86 @@ +package com.billy.cc.demo.lifecycle; + +import android.os.Bundle; +import android.support.annotation.IdRes; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.widget.TextView; +import android.widget.Toast; + +import com.billy.cc.core.component.CC; +import com.billy.cc.core.component.CCResult; +import com.billy.cc.core.component.IComponentCallback; +import com.billy.cc.demo.JsonFormat; +import com.billy.cc.demo.R; + +/** + * 测试activity.onDestroy和fragment.onDestroy时,CC自动cancel + * @author billy.qi + * @since 17/12/9 11:06 + */ +public class LifecycleActivity extends AppCompatActivity implements View.OnClickListener { + + private TextView textView; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_lifecycle); + textView = (TextView) findViewById(R.id.console); + addOnClickListeners(R.id.start_cc + , R.id.finish_activity + , R.id.replace_fragment + ); + + } + private void showResult(CCResult result) { + String text = JsonFormat.format(result.toString()); + textView.setText(text); + Toast.makeText(CC.getApplication(), text, Toast.LENGTH_SHORT).show(); + } + + private void addOnClickListeners(@IdRes int... ids) { + if (ids != null) { + for (@IdRes int id : ids) { + findViewById(id).setOnClickListener(this); + } + } + } + + @Override + public void onClick(View v) { + + textView.setText(""); + switch (v.getId()) { + case R.id.start_cc: + CC.obtainBuilder("ComponentB") + .setActionName("getNetworkData") + .cancelOnDestroyWith(this) + .build() + .callAsyncCallbackOnMainThread(printResultCallback); + break; + case R.id.finish_activity: + finish(); + break; + case R.id.replace_fragment: + Fragment fragment = new LifecycleFragment(); + FragmentTransaction trans = getSupportFragmentManager().beginTransaction(); + trans.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left); + trans.replace(R.id.fragment, fragment); + trans.commit(); + break; + default: + break; + } + } + + IComponentCallback printResultCallback = new IComponentCallback() { + @Override + public void onResult(CC cc, CCResult result) { + showResult(result); + } + }; +} diff --git a/demo/src/main/java/com/billy/cc/demo/lifecycle/LifecycleFragment.java b/demo/src/main/java/com/billy/cc/demo/lifecycle/LifecycleFragment.java new file mode 100644 index 0000000..859240c --- /dev/null +++ b/demo/src/main/java/com/billy/cc/demo/lifecycle/LifecycleFragment.java @@ -0,0 +1,96 @@ +package com.billy.cc.demo.lifecycle; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.text.TextUtils; +import android.util.Log; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; +import android.widget.Toast; + +import com.billy.cc.core.component.CC; +import com.billy.cc.core.component.CCResult; +import com.billy.cc.core.component.IComponentCallback; +import com.billy.cc.demo.JsonFormat; + + +/** + * @author billy.qi + * @since 17/12/8 15:30 + */ +public class LifecycleFragment extends Fragment { + static int index = 1; + private int curIndex; + private TextView log; + + public LifecycleFragment() { + curIndex = index++; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.i("TestFragment", "TestFragment.onCreate:" + curIndex); + } + + private void log(String s) { + if (log != null && !TextUtils.isEmpty(s)) { + log.setText(s); + } + Log.i("TestFragment", s); + } + + @SuppressLint("SetTextI18n") + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + Context context = container.getContext(); + ScrollView scrollView = new ScrollView(context); + LinearLayout layout = new LinearLayout(context); + scrollView.addView(layout); + layout.setOrientation(LinearLayout.VERTICAL); + TextView textView = new TextView(context); + layout.addView(textView); + textView.setText("测试fragment生命周期绑定" + + "\n当前 fragment index=" + curIndex + + "\nCC耗时请求(3秒)已发送" + + "\n点击按钮销毁当前fragment并打开一个新的fragment."); + textView.setGravity(Gravity.CENTER); + CC.obtainBuilder("ComponentB") + .setActionName("getNetworkData") + .cancelOnDestroyWith(this) + .build() + .callAsyncCallbackOnMainThread(new IComponentCallback() { + @Override + public void onResult(CC cc, CCResult result) { + String text = "callId=" + cc.getCallId() + "\n" + JsonFormat.format(result.toString()); + Toast.makeText(CC.getApplication(), text, Toast.LENGTH_SHORT).show(); + log(text); + } + }); + log = new TextView(context); + + layout.addView(log); + return scrollView; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + log("TestFragment.onDestroyView:" + curIndex); + } + + @Override + public void onDestroy() { + super.onDestroy(); + log("TestFragment.onDestroy:" + curIndex); + } +} diff --git a/demo/src/main/res/anim/enter_from_right.xml b/demo/src/main/res/anim/enter_from_right.xml new file mode 100644 index 0000000..3150cd8 --- /dev/null +++ b/demo/src/main/res/anim/enter_from_right.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/demo/src/main/res/anim/exit_to_left.xml b/demo/src/main/res/anim/exit_to_left.xml new file mode 100644 index 0000000..f4a503a --- /dev/null +++ b/demo/src/main/res/anim/exit_to_left.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/demo/src/main/res/layout/activity_lifecycle.xml b/demo/src/main/res/layout/activity_lifecycle.xml new file mode 100644 index 0000000..4441f68 --- /dev/null +++ b/demo/src/main/res/layout/activity_lifecycle.xml @@ -0,0 +1,50 @@ + + + + +