evt = new HashMap<>();
45 | evt.put("time_consuming", (double) (System.currentTimeMillis() - elapsedTime) / 1000.);
46 | elapsedTime = -1;
47 | BusProvider.get().publishLocal("logReport/app_launch", evt);
48 | }
49 | }
50 |
51 | @Override
52 | public void onActivityPaused(Activity activity) {
53 |
54 | }
55 |
56 | @Override
57 | public void onActivityStopped(Activity activity) {
58 |
59 | }
60 |
61 | @Override
62 | public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
63 |
64 | }
65 |
66 | @Override
67 | public void onActivityDestroyed(Activity activity) {
68 |
69 | }
70 | });
71 | Router.init(application);
72 | }
73 |
74 | public ChannelInitProvider() {
75 | elapsedTime = System.currentTimeMillis();
76 | }
77 |
78 | @Override
79 | public void attachInfo(Context context, ProviderInfo info) {
80 | super.attachInfo(context, info);
81 | }
82 |
83 | @Override
84 | public boolean onCreate() {
85 | return false;
86 | }
87 |
88 | @Nullable
89 | @Override
90 | public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
91 | return null;
92 | }
93 |
94 | @Nullable
95 | @Override
96 | public String getType(@NonNull Uri uri) {
97 | return null;
98 | }
99 |
100 | @Nullable
101 | @Override
102 | public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
103 | return null;
104 | }
105 |
106 | @Override
107 | public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
108 | return 0;
109 | }
110 |
111 | @Override
112 | public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
113 | return 0;
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/realtime-channel/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | apply plugin: 'com.github.dcendents.android-maven'
4 | apply plugin: 'com.jfrog.bintray'
5 |
6 | android {
7 | compileSdkVersion 26
8 | buildToolsVersion "26.0.2"
9 |
10 | defaultConfig {
11 | minSdkVersion 15
12 | targetSdkVersion 26
13 | versionCode 1
14 | versionName "1.0"
15 |
16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
17 |
18 | }
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 | }
26 |
27 | dependencies {
28 | compile fileTree(dir: 'libs', include: ['*.jar'])
29 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
30 | exclude group: 'com.android.support', module: 'support-annotations'
31 | })
32 |
33 | // compile 'com.google.protobuf:protobuf-lite:3.0.1'
34 | // compile 'com.google.protobuf:protobuf-java-util:3.4.0'
35 | compile 'com.google.firebase:firebase-database:11.6.0'
36 | compile 'com.google.firebase:firebase-iid:11.6.0'
37 | // compile 'com.google.firebase:firebase-perf:11.6.0'
38 |
39 | testCompile 'junit:junit:4.12'
40 | }
41 |
42 | version = "0.8.0" // This is the library version used when deploying the artifact
43 | group = 'com.goodow.realtime' // 所在组
44 | def module_name = 'realtime-android' // 项目的名称
45 | def siteUrl = 'https://github.com/goodow/realtime-android' // 项目主页
46 | def gitUrl = 'https://github.com/goodow/realtime-android.git' // 项目的git地址
47 |
48 | install {
49 | repositories.mavenInstaller {
50 | // This generates POM.xml with proper parameters
51 | pom {
52 | project {
53 | packaging 'aar'
54 | name module_name // 名称
55 | url siteUrl
56 | licenses {
57 | license {
58 | name 'The Apache Software License, Version 2.0' // 开源协议名称
59 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' // 协议地址
60 | }
61 | }
62 | developers {
63 | developer {
64 | id 'larrytin' // 账号
65 | name 'Larry Tin' // 名称
66 | email 'dev@goodow.com' // 邮箱地址
67 | }
68 | }
69 | scm {
70 | connection gitUrl
71 | developerConnection gitUrl
72 | url siteUrl
73 | }
74 | }
75 | }
76 | }
77 | }
78 | task sourcesJar(type: Jar) {
79 | from android.sourceSets.main.java.srcDirs
80 | classifier = 'sources'
81 | }
82 | task javadoc(type: Javadoc) {
83 | source = android.sourceSets.main.java.srcDirs
84 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
85 | }
86 | task javadocJar(type: Jar, dependsOn: javadoc) {
87 | classifier = 'javadoc'
88 | from javadoc.destinationDir
89 | }
90 | artifacts {
91 | archives sourcesJar
92 | }
93 | Properties properties = new Properties()
94 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
95 | bintray {
96 | // 读取配置文件中的用户名和key
97 | user = properties.getProperty("bintray.user")
98 | key = properties.getProperty("bintray.apikey")
99 | configurations = ['archives']
100 | pkg {
101 | userOrg = "goodow"
102 | repo = "maven" // 你在bintray上创建的库的名称
103 | name = module_name // 在jcenter中的项目名称
104 | websiteUrl = siteUrl
105 | vcsUrl = gitUrl
106 | licenses = ["Apache-2.0"]
107 | publish = true
108 | }
109 | }
--------------------------------------------------------------------------------
/realtime-channel/protos/goodow_extras_option.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 | package goodow.protobuf;
3 |
4 | option objc_class_prefix = "GDDPB";
5 | option java_package = "com.goodow.realtime.channel.protobuf";
6 | option java_outer_classname = "ExtrasOptionProtos";
7 |
8 | import "google/protobuf/field_mask.proto";
9 | // import "goodow_bool.proto";
10 |
11 | // 用于指定bus中options的extras
12 |
13 | message ExtrasOption {
14 | ViewOption view_opt = 1;
15 | CacheControl caching = 2;
16 | string reply_topic = 3;
17 |
18 | RequestOption request_opt = 100;
19 | message RequestOption {
20 | uint32 retry_times = 1; // 重试次数, 推荐优先使用 GDCOptions qos = GDCQosAtLeastOnce 来设置需要重试
21 | };
22 | };
23 |
24 | message ViewOption {
25 | LaunchMode launch_mode = 1;
26 | StackMode stack_mode = 2; // 仅初始化时有效
27 |
28 | bool status_bar = 6;
29 | bool nav_bar = 7;
30 | uint32 status_bar_style = 8; // UIStatusBarStyle
31 | uint32 nav_bar_style = 9; // UIBarStyle
32 | bool hides_bottom_bar_when_pushed = 10; // 仅初始化时有效
33 | bool tab_bar = 11;
34 | uint32 supported_interface_orientations = 12; // UIInterfaceOrientationMask
35 | bool autorotate = 13; // 是否应该自动旋转
36 | bool nav_bar_translucent = 14;
37 |
38 | bool needs_refresh = 21; // 是否需要刷新数据
39 | bool attempt_rotation_to_device_orientation = 22;
40 | uint32 device_orientation = 23; // 更改设备的朝向 UIDeviceOrientation
41 | bool tool_bar = 24;
42 |
43 | uint32 preferred_interface_orientation_for_presentation = 30; // UIInterfaceOrientation
44 | uint32 modal_presentation_style = 31; // 仅初始化时有效 UIModalPresentationStyle
45 | uint32 modal_transition_style = 32; // 仅初始化时有效 UIModalTransitionStyle
46 | uint32 edges_for_extended_layout = 33; // 仅初始化时有效 UIRectEdge (不支持设为值UIRectEdgeNone)
47 | bool animated = 34; // 是否需要动画, 默认为 YES
48 | };
49 |
50 | // 是否创建新对象
51 | enum LaunchMode {
52 | LAUNCH_MODE_UNSET = 0;
53 | standard = 1; // 总是创建一个新的实例
54 | singleTop = 2; // 如果在堆栈顶部已有一个同类型的ViewController实例, 则复用该实例; 否则, 创建新实例
55 |
56 | singleTask = 3;
57 | singleInstance = 4; // 单例模式. 先寻找是否已存在该类型的实例, 若存在则回退历史栈直至可见, 不存在则新创建
58 |
59 | none = 5; // 不创建对象, 也不改变是否可见, 只转发消息
60 | }
61 |
62 | // 放入哪个历史回退栈里
63 | enum StackMode {
64 | STACK_MODE_UNSET = 0;
65 | push = 1; // push 到当前的 UINavigationController
66 | present = 2; // 使用 top presentViewController:controller
67 | presentPush = 3; // 使用 top presentViewController:[[UINavigationController alloc] initWithRootViewController:controller]
68 | root = 4; // 替换当前 window 的 rootViewController
69 | };
70 |
71 | // https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=zh-cn
72 | message CacheControl {
73 | Status status = 1;
74 | double max_age = 2; // 缓存过期时间, 单位为s
75 | string etag = 3; // 用于校验缓存是否有更新
76 | uint64 last_modified = 4;
77 | google.protobuf.FieldMask key_fields = 5; // 缓存的field paths 参见: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#fieldmask
78 | RequestCachePolicy request_policy = 6;
79 |
80 | enum Status {
81 | STATUS_UNSET = 0;
82 | Private = 1; // 只为单个用户缓存
83 | Public = 2; // 即使有关联的用户认证, 甚至响应状态码无法正常缓存, 响应也可以被缓存. 大多数情况下, public不是必须的, 因为明确的缓存信息(例如max_age)已表示响应可以被缓存.
84 | no_cache = 3; // 必须先与服务器确认是否有更新
85 | no_store = 4; // 禁止缓存
86 | unmodified = 5; // 服务器没有更新; 同时复用这个字段表示数据来自缓存
87 | }
88 |
89 | // 含义和 NSURLRequestCachePolicy 保持一致
90 | enum RequestCachePolicy {
91 | // reserved 4, 5;
92 | // reserved "ReloadIgnoringLocalAndRemoteCacheData", "ReloadRevalidatingCacheData";
93 |
94 | USE_PROTOCOL_CACHE_POLICY = 0; // 按 HTTP 缓存规范实现
95 | RELOAD_IGNORING_LOCAL_CACHE_DATA = 1; // 只发起网络请求, 不使用缓存
96 | RETURN_CACHE_DATA_ELSE_LOAD = 2; // 先读缓存, 不管是否过期, 有即返回缓存; 若无缓存则发起网络请求
97 | RETURN_CACHE_DATA_DONT_LOAD = 3; // 只读缓存, 不管是否过期, 有即返回缓存; 若无缓存则失败
98 |
99 | RELOAD_ELSE_RETURN_CACHE_DATA = 100; // 自定义策略: 先请求网络,若网络请求失败则返回缓存数据, 不管缓存是否过期
100 | };
101 | };
102 |
--------------------------------------------------------------------------------
/realtime-channel/src/main/java/com/goodow/realtime/channel/Bus.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Goodow.com
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 | * in compliance with the License. You may obtain a copy of the License at
6 | *
7 | * http://www.apache.org/licenses/LICENSE-2.0
8 | *
9 | * Unless required by applicable law or agreed to in writing, software distributed under the License
10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 | * or implied. See the License for the specific language governing permissions and limitations under
12 | * the License.
13 | */
14 | package com.goodow.realtime.channel;
15 |
16 | /**
17 | * A distributed lightweight event bus which can encompass multiple machines. The event bus
18 | * implements publish/subscribe, point to point messaging and request-response messaging.
19 | * Messages sent over the event bus are represented by instances of the {@link Message} class.
20 | * For publish/subscribe, messages can be published to a topic using one of the {@link #publish}
21 | * methods. A topic is a simple {@code String} instance.
22 | * Handlers are registered against a topic. There can be multiple handlers registered against
23 | * each topic, and a particular handler can be registered against multiple topics. The event
24 | * bus will route a sent message to all handlers which are registered against that topic.
25 | * For point to point messaging, messages can be sent to a topic using one of the {@link #send}
26 | * methods. The messages will be delivered to a single handler, if one is registered on that
27 | * topic. If more than one handler is registered on the same topic, the bus will choose one and
28 | * deliver the message to that. The bus will aim to fairly distribute messages in a round-robin way,
29 | * but does not guarantee strict round-robin under all circumstances.
30 | * The order of messages received by any specific handler from a specific sender should match the
31 | * order of messages sent from that sender.
32 | * When sending a message, a reply handler can be provided. If so, it will be called when the reply
33 | * from the receiver has been received. Reply messages can also be replied to, etc, ad infinitum
34 | * Different event bus instances can be clustered together over a network, to give a single logical
35 | * event bus.
36 | */
37 | public interface Bus {
38 |
39 | /**
40 | * Publish a message
41 | *
42 | * @param topic The topic to publish it to
43 | * @param msg The message
44 | */
45 | Bus publish(String topic, Object msg);
46 |
47 | /**
48 | * Publish a local message
49 | *
50 | * @param topic The topic to publish it to
51 | * @param msg The message
52 | */
53 | Bus publishLocal(String topic, Object msg);
54 |
55 | /**
56 | * Send a message
57 | *
58 | * @param topic The topic to send it to
59 | * @param msg The message
60 | * @param replyHandler Reply handler will be called when any reply from the recipient is received
61 | */
62 | Bus send(String topic, Object msg, Handler>> replyHandler);
63 |
64 | /**
65 | * Send a local message
66 | *
67 | * @param topic The topic to send it to
68 | * @param msg The message
69 | * @param replyHandler Reply handler will be called when any reply from the recipient is received
70 | */
71 | Bus sendLocal(String topic, Object msg, Handler>> replyHandler);
72 |
73 | /**
74 | * Registers a handler against the specified topic
75 | *
76 | * @param topicFilter The topicFilter to register it at
77 | * @param handler The handler
78 | * @return the handler registration, can be stored in order to unregister the handler later
79 | */
80 | @SuppressWarnings("rawtypes")
81 | Registration subscribe(String topicFilter, Handler extends Message> handler);
82 |
83 | /**
84 | * Registers a local handler against the specified topic. The handler info won't be propagated
85 | * across the cluster
86 | *
87 | * @param topicFilter The topicFilter to register it at
88 | * @param handler The handler
89 | */
90 | @SuppressWarnings("rawtypes")
91 | Registration subscribeLocal(String topicFilter, Handler extends Message> handler);
92 | }
--------------------------------------------------------------------------------
/realtime-channel/src/main/java/com/goodow/realtime/android/mvp/util/JsonMapper.java:
--------------------------------------------------------------------------------
1 | package com.goodow.realtime.android.mvp.util;
2 |
3 | import org.json.JSONArray;
4 | import org.json.JSONException;
5 | import org.json.JSONObject;
6 | import org.json.JSONStringer;
7 | import org.json.JSONTokener;
8 |
9 | import java.io.IOException;
10 | import java.util.ArrayList;
11 | import java.util.Collection;
12 | import java.util.HashMap;
13 | import java.util.Iterator;
14 | import java.util.List;
15 | import java.util.Map;
16 |
17 | /**
18 | * Created by larry on 2017/11/9.
19 | *
20 | * Helper class to convert from/to JSON strings.
21 | */
22 | public class JsonMapper {
23 |
24 | public static String serializeJson(Map object) throws IOException {
25 | return serializeJsonValue(object);
26 | }
27 |
28 | @SuppressWarnings("unchecked")
29 | public static String serializeJsonValue(Object object) throws IOException {
30 | if (object == null) {
31 | return "null";
32 | } else if (object instanceof String) {
33 | return JSONObject.quote((String) object);
34 | } else if (object instanceof Number) {
35 | try {
36 | return JSONObject.numberToString((Number) object);
37 | } catch (JSONException e) {
38 | throw new IOException("Could not serialize number", e);
39 | }
40 | } else if (object instanceof Boolean) {
41 | return ((Boolean) object) ? "true" : "false";
42 | } else {
43 | try {
44 | JSONStringer stringer = new JSONStringer();
45 | serializeJsonValue(object, stringer);
46 | return stringer.toString();
47 | } catch (JSONException e) {
48 | throw new IOException("Failed to serialize JSON", e);
49 | }
50 | }
51 | }
52 |
53 | private static void serializeJsonValue(Object object, JSONStringer stringer)
54 | throws IOException, JSONException {
55 | if (object instanceof Map) {
56 | stringer.object();
57 | @SuppressWarnings("unchecked")
58 | Map map = (Map) object;
59 | for (Map.Entry entry : map.entrySet()) {
60 | stringer.key(entry.getKey());
61 | serializeJsonValue(entry.getValue(), stringer);
62 | }
63 | stringer.endObject();
64 | } else if (object instanceof Collection) {
65 | Collection> collection = (Collection>) object;
66 | stringer.array();
67 | for (Object entry : collection) {
68 | serializeJsonValue(entry, stringer);
69 | }
70 | stringer.endArray();
71 | } else {
72 | stringer.value(object);
73 | }
74 | }
75 |
76 | public static Map parseJson(String json) throws IOException {
77 | try {
78 | return unwrapJsonObject(new JSONObject(json));
79 | } catch (JSONException e) {
80 | throw new IOException(e);
81 | }
82 | }
83 |
84 | public static Object parseJsonValue(String json) throws IOException {
85 | try {
86 | return unwrapJson(new JSONTokener(json).nextValue());
87 | } catch (JSONException e) {
88 | throw new IOException(e);
89 | }
90 | }
91 |
92 | @SuppressWarnings("unchecked")
93 | private static Map unwrapJsonObject(JSONObject jsonObject) throws JSONException {
94 | Map map = new HashMap<>(jsonObject.length());
95 | Iterator keys = jsonObject.keys();
96 | while (keys.hasNext()) {
97 | String key = keys.next();
98 | map.put(key, unwrapJson(jsonObject.get(key)));
99 | }
100 | return map;
101 | }
102 |
103 | private static List