毛毛 - IT · 程序员
type: channel
link: https://www.maolijun.com/index.php/category/IT
description: IT类技术文大集合。
items: 小程序实现不新开webview跳转H5页面
items: Git命令备忘
items: Fastjson JSONObject.toJSONString 出现 $ref: "$."解决办法（重复引用）
items: 解决EditText在ScrollView中获取焦点自动滚动问题
items: Android12 View.post()获取不到宽高值
items: Android实现阿里云oss图片上传/文件上传
items: JavaScript(原生)base64与byte数组互转
items: 使用Android应用签名进行加解密
items: AndroidStudio build.gradle 配置大全
items: 解决Android 国密SM4CBC加解密 导致okhttp报错：No System TLS / BKS not found

小程序实现不新开webview跳转H5页面
type: item
date: 2023-05-17T19:41:47+08:00
description: 小程序实现H5页面加载一般通过页面内使用<web-view>组件实现。有一种场景：商城H5页面展示之后点击落单需要完成实名登录，登录完成之后跳转落单页面。一般的做法是先跳转登录，完成之后，通过wx.redirectTo或wx.navigateTo等方法跳转到落单页面。事实上这种做法确实能达到效果。但是如果下一个页面（落单页面）依赖了上一个页面缓存的数据，比如缓存在sessionStorage的数据，就没办法带入到新的页面，因为新页面是新的webview不存在上一个页面缓存的数据。为了解决这样的问题，我们需要实现不新开webview也能加载页面。废话不多，代码如下。 //返回上一页 let pages = getCurrentPages(); let prevPage = pages[pages.length - 2]; if (prevPage.route.indexOf('h5container') !== -1) { //把需要回传的值保存到上一个页面 prevPage.setURL(decodeURIComponent(this._data.queryParam.targetUrl)); wx.navigateBack({ delta: 1 }) } else { wx.redirectTo({ url: `/pages/h5container/h5container?targetUrl=${this._data.queryParam.targetUrl}` }); }原理：上面的代码主要是拿到上一个H5页面的实例，然后通过调用里面的方法去重新设置页面链接url，然后返回到上一个H5页面。实现效果就是，页面刷新成了新的链接，但是没有打开新的webView页面。完美解决了上述问题。

Git命令备忘
type: item
date: 2023-05-15T23:17:00+08:00
description: Git命令行修改远程仓库地址--方法/实例设置仓库命令：git remote -v //查看修改前的仓库 git remote set-url origin xxx //xxx代表新的仓库地址 git remote -v //查看修改后的仓库如果执行git remote set-url origin xxx报如下错误：fatal: No such remote 'origin'说明以前还没有设置过远程仓库地址，此时需使用git remote add:git remote add origin xxx查看远程仓库：git remote -vgit init 仓库报错：Reinitialized existing Git repository in ...解决方法一、初始化git项目时，报错1、本地初始化项目cd到对应的项目路径下，打开bash。增对这里有很多很多的操作，例如cmd,ps,cmder,bash等执行git init2、报如下错误Reinitialized existing Git repository in…二、报错原因该路径中已经存在.git文件了三、解决方法1、可以在当前文件下的控制台中输入 ls-a 查看，有.git 的话，使用 rm -rf .git 删除之后重新初始化就可以了2、已经存在.git文件，说明可以直接使用git命令，使用上面的设置远程仓库命令或修改其他公用git参数即可使用。xcrun: error: invalid active developer path解决办法mac下如果安装过xcode,之后又卸载，再使用git等命令时就提示错误。invalid active path(Applications/Xcode.app/Contents/Developer)如果不想安装xcode,那么可以通过重置系统默认开发工具路径.可以通过xcode-select命令来重置系统默认的CommandLineTools路径，命令如下：sudo xcode-select -rsudo xcode-select --switch /Library/Developer/CommandLineToolsxcode-select -p参考链接：git--修改远程仓库的地址--方法/实例git报错：Reinitialized existing Git repository in ...解决方法

Fastjson JSONObject.toJSONString 出现 $ref: "$."解决办法（重复引用）
type: item
date: 2023-04-24T23:20:00+08:00
description: Fastjson JSONObject.toJSONString 出现 $ref: "$."的解决办法（重复引用）首先，fastjson作为一款序列化引擎，不可避免的会遇到循环引用的问题，为了避免StackOverflowError异常，fastjson会对引用进行检测。如果检测到存在重复/循环引用的情况，fastjson默认会以“引用标识”代替同一对象，而非继续循环解析导致StackOverflowError。解决办法1 关闭检查: JSON.toJSONString(object, SerializerFeature.DisableCircularReferenceDetect); 解决办法2: 创建新对象 不循环引用问题根本原因找到了。是因为我下面那段代码的时候，重复给map赋值了相同key的值，只是改变了值的内容。实际上，Map.get()出来的内容是持有map里的对象引用的，所以修改了对象的值之后，Map里的值自动改好了，不需要再进行Map.put();如果再做一次Map.put();相当于对同一个key插入了之前这个key的同一个引用。就会触发FastJson的循环引用保护机制。导致出现$ref: "$."当然，用JSON.toJSONString(object, SerializerFeature.DisableCircularReferenceDetect); 这个方法可以解决掉出现 $ref: “$.”的问题。但实际上，那个循环引用会导致别的key里面的内容被挤掉。最后数据还是错误的。解决方案所以根本解决办法是：不要对Map进行相同key的赋值操作。如果要修改这个key对应的value，如果value是对象，需要先get，再对对象引用进行修改，这样Map会自动修改。 //这里添加导致重复添加，map序列错乱，Fastjson编译出来的内容有问题； typeList.put(mImageNameTypeId, typeItem); Log.d(TAG + " maomao onDelete 删除照片前 fastjson typeList:" + JSON.toJSONString(typeList)); TypeItem typeItem = typeList.get(typeId); String imgNameStr = typeItem == null ? "" : typeItem.TypeValue; String[] imgNames = imgNameStr.split(","); imgNames[position] = ""; StringBuffer sb = new StringBuffer(); for (int i = 0; i < imgNames.length; i++) { if (!TextUtils.isEmpty(imgNames[i])) { sb.append(imgNames[i]).append(","); } } Log.d(TAG + " onDelete 删除照片 before imgNames：" + imgNameStr); Log.d(TAG + " onDelete 删除照片 after imgNames：" + sb.toString()); if (sb.lastIndexOf(",") >= 0 && sb.lastIndexOf(",") == sb.length() - 1) { String substring = sb.substring(0, sb.length() - 1); sb.replace(0, sb.length(), substring); } Log.d(TAG + " onDelete 删除照片 after sub string imgNames：" + sb.toString()); typeItem.TypeValue = sb.toString(); //typeList.put(mImageNameTypeId, typeItem);//这里添加导致重复添加，map序列错乱，Fastjson编译出来的内容有问题； Log.d(TAG + " maomao onDelete 删除照片后 fastjson typeList:" + JSON.toJSONString(typeList));

解决EditText在ScrollView中获取焦点自动滚动问题
type: item
date: 2023-04-24T22:45:00+08:00
description: 问题背景一个长页面内有ScrollView，也有EditText，EditText有焦点时，跳转到其他页面操完跳回当前页面，此时页面内容自动滚动到EditText所在的位置，用户体验非常不好。问题分析参考：https://www.jianshu.com/p/192f160f9e60private void scrollToChild(View child) { child.getDrawingRect(mTempRect); /* Offset from child's local coordinates to ScrollView coordinates */ offsetDescendantRectToMyCoords(child, mTempRect); int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); if (scrollDelta != 0) { scrollBy(0, scrollDelta); } }ScrollView的自滚动方法中基本都是通过调用computeScrollDeltaToGetChildRectOnScreen方法。如果该方法返回值为0，则不滚动。解决办法自定义ScrollView，重写ScrollView的computeScrollDeltaToGetChildRectOnScreen方法，将返回值设为0。public class NoScrollFocusScrollView extends ScrollView { public NoScrollFocusScrollView(Context context) { super(context); } public NoScrollFocusScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public NoScrollFocusScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) { return 0; } }

Android12 View.post()获取不到宽高值
type: item
date: 2023-04-24T17:56:00+08:00
description: 最近遇到一个Android12版本View.post()获取不到宽高值的问题。这个问题出在创建Bitmap的时候。下面是一段生成页面图片的代码，主要是将一个布局生成为一个图片内容Bitmap。报错代码 final View waterMark = CameraPhoto.getWaterMark(activity, tempPath, waterMarkBean); if (waterMark != null) { waterMark.post(new Runnable() { @Override public void run() { CameraPhoto.saveToTemp(CameraPhoto.getViewBitmap(waterMark)); CameraPhoto.removeTempletView(activity, waterMark); threadToSave(); } }); } /** * 截取view内容，生成图片 * * @param view 容器控件 * @return 图片bitmap */ public static Bitmap getViewBitmap(View view) { try { Bitmap bitmap; // 创建相应大小的bitmap bitmap = Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight(), Bitmap.Config.ARGB_8888); final Canvas canvas = new Canvas(bitmap); //绘制viewGroup内容 view.draw(canvas); return bitmap; } catch (Exception e) { e.printStackTrace(); return null; } } 报错原因上面代码在执行到Bitmap.createBitmap(view.getMeasuredWidth(), view.getMeasuredHeight()时，由于view.getMeasuredWidth()和view.getMeasuredHeight()值为0，导致crash。得到如下报错：java.lang.IllegalArgumentException: width and height must be > 0一般来说，在View.post()方法中获取宽高是能获取到的。 view.post(new Runnable() { @Override public void run() { //这里获取布局宽高 } }); 但是，在Android12系统中,view.post方法确实获取不到宽高。具体原因（源码）待分析。解决方案根据经验，我猜测刚inflate出来的view可能没有被加载到屏幕。为了解决这个问题，我尝试使用view.postDelayed方法，延时100毫秒获取宽高。 waterMark.postDelayed(new Runnable() { @Override public void run() { //这里获取布局宽高 } }, 100);亲测有效，问题得到解决。

Android实现阿里云oss图片上传/文件上传
type: item
date: 2023-04-22T15:24:00+08:00
description: 客户端阿里云oss图片上传有两种形式。一种是通过搭建私有服务，客户端通过获取实时授权口令之后上传图片。另一种是通过直传的方式，无需口令，直接将图片上传到阿里云服务器。两种各有优点，第一种适合安全性私密性要求比较高的项目，第二种则适合安全性要求低，实时性要求高的项目。两种上传方式又包含同步上传和异步上传两种传输形式。下面以【直传】方式的【同步上传】为例介绍阿里云oss图片上传流程。包含普通同步上传断点续传同步上传一、准备工作准备你的Config文件import android.os.Environment; import java.io.File; public class Config { // To run the sample correctly, the following variables must have valid values. // The endpoint value below is just the example. Please use proper value according to your region // 访问的endpoint地址，与你的存储区域有关系 public static final String OSS_ENDPOINT = "http://oss-cn-shanghai.aliyuncs.com"; //callback 测试地址 public static final String OSS_CALLBACK_URL = "http://oss-demo.aliyuncs.com:23450"; // STS 鉴权服务器地址，使用前请参照文档 https://help.aliyun.com/document_detail/31920.html 介绍配置STS 鉴权服务器地址。 // 或者根据工程sts_local_server目录中本地鉴权服务脚本代码启动本地STS 鉴权服务器。详情参见sts_local_server 中的脚本内容。 public static final String STS_SERVER_URL = "http://*.*.*.*:*/sts/getsts";//STS 地址，私有服务地址，非必选 public static final String BUCKET_NAME = "bucket-spar";// 上传的仓库名 public static final String OSS_ACCESS_KEY_ID = "你的OSS_ACCESS_KEY_ID"; public static final String OSS_ACCESS_KEY_SECRET = "你的OSS_ACCESS_KEY_SECRET"; //下面是非必选 public static final int DOWNLOAD_SUC = 1; public static final int DOWNLOAD_Fail = 2; public static final int UPLOAD_SUC = 3; public static final int UPLOAD_Fail = 4; public static final int UPLOAD_PROGRESS = 5; public static final int LIST_SUC = 6; public static final int HEAD_SUC = 7; public static final int RESUMABLE_SUC = 8; public static final int SIGN_SUC = 9; public static final int BUCKET_SUC = 10; public static final int GET_STS_SUC = 11; public static final int MULTIPART_SUC = 12; public static final int STS_TOKEN_SUC = 13; public static final int FAIL = 9999; public static final int REQUESTCODE_AUTH = 10111; public static final int REQUESTCODE_LOCALPHOTOS = 10112; public static final int REQUESTCODE_OPEN_DOCUMENT = 10113; } 二、普通同步上传示例： /** * 上传方法,普通同步上传（备份。原来用的这个，后续用的断点续传） * * @param objectKey 标识 * @para…

JavaScript(原生)base64与byte数组互转
type: item
date: 2022-08-09T13:00:00+08:00
description: 今天遇到一个RN传值的问题。Native代理网络请求的情况下，如果是PB请求，后台返回的数据是一个byte[]数组。但是RN数据传送只能传String类型的。可以通过fastJson装成Json字符串，转换过程中，会自动把byte[]类型的value转成base64字符串。RN页面端接收到数据之后，需要将数据进行PB解析，PB解析需要一个byte[]类型的数据（JS里是Uint8Array）。所以第一步要将base64字符串转换成byte[]数组。这里给出几个base64与byte数组互转的方法，供大家参考。一、base64字符串转byte[]数组方法一：window.atob() //base64转字节 function _base64ToArrayBuffer(base64) { var binary_string = window.atob(base64);//解码使用base64编码的字符串 var len = binary_string.length; //获取长度 var bytes = new Uint8Array(len); for (var i = 0; i < len; i++) { bytes[i] = binary_string.charCodeAt(i); } console.log(bytes) //打印解析出来的byte return bytes.buffer; } _base64ToArrayBuffer("CgRTMDAxEgRDSDAxHQjMBkMlsp0XwA==")方法二：原生JavaScript代码将base64转换成byte数组再转换成字符串var BASE64B = [62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51]; //'A'=65,'a'=97,'0'=48,'+'=43,'/'=47 统一减43 //将base64转换成byte数组再转换成字符串 function decode(params,ascii) { if (params == null) return null; if (typeof params === "string") params = stringToBytes(params,ascii); //该方法只适用于utf-8编码和ascii编码 if (params.length%4 != 0) return null; var length = params.length/4*3;if (params[params.length-2] == 61) length -= 2; else if (params[params.length-1] == 61) length -= 1; var result = new Array(); var index = 0; for (var i=0;i<params.length-4;i+=4) { var bits = (BASE64B[params[i]-43] & 0xff) << 18 | (BASE64B[params[i+1]-43] & 0xff) << 12 | (BASE64B[params[i+2]-43] & 0xff) << 6 | (BASE64B[params[i+3]-43] & 0xff); //通过BASE64B[params[i]-43]将原始数值进行还原,然后再向左移位 result[index++] = ((bits >>> 16) & 0xff); //&0xff表示保留8位数(类似求余),>>表示向右移多少位,低位会被丢弃,高位补0(无符号)或1(有符号),>>>表示向右移多少位,高位补0(无符号) result[index++] = ((bits >>> 8) & 0xff); result[index++] = (bits & 0xff); } if (params[params.length-2] == 61) { var bits = (BASE64B[params[params.length-4]-43] & 0xff) << 6 | (BASE64B[params[params.length-3]-43] & 0xff); result[index] = ((…

使用Android应用签名进行加解密
type: item
date: 2022-08-03T19:13:00+08:00
description: 发布出去的APP，签名是必须要的，为什么要签名？签名是为了证明APP的所有权是归公司所有。签名可以是CA认证的证书签名文件，也可以是自签名的文件。下面提供一种签名后的APP，在代码里获取签名信息的方式。该签名信息可以用于app加解密时的唯一密钥，或者在此基础上进行二次加工的结果作为密钥。既保证了密钥的安全性，也保证的密码的强度。获取签名信息 public static String getPublicKeyStrFromPackage() { PublicKey publicKey = getPublicKeyFromPackage(); return publicKey != null ? publicKey.toString() : ""; } public static PublicKey getPublicKeyFromPackage() { String packageName = IGlobalAppContainer.getInstance().getContext().getPackageName(); PublicKey publicKey = null; try { PackageInfo packageInfo = IGlobalAppContainer.getInstance().getContext().getPackageManager().getPackageInfo(packageName, 64); Signature[] signs = packageInfo.signatures; Signature sign = signs[0]; CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate)certFactory.generateCertificate(new ByteArrayInputStream(sign.toByteArray())); publicKey = cert.getPublicKey(); Log.d("public key = " + publicKey); } catch (CertificateException | NameNotFoundException var7) { var7.printStackTrace(); } return publicKey; } 通过签名信息进行加解密（仅供参考）加密： public static String encryptByPublicKey(String content) throws Exception { String publicKey = getPublicKeyStrFromPackage(); if (!TextUtils.isEmpty(publicKey)) { int start = !publicKey.contains("modulus=") ? 0 : publicKey.indexOf("modulus=") + 8; int end = publicKey.indexOf(",publicExponent="); publicKey = publicKey.substring(start, end); } publicKey = MD5Tools.getMD5(publicKey); return encrypt(content, publicKey); }解密： public static String decryptByPublicKey(String content) { try { String publicKey = getPublicKeyStrFromPackage(); if (!TextUtils.isEmpty(publicKey)) { int start = !publicKey.contains("modulus=") ? 0 : publicKey.indexOf("modulus=") + 8; int end = publicKey.indexOf(",publicExponent="); publicKey = publicKey.substring(start, end); } publicKey = MD5Tools.getMD5(publicKey); return decrypt(content, publicKey); } catch (NoSuchAlgorithmException var4) { Log.w("解密失败！ " + var4.toString()); retu…

AndroidStudio build.gradle 配置大全
type: item
date: 2022-08-03T18:56:00+08:00
description: apply plugin，声明是Android应用程序还是库模块android 闭包，配置项目构建的各种属性，compileSdkVersion用于指定项目的编译SDK版本，buildToolsVersion用于指定项目构建工具的版本。defaultConfig闭包：默认配置，应用程序包名，最小 sdk 版本，目标 sdk 版本，版本号，版本名buildTypes闭包：指定生成安装文件的配置，是否对代码进行混淆signingConfigs 闭包：签名信息配置sourceSets 闭包：源文件路径配置lintOptions 闭包：lint 配置dependencies 闭包，指定当前项目的所有依赖关系，本地依赖，库依赖以及远程依赖repositories闭包，仓库配置 // 声明是Android程序， //com.android.application 表示这是一个应用程序模块,可直接运行 //com.android.library 标识这是一个库模块，是依附别的应用程序运行 apply plugin: 'com.android.application' android { //程序在编译的时候会检查lint，有任何错误提示会停止build，我们可以关闭这个开关 lintOptions { //即使报错也不会停止打包 abortOnError false //打包release版本的时候是否进行检测 checkReleaseBuilds false } //编译sdk的版本，也就是API Level，例如API-19、API-20、API-21等等。 compileSdkVersion 26 //build tools的版本，其中包括了打包工具aapt、dx等等。 //这个工具的目录位于你的sdk目录/build-tools/下 buildToolsVersion '26.0.2' //关闭Android Studio的PNG合法性检查 aaptOptions.cruncherEnabled = false aaptOptions.useNewCruncher = false defaultConfig { //默认配置 applicationId "com.hebbe.espressotest" //应用程序的包名 minSdkVersion 22 //最小sdk版本，如果设备小于这个版本或者大于maxSdkVersion将无法安装这个应用 targetSdkVersion 26 //目标sdk版本，充分测试过的版本（建议版本） versionCode 1 //版本号，第一版是1，之后每更新一次加1 versionName "1.0" //版本名，显示给用户看到的版本号 archivesBaseName = "weshare-$versionName" //指定打包成Jar文件时候的文件名称 ndk { moduleName "hebbewifisafe" //设置库(so)文件名称 ldLibs "log", "z", "m", "jnigraphics", "android" //引入库，比如要用到的__android_log_print abiFilters "armeabi", "x86", "armeabi-v7a" //, "x86" 显示指定支持的ABIs cFlags "-std=c++11 -fexceptions" // C++11 stl "gnustl_static" } //当方法数超过65535(方法的索引使用的是一个short值， //而short最大值是65535)的时候允许打包成多个dex文件，动态加载dex。这里面坑很深啊 multiDexEnabled true //Instrumentation单元测试 testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } //默认的一些文件路径的配置 sourceSets { main { manifest.srcFile 'AndroidManifest.xml'//指定清单文件 res.srcDirs = ['res']//指定res资源目录 assets.srcDirs = ['assets'] //asset资源目录 jni.srcDirs 'src/main/jni' //jni代码目录 jniLibs.srcDir 'src/main/jniLibs' //jni库目录 java.srcDirs = ['src']//指定java源代码目录 resources.srcDirs = ['src']//指定resource目录 aidl.srcD…

解决Android 国密SM4CBC加解密 导致okhttp报错：No System TLS / BKS not found
type: item
date: 2022-08-02T20:49:00+08:00
description: APP国密改造已进入尾声，最近一次是埋点库里埋点数据的加解密，需改造成国密。业务代码合入主工程代码时，运行APP挂掉了。提示如下错误信息： 2022-08-01 09:50:01.738 15234-15234/com.maomao.test.activity E/AndroidRuntime: FATAL EXCEPTION: main Process: com.maomao.test.activity:push, PID: 15234 java.lang.RuntimeException: Unable to create application com.maomao.test.activity.tool.MyApp: com.tencent.tinker.loader.TinkerRuntimeException: Tinker Exception:No System TLS at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7409) at android.app.ActivityThread.access$2400(ActivityThread.java:251) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2284) at android.os.Handler.dispatchMessage(Handler.java:110) at android.os.Looper.loop(Looper.java:219) at android.app.ActivityThread.main(ActivityThread.java:8393) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1055) Caused by: com.tencent.tinker.loader.TinkerRuntimeException: Tinker Exception:No System TLS at com.tencent.tinker.entry.TinkerApplicationInlineFence.onCreateImpl_$noinline$(TinkerApplicationInlineFence.java:104) at com.tencent.tinker.entry.TinkerApplicationInlineFence.onCreate(TinkerApplicationInlineFence.java:110) at com.tencent.tinker.loader.app.TinkerApplication.onCreate(TinkerApplication.java:153) at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1202) at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7393) at android.app.ActivityThread.access$2400(ActivityThread.java:251) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2284) at android.os.Handler.dispatchMessage(Handler.java:110) at android.os.Looper.loop(Looper.java:219) at android.app.ActivityThread.main(ActivityThread.java:8393) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:513) at com.android.internal.os.…