
uniapp Android 自定义启动页相关
1、.9.png图的制作:
uniapp社区文档和经验贴
https://ask.dcloud.net.cn/article/35527
https://ask.dcloud.net.cn/article/37365
Android官方教程:
https://developer.android.com/studio/write/draw9patch?hl=zh-cn
博客园(这位博主的说明非常详细,照着做,问题不大):
https://www.cnblogs.com/minblog/p/12588561.html
制作.9图时,如果不好标记,把图放大一点(就是Zoom调大一点),边界上的像素位就比较明显
2、uniapp Android 端配置完成后,真机调试是看不到效果的,官方文档有这方面的说明:
https://uniapp.dcloud.io/collocation/manifest?id=splashscreen
1、.9.png图的制作:
uniapp社区文档和经验贴
https://ask.dcloud.net.cn/article/35527
https://ask.dcloud.net.cn/article/37365
Android官方教程:
https://developer.android.com/studio/write/draw9patch?hl=zh-cn
博客园(这位博主的说明非常详细,照着做,问题不大):
https://www.cnblogs.com/minblog/p/12588561.html
制作.9图时,如果不好标记,把图放大一点(就是Zoom调大一点),边界上的像素位就比较明显
2、uniapp Android 端配置完成后,真机调试是看不到效果的,官方文档有这方面的说明:
https://uniapp.dcloud.io/collocation/manifest?id=splashscreen

android 基于小程序sdk的网络日志抓取实现
android 基于uni小程序sdk的网络日志抓取实现
由于测试需求,需要在android端实现对小程序业务的网络日志捕获,来高效定位前端问题,需求如下:
1.打开小程序,能自动弹出一个debug的小浮窗。
2.点击小浮窗能进入并且看到当次小程序运行过程中的所有网络请求。
--以下uni小程序sdk统称为umpsdk
网络日志捕获
通过翻看umpsdk源码,发现umpsdk是基于weex实现,那么猜测网络层应该在weex里面,所以我们主要分析weex层应该能找到思路。对应源码包应该在uniapp-v8-release.aar。
首先我们从入口处出发,能看到WXSDKInstance这个类,用来做sdk初始化。
public IWXHttpAdapter getWXHttpAdapter() {
return WXSDKManager.getInstance().getIWXHttpAdapter();
}
public IWXStatisticsListener getWXStatisticsListener() {
return this.mStatisticsListener;
}
@Nullable
public IWebSocketAdapter getWXWebSocketAdapter() {
return WXSDKManager.getInstance().getIWXWebSocketAdapter();
}
通过读这块代码发现基础组件都是由WXSDKManager这个类来管理。我们再进入WXSDKManager分析。首先来看构造器
public static WXSDKManager getInstance() {
if (sManager == null) {
Class var0 = WXSDKManager.class;
synchronized(WXSDKManager.class) {
if (sManager == null) {
sManager = new WXSDKManager();
}
}
}
return sManager;
}
这是一个单例模式。
从语义理解getIWXHttpAdapter这个应该是一个负责网络请求的适配器组件。我们找到对应WXSDKManager中getIWXHttpAdapter的实现。
@NonNull
public IWXHttpAdapter getIWXHttpAdapter() {
if (this.mIWXHttpAdapter == null) {
this.mIWXHttpAdapter = new DefaultWXHttpAdapter();
}
return this.mIWXHttpAdapter;
}
同样也是一个判空赋值处理,我们再来看DefaultWXHttpAdapter。路径com.taobao.weex.adapter.DefaultWXHttpAdapter
public class DefaultWXHttpAdapter implements IWXHttpAdapter {
private static final DefaultWXHttpAdapter.IEventReporterDelegate DEFAULT_DELEGATE = new DefaultWXHttpAdapter.NOPEventReportDelegate();
private ExecutorService mExecutorService;
public DefaultWXHttpAdapter() {
}
private void execute(Runnable runnable) {
if (this.mExecutorService == null) {
this.mExecutorService = Executors.newFixedThreadPool(3);
}
this.mExecutorService.execute(runnable);
}
public void sendRequest(final WXRequest request, final OnHttpListener listener) {
if (listener != null) {
listener.onHttpStart();
}
this.execute(new Runnable() {
public void run() {
WXSDKInstance instance = (WXSDKInstance)WXSDKManager.getInstance().getAllInstanceMap().get(request.instanceId);
if (null != instance && !instance.isDestroy()) {
instance.getApmForInstance().actionNetRequest();
}
boolean isNetRequestSucceed = true;
WXResponse response = new WXResponse();
DefaultWXHttpAdapter.IEventReporterDelegate reporter = DefaultWXHttpAdapter.this.getEventReporterDelegate();
try {
HttpURLConnection connection = DefaultWXHttpAdapter.this.openConnection(request, listener);
reporter.preConnect(connection, request.body);
Map<String, List<String>> headers = connection.getHeaderFields();
int responseCode = connection.getResponseCode();
if (listener != null) {
listener.onHeadersReceived(responseCode, headers);
}
reporter.postConnect();
response.statusCode = String.valueOf(responseCode);
if (responseCode >= 200 && responseCode <= 299) {
InputStream rawStream = connection.getInputStream();
rawStream = reporter.interpretResponseStream(rawStream);
response.originalData = DefaultWXHttpAdapter.this.readInputStreamAsBytes(rawStream, listener);
} else {
response.errorMsg = DefaultWXHttpAdapter.this.readInputStream(connection.getErrorStream(), listener);
isNetRequestSucceed = false;
}
if (listener != null) {
listener.onHttpFinish(response);
}
} catch (IllegalArgumentException | IOException var10) {
Exception e = var10;
isNetRequestSucceed = false;
var10.printStackTrace();
response.statusCode = "-1";
response.errorCode = "-1";
response.errorMsg = var10.getMessage();
if (listener != null) {
listener.onHttpFinish(response);
}
if (var10 instanceof IOException) {
try {
reporter.httpExchangeFailed((IOException)e);
} catch (Throwable var9) {
var9.printStackTrace();
}
}
}
if (null != instance && !instance.isDestroy()) {
instance.getApmForInstance().actionNetResult(isNetRequestSucceed, (String)null);
}
}
});
}
private HttpURLConnection openConnection(WXRequest request, OnHttpListener listener) throws IOException {
URL url = new URL(request.url);
HttpURLConnection connection = this.createConnection(url);
connection.setConnectTimeout(request.timeoutMs);
connection.setReadTimeout(request.timeoutMs);
connection.setUseCaches(false);
connection.setDoInput(true);
if (request.paramMap != null) {
Set<String> keySets = request.paramMap.keySet();
Iterator var6 = keySets.iterator();
while(var6.hasNext()) {
String key = (String)var6.next();
connection.addRequestProperty(key, (String)request.paramMap.get(key));
}
}
if (!"POST".equals(request.method) && !"PUT".equals(request.method) && !"PATCH".equals(request.method)) {
if (!TextUtils.isEmpty(request.method)) {
connection.setRequestMethod(request.method);
} else {
connection.setRequestMethod("GET");
}
} else {
connection.setRequestMethod(request.method);
if (request.body != null) {
if (listener != null) {
listener.onHttpUploadProgress(0);
}
connection.setDoOutput(true);
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(request.body.getBytes());
out.close();
if (listener != null) {
listener.onHttpUploadProgress(100);
}
}
}
return connection;
}
private byte[] readInputStreamAsBytes(InputStream inputStream, OnHttpListener listener) throws IOException {
if (inputStream == null) {
return null;
} else {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int readCount = 0;
byte[] data = new byte[2048];
int nRead;
while((nRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
readCount += nRead;
if (listener != null) {
listener.onHttpResponseProgress(readCount);
}
}
buffer.flush();
return buffer.toByteArray();
}
}
private String readInputStream(InputStream inputStream, OnHttpListener listener) throws IOException {
if (inputStream == null) {
return null;
} else {
StringBuilder builder = new StringBuilder();
BufferedReader localBufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] data = new char[2048];
int len;
while((len = localBufferedReader.read(data)) != -1) {
builder.append(data, 0, len);
if (listener != null) {
listener.onHttpResponseProgress(builder.length());
}
}
localBufferedReader.close();
return builder.toString();
}
}
protected HttpURLConnection createConnection(URL url) throws IOException {
return (HttpURLConnection)url.openConnection();
}
@NonNull
public DefaultWXHttpAdapter.IEventReporterDelegate getEventReporterDelegate() {
return DEFAULT_DELEGATE;
}
private static class NOPEventReportDelegate implements DefaultWXHttpAdapter.IEventReporterDelegate {
private NOPEventReportDelegate() {
}
public void preConnect(HttpURLConnection connection, @Nullable String body) {
}
public void postConnect() {
}
public InputStream interpretResponseStream(@Nullable InputStream inputStream) {
return inputStream;
}
public void httpExchangeFailed(IOException e) {
}
}
public interface IEventReporterDelegate {
void preConnect(HttpURLConnection var1, @Nullable String var2);
void postConnect();
InputStream interpretResponseStream(@Nullable InputStream var1);
void httpExchangeFailed(IOException var1);
}
}
看到这里我们发现核心是HttpURLConnection来做网络请求的。这里我们发现sendRequest就是请求处理部分。
看到这里现在我们思路已经清晰了,首先做一下几点考虑:
1.从哪里hook
2.怎样hook侵入性最小
最后我们选择自定义适配器的方式,通过hook替换网络适配器来达成需求的后台处理部分。
class XXXWXHttpAdapter(private val function: (UniResponse) -> Unit) : IWXHttpAdapter {
private var mExecutorService: ExecutorService? = null
private fun execute(runnable: Runnable) {
if (mExecutorService == null) {
mExecutorService = Executors.newFixedThreadPool(3)
}
mExecutorService!!.execute(runnable)
}
override fun sendRequest(request: WXRequest, listener: IWXHttpAdapter.OnHttpListener) {
if (listener != null) {
listener.onHttpStart()
}
execute {
val instance: WXSDKInstance = WXSDKManager.getInstance().allInstanceMap
.get(request.instanceId) as WXSDKInstance
if (null != instance && !instance.isDestroy) {
instance.apmForInstance.actionNetRequest()
}
var isNetRequestSucceed = true
val response = WXResponse()
val reporter: IEventReporterDelegate = eventReporterDelegate
var uniResponse: UniResponse? = null
try {
val connection = openConnection(request, listener)
reporter.preConnect(connection, request.body)
val headers = connection.headerFields
val responseCode = connection.responseCode
if (listener != null) {
listener.onHeadersReceived(responseCode, headers)
}
reporter.postConnect()
response.statusCode = responseCode.toString()
if (responseCode in 200..299) {
var rawStream = connection.inputStream
rawStream = reporter.interpretResponseStream(rawStream)
response.originalData =
readInputStreamAsBytes(rawStream, listener)
} else {
response.errorMsg =
readInputStream(connection.errorStream, listener)
isNetRequestSucceed = false
}
uniResponse = convertUni(request, response)
if (listener != null) {
listener.onHttpFinish(response)
}
} catch (var10: IllegalArgumentException) {
val e: Exception = var10
isNetRequestSucceed = false
var10.printStackTrace()
response.statusCode = "-1"
response.errorCode = "-1"
response.errorMsg = var10.message
uniResponse = convertUni(request, response)
if (listener != null) {
listener.onHttpFinish(response)
}
if (var10 is IOException) {
try {
reporter.httpExchangeFailed(e as IOException)
} catch (var9: Throwable) {
var9.printStackTrace()
}
}
} catch (var10: IOException) {
val e: Exception = var10
isNetRequestSucceed = false
var10.printStackTrace()
response.statusCode = "-1"
response.errorCode = "-1"
response.errorMsg = var10.message
uniResponse = convertUni(request, response)
if (listener != null) {
listener.onHttpFinish(response)
}
if (var10 is IOException) {
try {
reporter.httpExchangeFailed(e as IOException)
} catch (var9: Throwable) {
var9.printStackTrace()
}
}
}
if (uniResponse != null) {
uniLog(uniResponse, function)
}
if (null != instance && !instance.isDestroy) {
instance.apmForInstance
.actionNetResult(isNetRequestSucceed, null as String?)
}
}
}
@Throws(IOException::class)
private fun openConnection(
request: WXRequest,
listener: IWXHttpAdapter.OnHttpListener?
): HttpURLConnection {
val url = URL(request.url)
val connection = createConnection(url)
connection.connectTimeout = request.timeoutMs
connection.readTimeout = request.timeoutMs
connection.useCaches = false
connection.doInput = true
if (request.paramMap != null) {
val keySets: Set<String> = request.paramMap.keys
val var6: Iterator<*> = keySets.iterator()
while (var6.hasNext()) {
val key = var6.next() as String
connection.addRequestProperty(key, request.paramMap.get(key) as String)
}
}
if ("POST" != request.method && "PUT" != request.method && "PATCH" != request.method) {
if (!TextUtils.isEmpty(request.method)) {
connection.requestMethod = request.method
} else {
connection.requestMethod = "GET"
}
} else {
connection.requestMethod = request.method
if (request.body != null) {
listener?.onHttpUploadProgress(0)
connection.doOutput = true
val out = DataOutputStream(connection.outputStream)
out.write(request.body.toByteArray())
out.close()
listener?.onHttpUploadProgress(100)
}
}
return connection
}
@Throws(IOException::class)
private fun readInputStreamAsBytes(
inputStream: InputStream?,
listener: IWXHttpAdapter.OnHttpListener?
): ByteArray? {
return if (inputStream == null) {
null
} else {
val buffer = ByteArrayOutputStream()
var readCount = 0
val data = ByteArray(2048)
var nRead: Int
while (inputStream.read(data, 0, data.size).also { nRead = it } != -1) {
buffer.write(data, 0, nRead)
readCount += nRead
listener?.onHttpResponseProgress(readCount)
}
buffer.flush()
buffer.toByteArray()
}
}
@Throws(IOException::class)
private fun readInputStream(
inputStream: InputStream?,
listener: IWXHttpAdapter.OnHttpListener?
): String? {
return if (inputStream == null) {
null
} else {
val builder = StringBuilder()
val localBufferedReader = BufferedReader(InputStreamReader(inputStream))
val data = CharArray(2048)
var len: Int
while (localBufferedReader.read(data).also { len = it } != -1) {
builder.append(data, 0, len)
listener?.onHttpResponseProgress(builder.length)
}
localBufferedReader.close()
builder.toString()
}
}
@Throws(IOException::class)
protected fun createConnection(url: URL): HttpURLConnection {
return url.openConnection() as HttpURLConnection
}
private class NOPEventReportDelegate : IEventReporterDelegate {
override fun preConnect(connection: HttpURLConnection?, body: String?) {}
override fun postConnect() {}
override fun interpretResponseStream(inputStream: InputStream?): InputStream? {
return inputStream
}
override fun httpExchangeFailed(e: IOException?) {}
}
interface IEventReporterDelegate {
fun preConnect(var1: HttpURLConnection?, var2: String?)
fun postConnect()
fun interpretResponseStream(var1: InputStream?): InputStream?
fun httpExchangeFailed(var1: IOException?)
}
companion object {
val eventReporterDelegate: IEventReporterDelegate = NOPEventReportDelegate()
}
private fun convertUni(wxRequest: WXRequest, wxResponse: WXResponse): UniResponse? =
if (wxRequest.url.contains("xxx") && wxRequest.paramMap.containsValue("application/json")) {
when {
wxRequest.paramMap.containsValue("application/json") -> {
UniResponse(
UniRequest(
wxRequest.paramMap,
wxRequest.url,
wxRequest.method,
JSONObject.parse(wxRequest.body)
),
wxResponse.statusCode,
wxResponse.data,
System.currentTimeMillis(),
wxResponse.originalData,
wxResponse.errorCode,
wxResponse.errorMsg,
wxResponse.toastMsg,
wxResponse.extendParams
)
}
wxRequest.paramMap.containsValue("application/x-www-form-urlencoded") -> {
UniResponse(
UniRequest(
wxRequest.paramMap,
wxRequest.url,
wxRequest.method,
wxRequest.body
),
wxResponse.statusCode,
wxResponse.data,
System.currentTimeMillis(),
wxResponse.originalData,
wxResponse.errorCode,
wxResponse.errorMsg,
wxResponse.toastMsg,
wxResponse.extendParams
)
}
else -> {
null
}
}
} else {
null
}
private fun uniLog(resp: UniResponse, function: (UniResponse) -> Unit) {
function(resp)
Log.i("uni-web-req", resp.toJSON())
Log.i("uni-web-req-curl", resp.convertorCURL())
}
}
fun UniResponse.toJSON(): String {
val temp = this
this.originalData?.apply {
temp.data = JSONObject.parse(this)
}
return JSONObject.toJSONString(temp)
}
fun UniResponse.convertorCURL(): String {
var curlCmd: String = "curl "
if (this.request.body != null) {
curlCmd += if (this.request.paramMap?.containsValue("application/json") == true) {
"-d '${JSONObject.toJSON(this.request.body)}' "
} else {
"-d '${this.request.body as String}'"
}
}
//parse Headers
this.request.paramMap?.forEach { item ->
curlCmd += "-H '${item.key}: ${item.value}' "
}
curlCmd += "${this.request.method} ${this.request.url}"
return curlCmd
}
如下hook替换适配器
val cls = Class.forName("com.taobao.weex.WXSDKManager")
val method = cls.getDeclaredMethod("getInstance")
val manager = method.invoke(null)
val adapterField = cls.getDeclaredField("mIWXHttpAdapter")
adapterField.isAccessible = true
adapterField.set(
manager,
XXXWXHttpAdapter {
JLog.i("onEvent ", " it >>> $it")
responseList.add(it)
}
)
自此完成日志捕获,新版本小程序sdk是基于多进程的所以我们把hook移入service,对应三个service0,1,2。分别对应进程unimp0,nuimp1,nuimp2。之后我们需要做的就是在启动小程序时,获取当前运行的进程名再做相对应的service启动即可。
浮窗控制
我们需要考虑一下几点:
1.代码尽可能少的侵入
2.尽可能少的暴露过多到业务层(因为我们需要区分debug以及release)
通过调研发现weex中有关WXModule的部分,其中框架层有对activity 生命周期的hook。经过试验发现,这里有这样一个规律:onCreate不生效,onResume仅在二次之后打开生效,onDestory每次生效。
通过阅读weex源码发现(这里不做深入探讨),WXModule在打开小程序的过程会进行一次初始化(WXModuleManager)。
至此思路已经清晰
1.我们在wxmodule构造方法中去标记小程序在前台的标志
2.在onDestory中标记小程序退出
3.采用aidl封装进程间通讯
4.通过浮窗点击的跳转自定义activity需要采用startActivityFromUniTask压入小程序进程
android 基于uni小程序sdk的网络日志抓取实现
由于测试需求,需要在android端实现对小程序业务的网络日志捕获,来高效定位前端问题,需求如下:
1.打开小程序,能自动弹出一个debug的小浮窗。
2.点击小浮窗能进入并且看到当次小程序运行过程中的所有网络请求。
--以下uni小程序sdk统称为umpsdk
网络日志捕获
通过翻看umpsdk源码,发现umpsdk是基于weex实现,那么猜测网络层应该在weex里面,所以我们主要分析weex层应该能找到思路。对应源码包应该在uniapp-v8-release.aar。
首先我们从入口处出发,能看到WXSDKInstance这个类,用来做sdk初始化。
public IWXHttpAdapter getWXHttpAdapter() {
return WXSDKManager.getInstance().getIWXHttpAdapter();
}
public IWXStatisticsListener getWXStatisticsListener() {
return this.mStatisticsListener;
}
@Nullable
public IWebSocketAdapter getWXWebSocketAdapter() {
return WXSDKManager.getInstance().getIWXWebSocketAdapter();
}
通过读这块代码发现基础组件都是由WXSDKManager这个类来管理。我们再进入WXSDKManager分析。首先来看构造器
public static WXSDKManager getInstance() {
if (sManager == null) {
Class var0 = WXSDKManager.class;
synchronized(WXSDKManager.class) {
if (sManager == null) {
sManager = new WXSDKManager();
}
}
}
return sManager;
}
这是一个单例模式。
从语义理解getIWXHttpAdapter这个应该是一个负责网络请求的适配器组件。我们找到对应WXSDKManager中getIWXHttpAdapter的实现。
@NonNull
public IWXHttpAdapter getIWXHttpAdapter() {
if (this.mIWXHttpAdapter == null) {
this.mIWXHttpAdapter = new DefaultWXHttpAdapter();
}
return this.mIWXHttpAdapter;
}
同样也是一个判空赋值处理,我们再来看DefaultWXHttpAdapter。路径com.taobao.weex.adapter.DefaultWXHttpAdapter
public class DefaultWXHttpAdapter implements IWXHttpAdapter {
private static final DefaultWXHttpAdapter.IEventReporterDelegate DEFAULT_DELEGATE = new DefaultWXHttpAdapter.NOPEventReportDelegate();
private ExecutorService mExecutorService;
public DefaultWXHttpAdapter() {
}
private void execute(Runnable runnable) {
if (this.mExecutorService == null) {
this.mExecutorService = Executors.newFixedThreadPool(3);
}
this.mExecutorService.execute(runnable);
}
public void sendRequest(final WXRequest request, final OnHttpListener listener) {
if (listener != null) {
listener.onHttpStart();
}
this.execute(new Runnable() {
public void run() {
WXSDKInstance instance = (WXSDKInstance)WXSDKManager.getInstance().getAllInstanceMap().get(request.instanceId);
if (null != instance && !instance.isDestroy()) {
instance.getApmForInstance().actionNetRequest();
}
boolean isNetRequestSucceed = true;
WXResponse response = new WXResponse();
DefaultWXHttpAdapter.IEventReporterDelegate reporter = DefaultWXHttpAdapter.this.getEventReporterDelegate();
try {
HttpURLConnection connection = DefaultWXHttpAdapter.this.openConnection(request, listener);
reporter.preConnect(connection, request.body);
Map<String, List<String>> headers = connection.getHeaderFields();
int responseCode = connection.getResponseCode();
if (listener != null) {
listener.onHeadersReceived(responseCode, headers);
}
reporter.postConnect();
response.statusCode = String.valueOf(responseCode);
if (responseCode >= 200 && responseCode <= 299) {
InputStream rawStream = connection.getInputStream();
rawStream = reporter.interpretResponseStream(rawStream);
response.originalData = DefaultWXHttpAdapter.this.readInputStreamAsBytes(rawStream, listener);
} else {
response.errorMsg = DefaultWXHttpAdapter.this.readInputStream(connection.getErrorStream(), listener);
isNetRequestSucceed = false;
}
if (listener != null) {
listener.onHttpFinish(response);
}
} catch (IllegalArgumentException | IOException var10) {
Exception e = var10;
isNetRequestSucceed = false;
var10.printStackTrace();
response.statusCode = "-1";
response.errorCode = "-1";
response.errorMsg = var10.getMessage();
if (listener != null) {
listener.onHttpFinish(response);
}
if (var10 instanceof IOException) {
try {
reporter.httpExchangeFailed((IOException)e);
} catch (Throwable var9) {
var9.printStackTrace();
}
}
}
if (null != instance && !instance.isDestroy()) {
instance.getApmForInstance().actionNetResult(isNetRequestSucceed, (String)null);
}
}
});
}
private HttpURLConnection openConnection(WXRequest request, OnHttpListener listener) throws IOException {
URL url = new URL(request.url);
HttpURLConnection connection = this.createConnection(url);
connection.setConnectTimeout(request.timeoutMs);
connection.setReadTimeout(request.timeoutMs);
connection.setUseCaches(false);
connection.setDoInput(true);
if (request.paramMap != null) {
Set<String> keySets = request.paramMap.keySet();
Iterator var6 = keySets.iterator();
while(var6.hasNext()) {
String key = (String)var6.next();
connection.addRequestProperty(key, (String)request.paramMap.get(key));
}
}
if (!"POST".equals(request.method) && !"PUT".equals(request.method) && !"PATCH".equals(request.method)) {
if (!TextUtils.isEmpty(request.method)) {
connection.setRequestMethod(request.method);
} else {
connection.setRequestMethod("GET");
}
} else {
connection.setRequestMethod(request.method);
if (request.body != null) {
if (listener != null) {
listener.onHttpUploadProgress(0);
}
connection.setDoOutput(true);
DataOutputStream out = new DataOutputStream(connection.getOutputStream());
out.write(request.body.getBytes());
out.close();
if (listener != null) {
listener.onHttpUploadProgress(100);
}
}
}
return connection;
}
private byte[] readInputStreamAsBytes(InputStream inputStream, OnHttpListener listener) throws IOException {
if (inputStream == null) {
return null;
} else {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int readCount = 0;
byte[] data = new byte[2048];
int nRead;
while((nRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
readCount += nRead;
if (listener != null) {
listener.onHttpResponseProgress(readCount);
}
}
buffer.flush();
return buffer.toByteArray();
}
}
private String readInputStream(InputStream inputStream, OnHttpListener listener) throws IOException {
if (inputStream == null) {
return null;
} else {
StringBuilder builder = new StringBuilder();
BufferedReader localBufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] data = new char[2048];
int len;
while((len = localBufferedReader.read(data)) != -1) {
builder.append(data, 0, len);
if (listener != null) {
listener.onHttpResponseProgress(builder.length());
}
}
localBufferedReader.close();
return builder.toString();
}
}
protected HttpURLConnection createConnection(URL url) throws IOException {
return (HttpURLConnection)url.openConnection();
}
@NonNull
public DefaultWXHttpAdapter.IEventReporterDelegate getEventReporterDelegate() {
return DEFAULT_DELEGATE;
}
private static class NOPEventReportDelegate implements DefaultWXHttpAdapter.IEventReporterDelegate {
private NOPEventReportDelegate() {
}
public void preConnect(HttpURLConnection connection, @Nullable String body) {
}
public void postConnect() {
}
public InputStream interpretResponseStream(@Nullable InputStream inputStream) {
return inputStream;
}
public void httpExchangeFailed(IOException e) {
}
}
public interface IEventReporterDelegate {
void preConnect(HttpURLConnection var1, @Nullable String var2);
void postConnect();
InputStream interpretResponseStream(@Nullable InputStream var1);
void httpExchangeFailed(IOException var1);
}
}
看到这里我们发现核心是HttpURLConnection来做网络请求的。这里我们发现sendRequest就是请求处理部分。
看到这里现在我们思路已经清晰了,首先做一下几点考虑:
1.从哪里hook
2.怎样hook侵入性最小
最后我们选择自定义适配器的方式,通过hook替换网络适配器来达成需求的后台处理部分。
class XXXWXHttpAdapter(private val function: (UniResponse) -> Unit) : IWXHttpAdapter {
private var mExecutorService: ExecutorService? = null
private fun execute(runnable: Runnable) {
if (mExecutorService == null) {
mExecutorService = Executors.newFixedThreadPool(3)
}
mExecutorService!!.execute(runnable)
}
override fun sendRequest(request: WXRequest, listener: IWXHttpAdapter.OnHttpListener) {
if (listener != null) {
listener.onHttpStart()
}
execute {
val instance: WXSDKInstance = WXSDKManager.getInstance().allInstanceMap
.get(request.instanceId) as WXSDKInstance
if (null != instance && !instance.isDestroy) {
instance.apmForInstance.actionNetRequest()
}
var isNetRequestSucceed = true
val response = WXResponse()
val reporter: IEventReporterDelegate = eventReporterDelegate
var uniResponse: UniResponse? = null
try {
val connection = openConnection(request, listener)
reporter.preConnect(connection, request.body)
val headers = connection.headerFields
val responseCode = connection.responseCode
if (listener != null) {
listener.onHeadersReceived(responseCode, headers)
}
reporter.postConnect()
response.statusCode = responseCode.toString()
if (responseCode in 200..299) {
var rawStream = connection.inputStream
rawStream = reporter.interpretResponseStream(rawStream)
response.originalData =
readInputStreamAsBytes(rawStream, listener)
} else {
response.errorMsg =
readInputStream(connection.errorStream, listener)
isNetRequestSucceed = false
}
uniResponse = convertUni(request, response)
if (listener != null) {
listener.onHttpFinish(response)
}
} catch (var10: IllegalArgumentException) {
val e: Exception = var10
isNetRequestSucceed = false
var10.printStackTrace()
response.statusCode = "-1"
response.errorCode = "-1"
response.errorMsg = var10.message
uniResponse = convertUni(request, response)
if (listener != null) {
listener.onHttpFinish(response)
}
if (var10 is IOException) {
try {
reporter.httpExchangeFailed(e as IOException)
} catch (var9: Throwable) {
var9.printStackTrace()
}
}
} catch (var10: IOException) {
val e: Exception = var10
isNetRequestSucceed = false
var10.printStackTrace()
response.statusCode = "-1"
response.errorCode = "-1"
response.errorMsg = var10.message
uniResponse = convertUni(request, response)
if (listener != null) {
listener.onHttpFinish(response)
}
if (var10 is IOException) {
try {
reporter.httpExchangeFailed(e as IOException)
} catch (var9: Throwable) {
var9.printStackTrace()
}
}
}
if (uniResponse != null) {
uniLog(uniResponse, function)
}
if (null != instance && !instance.isDestroy) {
instance.apmForInstance
.actionNetResult(isNetRequestSucceed, null as String?)
}
}
}
@Throws(IOException::class)
private fun openConnection(
request: WXRequest,
listener: IWXHttpAdapter.OnHttpListener?
): HttpURLConnection {
val url = URL(request.url)
val connection = createConnection(url)
connection.connectTimeout = request.timeoutMs
connection.readTimeout = request.timeoutMs
connection.useCaches = false
connection.doInput = true
if (request.paramMap != null) {
val keySets: Set<String> = request.paramMap.keys
val var6: Iterator<*> = keySets.iterator()
while (var6.hasNext()) {
val key = var6.next() as String
connection.addRequestProperty(key, request.paramMap.get(key) as String)
}
}
if ("POST" != request.method && "PUT" != request.method && "PATCH" != request.method) {
if (!TextUtils.isEmpty(request.method)) {
connection.requestMethod = request.method
} else {
connection.requestMethod = "GET"
}
} else {
connection.requestMethod = request.method
if (request.body != null) {
listener?.onHttpUploadProgress(0)
connection.doOutput = true
val out = DataOutputStream(connection.outputStream)
out.write(request.body.toByteArray())
out.close()
listener?.onHttpUploadProgress(100)
}
}
return connection
}
@Throws(IOException::class)
private fun readInputStreamAsBytes(
inputStream: InputStream?,
listener: IWXHttpAdapter.OnHttpListener?
): ByteArray? {
return if (inputStream == null) {
null
} else {
val buffer = ByteArrayOutputStream()
var readCount = 0
val data = ByteArray(2048)
var nRead: Int
while (inputStream.read(data, 0, data.size).also { nRead = it } != -1) {
buffer.write(data, 0, nRead)
readCount += nRead
listener?.onHttpResponseProgress(readCount)
}
buffer.flush()
buffer.toByteArray()
}
}
@Throws(IOException::class)
private fun readInputStream(
inputStream: InputStream?,
listener: IWXHttpAdapter.OnHttpListener?
): String? {
return if (inputStream == null) {
null
} else {
val builder = StringBuilder()
val localBufferedReader = BufferedReader(InputStreamReader(inputStream))
val data = CharArray(2048)
var len: Int
while (localBufferedReader.read(data).also { len = it } != -1) {
builder.append(data, 0, len)
listener?.onHttpResponseProgress(builder.length)
}
localBufferedReader.close()
builder.toString()
}
}
@Throws(IOException::class)
protected fun createConnection(url: URL): HttpURLConnection {
return url.openConnection() as HttpURLConnection
}
private class NOPEventReportDelegate : IEventReporterDelegate {
override fun preConnect(connection: HttpURLConnection?, body: String?) {}
override fun postConnect() {}
override fun interpretResponseStream(inputStream: InputStream?): InputStream? {
return inputStream
}
override fun httpExchangeFailed(e: IOException?) {}
}
interface IEventReporterDelegate {
fun preConnect(var1: HttpURLConnection?, var2: String?)
fun postConnect()
fun interpretResponseStream(var1: InputStream?): InputStream?
fun httpExchangeFailed(var1: IOException?)
}
companion object {
val eventReporterDelegate: IEventReporterDelegate = NOPEventReportDelegate()
}
private fun convertUni(wxRequest: WXRequest, wxResponse: WXResponse): UniResponse? =
if (wxRequest.url.contains("xxx") && wxRequest.paramMap.containsValue("application/json")) {
when {
wxRequest.paramMap.containsValue("application/json") -> {
UniResponse(
UniRequest(
wxRequest.paramMap,
wxRequest.url,
wxRequest.method,
JSONObject.parse(wxRequest.body)
),
wxResponse.statusCode,
wxResponse.data,
System.currentTimeMillis(),
wxResponse.originalData,
wxResponse.errorCode,
wxResponse.errorMsg,
wxResponse.toastMsg,
wxResponse.extendParams
)
}
wxRequest.paramMap.containsValue("application/x-www-form-urlencoded") -> {
UniResponse(
UniRequest(
wxRequest.paramMap,
wxRequest.url,
wxRequest.method,
wxRequest.body
),
wxResponse.statusCode,
wxResponse.data,
System.currentTimeMillis(),
wxResponse.originalData,
wxResponse.errorCode,
wxResponse.errorMsg,
wxResponse.toastMsg,
wxResponse.extendParams
)
}
else -> {
null
}
}
} else {
null
}
private fun uniLog(resp: UniResponse, function: (UniResponse) -> Unit) {
function(resp)
Log.i("uni-web-req", resp.toJSON())
Log.i("uni-web-req-curl", resp.convertorCURL())
}
}
fun UniResponse.toJSON(): String {
val temp = this
this.originalData?.apply {
temp.data = JSONObject.parse(this)
}
return JSONObject.toJSONString(temp)
}
fun UniResponse.convertorCURL(): String {
var curlCmd: String = "curl "
if (this.request.body != null) {
curlCmd += if (this.request.paramMap?.containsValue("application/json") == true) {
"-d '${JSONObject.toJSON(this.request.body)}' "
} else {
"-d '${this.request.body as String}'"
}
}
//parse Headers
this.request.paramMap?.forEach { item ->
curlCmd += "-H '${item.key}: ${item.value}' "
}
curlCmd += "${this.request.method} ${this.request.url}"
return curlCmd
}
如下hook替换适配器
val cls = Class.forName("com.taobao.weex.WXSDKManager")
val method = cls.getDeclaredMethod("getInstance")
val manager = method.invoke(null)
val adapterField = cls.getDeclaredField("mIWXHttpAdapter")
adapterField.isAccessible = true
adapterField.set(
manager,
XXXWXHttpAdapter {
JLog.i("onEvent ", " it >>> $it")
responseList.add(it)
}
)
自此完成日志捕获,新版本小程序sdk是基于多进程的所以我们把hook移入service,对应三个service0,1,2。分别对应进程unimp0,nuimp1,nuimp2。之后我们需要做的就是在启动小程序时,获取当前运行的进程名再做相对应的service启动即可。
浮窗控制
我们需要考虑一下几点:
1.代码尽可能少的侵入
2.尽可能少的暴露过多到业务层(因为我们需要区分debug以及release)
通过调研发现weex中有关WXModule的部分,其中框架层有对activity 生命周期的hook。经过试验发现,这里有这样一个规律:onCreate不生效,onResume仅在二次之后打开生效,onDestory每次生效。
通过阅读weex源码发现(这里不做深入探讨),WXModule在打开小程序的过程会进行一次初始化(WXModuleManager)。
至此思路已经清晰
1.我们在wxmodule构造方法中去标记小程序在前台的标志
2.在onDestory中标记小程序退出
3.采用aidl封装进程间通讯
4.通过浮窗点击的跳转自定义activity需要采用startActivityFromUniTask压入小程序进程
收起阅读 »
uni-app android 不使用原生插件搞的文件管理器,UI可定制,可分类,可多选
android 不使用原生插件搞的文件管理器,UI可定制,可分类,可多选
有偿提供源码
QQ:543610866
android 不使用原生插件搞的文件管理器,UI可定制,可分类,可多选
有偿提供源码
QQ:543610866

uniapp 动态申请位置权限 android
动态申请权限 除了 网上 写的 在 app.vue 写android 的 动态设置外 ,切记 还得 调用一下 uniapp的 geiLocation()方法 否则无法在onlaunch()方法调用时 触发权限申请
动态申请权限 除了 网上 写的 在 app.vue 写android 的 动态设置外 ,切记 还得 调用一下 uniapp的 geiLocation()方法 否则无法在onlaunch()方法调用时 触发权限申请

版本更新的 RSS 订阅源
使用 feedfry 抓取 Github 页面生成的,因为是免费账号所以开头有广告,但不影响使用
正式版本:
https://feedfry.com/rss/11ec9512a987f386a3f70c8ddd7d8f7e
Alpha 版本:
https://feedfry.com/rss/11ec951145a414e48afbaab02ed597e3
使用 feedfry 抓取 Github 页面生成的,因为是免费账号所以开头有广告,但不影响使用
正式版本:
https://feedfry.com/rss/11ec9512a987f386a3f70c8ddd7d8f7e
Alpha 版本:
https://feedfry.com/rss/11ec951145a414e48afbaab02ed597e3
收起阅读 »
项目找前端,预算3万,周期1月
项目找前端,预算3万,周期1月
**需远程连接进行开发,可个人,可签合同。
要求:
uniapp技术栈:必须熟练掌握以下 (融云uniapp[https://ext.dcloud.net.cn/publisher?id=689034]和npro和uview,nvue,sass,unicloud)
项目经验丰富,能够快速理解并上手他人项目,对项目进行继续开发。
时间充裕,工作态度认真,代码逻辑严谨**
有意者联系V:oyo476
项目找前端,预算3万,周期1月
**需远程连接进行开发,可个人,可签合同。
要求:
uniapp技术栈:必须熟练掌握以下 (融云uniapp[https://ext.dcloud.net.cn/publisher?id=689034]和npro和uview,nvue,sass,unicloud)
项目经验丰富,能够快速理解并上手他人项目,对项目进行继续开发。
时间充裕,工作态度认真,代码逻辑严谨**
有意者联系V:oyo476
收起阅读 »
项目招前端,预算3万,项目周期1月
**需远程连接进行开发,可个人,可签合同。
要求:
uniapp技术栈:必须熟练掌握以下 (融云uniapp[https://ext.dcloud.net.cn/publisher?id=689034]和npro和uview,nvue,sass,unicloud)
项目经验丰富,能够快速理解并上手他人项目,对项目进行继续开发。
时间充裕,工作态度认真,代码逻辑严谨**
有意者联系V:oyo476
**需远程连接进行开发,可个人,可签合同。
要求:
uniapp技术栈:必须熟练掌握以下 (融云uniapp[https://ext.dcloud.net.cn/publisher?id=689034]和npro和uview,nvue,sass,unicloud)
项目经验丰富,能够快速理解并上手他人项目,对项目进行继续开发。
时间充裕,工作态度认真,代码逻辑严谨**
有意者联系V:oyo476
收起阅读 »
vue3 app端组件异步渲染 defineAsyncComponent
vue3中 defineAsyncComponent 异步渲染 在打包app的时候会报错
Invalid value "iife" for option "output.format" - UMD and IIFE output formats are not supported for code-splitting builds
去掉 defineAsyncComponent 方法 改为正常的引入方式
vue3中 defineAsyncComponent 异步渲染 在打包app的时候会报错
Invalid value "iife" for option "output.format" - UMD and IIFE output formats are not supported for code-splitting builds
去掉 defineAsyncComponent 方法 改为正常的引入方式
收起阅读 »
企业和个人发卡网源码带代理系统和搭建教程
今天,我们就和大家分享一下企业和个人自动发卡网源码。在这篇文章中,我们将向你展示在php中创建自动发卡网系统,完整解析相关源代码,我们将给你演示和示例实现。
完整企业和个人发卡网源码:fakaysw.top
步骤1:创建Sendbox帐户
在这个步骤中,如果您没有sendbox帐户,我们将创建测试。
步骤2:创建索引文件
index.php
.price.panel-red>.panel-heading {
color: #fff;
background-color: #D04E50;
border-color: #FF6062;
border-bottom: 1px solid #FF6062;
}
.price.panel-red>.panel-body {
color: #fff;
background-color: #EF5A5C;
}
.price .list-group-product{
border-bottom-:1px solid rgba(250,250,250, .5);
}
.panel.price .list-group-product:last-child {
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
}
.panel.price .list-group-product:first-child {
border-top-right-radius: 0px;
border-top-left-radius: 0px;
}
.price .panel-footer {
color: #fff;
border-bottom:0px;
background-color: rgba(0,0,0, .1);
box-shadow: 0px 3px 0px rgba(0,0,0, .3);
}
.panel.price .btn{
box-shadow: 0 -1px 0px rgba(50,50,50, .2) inset;
border:0px;
}
</style>
<?php
$paypalId='kvs3944-facilitator@gmail.com';
?>
<div class="container text-center">
<br/>
<br/>
<div class="row">
<div class="col-xs-6 col-sm-6 col-md-3 col-md-offset-4 col-lg-3">
<form action="<?php echo $paypalUrl; ?>" method="post" name="frmPayPal1">
<div class="panel price panel-red">
<input type="hidden" name="business" value="<?php echo $paypalId; ?>">
<input type="hidden" name="cmd" value="_xclick">
<input type="hidden" name="product_name" value="Pakainfo website">
<input type="hidden" name="product_number" value="2">
<input type="hidden" name="amount" value="20">
<input type="hidden" name="no_shipping" value="1">
<input type="hidden" name="currency_code" value="USD">
<div class="panel-heading text-center">
<h3>PRO PLAN</h3>
</div>
<div class="panel-body text-center">
<p class="lead" style="font-size:40px"><strong>$20 / month</strong></p>
</div>
<ul class="list-group list-group-flush text-center">
<li class="list-group-product"><i class="icon-ok text-danger"></i> Personal use</li>
<li class="list-group-product"><i class="icon-ok text-danger"></i> Unlimited projects</li>
<li class="list-group-product"><i class="icon-ok text-danger"></i> 27/7 support</li>
</ul>
<div class="panel-footer">
<button class="btn btn-lg btn-block btn-danger" href="#">BUY NOW!</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
步骤3:创建支付回调
success.php
$productNo = $_REQUEST['product_number'];
$productTransaction = $_REQUEST['tx'];
$productPrice = $_REQUEST['amt'];
$productCurrency = $_REQUEST['cc'];
$price = '20.00';
$currency='USD';
if($productPrice==$price && $productCurrency==$currency)
{
echo "Your Payment Successful, Good Luck";
}
else
{
echo "Sorry, Dear Your Payment Failed";
}
步骤4:创建取消文件
echo "Payment Canceled";
步骤5:发卡网支付网关URL
$paypalUrl='localhost/cgi-bin/webscr';
INTO
$paypalUrl='localhost/cgi-bin/webscr';
今天,我们就和大家分享一下企业和个人自动发卡网源码。在这篇文章中,我们将向你展示在php中创建自动发卡网系统,完整解析相关源代码,我们将给你演示和示例实现。
完整企业和个人发卡网源码:fakaysw.top
步骤1:创建Sendbox帐户
在这个步骤中,如果您没有sendbox帐户,我们将创建测试。
步骤2:创建索引文件
index.php
.price.panel-red>.panel-heading {
color: #fff;
background-color: #D04E50;
border-color: #FF6062;
border-bottom: 1px solid #FF6062;
}
.price.panel-red>.panel-body {
color: #fff;
background-color: #EF5A5C;
}
.price .list-group-product{
border-bottom-:1px solid rgba(250,250,250, .5);
}
.panel.price .list-group-product:last-child {
border-bottom-right-radius: 0px;
border-bottom-left-radius: 0px;
}
.panel.price .list-group-product:first-child {
border-top-right-radius: 0px;
border-top-left-radius: 0px;
}
.price .panel-footer {
color: #fff;
border-bottom:0px;
background-color: rgba(0,0,0, .1);
box-shadow: 0px 3px 0px rgba(0,0,0, .3);
}
.panel.price .btn{
box-shadow: 0 -1px 0px rgba(50,50,50, .2) inset;
border:0px;
}
</style>
<?php
$paypalId='kvs3944-facilitator@gmail.com';
?>
<div class="container text-center">
<br/>
<br/>
<div class="row">
<div class="col-xs-6 col-sm-6 col-md-3 col-md-offset-4 col-lg-3">
<form action="<?php echo $paypalUrl; ?>" method="post" name="frmPayPal1">
<div class="panel price panel-red">
<input type="hidden" name="business" value="<?php echo $paypalId; ?>">
<input type="hidden" name="cmd" value="_xclick">
<input type="hidden" name="product_name" value="Pakainfo website">
<input type="hidden" name="product_number" value="2">
<input type="hidden" name="amount" value="20">
<input type="hidden" name="no_shipping" value="1">
<input type="hidden" name="currency_code" value="USD">
<div class="panel-heading text-center">
<h3>PRO PLAN</h3>
</div>
<div class="panel-body text-center">
<p class="lead" style="font-size:40px"><strong>$20 / month</strong></p>
</div>
<ul class="list-group list-group-flush text-center">
<li class="list-group-product"><i class="icon-ok text-danger"></i> Personal use</li>
<li class="list-group-product"><i class="icon-ok text-danger"></i> Unlimited projects</li>
<li class="list-group-product"><i class="icon-ok text-danger"></i> 27/7 support</li>
</ul>
<div class="panel-footer">
<button class="btn btn-lg btn-block btn-danger" href="#">BUY NOW!</button>
</div>
</div>
</form>
</div>
</div>
</div>
</body>
</html>
步骤3:创建支付回调
success.php
$productNo = $_REQUEST['product_number'];
$productTransaction = $_REQUEST['tx'];
$productPrice = $_REQUEST['amt'];
$productCurrency = $_REQUEST['cc'];
$price = '20.00';
$currency='USD';
if($productPrice==$price && $productCurrency==$currency)
{
echo "Your Payment Successful, Good Luck";
}
else
{
echo "Sorry, Dear Your Payment Failed";
}
步骤4:创建取消文件
echo "Payment Canceled";
步骤5:发卡网支付网关URL
$paypalUrl='localhost/cgi-bin/webscr';
INTO
$paypalUrl='localhost/cgi-bin/webscr';