originCookies);
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/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/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/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/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/WebViewCache.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache;
2 |
3 | import android.webkit.WebResourceRequest;
4 | import android.webkit.WebResourceResponse;
5 |
6 | import com.youzanyun.sdk.sample.cache.api.FastOpenApi;
7 | import com.youzanyun.sdk.sample.cache.offline.Destroyable;
8 |
9 | /**
10 | * Created by Ryan
11 | * 2018/2/7 下午5:06
12 | */
13 | public interface WebViewCache extends FastOpenApi, Destroyable {
14 |
15 | WebResourceResponse getResource(WebResourceRequest request, int webViewCacheMode, String userAgent);
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/YouzanBasicSample/src/main/java/com/youzanyun/sdk/sample/basic/KaeConfig.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.basic;
2 |
3 | /**
4 | * auther: liusaideng
5 | * created on : 2024/7/2 19:44
6 | * desc:
7 | */
8 | public class KaeConfig {
9 | // public static String URL_MAIN = "https://shop139935761.m.youzan.com/wscshop/showcase/homepage?kdt_id=139743593";
10 | // public static String URL_MAIN = "https://shop143703561.youzan.com/v2/showcase/homepage?alias=b0hB2PIg6s&dc_ps=3626055956015745029.300001";
11 | public static String URL_MAIN = "https://shop141337475.m.youzan.com/wscshop/showcase/homepage?kdt_id=141145307";
12 |
13 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Android template
3 | # Built application files
4 | *.apk
5 | *.ap_
6 |
7 | # Files for the Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 |
17 | # Gradle files
18 | .gradle/
19 | build/
20 |
21 | #demo jar
22 | sample/libs/
23 |
24 | # idea
25 | .idea/
26 | *.iml
27 |
28 | # Local configuration file (sdk path, etc)
29 | local.properties
30 |
31 | # Proguard folder generated by Eclipse
32 | proguard/
33 |
34 | # Log Files
35 | *.log
36 |
37 | # Android Studio Navigation editor temp files
38 | .navigation/
39 |
40 | #infer
41 | infer-out/
42 |
43 | .gradletasknamecache
44 | captures/
45 |
46 | #debug jar
47 | /jar/debug
48 |
49 | YouzanSDKAndroidAPI/
50 |
51 | #Local test repo
52 | repo/
53 | core/project.properties
54 |
55 | demo/nativedemo/libs/
56 |
57 |
58 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/layout/fg_logout.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
--------------------------------------------------------------------------------
/YouzanBasicSample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | #3F51B5
20 | #303F9F
21 | #FF4081
22 |
23 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/layout/activity_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/layout/activity_placeholder.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
23 |
--------------------------------------------------------------------------------
/YouzanBasicSample/src/main/res/layout/activity_placeholder.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
22 |
23 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | 有赞X5开店
19 | 清除记录
20 | 打开入口
21 | 页面加载中…
22 | 分享
23 |
24 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | #3F51B5
20 | #303F9F
21 | #FF4081
22 | #F5F5F5
23 |
24 |
--------------------------------------------------------------------------------
/YouzanBasicSample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | 有赞Sample-basic
19 | 清除记录
20 | 打开入口
21 | 页面加载中…
22 | 分享
23 |
24 |
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/YouzanBasicSample/src/main/res/menu/menu_youzan_share.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/YouzanBasicSample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/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/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/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/res/menu/menu_youzan_share.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
23 |
24 |
29 |
30 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/YouzanBasicSample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
24 |
25 |
30 |
31 |
32 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/YouzanBasicSample/src/main/java/com/youzanyun/sdk/sample/basic/YouzanActivity.java:
--------------------------------------------------------------------------------
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 | package com.youzanyun.sdk.sample.basic;
18 |
19 | import android.os.Bundle;
20 | import android.support.annotation.Nullable;
21 | import android.support.v7.app.AppCompatActivity;
22 |
23 |
24 | public class YouzanActivity extends AppCompatActivity {
25 | public static final String KEY_URL = "url";
26 | private YouzanFragment mFragment;
27 |
28 |
29 | @Override
30 | protected void onCreate(@Nullable Bundle savedInstanceState) {
31 | super.onCreate(savedInstanceState);
32 | setContentView(R.layout.activity_placeholder);
33 | mFragment = new YouzanFragment();
34 | mFragment.setArguments(getIntent().getExtras());
35 |
36 | getSupportFragmentManager()
37 | .beginTransaction()
38 | .replace(R.id.placeholder, mFragment)
39 | .commitAllowingStateLoss();
40 | }
41 |
42 | @Override
43 | public void onBackPressed() {
44 | if (mFragment == null || !mFragment.onBackPressed()) {
45 | super.onBackPressed();
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/YouzanX5Sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/qbeenslee/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
27 | # Youzan SDK
28 | -dontwarn com.youzan.androidsdk.***
29 | -keep class com.youzan.androidsdk.**{*;}
30 |
31 | # OkHttp
32 | -dontwarn okhttp3.**
33 | -dontwarn okio.**
34 | -dontwarn com.squareup.okhttp.**
35 | -keep class okio.**{*;}
36 | -keep class com.squareup.okhttp.** { *; }
37 | -keep interface com.squareup.okhttp.** { *; }
38 |
39 | -dontwarn java.nio.file.*
40 | -dontwarn javax.annotation.**
41 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
42 |
43 | # Image Loader
44 | -keep class com.squareup.picasso.Picasso
45 | -keep class com.android.volley.toolbox.Volley
46 | -keep class com.bumptech.glide.Glide
47 | -keep class com.nostra13.universalimageloader.core.ImageLoader
48 | -keep class com.facebook.drawee.backends.pipeline.Fresco
--------------------------------------------------------------------------------
/YouzanBasicSample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/qbeenslee/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
27 | # Youzan SDK
28 | -dontwarn com.youzan.androidsdk.***
29 | -keep class com.youzan.androidsdk.**{*;}
30 |
31 | # OkHttp
32 | -dontwarn okhttp3.**
33 | -dontwarn okio.**
34 | -dontwarn com.squareup.okhttp.**
35 | -keep class okio.**{*;}
36 | -keep class com.squareup.okhttp.** { *; }
37 | -keep interface com.squareup.okhttp.** { *; }
38 |
39 | -dontwarn java.nio.file.*
40 | -dontwarn javax.annotation.**
41 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
42 |
43 | # Image Loader
44 | -keep class com.squareup.picasso.Picasso
45 | -keep class com.android.volley.toolbox.Volley
46 | -keep class com.bumptech.glide.Glide
47 | -keep class com.nostra13.universalimageloader.core.ImageLoader
48 | -keep class com.facebook.drawee.backends.pipeline.Fresco
--------------------------------------------------------------------------------
/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/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 | }
--------------------------------------------------------------------------------
/YouzanBasicSample/src/main/java/com/youzanyun/sdk/sample/basic/MyApplication.java:
--------------------------------------------------------------------------------
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 | package com.youzanyun.sdk.sample.basic;
18 |
19 | import android.app.Application;
20 |
21 | import com.youzan.androidsdk.InitCallBack;
22 | import com.youzan.androidsdk.InitConfig;
23 | import com.youzan.androidsdk.YouzanSDK;
24 | import com.youzan.androidsdk.basic.YouzanBasicSDKAdapter;
25 | import com.youzan.androidsdk.basic.YouzanPreloader;
26 |
27 |
28 | public class MyApplication extends Application {
29 | @Override
30 | public void onCreate() {
31 | super.onCreate();
32 |
33 | // 初始化SDK
34 | //appkey:可以前往有赞开放平台申请
35 | YouzanSDK.isDebug(true);
36 | YouzanSDK.init(this, new InitConfig.Builder()
37 | .clientId("0073bccbaf5369028a")
38 | .appkey("")
39 | .initCallBack(new InitCallBack() {
40 | @Override
41 | public void readyCallBack(boolean ready, String message) {
42 | }
43 | })
44 | .adapter(new YouzanBasicSDKAdapter())
45 | .build()
46 | );
47 |
48 | YouzanPreloader.preloadHtml(this, KaeConfig.URL_MAIN);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/YouzanBasicSample/src/main/res/layout/fragment_youzan.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
25 |
26 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/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/cache/OfflineYouzanWebView.kt:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache
2 |
3 | import android.content.Context
4 | import android.os.Build
5 | import android.support.annotation.RequiresApi
6 | import android.text.TextUtils
7 | import android.util.AttributeSet
8 | import com.tencent.smtt.export.external.interfaces.WebResourceRequest
9 | import com.tencent.smtt.export.external.interfaces.WebResourceResponse
10 | import com.youzan.androidsdk.tool.WebUtil
11 | import com.youzan.androidsdkx5.YouzanBrowser
12 | import com.youzan.androidsdkx5.cache.WebResourceRequestAdapter
13 | import com.youzan.androidsdkx5.cache.WebResourceResponseAdapter
14 |
15 | /**
16 | * auther: liusaideng
17 | * created on : 2024/7/5 10:27
18 | * desc:
19 | */
20 | class OfflineYouzanWebView : YouzanBrowser {
21 | constructor(context: Context) : super(context)
22 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
23 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
24 |
25 |
26 | // @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
27 | // private fun loadFromWebViewCache(request: WebResourceRequest): WebResourceResponse? {
28 | // val scheme = request.url.scheme!!.trim { it <= ' ' }
29 | // val method = request.method.trim { it <= ' ' }
30 | // if ((TextUtils.equals(WebUtil.SCHEME_HTTP, scheme)
31 | // || TextUtils.equals(WebUtil.SCHEME_HTTPS, scheme))
32 | // && method.equals(WebUtil.METHOD_GET, ignoreCase = true)
33 | // ) {
34 | // val resourceResponse: android.webkit.WebResourceResponse =
35 | // mWebViewCache.getResource(WebResourceRequestAdapter.adapter(request), mWebSetting.getCacheMode(), mWebSetting.getUserAgentString())
36 | // if (resourceResponse != null) {
37 | // return WebResourceResponseAdapter.adapter(resourceResponse)
38 | // }
39 | // }
40 | // return null
41 | // }
42 | }
--------------------------------------------------------------------------------
/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/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
21 |
22 |
25 |
26 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
38 |
39 |
40 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/YouzanBasicSample/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 | apply plugin: 'com.android.application'
18 | def switchDep = "../../deploy/switchDep.gradle"
19 |
20 | if (file(switchDep).exists()) {
21 | apply from: switchDep
22 | } else {
23 | println("switchDep 文件不存在")
24 | }
25 |
26 | android {
27 | compileSdkVersion 28
28 |
29 | defaultConfig {
30 | applicationId "com.youzanyun.sdk.sample.basic"
31 | minSdkVersion 16
32 | targetSdkVersion 28
33 | versionCode 1
34 | versionName "1.0"
35 | }
36 | buildTypes {
37 | release {
38 | minifyEnabled false
39 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
40 | }
41 | }
42 |
43 | compileOptions {
44 | sourceCompatibility JavaVersion.VERSION_1_8
45 | targetCompatibility JavaVersion.VERSION_1_8
46 | }
47 |
48 | }
49 |
50 | repositories {
51 | flatDir {
52 | dirs 'libs'
53 | }
54 | }
55 | dependencies {
56 | implementation fileTree(include: ['*.jar'], dir: 'libs')
57 | testImplementation 'junit:junit:4.12'
58 | testImplementation('com.android.support.test.espresso:espresso-core:3.0.1', {
59 | exclude group: 'com.android.support', module: 'support-annotations'
60 | })
61 | //basic版本
62 | implementation("com.youzanyun.open.mobile:basic:7.15.1")
63 | implementation 'com.android.support:appcompat-v7:28.0.0'
64 |
65 | implementation "com.alibaba:fastjson:1.2.78"
66 | }
67 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/YouzanBasicSample/src/main/java/com/youzanyun/sdk/sample/basic/MainActivity.java:
--------------------------------------------------------------------------------
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 | package com.youzanyun.sdk.sample.basic;
18 |
19 | import android.app.Activity;
20 | import android.content.Intent;
21 | import android.os.Bundle;
22 | import android.view.View;
23 |
24 | import com.youzan.androidsdk.YouzanSDK;
25 |
26 |
27 | public class MainActivity extends Activity implements View.OnClickListener {
28 |
29 | @Override
30 | protected void onCreate(Bundle savedInstanceState) {
31 | super.onCreate(savedInstanceState);
32 | setContentView(R.layout.activity_main);
33 | findViewById(R.id.button_open).setOnClickListener(this);
34 | findViewById(R.id.button_clear).setOnClickListener(this);
35 | }
36 |
37 | @Override
38 | public void onClick(View v) {
39 | switch (v.getId()) {
40 | case R.id.button_open:
41 | //店铺链接, 可以从有赞后台`店铺=>店铺概况=>访问店铺`复制到相应的链接,这里是一个测试链接
42 | // gotoActivity("https://shop16911610.m.youzan.com/wscshop/showcase/homepage?kdt_id=16719442");
43 | gotoActivity(KaeConfig.URL_MAIN);
44 | break;
45 | case R.id.button_clear:
46 | YouzanSDK.userLogout(this);
47 | break;
48 | default:
49 | break;
50 | }
51 | }
52 |
53 | private void gotoActivity(String url){
54 | Intent intent = new Intent(this, YouzanActivity.class);
55 | intent.putExtra(YouzanActivity.KEY_URL, url);
56 | startActivity(intent);
57 | }
58 | }
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/YouzanBasicSample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/YouzanX5Sample/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 | apply plugin: 'com.android.application'
18 | //编译kotlin代码
19 | apply plugin: 'kotlin-android'
20 | //可选 代码支持通过view id获取对象,不再需要findViewById
21 | apply plugin: 'kotlin-android-extensions'
22 |
23 | //def switchDep = "../../deploy/switchDep.gradle"
24 | //if (file(switchDep).exists()) {
25 | // apply from: switchDep
26 | //} else {
27 | // println("switchDep 文件不存在")
28 | //}
29 |
30 | android {
31 | compileSdkVersion 32
32 |
33 | defaultConfig {
34 | applicationId "com.youzanyun.sdk.sample.x5"
35 | minSdkVersion 16
36 | targetSdkVersion 32
37 | versionCode 1
38 | versionName "1.0"
39 | }
40 | buildTypes {
41 | release {
42 | minifyEnabled false
43 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
44 | }
45 | }
46 |
47 | compileOptions {
48 | sourceCompatibility JavaVersion.VERSION_1_8
49 | targetCompatibility JavaVersion.VERSION_1_8
50 | }
51 |
52 | }
53 |
54 | repositories {
55 | flatDir {
56 | dirs 'libs'
57 | }
58 | }
59 | dependencies {
60 | implementation fileTree(include: ['*.jar'], dir: 'libs')
61 | implementation 'com.android.support.constraint:constraint-layout:2.0.4'
62 | testImplementation('com.android.support.test.espresso:espresso-core:3.0.1', {
63 | exclude group: 'com.android.support', module: 'support-annotations'
64 | })
65 |
66 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
67 | //basic版本
68 | // implementation("com.youzanyun.open.mobile:basic:7.1.16")
69 | implementation 'com.android.support:appcompat-v7:28.0.0'
70 | //x5版本
71 | implementation("com.youzanyun.open.mobile:x5sdk:7.17.1")
72 | implementation 'com.ashokvarma.android:bottom-navigation-bar:2.0.3'
73 | implementation 'ren.yale.android:cachewebviewlib:2.2.1'
74 | }
75 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/youzan/SigmaTableViewModel/blob/master/LICENSE)
2 | [](https://www.android.com)
3 |
4 | [集成腾讯X5内核的开店SDK][](https://bintray.com/youzanyun/maven/)
5 | (注意:如果从原生WebView的SDK版本升级到X5内核的SDK版本时,务必查看[相关文档](https://github.com/youzan/YouzanMobileSDK-Android/wiki/%E5%9F%BA%E4%BA%8E%E5%8E%9F%E7%94%9FWebView%E7%9A%84%E5%BC%80%E5%BA%97SDK%E5%88%87%E6%8D%A2%E5%88%B0X5%E7%89%88%E6%9C%AC%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9))
6 |
7 |
8 | [基于原生WebView的开店SDK][](https://bintray.com/youzanyun/maven/)不建议继续使用
9 |
10 |
11 |
12 |
13 |
14 | ## 有赞云App SDK(Android端)
15 |
16 | 为了满足移动应用搭建商城的需求,有赞云将有赞积累多年的电商交易系统开放,移动开发者**通过一个 SDK 便可以在 App 内集成有赞提供的整个交易服务**,除了享受完善的商城功能、丰富的营销玩法,更有有赞强劲的技术及服务作保障,实现低成本、高效率、强融合的移动电商方案,快速获得 App 流量的商业化变现。该SDK基于 WebView 将有赞提供的 HTML5 页面嵌入到 App 中,基于此提供帐号打通、资产合并、客服 IM、多渠道支付、营销能力开放等 App 应用特色功能,更拥有媲美原生页面的性能。
17 |
18 | ### X5内核版本开店SDK(推荐使用)
19 | 依托腾讯浏览服务的开店SDK,加载速度更快,兼容性与安全性更好,视频性能更出色。安装包大约增加400k。**特别建议在App开店中需要播放视频(例如知识付费)的开发者使用此版本**
20 |
21 | ### 系统原生WebView(不建议继续使用)
22 | 基于系统原生WebView提供服务,在兼容性与性能上比X5内核版本较差,不建议继续使用。
23 |
24 |
25 | 对接过程中有任何问题、需求、建议,或是想了解 AppSDK 更多玩法,欢迎联系有赞青溪,yuzan_qingxi(微信)。
26 |
27 | ## 引入
28 |
29 | 在项目根目录的build.gradle中声明maven仓库, 如下所示:
30 |
31 | ``` groove
32 | allprojects {
33 | repositories {
34 | jcenter()
35 | maven { url 'http://maven.youzanyun.com/repository/maven-releases' }
36 | }
37 | }
38 | ```
39 |
40 | 在子项目build.gradle的dependencies中根据需求引入依赖:
41 |
42 | 基于腾讯x5内核的开店SDK:
43 | ``` groovy
44 | implementation 'com.youzanyun.open.mobile:x5sdk:最新版本',{
45 | exclude group: 'com.youzan.mobile', module: 'x5official'
46 | }
47 | ```
48 | 基于Android原生WebView的开店SDK:
49 | ``` groovy
50 | implementation 'com.youzanyun.open.mobile:basic:最新版本'
51 | ```
52 |
53 | ## 文档
54 |
55 | * [开发文档](https://github.com/youzan/YouzanMobileSDK-Android/wiki)
56 | * [X5版本SDK更新文档](https://github.com/youzan/YouzanMobileSDK-Android/wiki/%E6%9B%B4%E6%96%B0%E8%AF%B4%E6%98%8E-X5%E5%86%85%E6%A0%B8%E7%89%88%E6%9C%AC)
57 | * [原生内核版本SDK更新文档](https://github.com/youzan/YouzanMobileSDK-Android/wiki/%E6%9B%B4%E6%96%B0%E8%AF%B4%E6%98%8E-%E5%8E%9F%E7%94%9F%E5%86%85%E6%A0%B8%E7%89%88%E6%9C%AC)
58 | * [常见问题](https://github.com/youzan/YouzanMobileSDK-Android/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)
59 |
60 | ## 交流&反馈
61 |
62 | * [有赞官方论坛](https://bbs.youzan.com/forum-98-1.html)
63 | * [github issue](https://github.com/youzan/YouzanMobileSDK-Android/issues)
64 |
65 | ## License
66 | [MIT](https://zh.wikipedia.org/wiki/MIT%E8%A8%B1%E5%8F%AF%E8%AD%89)
67 |
68 |
--------------------------------------------------------------------------------
/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/WebResource.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache;
2 |
3 | import static java.net.HttpURLConnection.HTTP_BAD_METHOD;
4 | import static java.net.HttpURLConnection.HTTP_GONE;
5 | import static java.net.HttpURLConnection.HTTP_MOVED_PERM;
6 | import static java.net.HttpURLConnection.HTTP_MULT_CHOICE;
7 | import static java.net.HttpURLConnection.HTTP_NOT_AUTHORITATIVE;
8 | import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
9 | import static java.net.HttpURLConnection.HTTP_NOT_IMPLEMENTED;
10 | import static java.net.HttpURLConnection.HTTP_NO_CONTENT;
11 | import static java.net.HttpURLConnection.HTTP_OK;
12 | import static java.net.HttpURLConnection.HTTP_REQ_TOO_LONG;
13 |
14 | import java.util.Map;
15 |
16 | import okhttp3.internal.http.StatusLine;
17 |
18 | /**
19 | * Created by Ryan
20 | * 2018/3/1 下午5:40
21 | */
22 | public class WebResource {
23 |
24 | private int responseCode;
25 |
26 | private String reasonPhrase;
27 |
28 | private Map responseHeaders;
29 |
30 | private boolean isModified = true;
31 |
32 | private boolean isCacheByOurselves = false;
33 |
34 | private byte[] originBytes;
35 |
36 | public void setModified(boolean modified) {
37 | isModified = modified;
38 | }
39 |
40 | public boolean isModified() {
41 | return isModified;
42 | }
43 |
44 | public Map getResponseHeaders() {
45 | return responseHeaders;
46 | }
47 |
48 | public void setResponseHeaders(Map responseHeaders) {
49 | this.responseHeaders = responseHeaders;
50 | }
51 |
52 | public void setCacheByOurselves(boolean isCacheByOurselves) {
53 | this.isCacheByOurselves = isCacheByOurselves;
54 | }
55 |
56 | public boolean isCacheByOurselves() {
57 | return isCacheByOurselves;
58 | }
59 |
60 | public void setOriginBytes(byte[] bytes) {
61 | this.originBytes = bytes;
62 | }
63 |
64 | public byte[] getOriginBytes() {
65 | return originBytes;
66 | }
67 |
68 | public void setReasonPhrase(String reasonPhrase) {
69 | this.reasonPhrase = reasonPhrase;
70 | }
71 |
72 | public String getReasonPhrase() {
73 | return reasonPhrase;
74 | }
75 |
76 | public void setResponseCode(int responseCode) {
77 | this.responseCode = responseCode;
78 | }
79 |
80 | public int getResponseCode() {
81 | return responseCode;
82 | }
83 |
84 | /**
85 | * reference {@link okhttp3.internal.cache.CacheStrategy}
86 | */
87 | public boolean isCacheable() {
88 | return responseCode == HTTP_OK
89 | || responseCode == HTTP_NOT_AUTHORITATIVE
90 | || responseCode == HTTP_NO_CONTENT
91 | || responseCode == HTTP_MULT_CHOICE
92 | || responseCode == HTTP_MOVED_PERM
93 | || responseCode == HTTP_NOT_FOUND
94 | || responseCode == HTTP_BAD_METHOD
95 | || responseCode == HTTP_GONE
96 | || responseCode == HTTP_REQ_TOO_LONG
97 | || responseCode == HTTP_NOT_IMPLEMENTED
98 | || responseCode == StatusLine.HTTP_PERM_REDIRECT;
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/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/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/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/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/MyApplication.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
17 |
18 | //import com.youzanyun.sdk.sample.config.KaeConfig.S_URL_MINE
19 | import android.app.AppOpsManager
20 | import android.app.Application
21 | import android.app.AsyncNotedAppOp
22 | import android.app.SyncNotedAppOp
23 | import android.os.Build
24 | import android.os.Looper
25 | import android.util.Log
26 | import com.youzan.androidsdk.InitConfig
27 | import com.youzan.androidsdk.LogCallback
28 | import com.youzan.androidsdk.YouzanSDK
29 | import com.youzan.androidsdkx5.YouZanSDKX5Adapter
30 | import com.youzan.androidsdkx5.YouzanPreloader
31 | import com.youzanyun.sdk.sample.config.KaeConfig
32 | import com.youzanyun.sdk.sample.helper.LoginHelper
33 | import ren.yale.android.cachewebviewlib.WebViewCacheInterceptor
34 | import ren.yale.android.cachewebviewlib.WebViewCacheInterceptorInst
35 |
36 | class MyApplication : Application() {
37 | override fun onCreate() {
38 | super.onCreate()
39 |
40 |
41 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
42 | val appOpsCallback = object : AppOpsManager.OnOpNotedCallback() {
43 | private fun logPrivateDataAccess(opCode: String, trace: String) {
44 | Log.i(
45 | "lsd", "Private data accessed. " +
46 | "Operation: $opCode\nStack Trace:\n$trace"
47 | )
48 | }
49 |
50 | override fun onNoted(syncNotedAppOp: SyncNotedAppOp) {
51 | logPrivateDataAccess(
52 | syncNotedAppOp.op, Throwable().stackTrace.toString()
53 | )
54 | }
55 |
56 | override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) {
57 | logPrivateDataAccess(
58 | syncNotedAppOp.op, Throwable().stackTrace.toString()
59 | )
60 | }
61 |
62 | override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) {
63 | logPrivateDataAccess(asyncNotedAppOp.op, asyncNotedAppOp.message)
64 | }
65 | }
66 | val appOpsManager = getSystemService(AppOpsManager::class.java) as AppOpsManager
67 | appOpsManager.setOnOpNotedCallback(mainExecutor, appOpsCallback)
68 | }
69 |
70 |
71 | // 初始化SDK
72 | //appkey:可以前往有赞开放平台申请
73 | YouzanSDK.isDebug(true)
74 | val config = InitConfig.builder()
75 | .clientId(KaeConfig.S_CLIENT_ID)
76 | .appkey("")
77 | .adapter(YouZanSDKX5Adapter())
78 | .initCallBack { ready, message ->
79 |
80 | }
81 | .logCallback(object : LogCallback {
82 | override fun onLog(eventType: String, message: String) {
83 |
84 | }
85 | })
86 | .build()
87 | YouzanSDK.init(this, config)
88 | YouzanPreloader.preloadHtml(this, KaeConfig.S_URL_MAIN)
89 | LoginHelper.init(this)
90 | }
91 | }
--------------------------------------------------------------------------------
/YouzanX5Sample/src/main/java/com/youzanyun/sdk/sample/cache/WebViewCacheImpl.java:
--------------------------------------------------------------------------------
1 | package com.youzanyun.sdk.sample.cache;
2 |
3 | import android.content.Context;
4 | import android.webkit.WebResourceRequest;
5 | import android.webkit.WebResourceResponse;
6 |
7 | import com.youzan.androidsdk.YouzanSDK;
8 | import com.youzanyun.sdk.sample.cache.config.CacheConfig;
9 | import com.youzanyun.sdk.sample.cache.config.FastCacheMode;
10 | import com.youzanyun.sdk.sample.cache.offline.CacheRequest;
11 | import com.youzanyun.sdk.sample.cache.offline.OfflineServer;
12 | import com.youzanyun.sdk.sample.cache.offline.OfflineServerImpl;
13 | import com.youzanyun.sdk.sample.cache.offline.ResourceInterceptor;
14 | import com.youzan.androidsdk.utils.MimeTypeMapUtils;
15 |
16 | import java.util.Map;
17 |
18 | /**
19 | * Created by Ryan
20 | * 2018/2/7 下午5:07
21 | */
22 | public class WebViewCacheImpl implements WebViewCache {
23 |
24 | private FastCacheMode mFastCacheMode = FastCacheMode.FORCE;
25 | private CacheConfig mCacheConfig;
26 | private OfflineServer mOfflineServer;
27 | private Context mContext;
28 |
29 | public WebViewCacheImpl(Context context) {
30 | mContext = context;
31 | }
32 |
33 | @Override
34 | public WebResourceResponse getResource(WebResourceRequest webResourceRequest, int cacheMode, String userAgent) {
35 | if (mFastCacheMode == FastCacheMode.DEFAULT) {
36 | return null;
37 | }
38 |
39 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
40 | String url = webResourceRequest.getUrl().toString();
41 | String extension = MimeTypeMapUtils.getFileExtensionFromUrl(url);
42 | String mimeType = MimeTypeMapUtils.getMimeTypeFromExtension(extension);
43 | if (mimeType != null && mimeType.startsWith("video")) {
44 | return null; // todo 暂不兼容video, 后续支持
45 | }
46 | CacheRequest cacheRequest = new CacheRequest();
47 | cacheRequest.setUrl(url);
48 | cacheRequest.setMime(mimeType);
49 | cacheRequest.setForceMode(mFastCacheMode == FastCacheMode.FORCE);
50 | cacheRequest.setUserAgent(userAgent);
51 | cacheRequest.setWebViewCacheMode(cacheMode);
52 | Map 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/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 |
--------------------------------------------------------------------------------
/YouzanBasicSample/src/main/java/com/youzanyun/sdk/sample/basic/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.basic;
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.androidsdk.basic.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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/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 |
--------------------------------------------------------------------------------
/YouzanBasicSample/src/main/java/com/youzanyun/sdk/sample/basic/YouzanFragment.java:
--------------------------------------------------------------------------------
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 | package com.youzanyun.sdk.sample.basic;
18 |
19 |
20 | import static android.app.Activity.RESULT_OK;
21 |
22 | import android.content.ActivityNotFoundException;
23 | import android.content.Context;
24 | import android.content.Intent;
25 | import android.net.http.SslError;
26 | import android.os.Build;
27 | import android.os.Bundle;
28 | import android.support.annotation.Nullable;
29 | import android.support.annotation.RequiresApi;
30 | import android.support.v4.widget.SwipeRefreshLayout;
31 | import android.support.v7.widget.Toolbar;
32 | import android.view.MenuItem;
33 | import android.view.View;
34 | import android.webkit.SslErrorHandler;
35 | import android.webkit.WebView;
36 | import android.webkit.WebViewClient;
37 | import android.widget.Toast;
38 |
39 | import com.google.gson.Gson;
40 | import com.youzan.androidsdk.YouzanLog;
41 | import com.youzan.androidsdk.YouzanSDK;
42 | import com.youzan.androidsdk.YouzanToken;
43 | import com.youzan.androidsdk.YzLoginCallback;
44 | import com.youzan.androidsdk.basic.YouzanBrowser;
45 | import com.youzan.androidsdk.basic.compat.CompatWebChromeClient;
46 | import com.youzan.androidsdk.basic.compat.VideoCallback;
47 | import com.youzan.androidsdk.basic.compat.WebChromeClientConfig;
48 | import com.youzan.androidsdk.event.AbsAuthEvent;
49 | import com.youzan.androidsdk.event.AbsCheckAuthMobileEvent;
50 | import com.youzan.androidsdk.event.AbsChooserEvent;
51 | import com.youzan.androidsdk.event.AbsPaymentFinishedEvent;
52 | import com.youzan.androidsdk.event.AbsShareEvent;
53 | import com.youzan.androidsdk.event.AbsStateEvent;
54 | import com.youzan.androidsdk.model.goods.GoodsShareModel;
55 | import com.youzan.androidsdk.model.trade.TradePayFinishedModel;
56 |
57 |
58 | /**
59 | * 这里使用{@link WebViewFragment}对{@link android.webkit.WebView}生命周期有更好的管控.
60 | */
61 | public class YouzanFragment extends WebViewFragment implements SwipeRefreshLayout.OnRefreshListener {
62 | private YouzanBrowser mView;
63 | private SwipeRefreshLayout mRefreshLayout;
64 | private Toolbar mToolbar;
65 | private static final int CODE_REQUEST_LOGIN = 0x1000;
66 |
67 | @Override
68 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
69 | super.onViewCreated(view, savedInstanceState);
70 | setupViews(view);
71 | setupYouzan();
72 |
73 | final String url = getArguments().getString(YouzanActivity.KEY_URL);
74 | mView.loadUrl(url);
75 | //加载H5时,开启默认loading
76 | //设置自定义loading图片
77 | // mView.setLoadingImage(R.mipmap.ic_launcher);
78 | }
79 |
80 | private void setupViews(View contentView) {
81 | //WebView
82 | mView = getWebView();
83 |
84 | mToolbar = (Toolbar) contentView.findViewById(R.id.toolbar);
85 | // mRefreshLayout = (SwipeRefreshLayout) contentView.findViewById(R.id.swipe);
86 |
87 | //分享按钮
88 | mToolbar.setTitle(R.string.loading_page);
89 | mToolbar.inflateMenu(R.menu.menu_youzan_share);
90 | mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
91 | @Override
92 | public boolean onMenuItemClick(MenuItem item) {
93 | switch (item.getItemId()) {
94 | case R.id.action_share:
95 | mView.sharePage();
96 | return true;
97 | default:
98 | return false;
99 | }
100 | }
101 | });
102 |
103 | //刷新
104 | // mRefreshLayout.setOnRefreshListener(this);
105 | // mRefreshLayout.setColorSchemeColors(Color.BLUE, Color.RED);
106 | // mRefreshLayout.setEnabled(false);
107 |
108 | mView.setWebViewClient(new WebViewClient() {
109 |
110 |
111 | @Override
112 | public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
113 | // 接入是需手动处理此部分证书逻辑
114 | handler.proceed();
115 | }
116 |
117 | @RequiresApi(api = Build.VERSION_CODES.KITKAT)
118 | @Override
119 | public void onPageFinished(WebView view, String url) {
120 | super.onPageFinished(view, url);
121 | }
122 | });
123 |
124 | mView.setWebChromeClient(new CompatWebChromeClient(
125 | new WebChromeClientConfig(
126 | true, new VideoCallback() {
127 | @Override
128 | public void onVideoCallback(boolean b) {
129 | Toast.makeText(getActivity(), "" + b, Toast.LENGTH_SHORT).show();
130 | }
131 |
132 |
133 | }
134 | )
135 | ) {
136 | @Override
137 | public void onReceivedTitle(WebView view, String title) {
138 | super.onReceivedTitle(view, title);
139 | }
140 | });
141 |
142 | }
143 |
144 | private void setupYouzan() {
145 | mView.subscribe(new AbsCheckAuthMobileEvent(){});
146 | //认证事件, 回调表示: 需要需要新的认证信息传入
147 | mView.subscribe(new AbsAuthEvent() {
148 |
149 | @Override
150 | public void call(Context context, boolean needLogin) {
151 | /**
152 | * 建议实现逻辑:
153 | *
154 | * 判断App内的用户是否登录?
155 | * => 已登录: 请求带用户角色的认证信息(login接口);
156 | * => 未登录: needLogin为true, 唤起App内登录界面, 请求带用户角色的认证信息(login接口);
157 | * => 未登录: needLogin为false, 请求不带用户角色的认证信息(initToken接口).
158 | *
159 | * 服务端接入文档: https://www.youzanyun.com/docs/guide/appsdk/683
160 | */
161 | //TODO 自行编码实现. 具体可参考开发文档中的伪代码实现
162 | //TODO 手机号自己填入
163 | YouzanSDK.yzlogin("31467761", "https://cdn.daddylab.com/Upload/android/20210113/021119/au9j4d6aed5xfweg.jpeg?w=1080&h=1080", "", "一百亿养乐多", "0", new YzLoginCallback() {
164 | @Override
165 | public void onSuccess(YouzanToken youzanToken) {
166 | mView.post(new Runnable() {
167 | @Override
168 | public void run() {
169 | mView.sync(youzanToken);
170 | }
171 | });
172 | }
173 |
174 | @Override
175 | public void onFail(String s) {
176 |
177 | }
178 | });
179 | }
180 | });
181 | mView.subscribe(new AbsCheckAuthMobileEvent() {});
182 | //文件选择事件, 回调表示: 发起文件选择. (如果app内使用的是系统默认的文件选择器, 该事件可以直接删除)
183 | mView.subscribe(new AbsChooserEvent() {
184 | @Override
185 | public void call(Context context, Intent intent, int requestCode) throws ActivityNotFoundException {
186 | startActivityForResult(intent, requestCode);
187 | }
188 | });
189 |
190 | //页面状态事件, 回调表示: 页面加载完成
191 | mView.subscribe(new AbsStateEvent() {
192 | @Override
193 | public void call(Context context) {
194 | mToolbar.setTitle(mView.getTitle());
195 |
196 | //停止刷新
197 | // mRefreshLayout.setRefreshing(false);
198 | // mRefreshLayout.setEnabled(true);
199 | }
200 | });
201 | //分享事件, 回调表示: 获取到当前页面的分享信息数据
202 | mView.subscribe(new AbsShareEvent() {
203 | @Override
204 | public void call(Context context, GoodsShareModel data) {
205 | /**
206 | * 在获取数据后, 可以使用其他分享SDK来提高分享体验.
207 | * 这里调用系统分享来简单演示分享的过程.
208 | */
209 | String content = data.getDesc() + data.getLink();
210 | Intent sendIntent = new Intent();
211 | sendIntent.setAction(Intent.ACTION_SEND);
212 | sendIntent.putExtra(Intent.EXTRA_TEXT, content);
213 | sendIntent.putExtra(Intent.EXTRA_SUBJECT, data.getTitle());
214 | sendIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
215 | sendIntent.setType("text/plain");
216 | startActivity(sendIntent);
217 | }
218 | });
219 |
220 | mView.subscribe(new AbsPaymentFinishedEvent() {
221 | @Override
222 | public void call(Context context, TradePayFinishedModel tradePayFinishedModel) {
223 |
224 | }
225 | });
226 | }
227 |
228 |
229 | @Override
230 | protected int getWebViewId() {
231 | //YouzanBrowser在布局文件中的id
232 | return R.id.view;
233 | }
234 |
235 |
236 | @Override
237 | protected int getLayoutId() {
238 | //布局文件
239 | return R.layout.fragment_youzan;
240 | }
241 |
242 | @Override
243 | public boolean onBackPressed() {
244 | //页面回退
245 | return getWebView().pageGoBack();
246 | }
247 |
248 | @Override
249 | public void onRefresh() {
250 | //重新加载页面
251 | mView.reload();
252 | }
253 |
254 | @Override
255 | public void onActivityResult(int requestCode, int resultCode, Intent data) {
256 | super.onActivityResult(requestCode, resultCode, data);
257 | if (CODE_REQUEST_LOGIN == requestCode) {// 如果是登录事件返回
258 | if (resultCode == RESULT_OK) {
259 | // 登录成功设置token
260 |
261 | } else {
262 | // 登录失败
263 | mView.syncNot();
264 | }
265 | } else {
266 | // 文件选择事件处理。
267 | mView.receiveFile(requestCode, data);
268 | }
269 | }
270 | }
271 |
272 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------