headers = webResourceRequest.getRequestHeaders();
53 | cacheRequest.setHeaders(headers);
54 | return getOfflineServer().get(cacheRequest);
55 | }
56 | throw new IllegalStateException("an error occurred.");
57 | }
58 |
59 | @Override
60 | public void setCacheMode(FastCacheMode mode, CacheConfig cacheConfig) {
61 | mFastCacheMode = mode;
62 | mCacheConfig = cacheConfig;
63 | }
64 |
65 | @Override
66 | public void addResourceInterceptor(ResourceInterceptor interceptor) {
67 | getOfflineServer().addResourceInterceptor(interceptor);
68 | }
69 |
70 | private synchronized OfflineServer getOfflineServer() {
71 | if (mOfflineServer == null) {
72 | mOfflineServer = new OfflineServerImpl(mContext, getCacheConfig());
73 | }
74 | return mOfflineServer;
75 | }
76 |
77 | private CacheConfig getCacheConfig() {
78 | return mCacheConfig != null ? mCacheConfig : generateDefaultCacheConfig();
79 | }
80 |
81 | private CacheConfig generateDefaultCacheConfig() {
82 | return new CacheConfig.Builder(mContext).build();
83 | }
84 |
85 | @Override
86 | public void destroy() {
87 | if (mOfflineServer != null) {
88 | mOfflineServer.destroy();
89 | }
90 | // help gc
91 | mCacheConfig = null;
92 | mOfflineServer = null;
93 | mContext = null;
94 | }
95 | }
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/api/FastOpenApi.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.api;
2 |
3 | import com.youzanyun.sdk.sample.cache.config.CacheConfig;
4 | import com.youzanyun.sdk.sample.cache.config.FastCacheMode;
5 | import com.youzanyun.sdk.sample.cache.offline.ResourceInterceptor;
6 |
7 | /**
8 | * Created by Ryan
9 | * at 2019/11/1
10 | */
11 | public interface FastOpenApi {
12 |
13 | void setCacheMode(FastCacheMode mode, CacheConfig cacheConfig);
14 |
15 | void addResourceInterceptor(ResourceInterceptor interceptor);
16 | }
17 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/config/CacheConfig.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.config;
2 |
3 | import android.content.Context;
4 |
5 |
6 | import com.youzan.androidsdk.utils.AppVersionUtil;
7 | import com.youzan.androidsdk.utils.MemorySizeCalculator;
8 |
9 | import java.io.File;
10 |
11 | /**
12 | * Created by Ryan
13 | * 2018/2/7 下午5:41
14 | */
15 | public class CacheConfig {
16 |
17 | private String mCacheDir;
18 | private int mVersion;
19 | private long mDiskCacheSize;
20 | private int mMemCacheSize;
21 | private MimeTypeFilter mFilter;
22 |
23 | private CacheConfig() {
24 |
25 | }
26 |
27 | public String getCacheDir() {
28 | return mCacheDir;
29 | }
30 |
31 | public int getVersion() {
32 | return mVersion;
33 | }
34 |
35 | public void setVersion(int version) {
36 | this.mVersion = version;
37 | }
38 |
39 | public MimeTypeFilter getFilter() {
40 | return mFilter;
41 | }
42 |
43 | public long getDiskCacheSize() {
44 | return mDiskCacheSize;
45 | }
46 |
47 | public int getMemCacheSize() {
48 | return mMemCacheSize;
49 | }
50 |
51 | public static class Builder {
52 |
53 | private static final String CACHE_DIR_NAME = "cached_webview_force";
54 | private static final int DEFAULT_DISK_CACHE_SIZE = 200 * 1024 * 1024;
55 | private String cacheDir;
56 | private int version;
57 | private long diskCacheSize = DEFAULT_DISK_CACHE_SIZE;
58 | private int memoryCacheSize = MemorySizeCalculator.getSize();
59 | private MimeTypeFilter filter = new DefaultMimeTypeFilter();
60 |
61 | public Builder(Context context) {
62 | cacheDir = context.getCacheDir() + File.separator + CACHE_DIR_NAME;
63 | version = AppVersionUtil.getVersionCode(context);
64 | }
65 |
66 | public Builder setCacheDir(String cacheDir) {
67 | this.cacheDir = cacheDir;
68 | return this;
69 | }
70 |
71 | public Builder setVersion(int version) {
72 | this.version = version;
73 | return this;
74 | }
75 |
76 | public Builder setDiskCacheSize(long diskCacheSize) {
77 | this.diskCacheSize = diskCacheSize;
78 | return this;
79 | }
80 |
81 | public Builder setExtensionFilter(MimeTypeFilter filter) {
82 | this.filter = filter;
83 | return this;
84 | }
85 |
86 | public Builder setMemoryCacheSize(int memoryCacheSize) {
87 | this.memoryCacheSize = memoryCacheSize;
88 | return this;
89 | }
90 |
91 | public CacheConfig build() {
92 | CacheConfig config = new CacheConfig();
93 | config.mCacheDir = cacheDir;
94 | config.mVersion = version;
95 | config.mDiskCacheSize = diskCacheSize;
96 | config.mFilter = filter;
97 | config.mMemCacheSize = memoryCacheSize;
98 | return config;
99 | }
100 | }
101 | }
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/config/DefaultMimeTypeFilter.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.config;
2 |
3 | import java.util.HashSet;
4 | import java.util.Set;
5 |
6 | /**
7 | * default filter.
8 | * 可缓存资源白名单
9 | *
10 | * Created by Ryan
11 | * 2018/2/11 下午3:16
12 | */
13 | public class DefaultMimeTypeFilter implements MimeTypeFilter {
14 |
15 | private Set mFilterMimeTypes;
16 |
17 | public DefaultMimeTypeFilter() {
18 | mFilterMimeTypes = new HashSet<>();
19 | // JavaScript
20 | addMimeType("application/javascript");
21 | addMimeType("application/ecmascript");
22 | addMimeType("application/x-ecmascript");
23 | addMimeType("application/x-javascript");
24 | addMimeType("text/ecmascript");
25 | addMimeType("text/javascript");
26 | addMimeType("text/javascript1.0");
27 | addMimeType("text/javascript1.1");
28 | addMimeType("text/javascript1.2");
29 | addMimeType("text/javascript1.3");
30 | addMimeType("text/javascript1.4");
31 | addMimeType("text/javascript1.5");
32 | addMimeType("text/jscript");
33 | addMimeType("text/livescript");
34 | addMimeType("text/x-ecmascript");
35 | addMimeType("text/x-javascript");
36 | // image
37 | addMimeType("image/gif");
38 | addMimeType("image/jpeg");
39 | addMimeType("image/png");
40 | addMimeType("image/svg+xml");
41 | addMimeType("image/bmp");
42 | addMimeType("image/webp");
43 | addMimeType("image/tiff");
44 | addMimeType("image/vnd.microsoft.icon");
45 | addMimeType("image/x-icon");
46 | // css
47 | addMimeType("text/css");
48 | // stream
49 | addMimeType("application/octet-stream");
50 | }
51 |
52 | @Override
53 | public boolean isFilter(String extension) {
54 | return !mFilterMimeTypes.contains(extension);
55 | }
56 |
57 | @Override
58 | public void addMimeType(String extension) {
59 | mFilterMimeTypes.add(extension);
60 | }
61 |
62 | @Override
63 | public void removeMimeType(String extension) {
64 | mFilterMimeTypes.remove(extension);
65 | }
66 |
67 | @Override
68 | public void clear() {
69 | mFilterMimeTypes.clear();
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/config/FastCacheMode.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.config;
2 |
3 | /**
4 | * Created by Ryan
5 | * at 2019/11/1
6 | */
7 | public enum FastCacheMode {
8 | DEFAULT,
9 | NORMAL,
10 | FORCE
11 | }
12 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/config/MimeTypeFilter.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.config;
2 |
3 | /**
4 | * filter some mime type resources without caching.
5 | *
6 | * Created by Ryan
7 | * 2018/2/11 下午2:56
8 | */
9 | public interface MimeTypeFilter {
10 |
11 | boolean isFilter(String mimeType);
12 |
13 | void addMimeType(String mimeType);
14 |
15 | void removeMimeType(String mimeType);
16 |
17 | void clear();
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/cookie/CookieInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.cookie;
2 |
3 | import java.util.List;
4 |
5 | import okhttp3.Cookie;
6 | import okhttp3.HttpUrl;
7 |
8 | /**
9 | * Created by Ryan
10 | * on 2019/10/29
11 | */
12 | public interface CookieInterceptor {
13 |
14 | List newCookies(HttpUrl url, List originCookies);
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/cookie/CookieJarImpl.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.cookie;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.text.TextUtils;
5 | import android.webkit.CookieManager;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | import okhttp3.Cookie;
11 | import okhttp3.CookieJar;
12 | import okhttp3.HttpUrl;
13 |
14 | /**
15 | * Created by Ryan
16 | * on 2019/10/29
17 | */
18 | public class CookieJarImpl implements CookieJar {
19 |
20 | private FastCookieManager mCookieManager;
21 |
22 | public CookieJarImpl() {
23 | mCookieManager = FastCookieManager.getInstance();
24 | }
25 |
26 | @Override
27 | public synchronized void saveFromResponse(HttpUrl url, List cookies) {
28 | List interceptors = mCookieManager.getRequestCookieInterceptors();
29 | if (interceptors != null && !interceptors.isEmpty()) {
30 | for (CookieInterceptor interceptor : interceptors) {
31 | cookies = interceptor.newCookies(url, cookies);
32 | }
33 | }
34 | CookieManager cookieManager = CookieManager.getInstance();
35 | cookieManager.setAcceptCookie(true);
36 | for (Cookie cookie : cookies) {
37 | cookieManager.setCookie(url.toString(), cookie.toString());
38 | }
39 | }
40 |
41 | @NonNull
42 | @Override
43 | public synchronized List loadForRequest(HttpUrl url) {
44 | List cookies = new ArrayList<>();
45 | String cookieFullStr = CookieManager.getInstance().getCookie(url.host());
46 | if (!TextUtils.isEmpty(cookieFullStr)) {
47 | String[] cookieArr = cookieFullStr.split(";");
48 | for (String cookieStr : cookieArr) {
49 | Cookie cookie = Cookie.parse(url, cookieStr);
50 | if (cookie != null) {
51 | cookies.add(cookie);
52 | }
53 | }
54 | }
55 | List interceptors = mCookieManager.getResponseCookieInterceptors();
56 | if (interceptors != null && !interceptors.isEmpty()) {
57 | for (CookieInterceptor interceptor : interceptors) {
58 | cookies = interceptor.newCookies(url, cookies);
59 | }
60 | }
61 | return cookies;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/cookie/CookieStore.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.cookie;
2 |
3 | import java.util.List;
4 |
5 | import okhttp3.Cookie;
6 | import okhttp3.HttpUrl;
7 |
8 | /**
9 | * Created by Ryan
10 | * on 2019/10/29
11 | */
12 | @Deprecated
13 | public interface CookieStore {
14 |
15 | void add(HttpUrl httpUrl, Cookie cookie);
16 |
17 | void add(HttpUrl httpUrl, List cookies);
18 |
19 | List get(HttpUrl httpUrl);
20 |
21 | List getCookies();
22 |
23 | boolean remove(HttpUrl httpUrl, Cookie cookie);
24 |
25 | boolean removeAll();
26 | }
27 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/cookie/CookieStrategy.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.cookie;
2 |
3 | /**
4 | * Created by Ryan
5 | * on 2019/10/29
6 | */
7 | @Deprecated
8 | public enum CookieStrategy {
9 | MEMORY,
10 | PERSISTENT
11 | }
12 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/cookie/FastCookieManager.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.cookie;
2 |
3 | import android.content.Context;
4 |
5 | import com.youzanyun.sdk.sample.cache.offline.Destroyable;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | import okhttp3.CookieJar;
11 |
12 | /**
13 | * Created by Ryan
14 | * on 2019/10/29
15 | */
16 | public class FastCookieManager implements Destroyable {
17 |
18 | private List mRequestCookieInterceptors;
19 | private List mResponseCookieInterceptors;
20 | private CookieJar mUserCookieJar;
21 |
22 | private FastCookieManager() {
23 | mRequestCookieInterceptors = new ArrayList<>();
24 | mResponseCookieInterceptors = new ArrayList<>();
25 | }
26 |
27 | @Override
28 | public void destroy() {
29 | mRequestCookieInterceptors.clear();
30 | mResponseCookieInterceptors.clear();
31 | mUserCookieJar = null;
32 | }
33 |
34 | private static class SingletonHolder {
35 | private static final FastCookieManager INSTANCE = new FastCookieManager();
36 | }
37 |
38 | public static FastCookieManager getInstance() {
39 | return SingletonHolder.INSTANCE;
40 | }
41 |
42 | List getRequestCookieInterceptors() {
43 | return mRequestCookieInterceptors;
44 | }
45 |
46 | public void addRequestCookieInterceptor(CookieInterceptor requestCookieInterceptor) {
47 | if (!mRequestCookieInterceptors.contains(requestCookieInterceptor)) {
48 | mRequestCookieInterceptors.add(requestCookieInterceptor);
49 | }
50 | }
51 |
52 | List getResponseCookieInterceptors() {
53 | return mResponseCookieInterceptors;
54 | }
55 |
56 | public void addResponseCookieInterceptor(CookieInterceptor responseCookieInterceptor) {
57 | if (!mResponseCookieInterceptors.contains(responseCookieInterceptor)) {
58 | mResponseCookieInterceptors.add(responseCookieInterceptor);
59 | }
60 | }
61 |
62 | public CookieJar getCookieJar(Context context) {
63 | return mUserCookieJar != null ? mUserCookieJar : new CookieJarImpl();
64 | }
65 |
66 | public void setCookieJar(CookieJar cookieJar) {
67 | this.mUserCookieJar = cookieJar;
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/cookie/MemoryCookieStore.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.cookie;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 | import java.util.HashMap;
6 | import java.util.List;
7 | import java.util.concurrent.ConcurrentHashMap;
8 |
9 | import okhttp3.Cookie;
10 | import okhttp3.HttpUrl;
11 |
12 | /**
13 | * Created by Ryan
14 | * on 2019/10/29
15 | */
16 | @Deprecated
17 | public class MemoryCookieStore implements CookieStore {
18 |
19 | private static final String HOST_NAME_PREFIX = "host_";
20 |
21 | private final HashMap> cookies;
22 |
23 | MemoryCookieStore() {
24 | this.cookies = new HashMap<>();
25 | }
26 |
27 | @Override
28 | public void add(HttpUrl httpUrl, Cookie cookie) {
29 | if (!cookie.persistent()) {
30 | return;
31 | }
32 | String name = this.cookieName(cookie);
33 | String hostKey = this.hostName(httpUrl);
34 | if (!this.cookies.containsKey(hostKey)) {
35 | this.cookies.put(hostKey, new ConcurrentHashMap());
36 | }
37 | cookies.get(hostKey).put(name, cookie);
38 | }
39 |
40 | @Override
41 | public void add(HttpUrl httpUrl, List cookies) {
42 | for (Cookie cookie : cookies) {
43 | if (isCookieExpired(cookie)) {
44 | continue;
45 | }
46 | this.add(httpUrl, cookie);
47 | }
48 | }
49 |
50 | @Override
51 | public List get(HttpUrl httpUrl) {
52 | return this.get(this.hostName(httpUrl));
53 | }
54 |
55 | @Override
56 | public List getCookies() {
57 | ArrayList result = new ArrayList<>();
58 | for (String hostKey : this.cookies.keySet()) {
59 | result.addAll(this.get(hostKey));
60 | }
61 | return result;
62 | }
63 |
64 | private List get(String hostKey) {
65 | ArrayList result = new ArrayList<>();
66 | if (this.cookies.containsKey(hostKey)) {
67 | Collection cookies = this.cookies.get(hostKey).values();
68 | for (Cookie cookie : cookies) {
69 | if (isCookieExpired(cookie)) {
70 | this.remove(hostKey, cookie);
71 | } else {
72 | result.add(cookie);
73 | }
74 | }
75 | }
76 | return result;
77 | }
78 |
79 | @Override
80 | public boolean remove(HttpUrl httpUrl, Cookie cookie) {
81 | return this.remove(this.hostName(httpUrl), cookie);
82 | }
83 |
84 | private boolean remove(String hostKey, Cookie cookie) {
85 | String name = this.cookieName(cookie);
86 | if (this.cookies.containsKey(hostKey) && this.cookies.get(hostKey).containsKey(name)) {
87 | this.cookies.get(hostKey).remove(name);
88 | return true;
89 | }
90 | return false;
91 | }
92 |
93 | @Override
94 | public boolean removeAll() {
95 | this.cookies.clear();
96 | return true;
97 | }
98 |
99 | private boolean isCookieExpired(Cookie cookie) {
100 | return cookie.expiresAt() < System.currentTimeMillis();
101 | }
102 |
103 | private String hostName(HttpUrl httpUrl) {
104 | return httpUrl.host().startsWith(HOST_NAME_PREFIX) ? httpUrl.host() : HOST_NAME_PREFIX + httpUrl.host();
105 | }
106 |
107 | private String cookieName(Cookie cookie) {
108 | return cookie == null ? null : cookie.name() + cookie.domain();
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/cookie/PersistentCookieStore.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.cookie;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.text.TextUtils;
6 | import android.util.Log;
7 |
8 | import java.io.ByteArrayInputStream;
9 | import java.io.ByteArrayOutputStream;
10 | import java.io.IOException;
11 | import java.io.ObjectInputStream;
12 | import java.io.ObjectOutputStream;
13 | import java.util.ArrayList;
14 | import java.util.Collection;
15 | import java.util.List;
16 | import java.util.Locale;
17 | import java.util.Map;
18 | import java.util.concurrent.ConcurrentHashMap;
19 |
20 | import okhttp3.Cookie;
21 | import okhttp3.HttpUrl;
22 |
23 | /**
24 | * Created by Ryan
25 | * on 2019/10/29
26 | */
27 | @Deprecated
28 | public class PersistentCookieStore implements CookieStore {
29 |
30 | private static final String LOG_TAG = PersistentCookieStore.class.getSimpleName();
31 | private static final String COOKIE_PREFS = "CookiePrefsFile";
32 | private static final String HOST_NAME_PREFIX = "host_";
33 | private static final String COOKIE_NAME_PREFIX = "cookie_";
34 | private final Map> mCookies;
35 | private final SharedPreferences mCookiePrefs;
36 | private boolean mOmitNonPersistentCookies = false;
37 |
38 | PersistentCookieStore(Context context) {
39 | mCookiePrefs = context.getSharedPreferences(COOKIE_PREFS, Context.MODE_PRIVATE);
40 | mCookies = new ConcurrentHashMap<>();
41 | // load persistent cookie from disk.
42 | loadPersistentCookie();
43 | // try clear expired cookie.
44 | clearExpired();
45 | }
46 |
47 | private void loadPersistentCookie() {
48 | Map tempCookieMap = mCookiePrefs.getAll();
49 | for (String key : tempCookieMap.keySet()) {
50 | if (key == null || !key.contains(HOST_NAME_PREFIX)) {
51 | continue;
52 | }
53 | String cookieNames = String.valueOf(tempCookieMap.get(key));
54 | if (TextUtils.isEmpty(cookieNames)) {
55 | continue;
56 | }
57 | if (!mCookies.containsKey(key)) {
58 | mCookies.put(key, new ConcurrentHashMap());
59 | }
60 | String[] cookieNameArr = cookieNames.split(",");
61 | for (String name : cookieNameArr) {
62 | String encodedCookie = mCookiePrefs.getString(COOKIE_NAME_PREFIX + name, null);
63 | if (encodedCookie == null) {
64 | continue;
65 | }
66 | Cookie decodedCookie = this.decodeCookie(encodedCookie);
67 | if (decodedCookie != null) {
68 | mCookies.get(key).put(name, decodedCookie);
69 | }
70 | }
71 | }
72 | tempCookieMap.clear();
73 | }
74 |
75 | private void clearExpired() {
76 | SharedPreferences.Editor prefsEditor = mCookiePrefs.edit();
77 | for (String key : mCookies.keySet()) {
78 | boolean changeFlag = false;
79 | Map singleKeyCookies = mCookies.get(key);
80 | for (Map.Entry entry : singleKeyCookies.entrySet()) {
81 | String name = entry.getKey();
82 | Cookie cookie = entry.getValue();
83 | if (isCookieExpired(cookie)) {
84 | // Clear cookie from local store
85 | singleKeyCookies.remove(name);
86 | // Clear cookie from persistent store
87 | prefsEditor.remove(COOKIE_NAME_PREFIX + name);
88 | changeFlag = true;
89 | }
90 | }
91 | // Update names in persistent store
92 | if (changeFlag) {
93 | prefsEditor.putString(key, TextUtils.join(",", mCookies.keySet()));
94 | }
95 | }
96 | prefsEditor.apply();
97 | }
98 |
99 | @Override
100 | public void add(HttpUrl httpUrl, Cookie cookie) {
101 | if (mOmitNonPersistentCookies && !cookie.persistent()) {
102 | return;
103 | }
104 | String name = cookieName(cookie);
105 | String hostName = hostName(httpUrl);
106 | // save cookie into local store, or remove if expired
107 | Map hostCookiesMap;
108 | if (mCookies.containsKey(hostName)) {
109 | hostCookiesMap = mCookies.get(hostName);
110 | } else {
111 | hostCookiesMap = new ConcurrentHashMap<>();
112 | mCookies.put(hostName, hostCookiesMap);
113 | }
114 | hostCookiesMap.put(name, cookie);
115 | // save cookie into persistent store
116 | SharedPreferences.Editor prefsEditor = mCookiePrefs.edit();
117 | prefsEditor.putString(hostName, TextUtils.join(",", hostCookiesMap.keySet()));
118 | prefsEditor.putString(COOKIE_NAME_PREFIX + name, encodeCookie(new SerializableCookie(cookie)));
119 | prefsEditor.apply();
120 | }
121 |
122 | @Override
123 | public void add(HttpUrl httpUrl, List cookies) {
124 | for (Cookie cookie : cookies) {
125 | if (isCookieExpired(cookie)) {
126 | continue;
127 | }
128 | this.add(httpUrl, cookie);
129 | }
130 | }
131 |
132 | @Override
133 | public List get(HttpUrl httpUrl) {
134 | return this.get(this.hostName(httpUrl));
135 | }
136 |
137 | @Override
138 | public List getCookies() {
139 | ArrayList result = new ArrayList<>();
140 | for (String hostKey : mCookies.keySet()) {
141 | result.addAll(this.get(hostKey));
142 | }
143 | return result;
144 | }
145 |
146 | private List get(String hostKey) {
147 | List result = new ArrayList<>();
148 | if (mCookies.containsKey(hostKey)) {
149 | Collection cookies = mCookies.get(hostKey).values();
150 | for (Cookie cookie : cookies) {
151 | if (isCookieExpired(cookie)) {
152 | remove(hostKey, cookie);
153 | } else {
154 | result.add(cookie);
155 | }
156 | }
157 | }
158 | return result;
159 | }
160 |
161 | @Override
162 | public boolean remove(HttpUrl httpUrl, Cookie cookie) {
163 | return remove(hostName(httpUrl), cookie);
164 | }
165 |
166 | private boolean remove(String hostKey, Cookie cookie) {
167 | String name = this.cookieName(cookie);
168 | if (mCookies.containsKey(hostKey) && mCookies.get(hostKey).containsKey(name)) {
169 | mCookies.get(hostKey).remove(name);
170 | SharedPreferences.Editor prefsEditor = mCookiePrefs.edit();
171 | prefsEditor.remove(COOKIE_NAME_PREFIX + name);
172 | prefsEditor.putString(hostKey, TextUtils.join(",", mCookies.get(hostKey).keySet()));
173 | prefsEditor.apply();
174 | return true;
175 | }
176 | return false;
177 | }
178 |
179 | @Override
180 | public boolean removeAll() {
181 | SharedPreferences.Editor prefsEditor = mCookiePrefs.edit();
182 | prefsEditor.clear();
183 | prefsEditor.apply();
184 | mCookies.clear();
185 | return true;
186 | }
187 |
188 | public void setOmitNonPersistentCookies(boolean omitNonPersistentCookies) {
189 | this.mOmitNonPersistentCookies = omitNonPersistentCookies;
190 | }
191 |
192 | private boolean isCookieExpired(Cookie cookie) {
193 | return cookie.expiresAt() < System.currentTimeMillis();
194 | }
195 |
196 | private String hostName(HttpUrl httpUrl) {
197 | return httpUrl.host().startsWith(HOST_NAME_PREFIX) ? httpUrl.host() : HOST_NAME_PREFIX + httpUrl.host();
198 | }
199 |
200 | private String cookieName(Cookie cookie) {
201 | return cookie == null ? null : cookie.name() + cookie.domain();
202 | }
203 |
204 | private String encodeCookie(SerializableCookie cookie) {
205 | if (cookie == null)
206 | return null;
207 | ByteArrayOutputStream os = new ByteArrayOutputStream();
208 | try {
209 | ObjectOutputStream outputStream = new ObjectOutputStream(os);
210 | outputStream.writeObject(cookie);
211 | } catch (IOException e) {
212 | Log.d(LOG_TAG, "IOException in encodeCookie", e);
213 | return null;
214 | }
215 | return byteArrayToHexString(os.toByteArray());
216 | }
217 |
218 | private Cookie decodeCookie(String cookieString) {
219 | byte[] bytes = hexStringToByteArray(cookieString);
220 | ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
221 | Cookie cookie = null;
222 | try {
223 | ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
224 | cookie = ((SerializableCookie) objectInputStream.readObject()).getCookie();
225 | } catch (IOException e) {
226 | Log.d(LOG_TAG, "IOException in decodeCookie", e);
227 | } catch (ClassNotFoundException e) {
228 | Log.d(LOG_TAG, "ClassNotFoundException in decodeCookie", e);
229 | }
230 | return cookie;
231 | }
232 |
233 | private String byteArrayToHexString(byte[] bytes) {
234 | StringBuilder sb = new StringBuilder(bytes.length * 2);
235 | for (byte element : bytes) {
236 | int v = element & 0xff;
237 | if (v < 16) {
238 | sb.append('0');
239 | }
240 | sb.append(Integer.toHexString(v));
241 | }
242 | return sb.toString().toUpperCase(Locale.US);
243 | }
244 |
245 | private byte[] hexStringToByteArray(String hexString) {
246 | int len = hexString.length();
247 | byte[] data = new byte[len / 2];
248 | for (int i = 0; i < len; i += 2) {
249 | data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) + Character.digit(hexString.charAt(i + 1), 16));
250 | }
251 | return data;
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/cookie/SerializableCookie.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.cookie;
2 |
3 | import java.io.IOException;
4 | import java.io.ObjectInputStream;
5 | import java.io.ObjectOutputStream;
6 | import java.io.Serializable;
7 |
8 | import okhttp3.Cookie;
9 |
10 | /**
11 | * Created by Ryan
12 | * on 2019/10/29
13 | */
14 | @Deprecated
15 | public class SerializableCookie implements Serializable {
16 | private static final long serialVersionUID = 6374381828722046732L;
17 |
18 | private transient final Cookie cookie;
19 | private transient Cookie clientCookie;
20 |
21 | SerializableCookie(Cookie cookie) {
22 | this.cookie = cookie;
23 | }
24 |
25 | public Cookie getCookie() {
26 | Cookie bestCookie = cookie;
27 | if (this.clientCookie != null) {
28 | bestCookie = this.clientCookie;
29 | }
30 | return bestCookie;
31 | }
32 |
33 | /**
34 | * 将cookie写到对象流中
35 | */
36 | private void writeObject(ObjectOutputStream out) throws IOException {
37 | out.writeObject(this.cookie.name());
38 | out.writeObject(this.cookie.value());
39 | out.writeLong(this.cookie.expiresAt());
40 | out.writeObject(this.cookie.domain());
41 | out.writeObject(this.cookie.path());
42 | out.writeBoolean(this.cookie.secure());
43 | out.writeBoolean(this.cookie.httpOnly());
44 | out.writeBoolean(this.cookie.hostOnly());
45 | out.writeBoolean(this.cookie.persistent());
46 | }
47 |
48 | /**
49 | * 从对象流中构建cookie对象
50 | */
51 | private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
52 | String name = (String) in.readObject();
53 | String value = (String) in.readObject();
54 | long expiresAt = in.readLong();
55 | String domain = (String) in.readObject();
56 | String path = (String) in.readObject();
57 | boolean secure = in.readBoolean();
58 | boolean httpOnly = in.readBoolean();
59 | boolean hostOnly = in.readBoolean();
60 |
61 | Cookie.Builder builder = new Cookie.Builder()
62 | .name(name)
63 | .value(value)
64 | .expiresAt(expiresAt)
65 | .path(path);
66 | builder = hostOnly ? builder.hostOnlyDomain(domain) : builder.domain(domain);
67 | builder = secure ? builder.secure() : builder;
68 | builder = httpOnly ? builder.httpOnly() : builder;
69 | this.clientCookie = builder.build();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/loader/DefaultResourceLoader.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.loader;
2 |
3 | import android.util.Log;
4 |
5 | import com.youzanyun.sdk.sample.cache.WebResource;
6 | import com.youzan.androidsdk.utils.HeaderUtils;
7 | import com.youzan.androidsdk.utils.StreamUtils;
8 |
9 | import java.io.IOException;
10 | import java.net.HttpURLConnection;
11 | import java.net.MalformedURLException;
12 | import java.net.URL;
13 | import java.util.Map;
14 |
15 | /**
16 | * load remote resources using HttpURLConnection.
17 | *
18 | * Created by Ryan
19 | * 2018/2/7 下午7:55
20 | */
21 | public class DefaultResourceLoader implements ResourceLoader {
22 |
23 | private static final String TAG = DefaultResourceLoader.class.getName();
24 |
25 | @Override
26 | public WebResource getResource(SourceRequest sourceRequest) {
27 | String url = sourceRequest.getUrl();
28 | try {
29 | URL urlRequest = new URL(url);
30 | HttpURLConnection httpURLConnection = (HttpURLConnection) urlRequest.openConnection();
31 | putHeader(httpURLConnection, sourceRequest.getHeaders());
32 | httpURLConnection.setRequestMethod("GET");
33 | httpURLConnection.setUseCaches(true);
34 | httpURLConnection.setConnectTimeout(20000);
35 | httpURLConnection.setReadTimeout(20000);
36 | httpURLConnection.connect();
37 | int responseCode = httpURLConnection.getResponseCode();
38 | if (responseCode == HttpURLConnection.HTTP_OK) {
39 | WebResource remoteResource = new WebResource();
40 | remoteResource.setOriginBytes(StreamUtils.streamToBytes(httpURLConnection.getInputStream()));
41 | remoteResource.setResponseCode(responseCode);
42 | remoteResource.setReasonPhrase(httpURLConnection.getResponseMessage());
43 | remoteResource.setResponseHeaders(HeaderUtils.generateHeadersMap(httpURLConnection.getHeaderFields()));
44 | return remoteResource;
45 | }
46 | httpURLConnection.disconnect();
47 | } catch (MalformedURLException e) {
48 | Log.d(TAG, e.toString() + " " + url);
49 | e.printStackTrace();
50 | } catch (IOException e) {
51 | Log.d(TAG, e.toString() + " " + url);
52 | e.printStackTrace();
53 | } catch (Exception e) {
54 | Log.d(TAG, e.toString() + " " + url);
55 | e.printStackTrace();
56 | }
57 | return null;
58 | }
59 |
60 | private void putHeader(HttpURLConnection httpURLConnection, Map headers) {
61 | for (Map.Entry entry : headers.entrySet()) {
62 | httpURLConnection.setRequestProperty(entry.getKey(), entry.getValue());
63 | }
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/loader/OkHttpResourceLoader.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.loader;
2 |
3 | import static android.webkit.WebSettings.LOAD_CACHE_ELSE_NETWORK;
4 | import static android.webkit.WebSettings.LOAD_CACHE_ONLY;
5 | import static android.webkit.WebSettings.LOAD_NO_CACHE;
6 | import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
7 |
8 | import android.content.Context;
9 | import android.text.TextUtils;
10 |
11 | import com.youzanyun.sdk.sample.cache.WebResource;
12 | import com.youzanyun.sdk.sample.cache.okhttp.OkHttpClientProvider;
13 | import com.youzan.androidsdk.utils.HeaderUtils;
14 | import com.youzan.androidsdk.utils.LogUtils;
15 | import com.youzan.spiderman.BuildConfig;
16 |
17 | import java.io.IOException;
18 | import java.util.Locale;
19 | import java.util.Map;
20 | import java.util.concurrent.TimeUnit;
21 |
22 | import okhttp3.CacheControl;
23 | import okhttp3.OkHttpClient;
24 | import okhttp3.Request;
25 | import okhttp3.Response;
26 | import okhttp3.ResponseBody;
27 |
28 | /**
29 | * load remote resources using okhttp.
30 | *
31 | * Created by Ryan
32 | * at 2019/9/26
33 | */
34 | public class OkHttpResourceLoader implements ResourceLoader {
35 |
36 | private static final String HEADER_USER_AGENT = "User-Agent";
37 | private static final String DEFAULT_USER_AGENT = "FastWebView" + BuildConfig.VERSION_NAME;
38 | private Context mContext;
39 |
40 | public OkHttpResourceLoader(Context context) {
41 | mContext = context;
42 | }
43 |
44 | @Override
45 | public WebResource getResource(SourceRequest sourceRequest) {
46 | String url = sourceRequest.getUrl();
47 | LogUtils.d(String.format("load url: %s", url));
48 | boolean isCacheByOkHttp = sourceRequest.isCacheable();
49 | OkHttpClient client = OkHttpClientProvider.get(mContext);
50 | CacheControl cacheControl = getCacheControl(sourceRequest.getWebViewCache(), isCacheByOkHttp);
51 | String userAgent = sourceRequest.getUserAgent();
52 | if (TextUtils.isEmpty(userAgent)) {
53 | userAgent = DEFAULT_USER_AGENT;
54 | }
55 | Locale locale = Locale.getDefault();
56 | String acceptLanguage;
57 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
58 | acceptLanguage = locale.toLanguageTag();
59 | } else {
60 | acceptLanguage = locale.getLanguage();
61 | }
62 | if (!acceptLanguage.equalsIgnoreCase("en-US")) {
63 | acceptLanguage += ",en-US;q=0.9";
64 | }
65 | Request.Builder requestBuilder = new Request.Builder()
66 | .removeHeader(HEADER_USER_AGENT)
67 | .addHeader(HEADER_USER_AGENT, userAgent)
68 | .addHeader("Upgrade-Insecure-Requests", "1")
69 | .addHeader("X-Requested-With", mContext.getPackageName())
70 | .addHeader("Accept", "*/*")
71 | .addHeader("Accept-Language", acceptLanguage);
72 | Map headers = sourceRequest.getHeaders();
73 | if (headers != null && !headers.isEmpty()) {
74 | for (Map.Entry entry : headers.entrySet()) {
75 | String header = entry.getKey();
76 | if (!isNeedStripHeader(header)) {
77 | requestBuilder.removeHeader(header);
78 | requestBuilder.addHeader(header, entry.getValue());
79 | }
80 | }
81 | }
82 | Request request = requestBuilder
83 | .url(url)
84 | .cacheControl(cacheControl)
85 | .get()
86 | .build();
87 | Response response = null;
88 | try {
89 | WebResource remoteResource = new WebResource();
90 | response = client.newCall(request).execute();
91 | if (isInterceptorThisRequest(response)) {
92 | remoteResource.setResponseCode(response.code());
93 | remoteResource.setReasonPhrase(response.message());
94 | remoteResource.setModified(response.code() != HTTP_NOT_MODIFIED);
95 | ResponseBody responseBody = response.body();
96 | if (responseBody != null) {
97 | remoteResource.setOriginBytes(responseBody.bytes());
98 | }
99 | remoteResource.setResponseHeaders(HeaderUtils.generateHeadersMap(response.headers()));
100 | remoteResource.setCacheByOurselves(!isCacheByOkHttp);
101 | return remoteResource;
102 | }
103 | } catch (IOException e) {
104 | e.printStackTrace();
105 | } finally {
106 | if (response != null) {
107 | response.close();
108 | }
109 | }
110 | return null;
111 | }
112 |
113 | private CacheControl getCacheControl(int webViewCacheMode, boolean isCacheByOkHttp) {
114 | // return the appropriate cache-control according to webview cache mode.
115 | switch (webViewCacheMode) {
116 | case LOAD_CACHE_ONLY:
117 | return CacheControl.FORCE_CACHE;
118 | case LOAD_CACHE_ELSE_NETWORK:
119 | if (!isCacheByOkHttp) {
120 | // if it happens, because there is no local cache.
121 | return createNoStoreCacheControl();
122 | }
123 | // tell okhttp that we are willing to receive expired cache.
124 | return new CacheControl.Builder().maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS).build();
125 | case LOAD_NO_CACHE:
126 | return CacheControl.FORCE_NETWORK;
127 | default: // LOAD_DEFAULT
128 | return isCacheByOkHttp ? new CacheControl.Builder().build() : createNoStoreCacheControl();
129 | }
130 | }
131 |
132 | private CacheControl createNoStoreCacheControl() {
133 | return new CacheControl.Builder().noStore().build();
134 | }
135 |
136 | private boolean isNeedStripHeader(String headerName) {
137 | return headerName.equalsIgnoreCase("If-Match")
138 | || headerName.equalsIgnoreCase("If-None-Match")
139 | || headerName.equalsIgnoreCase("If-Modified-Since")
140 | || headerName.equalsIgnoreCase("If-Unmodified-Since")
141 | || headerName.equalsIgnoreCase("Last-Modified")
142 | || headerName.equalsIgnoreCase("Expires")
143 | || headerName.equalsIgnoreCase("Cache-Control");
144 | }
145 |
146 | /**
147 | * references {@link android.webkit.WebResourceResponse} setStatusCodeAndReasonPhrase
148 | */
149 | private boolean isInterceptorThisRequest(Response response) {
150 | int code = response.code();
151 | return !(code < 100 || code > 599 || (code > 299 && code < 400));
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/loader/ResourceLoader.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.loader;
2 |
3 |
4 | import com.youzanyun.sdk.sample.cache.WebResource;
5 |
6 | /**
7 | * Created by Ryan
8 | * 2018/2/7 下午7:53
9 | */
10 | public interface ResourceLoader {
11 |
12 | WebResource getResource(SourceRequest request);
13 | }
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/loader/SourceRequest.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.loader;
2 |
3 |
4 | import com.youzanyun.sdk.sample.cache.offline.CacheRequest;
5 |
6 | import java.util.Map;
7 |
8 | /**
9 | * Created by Ryan
10 | * at 2019/9/27
11 | */
12 | public class SourceRequest {
13 |
14 | private String url;
15 | private boolean cacheable;
16 | private Map headers;
17 | private String userAgent;
18 | private int webViewCache;
19 |
20 | public SourceRequest(CacheRequest request, boolean cacheable){
21 | this.cacheable = cacheable;
22 | this.url = request.getUrl();
23 | this.headers = request.getHeaders();
24 | this.userAgent = request.getUserAgent();
25 | this.webViewCache = request.getWebViewCacheMode();
26 | }
27 |
28 | public String getUrl() {
29 | return url;
30 | }
31 |
32 | public void setCacheable(boolean cacheable) {
33 | this.cacheable = cacheable;
34 | }
35 |
36 | public boolean isCacheable() {
37 | return cacheable;
38 | }
39 |
40 | public void setUrl(String url) {
41 | this.url = url;
42 | }
43 |
44 | public void setHeaders(Map headers) {
45 | this.headers = headers;
46 | }
47 |
48 | public void setUserAgent(String userAgent) {
49 | this.userAgent = userAgent;
50 | }
51 |
52 | public String getUserAgent() {
53 | return userAgent;
54 | }
55 |
56 | public Map getHeaders() {
57 | return headers;
58 | }
59 |
60 | public int getWebViewCache() {
61 | return webViewCache;
62 | }
63 |
64 | public void setWebViewCache(int webViewCache) {
65 | this.webViewCache = webViewCache;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/offline/CacheRequest.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.offline;
2 |
3 | import com.youzan.androidsdk.utils.MD5Utils;
4 |
5 | import java.net.URLEncoder;
6 | import java.util.Map;
7 |
8 | /**
9 | * Created by Ryan
10 | * at 2019/9/27
11 | */
12 | public class CacheRequest {
13 |
14 | private String key;
15 | private String url;
16 | private String mime;
17 | private boolean forceMode;
18 | private Map mHeaders;
19 | private String mUserAgent;
20 | private int mWebViewCacheMode;
21 |
22 | public String getKey() {
23 | return key;
24 | }
25 |
26 | public String getMime() {
27 | return mime;
28 | }
29 |
30 | public void setMime(String mime) {
31 | this.mime = mime;
32 | }
33 |
34 | public void setForceMode(boolean forceMode) {
35 | this.forceMode = forceMode;
36 | }
37 |
38 | public boolean isForceMode() {
39 | return forceMode;
40 | }
41 |
42 | public void setHeaders(Map mHeaders) {
43 | this.mHeaders = mHeaders;
44 | }
45 |
46 | public Map getHeaders() {
47 | return mHeaders;
48 | }
49 |
50 | public String getUserAgent() {
51 | return mUserAgent;
52 | }
53 |
54 | public void setUserAgent(String userAgent) {
55 | this.mUserAgent = userAgent;
56 | }
57 |
58 | public String getUrl() {
59 | return url;
60 | }
61 |
62 | public void setUrl(String url) {
63 | this.url = url;
64 | this.key = generateKey(url);
65 | }
66 |
67 | public void setWebViewCacheMode(int webViewCacheMode) {
68 | this.mWebViewCacheMode = webViewCacheMode;
69 | }
70 |
71 | public int getWebViewCacheMode() {
72 | return mWebViewCacheMode;
73 | }
74 |
75 | private static String generateKey(String url) {
76 | return MD5Utils.getMD5(URLEncoder.encode(url), false);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/offline/Chain.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.offline;
2 |
3 |
4 | import com.youzanyun.sdk.sample.cache.WebResource;
5 |
6 | import java.util.List;
7 |
8 | /**
9 | * Created by Ryan
10 | * at 2019/9/27
11 | */
12 | public class Chain {
13 |
14 | private List mInterceptors;
15 | private int mIndex = -1;
16 | private CacheRequest mRequest;
17 |
18 | Chain(List interceptors) {
19 | mInterceptors = interceptors;
20 | }
21 |
22 | public WebResource process(CacheRequest request) {
23 | if (++mIndex >= mInterceptors.size()) {
24 | return null;
25 | }
26 | mRequest = request;
27 | ResourceInterceptor interceptor = mInterceptors.get(mIndex);
28 | return interceptor.load(this);
29 | }
30 |
31 | public CacheRequest getRequest() {
32 | return mRequest;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/offline/DefaultRemoteResourceInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.offline;
2 |
3 | import android.content.Context;
4 |
5 | import com.youzanyun.sdk.sample.cache.WebResource;
6 | import com.youzanyun.sdk.sample.cache.loader.OkHttpResourceLoader;
7 | import com.youzanyun.sdk.sample.cache.loader.ResourceLoader;
8 | import com.youzanyun.sdk.sample.cache.loader.SourceRequest;
9 |
10 | /**
11 | * Created by Ryan
12 | * at 2019/9/27
13 | */
14 | public class DefaultRemoteResourceInterceptor implements ResourceInterceptor {
15 |
16 | private ResourceLoader mResourceLoader;
17 |
18 | DefaultRemoteResourceInterceptor(Context context) {
19 | mResourceLoader = new OkHttpResourceLoader(context);
20 | }
21 |
22 | @Override
23 | public WebResource load(Chain chain) {
24 | CacheRequest request = chain.getRequest();
25 | SourceRequest sourceRequest = new SourceRequest(request, true);
26 | WebResource resource = mResourceLoader.getResource(sourceRequest);
27 | if (resource != null) {
28 | return resource;
29 | }
30 | return chain.process(request);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/offline/DefaultWebResponseGenerator.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.offline;
2 |
3 | import android.os.Build;
4 | import android.text.TextUtils;
5 | import android.webkit.WebResourceResponse;
6 |
7 |
8 | import com.youzanyun.sdk.sample.cache.WebResource;
9 | import com.youzan.androidsdk.utils.LogUtils;
10 |
11 | import java.io.ByteArrayInputStream;
12 | import java.io.InputStream;
13 | import java.util.Map;
14 |
15 | /**
16 | * Created by Ryan
17 | * at 2019/10/8
18 | */
19 | public class DefaultWebResponseGenerator implements WebResourceResponseGenerator {
20 |
21 | public static final String KEY_CONTENT_TYPE = "Content-Type";
22 |
23 | @Override
24 | public WebResourceResponse generate(WebResource resource, String urlMime) {
25 | if (resource == null) {
26 | return null;
27 | }
28 | Map headers = resource.getResponseHeaders();
29 | String contentType = null;
30 | String charset = null;
31 | if (headers != null) {
32 | String contentTypeValue = getContentType(headers, KEY_CONTENT_TYPE);
33 | if (!TextUtils.isEmpty(contentTypeValue)) {
34 | String[] contentTypeArray = contentTypeValue.split(";");
35 | if (contentTypeArray.length >= 1) {
36 | contentType = contentTypeArray[0];
37 | }
38 | if (contentTypeArray.length >= 2) {
39 | charset = contentTypeArray[1];
40 | String[] charsetArray = charset.split("=");
41 | if (charsetArray.length >= 2) {
42 | charset = charsetArray[1];
43 | }
44 | }
45 | }
46 | }
47 | if (!TextUtils.isEmpty(contentType)) {
48 | urlMime = contentType;
49 | }
50 | if (TextUtils.isEmpty(urlMime)) {
51 | return null;
52 | }
53 | byte[] resourceBytes = resource.getOriginBytes();
54 | if (resourceBytes == null || resourceBytes.length < 0) {
55 | return null;
56 | }
57 | if (resourceBytes.length == 0 && resource.getResponseCode() == 304) {
58 | LogUtils.d("the response bytes can not be empty if we get 304.");
59 | return null;
60 | }
61 | InputStream bis = new ByteArrayInputStream(resourceBytes);
62 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
63 | int status = resource.getResponseCode();
64 | String reasonPhrase = resource.getReasonPhrase();
65 | if (TextUtils.isEmpty(reasonPhrase)) {
66 | reasonPhrase = PhraseList.getPhrase(status);
67 | }
68 | return new WebResourceResponse(urlMime, charset, status, reasonPhrase, resource.getResponseHeaders(), bis);
69 | }
70 | return new WebResourceResponse(urlMime, charset, bis);
71 | }
72 |
73 | private String getContentType(Map headers, String key) {
74 | if (headers != null) {
75 | String value = headers.get(key);
76 | return value != null ? value : headers.get(key.toLowerCase());
77 | }
78 | return null;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/offline/Destroyable.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.offline;
2 |
3 | /**
4 | * Created by Ryan
5 | * at 2019/9/27
6 | */
7 | public interface Destroyable {
8 |
9 | void destroy();
10 | }
11 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/offline/DiskResourceInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.offline;
2 |
3 | import android.text.TextUtils;
4 |
5 | import com.youzan.androidsdk.YouzanLog;
6 | import com.youzanyun.sdk.sample.cache.WebResource;
7 | import com.youzanyun.sdk.sample.cache.config.CacheConfig;
8 | import com.youzan.androidsdk.utils.HeaderUtils;
9 | import com.youzan.androidsdk.utils.LogUtils;
10 | import com.youzan.androidsdk.utils.StreamUtils;
11 | import com.youzan.androidsdk.utils.lru.DiskLruCache;
12 |
13 | import java.io.File;
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 | import java.io.OutputStream;
17 | import java.util.Map;
18 |
19 | import okhttp3.Headers;
20 | import okio.BufferedSink;
21 | import okio.BufferedSource;
22 | import okio.Okio;
23 |
24 | /**
25 | * Created by Ryan
26 | * at 2019/9/27
27 | */
28 | public class DiskResourceInterceptor implements Destroyable, ResourceInterceptor {
29 |
30 | private static final int ENTRY_META = 0;
31 | private static final int ENTRY_BODY = 1;
32 | private static final int ENTRY_COUNT = 2;
33 | private DiskLruCache mDiskLruCache;
34 | private CacheConfig mCacheConfig;
35 |
36 | DiskResourceInterceptor(CacheConfig cacheConfig) {
37 | mCacheConfig = cacheConfig;
38 | }
39 |
40 | private synchronized void ensureDiskLruCacheCreate() {
41 | if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {
42 | return;
43 | }
44 | String dir = mCacheConfig.getCacheDir();
45 | int version = mCacheConfig.getVersion();
46 | long cacheSize = mCacheConfig.getDiskCacheSize();
47 | try {
48 | mDiskLruCache = DiskLruCache.open(new File(dir), version, ENTRY_COUNT, cacheSize);
49 | } catch (IOException e) {
50 | e.printStackTrace();
51 | }
52 | }
53 |
54 | private WebResource getFromDiskCache(String key) {
55 | try {
56 | if (mDiskLruCache.isClosed()) {
57 | return null;
58 | }
59 | DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
60 | if (snapshot != null) {
61 | BufferedSource entrySource = Okio.buffer(Okio.source(snapshot.getInputStream(ENTRY_META)));
62 | // 1. read status
63 | String responseCode = entrySource.readUtf8LineStrict();
64 | String reasonPhrase = entrySource.readUtf8LineStrict();
65 | // 2. read headers
66 | long headerSize = entrySource.readDecimalLong();
67 | Map headers;
68 | Headers.Builder responseHeadersBuilder = new Headers.Builder();
69 | // read first placeholder line
70 | String placeHolder = entrySource.readUtf8LineStrict();
71 | if (!TextUtils.isEmpty(placeHolder.trim())) {
72 | responseHeadersBuilder.add(placeHolder);
73 | headerSize--;
74 | }
75 | for (int i = 0; i < headerSize; i++) {
76 | String line = entrySource.readUtf8LineStrict();
77 | if (!TextUtils.isEmpty(line)) {
78 | responseHeadersBuilder.add(line);
79 | }
80 | }
81 | headers = HeaderUtils.generateHeadersMap(responseHeadersBuilder.build());
82 | // 3. read body
83 | InputStream inputStream = snapshot.getInputStream(ENTRY_BODY);
84 | if (inputStream != null) {
85 | WebResource webResource = new WebResource();
86 | webResource.setReasonPhrase(reasonPhrase);
87 | webResource.setResponseCode(Integer.valueOf(responseCode));
88 | webResource.setOriginBytes(StreamUtils.streamToBytes(inputStream));
89 | webResource.setResponseHeaders(headers);
90 | webResource.setModified(false);
91 | return webResource;
92 | }
93 | snapshot.close();
94 | }
95 | } catch (Exception e) {
96 | e.printStackTrace();
97 | }
98 | return null;
99 | }
100 |
101 |
102 | @Override
103 | public WebResource load(Chain chain) {
104 | CacheRequest request = chain.getRequest();
105 | ensureDiskLruCacheCreate();
106 | WebResource webResource = getFromDiskCache(request.getKey());
107 | if (webResource != null && isRealMimeTypeCacheable(webResource)) {
108 | LogUtils.d(String.format("disk cache hit: %s", request.getUrl()));
109 | YouzanLog.addLog(YouzanLog.S_EVENT_TYPE_OFFLINE, "命中 disk cache, request url = " + request.getUrl() );
110 | return webResource;
111 | }
112 | webResource = chain.process(request);
113 | if (webResource != null && (webResource.isCacheByOurselves() || isRealMimeTypeCacheable(webResource))) {
114 | cacheToDisk(request.getKey(), webResource);
115 | }
116 | return webResource;
117 | }
118 |
119 | @Override
120 | public void destroy() {
121 | if (mDiskLruCache != null && !mDiskLruCache.isClosed()) {
122 | try {
123 | mDiskLruCache.close();
124 | } catch (IOException e) {
125 | e.printStackTrace();
126 | }
127 | }
128 | }
129 |
130 | private void cacheToDisk(String key, WebResource webResource) {
131 | if (webResource == null || !webResource.isCacheable()) {
132 | return;
133 | }
134 | if (mDiskLruCache.isClosed()) {
135 | return;
136 | }
137 | try {
138 | DiskLruCache.Editor editor = mDiskLruCache.edit(key);
139 | if (editor == null) {
140 | LogUtils.d("Another edit is in progress!");
141 | return;
142 | }
143 | OutputStream metaOutput = editor.newOutputStream(ENTRY_META);
144 | BufferedSink sink = Okio.buffer(Okio.sink(metaOutput));
145 | // 1. write status
146 | sink.writeUtf8(String.valueOf(webResource.getResponseCode())).writeByte('\n');
147 | sink.writeUtf8(webResource.getReasonPhrase()).writeByte('\n');
148 | // 2. write response header
149 | Map headers = webResource.getResponseHeaders();
150 | sink.writeDecimalLong(headers.size()).writeByte('\n');
151 | for (Map.Entry entry : headers.entrySet()) {
152 | String headerKey = entry.getKey();
153 | String headerValue = entry.getValue();
154 | sink.writeUtf8(headerKey)
155 | .writeUtf8(": ")
156 | .writeUtf8(headerValue)
157 | .writeByte('\n');
158 | }
159 | sink.flush();
160 | sink.close();
161 | // 3. write response body
162 | OutputStream bodyOutput = editor.newOutputStream(ENTRY_BODY);
163 | sink = Okio.buffer(Okio.sink(bodyOutput));
164 | byte[] originBytes = webResource.getOriginBytes();
165 | if (originBytes != null && originBytes.length > 0) {
166 | sink.write(originBytes);
167 | sink.flush();
168 | editor.commit();
169 | }
170 | sink.close();
171 | } catch (IOException e) {
172 | LogUtils.e("cache to disk failed. cause by: " + e.getMessage());
173 | try {
174 | // clean the redundant data
175 | mDiskLruCache.remove(key);
176 | } catch (IOException ignore) {
177 | }
178 | } catch (Exception e) {
179 | LogUtils.e(e.getMessage());
180 | }
181 | }
182 |
183 | private boolean isRealMimeTypeCacheable(WebResource resource) {
184 | if (resource == null) {
185 | return false;
186 | }
187 | Map headers = resource.getResponseHeaders();
188 | String contentType = null;
189 | if (headers != null) {
190 | String uppercaseKey = "Content-Type";
191 | String lowercaseKey = uppercaseKey.toLowerCase();
192 | String contentTypeValue = headers.containsKey(uppercaseKey) ? headers.get(uppercaseKey) : headers.get(lowercaseKey);
193 | if (!TextUtils.isEmpty(contentTypeValue)) {
194 | String[] contentTypeArray = contentTypeValue.split(";");
195 | if (contentTypeArray.length >= 1) {
196 | contentType = contentTypeArray[0];
197 | }
198 | }
199 | }
200 | return contentType != null && !mCacheConfig.getFilter().isFilter(contentType);
201 | }
202 | }
203 |
204 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/offline/ForceRemoteResourceInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.offline;
2 |
3 | import android.content.Context;
4 | import android.text.TextUtils;
5 |
6 | import com.youzan.androidsdk.YouzanLog;
7 | import com.youzanyun.sdk.sample.cache.WebResource;
8 | import com.youzanyun.sdk.sample.cache.config.CacheConfig;
9 | import com.youzanyun.sdk.sample.cache.config.MimeTypeFilter;
10 | import com.youzanyun.sdk.sample.cache.loader.OkHttpResourceLoader;
11 | import com.youzanyun.sdk.sample.cache.loader.ResourceLoader;
12 | import com.youzanyun.sdk.sample.cache.loader.SourceRequest;
13 |
14 | /**
15 | * Created by Ryan
16 | * at 2019/9/27
17 | */
18 | public class ForceRemoteResourceInterceptor implements Destroyable, ResourceInterceptor {
19 |
20 | private ResourceLoader mResourceLoader;
21 | private MimeTypeFilter mMimeTypeFilter;
22 |
23 | ForceRemoteResourceInterceptor(Context context, CacheConfig cacheConfig) {
24 | mResourceLoader = new OkHttpResourceLoader(context);
25 | mMimeTypeFilter = cacheConfig != null ? cacheConfig.getFilter() : null;
26 | }
27 |
28 | @Override
29 | public WebResource load(Chain chain) {
30 | CacheRequest request = chain.getRequest();
31 | String mime = request.getMime();
32 | boolean isFilter;
33 | if (TextUtils.isEmpty(mime)) {
34 | isFilter = isFilterHtml();
35 | } else {
36 | isFilter = mMimeTypeFilter.isFilter(mime);
37 | }
38 |
39 |
40 | SourceRequest sourceRequest = new SourceRequest(request, isFilter);
41 | WebResource resource = mResourceLoader.getResource(sourceRequest);
42 | if (resource != null) {
43 | YouzanLog.addLog(YouzanLog.S_EVENT_TYPE_OFFLINE, "命中 Okhttp, request url = " + request.getUrl() + "mimeType = " + mime );
44 | return resource;
45 | }
46 | return chain.process(request);
47 | }
48 |
49 | @Override
50 | public void destroy() {
51 | if (mMimeTypeFilter != null) {
52 | mMimeTypeFilter.clear();
53 | }
54 | }
55 |
56 | private boolean isFilterHtml() {
57 | return mMimeTypeFilter.isFilter("text/html");
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/offline/MemResourceInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.offline;
2 |
3 |
4 | import com.youzan.androidsdk.YouzanLog;
5 | import com.youzanyun.sdk.sample.cache.WebResource;
6 | import com.youzanyun.sdk.sample.cache.config.CacheConfig;
7 | import android.support.v4.util.LruCache;
8 |
9 | /**
10 | * Created by Ryan
11 | * on 2020/4/14
12 | */
13 | public class MemResourceInterceptor implements ResourceInterceptor, Destroyable {
14 |
15 | private LruCache mLruCache;
16 |
17 | private static volatile MemResourceInterceptor sInstance;
18 |
19 | public static MemResourceInterceptor getInstance(CacheConfig cacheConfig) {
20 | if (sInstance == null) {
21 | synchronized (MemResourceInterceptor.class) {
22 | if (sInstance == null) {
23 | sInstance = new MemResourceInterceptor(cacheConfig);
24 | }
25 | }
26 | }
27 | return sInstance;
28 | }
29 |
30 | private MemResourceInterceptor(CacheConfig cacheConfig) {
31 | int memorySize = cacheConfig.getMemCacheSize();
32 | if (memorySize > 0) {
33 | mLruCache = new ResourceMemCache(memorySize);
34 | }
35 | }
36 |
37 | @Override
38 | public WebResource load(Chain chain) {
39 | CacheRequest request = chain.getRequest();
40 | if (mLruCache != null) {
41 | WebResource resource = mLruCache.get(request.getKey());
42 | if (checkResourceValid(resource)) {
43 | YouzanLog.addLog(YouzanLog.S_EVENT_TYPE_OFFLINE, "命中 mem cache, request url = " + request.getUrl() );
44 | return resource;
45 | }
46 | }
47 | WebResource resource = chain.process(request);
48 | if (mLruCache != null && checkResourceValid(resource) && resource.isCacheable()) {
49 | mLruCache.put(request.getKey(), resource);
50 | }
51 | return resource;
52 | }
53 |
54 | private boolean checkResourceValid(WebResource resource) {
55 | return resource != null
56 | && resource.getOriginBytes() != null
57 | && resource.getOriginBytes().length >= 0
58 | && resource.getResponseHeaders() != null
59 | && !resource.getResponseHeaders().isEmpty();
60 | }
61 |
62 | @Override
63 | public void destroy() {
64 | if (mLruCache != null) {
65 | mLruCache.evictAll();
66 | mLruCache = null;
67 | }
68 | }
69 |
70 | private static class ResourceMemCache extends LruCache {
71 |
72 | /**
73 | * @param maxSize for caches that do not override {@link #sizeOf}, this is
74 | * the maximum number of entries in the cache. For all other caches,
75 | * this is the maximum sum of the sizes of the entries in this cache.
76 | */
77 | ResourceMemCache(int maxSize) {
78 | super(maxSize);
79 | }
80 |
81 | @Override
82 | protected int sizeOf(String key, WebResource value) {
83 | int size = 0;
84 | if (value != null && value.getOriginBytes() != null) {
85 | size = value.getOriginBytes().length;
86 | }
87 | return size;
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/offline/OfflineServer.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.offline;
2 |
3 | import android.webkit.WebResourceResponse;
4 |
5 | /**
6 | * Created by Ryan
7 | * at 2019/9/27
8 | */
9 | public interface OfflineServer {
10 |
11 | WebResourceResponse get(CacheRequest request);
12 |
13 | void addResourceInterceptor(ResourceInterceptor interceptor);
14 |
15 | void destroy();
16 | }
17 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/offline/OfflineServerImpl.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.offline;
2 |
3 | import android.content.Context;
4 | import android.webkit.WebResourceResponse;
5 |
6 |
7 | import com.youzanyun.sdk.sample.cache.WebResource;
8 | import com.youzanyun.sdk.sample.cache.config.CacheConfig;
9 |
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | /**
14 | * Created by Ryan
15 | * at 2019/9/27
16 | */
17 | public class OfflineServerImpl implements OfflineServer {
18 |
19 | private Context mContext;
20 | private CacheConfig mCacheConfig;
21 | private List mBaseInterceptorList;
22 | private List mForceModeChainList;
23 | private List mDefaultModeChainList;
24 | private WebResourceResponseGenerator mResourceResponseGenerator;
25 |
26 | public OfflineServerImpl(Context context, CacheConfig cacheConfig) {
27 | mContext = context.getApplicationContext();
28 | mCacheConfig = cacheConfig;
29 | mResourceResponseGenerator = new DefaultWebResponseGenerator();
30 | }
31 |
32 | private List buildForceModeChain(Context context, CacheConfig cacheConfig) {
33 | if (mForceModeChainList == null) {
34 | int interceptorsCount = 3 + getBaseInterceptorsCount();
35 | List interceptors = new ArrayList<>(interceptorsCount);
36 | if (mBaseInterceptorList != null && !mBaseInterceptorList.isEmpty()) {
37 | interceptors.addAll(mBaseInterceptorList);
38 | }
39 | interceptors.add(MemResourceInterceptor.getInstance(cacheConfig));
40 | interceptors.add(new DiskResourceInterceptor(cacheConfig));
41 | interceptors.add(new ForceRemoteResourceInterceptor(context, cacheConfig));
42 | mForceModeChainList = interceptors;
43 | }
44 | return mForceModeChainList;
45 | }
46 |
47 | private List buildDefaultModeChain(Context context) {
48 | if (mDefaultModeChainList == null) {
49 | int interceptorsCount = 1 + getBaseInterceptorsCount();
50 | List interceptors = new ArrayList<>(interceptorsCount);
51 | if (mBaseInterceptorList != null && !mBaseInterceptorList.isEmpty()) {
52 | interceptors.addAll(mBaseInterceptorList);
53 | }
54 | interceptors.add(new DefaultRemoteResourceInterceptor(context));
55 | mDefaultModeChainList = interceptors;
56 | }
57 | return mDefaultModeChainList;
58 | }
59 |
60 | @Override
61 | public WebResourceResponse get(CacheRequest request) {
62 | boolean isForceMode = request.isForceMode();
63 | Context context = mContext;
64 | CacheConfig config = mCacheConfig;
65 | List interceptors = isForceMode ? buildForceModeChain(context, config) : buildDefaultModeChain(context);
66 | WebResource resource = callChain(interceptors, request);
67 | return mResourceResponseGenerator.generate(resource, request.getMime());
68 | }
69 |
70 | @Override
71 | public synchronized void addResourceInterceptor(ResourceInterceptor interceptor) {
72 | if (mBaseInterceptorList == null) {
73 | mBaseInterceptorList = new ArrayList<>();
74 | }
75 | mBaseInterceptorList.add(interceptor);
76 | }
77 |
78 | @Override
79 | public synchronized void destroy() {
80 | destroyAll(mDefaultModeChainList);
81 | destroyAll(mForceModeChainList);
82 | }
83 |
84 | private WebResource callChain(List interceptors, CacheRequest request) {
85 |
86 | Chain chain = new Chain(interceptors);
87 | return chain.process(request);
88 | }
89 |
90 | private void destroyAll(List interceptors) {
91 | if (interceptors == null || interceptors.isEmpty()) {
92 | return;
93 | }
94 | for (ResourceInterceptor interceptor : interceptors) {
95 | if (interceptor instanceof Destroyable) {
96 | ((Destroyable) interceptor).destroy();
97 | }
98 | }
99 | }
100 |
101 | private int getBaseInterceptorsCount() {
102 | return mBaseInterceptorList == null ? 0 : mBaseInterceptorList.size();
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/offline/PhraseList.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.offline;
2 |
3 | /**
4 | * provider HTTP status code to reasonPhrase map.
5 | *
6 | * Created by Ryan
7 | * on 2020/6/11
8 | */
9 | class PhraseList {
10 |
11 | static String getPhrase(int statusCode) {
12 | switch (statusCode) {
13 | case 100:
14 | return "Continue";
15 | case 101:
16 | return "Switching Protocols";
17 | case 200:
18 | return "OK";
19 | case 201:
20 | return "Created";
21 | case 202:
22 | return "Accepted";
23 | case 203:
24 | return "Non-Authoritative Information";
25 | case 204:
26 | return "No Content";
27 | case 205:
28 | return "Reset Content";
29 | case 206:
30 | return "Partial Content";
31 | case 300:
32 | return "Multiple Choices";
33 | case 301:
34 | return "Moved Permanently";
35 | case 302:
36 | return "Found";
37 | case 303:
38 | return "See Other";
39 | case 304:
40 | return "Not Modified";
41 | case 305:
42 | return "Use Proxy";
43 | case 306:
44 | return "Unused";
45 | case 307:
46 | return "Temporary Redirect";
47 | case 400:
48 | return "Bad Request";
49 | case 401:
50 | return "Unauthorized";
51 | case 402:
52 | return "Payment Required";
53 | case 403:
54 | return "Forbidden";
55 | case 404:
56 | return "Not Found";
57 | case 405:
58 | return "Method Not Allowed";
59 | case 406:
60 | return "Not Acceptable";
61 | case 407:
62 | return "Proxy Authentication Required";
63 | case 408:
64 | return "Request Time-out";
65 | case 409:
66 | return "Conflict";
67 | case 410:
68 | return "Gone";
69 | case 411:
70 | return "Length Required";
71 | case 412:
72 | return "Precondition Failed";
73 | case 413:
74 | return "Request Entity Too Large";
75 | case 414:
76 | return "Request-URI Too Large";
77 | case 415:
78 | return "Unsupported Media Type";
79 | case 416:
80 | return "Requested range not satisfiable";
81 | case 417:
82 | return "Expectation Failed";
83 | case 500:
84 | return "Internal Server Error";
85 | case 501:
86 | return "Not Implemented";
87 | case 502:
88 | return "Bad Gateway";
89 | case 503:
90 | return "Service Unavailable";
91 | case 504:
92 | return "Gateway Time-out";
93 | case 505:
94 | return "HTTP Version not supported";
95 | default:
96 | return "unknown";
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/offline/ResourceInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.offline;
2 |
3 | import com.youzanyun.sdk.sample.cache.WebResource;
4 |
5 | /**
6 | * Created by Ryan
7 | * at 2019/9/27
8 | */
9 | public interface ResourceInterceptor {
10 |
11 | WebResource load(Chain chain);
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/offline/WebResourceResponseGenerator.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.offline;
2 |
3 | import android.webkit.WebResourceResponse;
4 |
5 | import com.youzanyun.sdk.sample.cache.WebResource;
6 |
7 |
8 | /**
9 | * Created by Ryan
10 | * at 2019/10/8
11 | */
12 | public interface WebResourceResponseGenerator {
13 |
14 | WebResourceResponse generate(WebResource resource, String urlMime);
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/okhttp/OkHttpClientProvider.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache.okhttp;
2 |
3 | import android.content.Context;
4 |
5 | import com.youzanyun.sdk.sample.cache.cookie.FastCookieManager;
6 |
7 | import java.io.File;
8 | import java.util.concurrent.TimeUnit;
9 |
10 | import okhttp3.Cache;
11 | import okhttp3.OkHttpClient;
12 |
13 | /**
14 | * Created by Ryan
15 | * at 2019/9/26
16 | */
17 | public class OkHttpClientProvider {
18 |
19 | private static final String CACHE_OKHTTP_DIR_NAME = "cached_webview_okhttp";
20 | private static final int OKHTTP_CACHE_SIZE = 100 * 1024 * 1024;
21 | private static volatile OkHttpClientProvider sInstance;
22 | private OkHttpClient mClient;
23 |
24 | private OkHttpClientProvider(Context context) {
25 | createOkHttpClient(context);
26 | }
27 |
28 | private static OkHttpClientProvider getInstance(Context context) {
29 | if (sInstance == null) {
30 | synchronized (OkHttpClientProvider.class) {
31 | if (sInstance == null) {
32 | sInstance = new OkHttpClientProvider(context);
33 | }
34 | }
35 | }
36 | return sInstance;
37 | }
38 |
39 |
40 | private void createOkHttpClient(Context context) {
41 | String dir = context.getCacheDir() + File.separator + CACHE_OKHTTP_DIR_NAME;
42 | mClient = new OkHttpClient.Builder()
43 | .cookieJar(FastCookieManager.getInstance().getCookieJar(context))
44 | .cache(new Cache(new File(dir), OKHTTP_CACHE_SIZE))
45 | .readTimeout(20, TimeUnit.SECONDS)
46 | .writeTimeout(20, TimeUnit.SECONDS)
47 | .connectTimeout(20, TimeUnit.SECONDS)
48 | // auto redirects is not allowed, bc we need to notify webview to do some internal processing.
49 | .followSslRedirects(false)
50 | .followRedirects(false)
51 | .build();
52 | }
53 |
54 | public static OkHttpClient get(Context context) {
55 | return getInstance(context).mClient;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/config/KaeConfig.kt:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.config
2 |
3 | /**
4 | * auther: liusaideng
5 | * created on : 2023/7Youz/17 4:16 PM
6 | * desc:
7 | */
8 | object KaeConfig {
9 | // clientId
10 | const val S_CLIENT_ID = "0073bccbaf5369028a"
11 | // const val S_URL_MAIN = "https://shop156571076.m.youzan.com/wscshop/showcase/homepage?kdt_id=156378908"
12 | // const val S_URL_MAIN = "https://shop139935761.m.youzan.com/wscshop/showcase/homepage?kdt_id=139743593"
13 | // const val S_URL_MAIN = "https://shop143703561.youzan.com/v2/showcase/homepage?alias=b0hB2PIg6s&dc_ps=3626055956015745029.300001"
14 | const val S_URL_MAIN = "https://shop141337475.m.youzan.com/wscshop/showcase/homepage?kdt_id=141145307"
15 |
16 |
17 | // const val S_URL_MAIN = "https://shop122003905.m.youzan.com/v2/showcase/homepage?alias=I7OEE6dEc2&showRetailComps=1"
18 | ////// const val S_URL_MINE = "https://shop139935761.youzan.com/wscuser/membercenter?alias=Qn7FnnQwAB&reft=1715852464115&spm=f.131511492"
19 | // const val S_URL_MAIN = "https://shop16911610.m.youzan.com/wscshop/showcase/homepage?kdt_id=16719442"
20 | // const val S_URL_MAIN ="https://shop42618405.m.youzan.com/v2/showcase/homepage?kdt_id=42426237"
21 | }
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/helper/LoginHelper.kt:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.helper
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 |
6 | /**
7 | * auther: liusaideng
8 | * created on : 2023/7/17 7:39 PM
9 | * desc:
10 | */
11 | object LoginHelper {
12 | private var sp: SharedPreferences? = null
13 |
14 |
15 | fun init(context: Context) {
16 | sp = context.getSharedPreferences("x5", Context.MODE_PRIVATE)
17 | }
18 |
19 | fun isLogin(): Boolean {
20 | return sp?.getBoolean("is_login", false) == true
21 | }
22 |
23 | fun setLogin(isLogin: Boolean) {
24 | sp?.edit()?.putBoolean("is_login", isLogin)?.apply()
25 | }
26 | }
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/helper/YouzanHelper.kt:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.helper
2 |
3 | import android.content.Context
4 | import com.youzan.androidsdk.YouzanSDK
5 | import com.youzan.androidsdk.YouzanToken
6 | import com.youzan.androidsdk.YzLoginCallback
7 |
8 | /**
9 | * auther: liusaideng
10 | * created on : 2023/7/18 10:12 AM
11 | * desc:
12 | */
13 | object YouzanHelper {
14 |
15 | fun loginYouzan(context: Context, callback: (youzanToken: YouzanToken) -> Unit) {
16 | YouzanSDK.yzlogin(
17 | "31467761",
18 | "https://cdn.daddylab.com/Upload/android/20210113/021119/au9j4d6aed5xfweg.jpeg?w=1080&h=1080",
19 | "",
20 | "一百亿养乐多",
21 | "0",
22 | object : YzLoginCallback {
23 | override fun onSuccess(youzanToken: YouzanToken) {
24 | YouzanSDK.sync(context, youzanToken)
25 | callback.invoke(youzanToken)
26 | }
27 |
28 | override fun onFail(s: String) {}
29 | })
30 | }
31 | }
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/x5/LoginActivity.kt:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.x5
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.support.v7.app.AppCompatActivity
6 | import android.view.View
7 | import com.youzan.androidsdk.YouzanSDK
8 | import com.youzan.androidsdk.YouzanToken
9 | import com.youzan.androidsdk.YzLoginCallback
10 | import com.youzanyun.sdk.sample.helper.LoginHelper
11 | import com.youzanyun.sdk.sample.helper.YouzanHelper
12 | import com.youzanyun.sdk.sample.x5.R
13 |
14 | class LoginActivity : AppCompatActivity() {
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | setContentView(R.layout.activity_login)
18 |
19 | findViewById(R.id.login_tv).setOnClickListener {
20 |
21 | YouzanHelper.loginYouzan(this@LoginActivity) {
22 | LoginHelper.setLogin(false)
23 | Intent(this@LoginActivity, MainActivity::class.java).apply {
24 | this.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
25 | startActivity(this)
26 | this@LoginActivity.finish()
27 | }
28 | }
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/x5/LogoutFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 youzanyun.com, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.youzanyun.sdk.sample.x5
17 |
18 | import android.content.Intent
19 | import android.os.Bundle
20 | import android.support.v4.app.Fragment
21 | import android.view.LayoutInflater
22 | import android.view.View
23 | import android.view.ViewGroup
24 | import com.youzan.androidsdk.YouzanSDK
25 | import com.youzanyun.sdk.sample.helper.LoginHelper
26 |
27 | /**
28 | * 这里使用[WebViewFragment]对[WebView]生命周期有更好的管控.
29 | */
30 | class LogoutFragment : Fragment() {
31 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
32 | return inflater.inflate(R.layout.fg_logout, container, false)
33 | }
34 |
35 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
36 | super.onViewCreated(view, savedInstanceState)
37 | view.findViewById(R.id.logout_tv).setOnClickListener {
38 | LoginHelper.setLogin(false)
39 | activity?.let { it1 -> YouzanSDK.userLogout(it1) }
40 | Intent(requireActivity(), LoginActivity::class.java).apply {
41 | setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
42 | startActivity(this)
43 | }
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/x5/MainActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 youzanyun.com, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.youzanyun.sdk.sample.x5
17 |
18 | import android.content.Context
19 | import android.content.Intent
20 | import android.net.ConnectivityManager
21 | import android.net.ConnectivityManager.TYPE_WIFI
22 | import android.net.wifi.WifiManager
23 | import android.os.Bundle
24 | import android.support.v4.app.Fragment
25 | import android.support.v4.app.FragmentActivity
26 | import android.support.v4.app.FragmentPagerAdapter
27 | import android.support.v4.view.ViewPager
28 | import android.view.View
29 | import com.ashokvarma.bottomnavigation.BottomNavigationBar
30 | import com.ashokvarma.bottomnavigation.BottomNavigationBar.MODE_FIXED
31 | import com.ashokvarma.bottomnavigation.BottomNavigationBar.OnTabSelectedListener
32 | import com.ashokvarma.bottomnavigation.BottomNavigationItem
33 | import com.youzanyun.sdk.sample.config.KaeConfig
34 |
35 |
36 | class MainActivity : FragmentActivity(), View.OnClickListener {
37 | private lateinit var mBottomNavigator: BottomNavigationBar
38 | private lateinit var mViewPager: ViewPager
39 | private val fgLists = mutableListOf()
40 | override fun onCreate(savedInstanceState: Bundle?) {
41 | super.onCreate(savedInstanceState)
42 | setContentView(R.layout.activity_main)
43 | mBottomNavigator = findViewById(R.id.bottom_navigator)
44 | mBottomNavigator.addItem(BottomNavigationItem(R.drawable.ic_launcher, "主页"))
45 | .addItem(BottomNavigationItem(R.drawable.ic_launcher, "退出入口"))
46 | .setMode(MODE_FIXED)
47 | .initialise()
48 | mBottomNavigator.setBackgroundResource(R.color.x5_grey)
49 |
50 | mBottomNavigator.setTabSelectedListener(object : OnTabSelectedListener {
51 | override fun onTabSelected(position: Int) {
52 | mViewPager.setCurrentItem(position, true)
53 | }
54 |
55 | override fun onTabUnselected(position: Int) {
56 |
57 | }
58 |
59 | override fun onTabReselected(position: Int) {
60 |
61 | }
62 | })
63 |
64 |
65 | mViewPager = findViewById(R.id.vp)
66 | mViewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
67 | override fun onPageScrolled(p0: Int, p1: Float, p2: Int) {
68 | }
69 |
70 | override fun onPageSelected(p0: Int) {
71 | mBottomNavigator.selectTab(p0, false)
72 | }
73 |
74 | override fun onPageScrollStateChanged(p0: Int) {
75 | }
76 |
77 | })
78 | mViewPager.offscreenPageLimit = 3
79 | val fg0 = YouzanFragment.newInstance(intent.getStringExtra("url") ?: KaeConfig.S_URL_MAIN)
80 | val fg3 = LogoutFragment()
81 |
82 | fgLists.add(fg0)
83 | fgLists.add(fg3)
84 | mViewPager.adapter = object : FragmentPagerAdapter(supportFragmentManager) {
85 | override fun getCount(): Int {
86 | return 2
87 | }
88 |
89 | override fun getItem(p0: Int): Fragment {
90 | return when (p0) {
91 | 0 -> fg0
92 | 3 -> fg3
93 | else -> fg3
94 | }
95 | }
96 |
97 |
98 | }
99 | }
100 |
101 | // override fun onClick(v: View) {
102 | // when (v.id) {
103 | // R.id.button_open -> {
104 | // //店铺链接, 可以从有赞后台`店铺=>店铺概况=>访问店铺`复制到相应的链接,这里是一个测试链接
105 | // var url: String
106 | // //
107 | //// "https://shop118687317.m.youzan.com/wscshop/showcase/homepage?kdt_id=118495149";
108 | //// "https://h5.youzan.com/v2/showcase/homepage?alias=lUWblj8NNI";
109 | // gotoActivity(url)
110 | // }
111 | // R.id.button_clear -> YouzanSDK.userLogout(this)
112 | // else -> {}
113 | // }
114 | // }
115 |
116 | private fun gotoActivity(url: String) {
117 | val intent = Intent(this, YouzanActivity::class.java)
118 | intent.putExtra(YouzanActivity.KEY_URL, url)
119 | startActivity(intent)
120 | }
121 |
122 | override fun onClick(v: View?) {
123 |
124 | }
125 |
126 | override fun onBackPressed() {
127 | if ((fgLists.get(index = mViewPager.currentItem) as? YouzanFragment)?.onBackPressed() == true) {
128 | return
129 | }
130 |
131 | super.onBackPressed()
132 | }
133 |
134 | override fun onResume() {
135 | super.onResume()
136 | getWifiSSID(this@MainActivity)
137 | }
138 |
139 | fun getWifiSSID(context: Context): String? {
140 | var bssid = ""
141 | val manager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
142 | ?: return bssid
143 | val activeNetworkInfo = manager.activeNetworkInfo
144 | if (activeNetworkInfo != null && activeNetworkInfo.isConnectedOrConnecting && activeNetworkInfo.type == TYPE_WIFI) {
145 | val wifiManager = context.getApplicationContext().getSystemService(Context.WIFI_SERVICE) as WifiManager
146 | ?: return bssid
147 | val connectionInfo = wifiManager.connectionInfo
148 | bssid = connectionInfo.bssid
149 | }
150 | return bssid
151 | }
152 |
153 | }
154 |
155 |
156 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/x5/SplashActivity.kt:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.x5
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.support.v7.app.AppCompatActivity
6 | import android.view.View
7 | import android.widget.EditText
8 | import com.youzan.androidsdk.YouzanSDK
9 | import com.youzan.androidsdkx5.YouzanPreloader
10 | import com.youzanyun.sdk.sample.config.KaeConfig
11 |
12 | class SplashActivity : AppCompatActivity() {
13 | override fun onCreate(savedInstanceState: Bundle?) {
14 | super.onCreate(savedInstanceState)
15 | setContentView(R.layout.activity_splash)
16 | findViewById(R.id.go_with_login).setOnClickListener { goWithLogin() }
17 | findViewById(R.id.go_without_login).setOnClickListener { go() }
18 | findViewById(R.id.go).setOnClickListener {
19 | val url: String = findViewById(R.id.url).text.toString()
20 | if (url.startsWith("http")) {
21 | val intent = Intent(this@SplashActivity, MainActivity::class.java)
22 | intent.putExtra("url", url)
23 | startActivity(intent)
24 | }
25 | }
26 |
27 |
28 | findViewById(R.id.logout).setOnClickListener {
29 | YouzanSDK.userLogout(this@SplashActivity)
30 | }
31 |
32 | }
33 |
34 | fun goWithLogin() {
35 | val clz = LoginActivity::class.java
36 | val intent = Intent(this@SplashActivity, clz)
37 | startActivity(intent)
38 | }
39 |
40 | fun go() {
41 | val clz = MainActivity::class.java
42 | val intent = Intent(this@SplashActivity, clz)
43 | startActivity(intent)
44 | }
45 | }
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/x5/WebViewFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2010 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.youzanyun.sdk.sample.x5;
18 |
19 | import android.os.Bundle;
20 | import android.support.annotation.IdRes;
21 | import android.support.annotation.LayoutRes;
22 | import android.support.v4.app.Fragment;
23 | import android.view.LayoutInflater;
24 | import android.view.View;
25 | import android.view.ViewGroup;
26 |
27 | import com.youzan.androidsdkx5.YouzanBrowser;
28 |
29 |
30 | /**
31 | * A fragment that displays a WebView.
32 | * The WebView is automically paused or resumed when the Fragment is paused or resumed.
33 | */
34 | public abstract class WebViewFragment extends Fragment {
35 | private YouzanBrowser mWebView;
36 | private boolean mIsWebViewAvailable;
37 |
38 | public WebViewFragment() {
39 | }
40 |
41 | /**
42 | * Called to instantiate the view. Creates and returns the WebView.
43 | */
44 | @Override
45 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
46 | if (mWebView != null) {
47 | mWebView.destroy();
48 | }
49 | View contentView = inflater.inflate(getLayoutId(), container, false);
50 | mWebView = (YouzanBrowser) contentView.findViewById(getWebViewId());
51 | mIsWebViewAvailable = true;
52 | return contentView;
53 | }
54 |
55 | /**
56 | * @return The id of WebView in layout
57 | */
58 | @IdRes
59 | protected abstract int getWebViewId();
60 |
61 | /**
62 | * @return the layout id for Fragment
63 | */
64 | @LayoutRes
65 | protected abstract int getLayoutId();
66 |
67 | /**
68 | * Called when the fragment is visible to the user and actively running. Resumes the WebView.
69 | */
70 | @Override
71 | public void onPause() {
72 | super.onPause();
73 | // mWebView.onPause();
74 | }
75 |
76 | /**
77 | * Called when the fragment is no longer resumed. Pauses the WebView.
78 | */
79 | @Override
80 | public void onResume() {
81 | mWebView.onResume();
82 | super.onResume();
83 | }
84 |
85 | /**
86 | * Called when the WebView has been detached from the fragment.
87 | * The WebView is no longer available after this time.
88 | */
89 | @Override
90 | public void onDestroyView() {
91 | mIsWebViewAvailable = false;
92 | super.onDestroyView();
93 | }
94 |
95 | /**
96 | * Called when the fragment is no longer in use. Destroys the internal state of the WebView.
97 | */
98 | @Override
99 | public void onDestroy() {
100 | if (mWebView != null) {
101 | mWebView.destroy();
102 | mWebView = null;
103 | }
104 | super.onDestroy();
105 | }
106 |
107 | /**
108 | * Take care of popping the fragment back stack or finishing the activity
109 | * as appropriate.
110 | *
111 | * @return True if the host application wants to handle back press by itself, otherwise return false.
112 | */
113 | public boolean onBackPressed() {
114 | return false;
115 | }
116 |
117 | /**
118 | * Gets the WebView.
119 | */
120 | public YouzanBrowser getWebView() {
121 | return mIsWebViewAvailable ? mWebView : null;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/x5/YouzanActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 youzanyun.com, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.youzanyun.sdk.sample.x5
17 |
18 | import android.app.AppOpsManager
19 | import android.app.AsyncNotedAppOp
20 | import android.app.SyncNotedAppOp
21 | import android.net.wifi.WifiManager
22 | import android.os.Build
23 | import android.os.Bundle
24 | import android.support.v7.app.AppCompatActivity
25 | import android.util.Log
26 |
27 | class YouzanActivity : AppCompatActivity() {
28 | private var mFragment: YouzanFragment? = null
29 | private val MY_APP_TAG = "MY_APP_TAG"
30 |
31 | override fun onCreate(savedInstanceState: Bundle?) {
32 | super.onCreate(savedInstanceState)
33 | setContentView(R.layout.activity_placeholder)
34 |
35 |
36 |
37 | mFragment = YouzanFragment()
38 | mFragment!!.arguments = intent.extras
39 | supportFragmentManager
40 | .beginTransaction()
41 | .replace(R.id.placeholder, mFragment!!)
42 | .commitAllowingStateLoss()
43 | }
44 |
45 |
46 | override fun onResume() {
47 | super.onResume()
48 | }
49 |
50 | override fun onBackPressed() {
51 | if (mFragment == null || !mFragment!!.onBackPressed()) {
52 | super.onBackPressed()
53 | }
54 | }
55 |
56 | companion object {
57 | const val KEY_URL = "url"
58 | }
59 | }
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/x5/YouzanFragment.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 youzanyun.com, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package com.youzanyun.sdk.sample.x5
17 |
18 | import android.annotation.TargetApi
19 | import android.app.Activity
20 | import android.content.ActivityNotFoundException
21 | import android.content.Context
22 | import android.content.Intent
23 | import android.graphics.Bitmap
24 | import android.net.Uri
25 | import android.os.Build
26 | import android.os.Bundle
27 | import android.support.annotation.RequiresApi
28 | import android.support.v4.app.Fragment
29 | import android.support.v4.widget.SwipeRefreshLayout
30 | import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener
31 | import android.support.v7.widget.Toolbar
32 | import android.util.Log
33 | import android.view.View
34 | import android.widget.Toast
35 | import com.tencent.smtt.export.external.interfaces.WebResourceError
36 | import com.tencent.smtt.export.external.interfaces.WebResourceRequest
37 | import com.tencent.smtt.export.external.interfaces.WebResourceResponse
38 | import com.tencent.smtt.sdk.WebSettings
39 | import com.tencent.smtt.sdk.WebView
40 | import com.tencent.smtt.sdk.WebViewClient
41 | import com.youzan.androidsdk.event.*
42 | import com.youzan.androidsdk.model.goods.GoodsShareModel
43 | import com.youzan.androidsdk.model.refresh.RefreshChangeModel
44 | import com.youzan.androidsdk.model.trade.TradePayFinishedModel
45 | import com.youzan.androidsdkx5.YouzanBrowser
46 | import com.youzan.androidsdkx5.compat.CompatWebChromeClient
47 | import com.youzan.androidsdkx5.compat.VideoCallback
48 | import com.youzan.androidsdkx5.compat.WebChromeClientConfig
49 | import com.youzan.spiderman.cache.SpiderMan
50 | import com.youzan.spiderman.html.HtmlHeader
51 | import com.youzan.spiderman.html.HtmlStatistic
52 | import com.youzanyun.sdk.sample.helper.YouzanHelper
53 | import kotlinx.android.synthetic.main.activity_splash.*
54 | import okhttp3.*
55 | import org.json.JSONException
56 | import org.json.JSONObject
57 | import java.io.IOException
58 | import java.io.InputStream
59 | import java.util.*
60 |
61 |
62 | /**
63 | * 这里使用[WebViewFragment]对[WebView]生命周期有更好的管控.
64 | */
65 | class YouzanFragment : WebViewFragment(), OnRefreshListener {
66 | private val client: OkHttpClient = OkHttpClient()
67 | private lateinit var mView: YouzanBrowser
68 | private val mRefreshLayout: SwipeRefreshLayout? = null
69 | private var mToolbar: Toolbar? = null
70 |
71 | companion object {
72 | private const val CODE_REQUEST_LOGIN = 0x1000
73 |
74 | fun newInstance(url: String): Fragment {
75 | val fg = YouzanFragment()
76 | fg.arguments = Bundle().apply {
77 | putString(YouzanActivity.KEY_URL, url)
78 | }
79 | return fg
80 | }
81 | }
82 |
83 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
84 | super.onViewCreated(view, savedInstanceState)
85 | view.findViewById(R.id.back).setOnClickListener {
86 | onBackPressed()
87 | }
88 | setupViews(view)
89 | setupYouzan()
90 | val settings = webView.settings
91 | settings.cacheMode = WebSettings.LOAD_NO_CACHE
92 |
93 | val url : String? = arguments!!.getString(YouzanActivity.KEY_URL)
94 | if (url != null) {
95 | mView.loadUrl(url)
96 | }
97 |
98 |
99 | //加载H5时,开启默认loading
100 | //设置自定义loading图片
101 | // mView.setLoadingImage(R.mipmap.ic_launcher);
102 | }
103 |
104 | private fun setupViews(contentView: View) {
105 | //WebView
106 | mView = webView
107 | if (mView.getX5WebViewExtension() != null) {
108 | val data = Bundle()
109 | data.putBoolean("standardFullScreen", true) // true表示标准全屏,false表示X5全屏;不设置默认false,
110 | data.putBoolean("supportLiteWnd", true) // false:关闭小窗;true:开启小窗;不设置默认true,
111 | data.putInt("DefaultVideoScreen", 2) // 1:以页面内开始播放,2:以全屏开始播放;不设置默认:1
112 | mView.getX5WebViewExtension().invokeMiscMethod("setVideoParams", data)
113 | }
114 | mToolbar = contentView.findViewById(R.id.toolbar) as Toolbar
115 | // mRefreshLayout = (SwipeRefreshLayout) contentView.findViewById(R.id.swipe);
116 |
117 | // mView.setSaveImageListener(object : SaveImageListener {
118 | // override fun onSaveImage(result: WebView.HitTestResult?): Boolean {
119 | // // 长按保存图片流程
120 | // return true
121 | // }
122 | // })
123 |
124 | //分享按钮
125 | mToolbar!!.setTitle(R.string.loading_page)
126 | mToolbar!!.inflateMenu(R.menu.menu_youzan_share)
127 | mToolbar!!.setOnMenuItemClickListener { item ->
128 | when (item.itemId) {
129 | R.id.action_share -> {
130 | // mView.sharePage()
131 | mView.loadUrl("javascript:prompt('spiderman://callback?timing=')")
132 |
133 | true
134 | }
135 | R.id.action_refresh -> {
136 | mView.reload()
137 | true
138 | }
139 | else -> false
140 | }
141 | }
142 |
143 | //刷新
144 | // mRefreshLayout.setOnRefreshListener(this);
145 | // mRefreshLayout.setColorSchemeColors(Color.BLUE, Color.RED);
146 | // mRefreshLayout.setEnabled(false);
147 | // mView.setWebChromeClient(object : WebChromeClient() {
148 | // override fun onShowCustomView(view: View, customViewCallback: IX5WebChromeClient.CustomViewCallback) {
149 | // super.onShowCustomView(view, customViewCallback)
150 | // customViewCallback.onCustomViewHidden() // 避免视频未播放时,点击全屏白屏的问题
151 | // }
152 | //
153 | //
154 | // })
155 |
156 | mView.setWebChromeClient(object: CompatWebChromeClient(
157 | WebChromeClientConfig(
158 | true, object : VideoCallback {
159 | override fun onVideoCallback(b: Boolean) {
160 | Toast.makeText(activity, "" + b, Toast.LENGTH_SHORT).show()
161 | }
162 | }
163 | )
164 | ) {
165 | override fun onReceivedTitle(p0: WebView?, p1: String?) {
166 | super.onReceivedTitle(p0, p1)
167 | mToolbar?.title = p1
168 | }
169 | })
170 |
171 | mView.setWebViewClient(object : WebViewClient() {
172 |
173 | override fun onReceivedError(p0: WebView?, p1: WebResourceRequest?, p2: WebResourceError?) {
174 | super.onReceivedError(p0, p1, p2)
175 | }
176 |
177 | override fun shouldOverrideUrlLoading(p0: WebView?, p1: WebResourceRequest?): Boolean {
178 | return super.shouldOverrideUrlLoading(p0, p1)
179 | }
180 | override fun onPageFinished(p0: WebView?, p1: String?) {
181 | super.onPageFinished(p0, p1)
182 | Log.d("lsd", "onPageFinished")
183 | }
184 |
185 | override fun onPageStarted(p0: WebView?, p1: String?, p2: Bitmap?) {
186 | super.onPageStarted(p0, p1, p2)
187 | Log.d("lsd", "onPageStarted")
188 | Toast.makeText(activity, "onPageStarted", Toast.LENGTH_SHORT).show()
189 | }
190 |
191 | private fun interceptHtmlRequest(context: Context, url: String): WebResourceResponse? {
192 | val statistic = HtmlStatistic(url)
193 | val htmlResponse = SpiderMan.getInstance().interceptHtml(context, url, statistic)
194 | if (htmlResponse != null) {
195 | val webResourceResponse = WebResourceResponse(
196 | "text/html", htmlResponse.encoding, htmlResponse.contentStream
197 | )
198 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
199 | webResourceResponse.responseHeaders = HtmlHeader.transferHeaderMapList(htmlResponse.header) // add response header
200 | }
201 | return webResourceResponse
202 | }
203 | return null
204 | }
205 |
206 | @TargetApi(21)
207 | override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
208 | val res = super.shouldInterceptRequest(view, request)
209 |
210 | // if (res == null && request != null && request.url.toString().contains("init.json")) {
211 | //
212 | // return try {
213 | // // 构造 OkHttp 请求
214 | // val okhttpRequest: Request = Request.Builder()
215 | // .url(request.url.toString())
216 | // .build()
217 | //
218 | // // 发送 OkHttp 请求
219 | // val okhttpResponse = client.newCall(okhttpRequest).execute()
220 | // // 获取响应数据
221 | // val body = okhttpResponse.body()
222 | // val mimeType = okhttpResponse.header("Content-Type")
223 | // val encoding = if (body != null) body.contentType()!!.charset()!!.name() else "UTF-8"
224 | // val inputStream = body?.byteStream()
225 | //
226 | // // 构造 WebResourceResponse
227 | // val response = WebResourceResponse(mimeType, encoding, inputStream)
228 | // response.responseHeaders = Collections.singletonMap("Access-Control-Allow-Origin", "*.youzan.com");
229 | // null
230 | // } catch (e: IOException) {
231 | // e.printStackTrace()
232 | // null
233 | // }
234 | // }
235 | return res;
236 | }
237 | })
238 | }
239 |
240 | private fun setupYouzan() {
241 |
242 | mView!!.subscribe(object : AbsCheckAuthMobileEvent() {})
243 | //认证事件, 回调表示: 需要需要新的认证信息传入
244 | mView!!.subscribe(object : AbsAuthEvent() {
245 | override fun call(context: Context, needLogin: Boolean) {
246 | /**
247 | * 建议实现逻辑:
248 | *
249 | * 判断App内的用户是否登录?
250 | * => 已登录: 请求带用户角色的认证信息(login接口);
251 | * => 未登录: needLogin为true, 唤起App内登录界面, 请求带用户角色的认证信息(login接口);
252 | * => 未登录: needLogin为false, 请求不带用户角色的认证信息(initToken接口).
253 | *
254 | * 服务端接入文档: https://www.youzanyun.com/docs/guide/appsdk/683
255 | */
256 | //TODO 自行编码实现. 具体可参考开发文档中的伪代码实现
257 | //TODO 手机号自己填入
258 | YouzanHelper.loginYouzan(activity!!, {
259 | mView.postDelayed({
260 | mView.reload()
261 | }, 500)
262 |
263 | })
264 |
265 | }
266 | })
267 | mView!!.subscribe(object : AbsCheckAuthMobileEvent() {})
268 | //文件选择事件, 回调表示: 发起文件选择. (如果app内使用的是系统默认的文件选择器, 该事件可以直接删除)
269 | mView!!.subscribe(object : AbsChooserEvent() {
270 | @kotlin.jvm.Throws(ActivityNotFoundException::class)
271 | override fun call(context: Context, intent: Intent, requestCode: Int) {
272 | startActivityForResult(intent, requestCode)
273 | }
274 | })
275 |
276 | mView!!.subscribe(object : AbsChangePullRefreshEvent() {
277 | override fun call(refreshChangeModel: RefreshChangeModel?) {
278 | if (refreshChangeModel != null && refreshChangeModel.enable != null) {
279 | //新建收货地址页下滑与页面下拉刷新冲突时,禁止该页面下拉刷新
280 | // mRefreshLayout.setEnabled(refreshChangeModel.getEnable());
281 | }
282 | }
283 | })
284 |
285 | //页面状态事件, 回调表示: 页面加载完成
286 | mView!!.subscribe(object : AbsStateEvent() {
287 | override fun call(context: Context) {
288 | mToolbar!!.title = mView!!.title
289 | //停止刷新
290 | // mRefreshLayout.setRefreshing(false);
291 | // mRefreshLayout.setEnabled(true);
292 | }
293 | })
294 | mView!!.subscribe(object : AbsCustomEvent() {
295 | override fun callAction(context: Context, action: String, data: String) {
296 | when (action) {
297 | "openHome" -> //此处仅举例,具体实现根据对应需求做调整
298 | try {
299 | val jsonObject = JSONObject(data)
300 | val paramObj = jsonObject.optJSONObject("params")
301 | val result = paramObj.optString("test")
302 | Toast.makeText(activity, "test:$result", Toast.LENGTH_LONG).show()
303 | } catch (e: JSONException) {
304 | throw RuntimeException(e)
305 | }
306 | }
307 | }
308 | })
309 | //分享事件, 回调表示: 获取到当前页面的分享信息数据
310 | mView!!.subscribe(object : AbsShareEvent() {
311 | override fun call(context: Context, data: GoodsShareModel) {
312 | /**
313 | * 在获取数据后, 可以使用其他分享SDK来提高分享体验.
314 | * 这里调用系统分享来简单演示分享的过程.
315 | */
316 | val content = data.desc + data.link
317 | val sendIntent = Intent()
318 | sendIntent.action = Intent.ACTION_SEND
319 | sendIntent.putExtra(Intent.EXTRA_TEXT, content)
320 | sendIntent.putExtra(Intent.EXTRA_SUBJECT, data.title)
321 | sendIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
322 | sendIntent.type = "text/plain"
323 | startActivity(sendIntent)
324 | }
325 | })
326 | mView!!.subscribe(object : AbsPaymentFinishedEvent() {
327 | override fun call(context: Context, tradePayFinishedModel: TradePayFinishedModel) {}
328 | })
329 | }
330 |
331 | override fun onResume() {
332 | super.onResume()
333 | }
334 |
335 | override fun getWebViewId(): Int {
336 | //YouzanBrowser在布局文件中的id
337 | // return 0
338 | return R.id.view
339 | }
340 |
341 | override fun getLayoutId(): Int {
342 | //布局文件
343 | return R.layout.fragment_youzan
344 | }
345 |
346 | override fun onBackPressed(): Boolean {
347 | //页面回退
348 | return mView.pageGoBack();
349 | }
350 |
351 | override fun onRefresh() {
352 | //重新加载页面
353 | mView!!.reload()
354 | }
355 |
356 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
357 | super.onActivityResult(requestCode, resultCode, data)
358 | if (CODE_REQUEST_LOGIN == requestCode) { // 如果是登录事件返回
359 | if (resultCode == Activity.RESULT_OK) {
360 | // 登录成功设置token
361 | } else {
362 | // 登录失败
363 | mView!!.syncNot()
364 | }
365 | } else if (mView!!.receiveFile(requestCode, data)){
366 | // return true 标识处理的上传了文件
367 | } else {
368 |
369 | }
370 | }
371 | }
372 |
373 | class WebResourceResponseAdapter private constructor(private val mWebResourceResponse: android.webkit.WebResourceResponse) : WebResourceResponse() {
374 | override fun getMimeType(): String {
375 | return mWebResourceResponse.mimeType
376 | }
377 |
378 | override fun getData(): InputStream {
379 | return mWebResourceResponse.data
380 | }
381 |
382 | @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
383 | override fun getStatusCode(): Int {
384 | return mWebResourceResponse.statusCode
385 | }
386 |
387 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
388 | override fun getResponseHeaders(): Map {
389 | return mWebResourceResponse.responseHeaders
390 | }
391 |
392 | override fun getEncoding(): String {
393 | return mWebResourceResponse.encoding
394 | }
395 |
396 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
397 | override fun getReasonPhrase(): String {
398 | return mWebResourceResponse.reasonPhrase
399 | }
400 |
401 | companion object {
402 | fun adapter(webResourceResponse: android.webkit.WebResourceResponse?): WebResourceResponseAdapter? {
403 | return webResourceResponse?.let { WebResourceResponseAdapter(it) }
404 | }
405 | }
406 | }
407 |
408 |
409 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
410 | class WebResourceRequestAdapter private constructor(private val mWebResourceRequest: WebResourceRequest) : android.webkit.WebResourceRequest {
411 | override fun getUrl(): Uri {
412 | return mWebResourceRequest.url
413 | }
414 |
415 | override fun isForMainFrame(): Boolean {
416 | return mWebResourceRequest.isForMainFrame
417 | }
418 |
419 | override fun isRedirect(): Boolean {
420 | return mWebResourceRequest.isRedirect
421 | }
422 |
423 | override fun hasGesture(): Boolean {
424 | return mWebResourceRequest.hasGesture()
425 | }
426 |
427 | override fun getMethod(): String {
428 | return mWebResourceRequest.method
429 | }
430 |
431 | override fun getRequestHeaders(): Map {
432 | return mWebResourceRequest.requestHeaders
433 | }
434 |
435 | companion object {
436 | fun adapter(x5Request: WebResourceRequest): WebResourceRequestAdapter {
437 | return WebResourceRequestAdapter(x5Request)
438 | }
439 | }
440 | }
441 |
442 |
443 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/drawable-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youzan/YouzanMobileSDK-Android/14cc0f2cfe84f9b287287d23351bf55231811aee/YouzanX5Sample/src/main/res/drawable-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/drawable-xxhdpi/refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youzan/YouzanMobileSDK-Android/14cc0f2cfe84f9b287287d23351bf55231811aee/YouzanX5Sample/src/main/res/drawable-xxhdpi/refresh.png
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
23 |
24 |
29 |
30 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/layout/activity_placeholder.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
23 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/layout/activity_splash.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
23 |
24 |
29 |
30 |
31 |
39 |
40 |
49 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/layout/fg_logout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/layout/fragment_youzan.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
25 |
26 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/menu/menu_youzan_share.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youzan/YouzanMobileSDK-Android/14cc0f2cfe84f9b287287d23351bf55231811aee/YouzanX5Sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youzan/YouzanMobileSDK-Android/14cc0f2cfe84f9b287287d23351bf55231811aee/YouzanX5Sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youzan/YouzanMobileSDK-Android/14cc0f2cfe84f9b287287d23351bf55231811aee/YouzanX5Sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youzan/YouzanMobileSDK-Android/14cc0f2cfe84f9b287287d23351bf55231811aee/YouzanX5Sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youzan/YouzanMobileSDK-Android/14cc0f2cfe84f9b287287d23351bf55231811aee/YouzanX5Sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | #3F51B5
20 | #303F9F
21 | #FF4081
22 | #F5F5F5
23 |
24 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | 有赞X5开店
19 | 清除记录
20 | 打开入口
21 | 页面加载中…
22 | 分享
23 |
24 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 youzanyun.com, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
18 | buildscript {
19 | ext.kotlin_version = '1.6.0'
20 | repositories {
21 | maven {
22 | url 'https://maven.aliyun.com/repository/public/'
23 | }
24 | google()
25 | mavenCentral()
26 | }
27 | dependencies {
28 | classpath 'com.android.tools.build:gradle:4.2.2'
29 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
30 | }
31 | }
32 |
33 | task clean(type: Delete) {
34 | delete rootProject.buildDir
35 | }
36 |
37 | allprojects {
38 | repositories {
39 | maven {
40 | url 'https://maven.aliyun.com/repository/public/'
41 | }
42 | google()
43 | mavenCentral()
44 | maven { url 'http://maven.youzanyun.com/repository/maven-releases' }
45 | }
46 | }
47 |
48 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright (C) 2017 youzanyun.com, Inc.
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # http://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | # Project-wide Gradle settings.
18 |
19 | # IDE (e.g. Android Studio) users:
20 | # Gradle settings configured through the IDE *will override*
21 | # any settings specified in this file.
22 |
23 | # For more details on how to configure your build environment visit
24 | # http://www.gradle.org/docs/current/userguide/build_environment.html
25 |
26 | # Specifies the JVM arguments used for the daemon process.
27 | # The setting is particularly useful for tweaking memory settings.
28 | org.gradle.jvmargs=-Xmx1536m
29 |
30 | # When configured, Gradle will run in incubating parallel mode.
31 | # This option should only be used with decoupled projects. More details, visit
32 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
33 | # org.gradle.parallel=true
34 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/youzan/YouzanMobileSDK-Android/14cc0f2cfe84f9b287287d23351bf55231811aee/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Nov 15 10:26:55 CST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 youzanyun.com, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | include ':YouzanBasicSample',':YouzanX5Sample'
18 |
--------------------------------------------------------------------------------