weex 打包android不能加载本地图片且不支持base64
官方文档虽然有说
Schemes 本地资源 Weex SDK 提供 local scheme 来访问打包在应用程序中的资源,此 scheme 无法在 H5 环境下使用。目前,开发者可以在 image 组件和字体文件中使用本地资源。 在 iOS 中,Weex 会在 bundle resources 中查找。例如,image 组件的 src 属性为 local:///app_icon', Weex 会加载 bundle resouce 中名为 app_icon 的图像资源,而字体文件也以相同的方式工作。 在 Android 中,image 组件将从 drawable 资源文件夹加载,如 res/drawable-xxx。但加载字体文件是不同的,Android 框架无法从 res 加载字体文件,因此 SDK 将从 asserts 文件夹加载它。
但使用local加载图片时在ios下可以成功加载图片base64,android不能加载
解决方案:
用Android Studio打开.. ... weex-project-test/platforms/android,
修改... .../platforms/android/app/src/main/java/com/weex/app/extend/ImageAdapter.java
将图片放在... .../platforms/android/app/src/main/assets/images

package com.weex.app.extend; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.text.TextUtils; import android.util.Base64; import android.widget.ImageView; import com.squareup.picasso.Callback; import com.squareup.picasso.Picasso; import com.taobao.weex.WXEnvironment; import com.taobao.weex.WXSDKManager; import com.taobao.weex.adapter.IWXImgLoaderAdapter; import com.taobao.weex.common.WXImageStrategy; import com.taobao.weex.dom.WXImageQuality; public class ImageAdapter implements IWXImgLoaderAdapter { public ImageAdapter() { } public Bitmap stringtoBitmap(String string){ //将字符串转换成Bitmap类型 Bitmap bitmap=null; try { byte[]bitmapArray; bitmapArray= Base64.decode(string, Base64.DEFAULT); bitmap= BitmapFactory.decodeByteArray(bitmapArray, 0, bitmapArray.length); } catch (Exception e) { e.printStackTrace(); } return bitmap; } @Override public void setImage(final String url, final ImageView view, WXImageQuality quality, final WXImageStrategy strategy) { WXSDKManager.getInstance().postOnUiThread(new Runnable() { @Override public void run() { if(view==null||view.getLayoutParams()==null){ return; } if (TextUtils.isEmpty(url)) { view.setImageBitmap(null); return; } String temp = url; // base64 start if (url.startsWith("data")) { int index = url.indexOf(":"); String tempUrl = url.substring(index + 1,url.length()); Bitmap bitmap = stringtoBitmap(tempUrl); view.setImageBitmap(bitmap); return; } // base64 end if (url.startsWith("//")) { temp = "http:" + url; } // 加载本地图片 start if(url.startsWith("assets")){ int index = url.indexOf(":"); String tempUrl = url.substring(index + 1,url.length()); temp = "file:///android_asset/images/"+tempUrl; } // 加载本地图片 end if (view.getLayoutParams().width <= 0 || view.getLayoutParams().height <= 0) { return; } if(!TextUtils.isEmpty(strategy.placeHolder)){ Picasso.Builder builder=new Picasso.Builder(WXEnvironment.getApplication()); Picasso picasso=builder.build(); picasso.load(Uri.parse(strategy.placeHolder)).into(view); view.setTag(strategy.placeHolder.hashCode(),picasso); } Picasso.with(WXEnvironment.getApplication()) .load(temp) .transform(new BlurTransformation(strategy.blurRadius)) .into(view, new Callback() { @Override public void onSuccess() { if(strategy.getImageListener()!=null){ strategy.getImageListener().onImageFinish(url,view,true,null); } if(!TextUtils.isEmpty(strategy.placeHolder)){ ((Picasso) view.getTag(strategy.placeHolder.hashCode())).cancelRequest(view); } } @Override public void onError() { if(strategy.getImageListener()!=null){ strategy.getImageListener().onImageFinish(url,view,false,null); } } }); } },0); } }
tips: 使用weex框架中npm run android 和genymotion调试android时看不到效果,npm run pack:android打包后即可
<template> <image style="width:550px;height:296px" :src="loginbg"></image> <image style="width:550px;height:296px" :src="'data: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'"></image> </template> <script> ... ... created () { let platform = weex.config.env.platform.toLowerCase(); if (platform === 'android') { this.loginbg = 'assets:loginbg.png'; } else if (platform === 'ios') { this.loginbg = 'local://images/loginbg.png'; } }, ... .. </script>
android打包信息修改
... ... /platforms/android/build.gradle
android 字体文件
直接将字体文件拷到... .../platforms/android/app/src/main/assets 即可,引用方式同下ios引用字体代码片段
tips: 每次都要拷贝文件麻烦,可在package.json中添加命令在打包和开发时拷贝文件到该目录下,如
"copy:android": "mkdir -p platforms/android/app/src/main/assets/; cp -rf src/assets/fonts platforms/android/app/src/main/assets/; cp -rf src/assets/images platforms/android/app/src/main/assets/",
"pack:android": "npm run copy:android && npm run clean:android && weex build android",
"android": "npm run copy:android && weex run android",
报错:
Process: com.yiducloud.misdiag, PID: 2457 java.lang.RuntimeException: Unable to instantiate application com.weex.app.WXApplication: java.lang.ClassNotFoundException: Didn't find class "com.weex.app.WXApplication" on path: DexPathList[[zip file "/data/app/com.yiducloud.misdiag-2/base.apk"],nativeLibraryDirectories=[/data/app/com.yiducloud.misdiag-2/lib/x86, /data/app/com.yiducloud.misdiag-2/base.apk!/lib/x86, /system/lib, /vendor/lib]] at android.app.LoadedApk.makeApplication(LoadedApk.java:802) at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5335) at android.app.ActivityThread.-wrap2(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1528) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6077) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)
解决:参考https://www.aliyun.com/zixun/wenji/1224263.html
Android的项目目录里是有两个build文件夹的,一个是:项目目录/app/build,另一个是:项目目录/build。
把两个build都删掉,然后重新编译运行即可。
注意:在提交代码的时候两个build是不应该提交的。
问题: 安卓webview中的字体会随着系统字体改变大小
在../platforms/android/sdk/src/main/java/com/taobao/weex/ui/view/WXWebView.java 添加代码,
// 防止安卓手机放大webview中的字体 settings.setTextZoom(100);
问题:please select andriod sdk
android-webview cookie 同步问题
weex-android默认没有做http请求同步cookie到webview中,参考网上实现代码,如下:
这里是将weex的android的sdk下载并拷贝到了工程中,需要在.../platforms/android/app/build.gradle中配置
../platforms/android/settings.gradle中配置
include ':app', ':sdk'
在../platforms/android/sdk/src/main/java/com/taobao/weex/ui/view/WXWebView.java

/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package com.taobao.weex.ui.view; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Color; import android.net.Uri; import android.net.http.SslError; import android.os.Build; import android.os.Handler; import android.os.Message; import android.support.annotation.Nullable; import android.text.TextUtils; import android.view.Gravity; import android.view.View; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; import android.webkit.JavascriptInterface; import android.webkit.JsPromptResult; import android.webkit.SslErrorHandler; import android.webkit.WebChromeClient; import android.webkit.WebResourceError; import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.FrameLayout; import android.widget.ProgressBar; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONException; import com.alibaba.fastjson.JSONObject; import com.taobao.weex.WXSDKEngine; import com.taobao.weex.adapter.RouterTracker; import com.taobao.weex.appfram.storage.IWXStorageAdapter; import com.taobao.weex.utils.WXLogUtils; import com.taobao.weex.utils.YiduCommonUtil; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; public class WXWebView implements IWebView { private Context mContext; private String mOrigin; private WebView mWebView; private ProgressBar mProgressBar; private boolean mShowLoading = true; private Handler mMessageHandler; private static final int POST_MESSAGE = 1; private static final String BRIDGE_NAME = "__WEEX_WEB_VIEW_BRIDGE"; private static final int SDK_VERSION = Build.VERSION.SDK_INT; // downgraded by CVE-2012-6636(https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-6636) private static final boolean DOWNGRADE_JS_INTERFACE = SDK_VERSION < 17; private OnErrorListener mOnErrorListener; private OnPageListener mOnPageListener; private OnMessageListener mOnMessageListener; public WXWebView(Context context, String origin) { WXLogUtils.v("test", "WXWebView-construct"); mContext = context; mOrigin = origin; } @Override public View getView() { FrameLayout root = new FrameLayout(mContext); root.setBackgroundColor(Color.WHITE); mWebView = new WebView(mContext);//mContext.getApplicationContext(); FrameLayout.LayoutParams wvLayoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); wvLayoutParams.gravity = Gravity.CENTER; mWebView.setLayoutParams(wvLayoutParams); root.addView(mWebView); initWebView(mWebView); mProgressBar = new ProgressBar(mContext); showProgressBar(false); FrameLayout.LayoutParams pLayoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT); mProgressBar.setLayoutParams(pLayoutParams); pLayoutParams.gravity = Gravity.CENTER; root.addView(mProgressBar); mMessageHandler = new MessageHandler(this); return root; } @Override public void destroy() { if (getWebView() != null) { getWebView().removeAllViews(); getWebView().destroy(); mWebView = null; } } @Override public void loadUrl(String url) { if(getWebView() == null) return; getWebView().loadUrl(url); } @Override public void loadDataWithBaseURL(String source) { if (getWebView() == null) { return; } getWebView().loadDataWithBaseURL(mOrigin, source, "text/html", "utf-8", null); } @Override public void reload() { if(getWebView() == null) return; getWebView().reload(); } @Override public void goBack() { if(getWebView() == null) return; getWebView().goBack(); } @Override public void goForward() { if(getWebView() == null) return; getWebView().goForward(); } /*@Override public void setVisibility(int visibility) { if (mRootView != null) { mRootView.setVisibility(visibility); } }*/ @Override public void postMessage(Object msg) { if (getWebView() == null) return; try { JSONObject initData = new JSONObject(); initData.put("type", "message"); initData.put("data", msg); initData.put("origin", mOrigin); evaluateJS("javascript:(function () {" + "var initData = " + initData.toString() + ";" + "try {" + "var event = new MessageEvent('message', initData);" + "window.dispatchEvent(event);" + "} catch (e) {}" + "})();"); } catch (JSONException e) { throw new RuntimeException(e); } } @Override public void setShowLoading(boolean shown) { mShowLoading = shown; } @Override public void setOnErrorListener(OnErrorListener listener) { mOnErrorListener = listener; } @Override public void setOnPageListener(OnPageListener listener) { mOnPageListener = listener; } @Override public void setOnMessageListener(OnMessageListener listener) { mOnMessageListener = listener; } private void showProgressBar(boolean shown) { if (mShowLoading) { mProgressBar.setVisibility(shown ? View.VISIBLE : View.GONE); } } private void showWebView(boolean shown) { mWebView.setVisibility(shown ? View.VISIBLE : View.INVISIBLE); } private @Nullable WebView getWebView() { //TODO: remove this, duplicate with getView semantically. return mWebView; } // 同步 cookie public void synCookies(final Context context) { try { CookieSyncManager.createInstance(context);//创建一个cookie管理器 CookieManager cookieManager = CookieManager.getInstance(); cookieManager.setAcceptCookie(true); cookieManager.removeSessionCookie();// 移除以前的cookie cookieManager.removeAllCookie(); WXLogUtils.v("test", "移除以前的cookie"); IWXStorageAdapter adapter = WXSDKEngine.getIWXStorageAdapter(); if (adapter == null) { return; } // 获取weexStorage中cookie /*** * storageCookie是自定义的用于标识存储cookie的key值,需要在weex代码中存入,如: * 其中Domain、path的设置比较关键 * let storageCookie = [ * 'SERAWEREWFSDFA;Path=/; HttpOnly', * 'ASDFWEASEWEASE;Max-Age=86400; Domain=.cnblogs.com; Path=/; HttpOnly' * ] * storage.setItem('storageCookie', JSON.stringify({"cookie": storageCookie, "url": [对应要同步cookie的域名]})); */ adapter.getItem("storageCookie", new IWXStorageAdapter.OnResultReceivedListener() { @Override public void onReceived(Map<String, Object> data) { if (TextUtils.equals("success", data.get("result").toString())) { String objectStr= data.get("data").toString(); JSONObject jsonObject=JSONObject.parseObject(objectStr); String tmpCookie = jsonObject.getString("cookie"); JSONArray cookies = JSONObject.parseArray(tmpCookie); String url = jsonObject.getString("url"); WXLogUtils.v("test", "拿到storageCookie-----" + cookies.toString() + "------" +url); if (cookies == null || cookies.size() == 0 || url == null || url.equals("")) { // 没有拿到url和cookie,直接退出 return; } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {// 5.0以下 CookieSyncManager.createInstance(context); CookieSyncManager.getInstance().startSync(); } CookieManager cookieManager = CookieManager.getInstance(); cookieManager.setAcceptCookie(true); for (int i = 0; i < cookies.size(); i++) { String cookie = cookies.getString(i); cookieManager.setCookie(url, cookie); } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {// 5.0以下 CookieSyncManager.getInstance().sync(); } else { // 5.0以上 刷新cookie CookieManager.getInstance().flush(); } String newCookie = cookieManager.getCookie(url); WXLogUtils.v("test", "设置后的--cookie---" + newCookie + "------"); } } }); } catch (Exception e) { e.printStackTrace(); } } private void initWebView(WebView wv) { WebSettings settings = wv.getSettings(); settings.setAllowFileAccess(true); settings.setJavaScriptEnabled(true); wv.addJavascriptInterface(new JSMethod(wv.getContext()), "runNative"); // 防止安卓手机放大webview中的字体 settings.setTextZoom(100); settings.setUserAgentString("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"); settings.setCacheMode(WebSettings.LOAD_NO_CACHE); settings.setUseWideViewPort(true); settings.setLoadWithOverviewMode(true); settings.setAppCacheEnabled(true); settings.setDomStorageEnabled(true); settings.setDatabaseEnabled(true); settings.setSupportZoom(false); settings.setBuiltInZoomControls(false); // todo update 同步cookie synCookies(wv.getContext()); wv.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { view.loadUrl(url); WXLogUtils.v("tag", "onPageOverride " + url); return true; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); WXLogUtils.v("tag", "onPageStarted test" + url); if (mOnPageListener != null) { mOnPageListener.onPageStart(url); } } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); WXLogUtils.v("tag", "onPageFinished " + url); if (mOnPageListener != null) { mOnPageListener.onPageFinish(url, view.canGoBack(), view.canGoForward()); } if (mOnMessageListener != null) { evaluateJS("javascript:(window.postMessage = function(message, targetOrigin) {" + "if (message == null || !targetOrigin) return;" + (DOWNGRADE_JS_INTERFACE ? "prompt('" + BRIDGE_NAME + "://postMessage?message=' + JSON.stringify(message) + '&targetOrigin=' + targetOrigin)" : BRIDGE_NAME + ".postMessage(JSON.stringify(message), targetOrigin);") + "})"); } } @Override public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) { super.onReceivedError(view, request, error); if (mOnErrorListener != null) { //mOnErrorListener.onError("error", "page error code:" + error.getErrorCode() + ", desc:" + error.getDescription() + ", url:" + request.getUrl()); mOnErrorListener.onError("error", "page error"); } } @Override public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) { super.onReceivedHttpError(view, request, errorResponse); if (mOnErrorListener != null) { mOnErrorListener.onError("error", "http error"); } } @Override public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { super.onReceivedSslError(view, handler, error); if (mOnErrorListener != null) { mOnErrorListener.onError("error", "ssl error"); } } }); wv.setWebChromeClient(new WebChromeClient() { @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); showWebView(newProgress == 100); showProgressBar(newProgress != 100); WXLogUtils.v("tag", "onPageProgressChanged " + newProgress); } @Override public void onReceivedTitle(WebView view, String title) { super.onReceivedTitle(view, title); if (mOnPageListener != null) { mOnPageListener.onReceivedTitle(view.getTitle()); } } @Override public boolean onJsPrompt(WebView view, String url, String text, String defaultValue, JsPromptResult result) { Uri uri = Uri.parse(text); String scheme = uri.getScheme(); if (TextUtils.equals(scheme, BRIDGE_NAME)) { if (TextUtils.equals(uri.getAuthority(), "postMessage")) { String message = uri.getQueryParameter("message"); String targetOrigin = uri.getQueryParameter("targetOrigin"); onMessage(message, targetOrigin); result.confirm("success"); } else { result.confirm("fail"); } return true; } return super.onJsPrompt(view, url, text, defaultValue, result); } }); if (!DOWNGRADE_JS_INTERFACE) { wv.addJavascriptInterface(new Object() { @JavascriptInterface public void postMessage(String message, String targetOrigin) { onMessage(message, targetOrigin); } }, BRIDGE_NAME); } } private void onMessage(String message, String targetOrigin) { if (message != null && targetOrigin != null && mOnMessageListener != null) { try { Map<String, Object> initData = new HashMap<>(); initData.put("data", JSON.parse(message)); initData.put("origin", targetOrigin); initData.put("type", "message"); Message threadMessage = new Message(); threadMessage.what = POST_MESSAGE; threadMessage.obj = initData; mMessageHandler.sendMessage(threadMessage); } catch (JSONException e) { throw new RuntimeException(e); } } } private void evaluateJS(String jsStr) { if (SDK_VERSION < 19) { mWebView.loadUrl(jsStr); } else { mWebView.evaluateJavascript(jsStr, null); } } private static class MessageHandler extends Handler { private final WeakReference<WXWebView> mWv; private MessageHandler(WXWebView wv) { mWv = new WeakReference<>(wv); } @Override @SuppressWarnings("unchecked") public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what) { case POST_MESSAGE: if (mWv.get() != null && mWv.get().mOnMessageListener != null) { mWv.get().mOnMessageListener.onMessage((Map<String, Object>) msg.obj); } break; default: break; } } } public static class JSMethod { private Context mContext; public JSMethod(Context mContext) { this.mContext = mContext; } @JavascriptInterface public void closePage() { //关闭当前页面 RouterTracker.popActivity(); } public void resignKeyboard() { YiduCommonUtil.resignKeyboard(mContext); } } }
版本号控制:
../platforms/android/app/build.gradle
- VersionCode:对消费者不可见,仅用于应用市场、程序内部识别版本,判断新旧等用途。应用商店就会以这个[VersionCode]为准,来判断版本。安装包的[VersionCode]数字越大就越新。一般[VersionCode]都是一个正整数
- VersionName:展示给消费者,消费者会通过它认知自己安装的版本
defaultConfig { applicationId "" minSdkVersion project.appMinSdkVersion targetSdkVersion project.targetSdkVersion versionCode 2 versionName "1.0.8" // here ndk { abiFilters "x86" abiFilters "armeabi" } }
外部应用调起安卓app :
参考:https://www.jianshu.com/p/8e13860cb6da
通过scheme跳转:.../platforms/android/sdk/src/main/AndroidManifest.xml中添加一个activity
<!-- schema --> <activity android:name="com.weex.app.SchemeActivity" android:label="@string/app_name"> <!-- 要想在别的App上能成功调起App,必须添加intent过滤器 --> <intent-filter> <!--协议部分,随便设置--> <!-- <data android:scheme="ydmisdiag" android:host="com.yiducloud.misdiag" android:path="/"/> --> <data android:scheme="ydmisdiag"/> <!--下面这几行也必须得设置--> <category android:name="android.intent.category.DEFAULT"/> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.BROWSABLE"/> </intent-filter> </activity>
新建../platforms/android/app/src/main/java/com/weex/app/SchemeActivity.java
package com.weex.app; import android.content.Intent; import android.os.Bundle; import android.widget.TextView; public class SchemeActivity extends AbsWeexActivity { private static final String TAG = "SchemeActivity"; private TextView schemeTv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = new Intent(); intent.setClass(SchemeActivity.this, WXPageActivity.class); //intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); } }
实现按返回键三次后退出应用:
新建../platforms/android/app/src/main/java/com/weex/app/StorageActivity.java , 用来存储实例activity
package com.weex.app; import android.app.Activity; import android.app.Application; import java.util.LinkedList; import java.util.List; public class StorageActivity extends Application { private List<Activity> activityList = new LinkedList<Activity>(); private static StorageActivity instance; private StorageActivity() { } @Override public void onCreate() { super.onCreate(); } //单例模式中获取唯一的StorageActivity实例 public static StorageActivity getInstance() { if(null == instance) { instance = new StorageActivity(); } return instance; } //添加Activity到容器中 public void addActivity(Activity activity) { activityList.add(activity); } //遍历所有Activity并finish public void exit() { for(Activity activity:activityList) { activity.finish(); } activityList.clear(); } }
在.../platforms/android/app/src/main/java/com/weex/app/AbsWeexActivity.java中新增

package com.weex.app; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.annotation.Nullable; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.widget.Toast; import com.taobao.weex.IWXRenderListener; import com.taobao.weex.WXSDKEngine; import com.taobao.weex.WXSDKInstance; import com.taobao.weex.common.IWXDebugProxy; import com.taobao.weex.common.WXRenderStrategy; import com.weex.app.util.CommonUtils; import java.util.HashMap; import java.util.Map; public abstract class AbsWeexActivity extends AppCompatActivity implements IWXRenderListener { private static final String TAG = "AbsWeexActivity"; protected BroadcastReceiver mBroadcastReceiver; protected ViewGroup mContainer; protected WXSDKInstance mInstance; protected Uri mUri; private WxReloadListener mReloadListener; private WxRefreshListener mRefreshListener; private String mUrl;// "http://your_current_IP:12580/examples/build/index.js"; private String mPageName = TAG; protected Boolean isLocalUrl = false; private static boolean isExit = false; // 标识是否退出 private static int count = 0; // 标识按键次数 Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); isExit = false; } }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 添加Activity到堆栈 StorageActivity.getInstance().addActivity(this); createWeexInstance(); mInstance.onActivityCreate(); registerBroadcastReceiver(mBroadcastReceiver, null); } protected final ViewGroup getContainer() { return mContainer; } protected final void setContainer(ViewGroup container) { mContainer = container; } protected void destoryWeexInstance() { if (mInstance != null) { mInstance.registerRenderListener(null); mInstance.destroy(); mInstance = null; } } protected void createWeexInstance() { destoryWeexInstance(); mInstance = new WXSDKInstance(this); mInstance.registerRenderListener(this); } protected void renderPageByURL(String url) { renderPageByURL(url, null); } protected void renderPageByURL(String url, String jsonInitData) { CommonUtils.throwIfNull(mContainer, new RuntimeException("Can't render page, container is null")); Map<String, Object> options = new HashMap<>(); options.put(WXSDKInstance.BUNDLE_URL, url); mInstance.renderByUrl( getPageName(), url, options, jsonInitData, CommonUtils.getDisplayWidth(this), CommonUtils.getDisplayHeight(this), WXRenderStrategy.APPEND_ASYNC); } public String getPageName() { return mPageName; } @Override public void onStart() { super.onStart(); if (mInstance != null) { mInstance.onActivityStart(); } } @Override public void onResume() { super.onResume(); if (mInstance != null) { mInstance.onActivityResume(); } } @Override public void onPause() { super.onPause(); if (mInstance != null) { mInstance.onActivityPause(); } } @Override public void onStop() { super.onStop(); if (mInstance != null) { mInstance.onActivityStop(); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (mInstance != null) { mInstance.onRequestPermissionsResult(requestCode, permissions, grantResults); } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (mInstance != null) { mInstance.onActivityResult(requestCode, resultCode, data); } super.onActivityResult(requestCode, resultCode, data); } @Override public void onDestroy() { super.onDestroy(); if (mInstance != null) { mInstance.onActivityDestroy(); } unregisterBroadcastReceiver(); } @Override public void onViewCreated(WXSDKInstance wxsdkInstance, View view) { if (mContainer != null) { mContainer.removeAllViews(); mContainer.addView(view); } } @Override public void onRefreshSuccess(WXSDKInstance wxsdkInstance, int width, int height) { } public void runWithPermissionsCheck(int requestCode, String permission, Runnable runnable) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) { Toast.makeText(this, "please give me the permission", Toast.LENGTH_SHORT).show(); } else { ActivityCompat.requestPermissions(this, new String[]{permission}, requestCode); } } else { if (runnable != null) { runnable.run(); } } } @Override public void onRenderSuccess(WXSDKInstance instance, int width, int height) { } @Override public void onException(WXSDKInstance instance, String errCode, String msg) { } public void setReloadListener(WxReloadListener reloadListener) { mReloadListener = reloadListener; } public void registerBroadcastReceiver(BroadcastReceiver receiver, IntentFilter filter) { mBroadcastReceiver = receiver != null ? receiver : new DefaultBroadcastReceiver(); if (filter == null) { filter = new IntentFilter(); } filter.addAction(IWXDebugProxy.ACTION_DEBUG_INSTANCE_REFRESH); filter.addAction(WXSDKEngine.JS_FRAMEWORK_RELOAD); LocalBroadcastManager.getInstance(getApplicationContext()) .registerReceiver(mBroadcastReceiver, filter); if (mReloadListener == null) { setReloadListener(new WxReloadListener() { @Override public void onReload() { createWeexInstance(); renderPage(); } }); } if (mRefreshListener == null) { setRefreshListener(new WxRefreshListener() { @Override public void onRefresh() { createWeexInstance(); renderPage(); } }); } } public void unregisterBroadcastReceiver() { if (mBroadcastReceiver != null) { LocalBroadcastManager.getInstance(getApplicationContext()) .unregisterReceiver(mBroadcastReceiver); mBroadcastReceiver = null; } setReloadListener(null); setRefreshListener(null); } public void setRefreshListener(WxRefreshListener refreshListener) { mRefreshListener = refreshListener; } public String getUrl() { return mUrl; } public void setUrl(String url) { mUrl = url; } public void loadUrl(String url) { setUrl(url); renderPage(); } protected void preRenderPage() { } protected void postRenderPage() { } protected void renderPage() { preRenderPage(); renderPageByURL(mUrl); postRenderPage(); } protected boolean isLocalPage() { boolean isLocalPage = true; if (mUri != null) { String scheme = mUri.getScheme(); isLocalPage = !mUri.isHierarchical() || (!TextUtils.equals(scheme, "http") && !TextUtils.equals(scheme, "https")); } return isLocalPage; } public void setPageName(String pageName) { mPageName = pageName; } public interface WxReloadListener { void onReload(); } public interface WxRefreshListener { void onRefresh(); } public class DefaultBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (IWXDebugProxy.ACTION_DEBUG_INSTANCE_REFRESH.equals(intent.getAction())) { if (mRefreshListener != null) { mRefreshListener.onRefresh(); } } else if (WXSDKEngine.JS_FRAMEWORK_RELOAD.equals(intent.getAction())) { if (mReloadListener != null) { mReloadListener.onReload(); } } } } // 获取按键信息 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { exit(); return false; } return super.onKeyDown(keyCode, event); } // 按返回键三次后退出,2000秒内 private void exit() { if (!isExit) { count++; if (count >= 2) { isExit = true; count = 0; Toast.makeText(getApplicationContext(), "再按一次退出程序", Toast.LENGTH_SHORT).show(); mHandler.sendEmptyMessageDelayed(0, 2000); } } else { StorageActivity.getInstance().exit(); System.exit(0); /*finish(); System.exit(0);*/ } } }
空包签名(背景:app上架oppo应用市场,提示已有相同包名,需要认领)
mac下查看jdk路径 /usr/libexec/java_home -V
安装jdk,方便起见,将oppo提供的OppoSignVerify.apk同签名文件放在同一个文件夹
执行
jarsigner -verbose -keystore [签名文件] -signedjar OppoSignVerify_signed.apk OppoSignVerify.apk [签名文件]