Commit 240ca20c authored by 张圣龙's avatar 张圣龙

Merge branch 'master' of https://git.zmcms.cn/wangwei/widget

parents 5310ed4d a74d216d
......@@ -8,4 +8,5 @@
.externalNativeBuild
/.idea
/.gradle
\ No newline at end of file
/.gradle
/fetch/build
\ No newline at end of file
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 29
......@@ -26,8 +25,18 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
configurations.all {
resolutionStrategy {
force 'androidx.core:core-ktx:1.6.0'
//force 'com.github.bumptech.glide:compiler:4.2.0'
}
}
buildFeatures {
viewBinding true
}
packagingOptions {
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
pickFirst 'lib/armeabi-v7a/libmarsxlog.so'
pickFirst 'lib/arm64-v8a/libc++_shared.so'
pickFirst 'lib/arm64-v8a/libmarsxlog.so'
......@@ -38,24 +47,31 @@ dependencies {
implementation 'com.hyman:flowlayout-lib:1.1.2'
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation project(path: ':KeyBoardView')
implementation project(path: ':log')
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.3.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation project(path: ':widgetlib')
// implementation 'com.qmai.android.sdk:widgetlib:1.1.2-SNAPSHOT'
implementation project(path: ':tools')
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'com.github.sebdomdev:android-drawer-sheet:1.0'
implementation 'com.lxj:xpopup:2.0.6-rc1'
implementation 'com.google.android.material:material:1.3.0-alpha01'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation project(path: ':printer')
// implementation 'com.android.tools.build:gradle:3.1.1'
// implementation 'com.android.tools.lint:lint-gradle:26.1.1'
implementation 'com.guolindev.permissionx:permissionx:1.5.1'
implementation 'com.eclipsesource.j2v8:j2v8:6.2.0@aar'
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.qimai.android.widget">
xmlns:tools="http://schemas.android.com/tools"
package="com.qimai.android.widget">
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".DrawerActivity"/>
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="AllowBackup">
<activity
android:name=".LogActivity"
android:exported="false" />
<activity android:name=".DrawerActivity" />
<activity android:name=".GridDividerActivity" />
<activity android:name=".TipActivity" />
<activity android:name=".CommomPopActivity" />
<activity android:name=".TopBarActivity" />
<activity android:name="com.qimai.android.widget.MainActivity">
</activity>
<activity android:name="BtListActivity"/>
<activity android:name=".MainActivityPtinter">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".UsbActivity"/>
<activity android:name=".BtListActivity" />
<activity android:name=".MainActivityPtinter"></activity>
<activity android:name=".UsbActivity" />
</application>
</manifest>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
function print1(template1,data){
var html = template1
// let encoding = require('./encoding')
// let TextEncoder = encoding.TextEncoder
// let template = require('./art-template')
// android.printString(html)
var render = template.render(html, JSON.parse(data));
// var htmlparser2 = require('./htmlparser2')
var byteArray = [27, 64, 10]
var uint8array = new TextEncoder('gbk', { NONSTANDARD_allowLegacyEncoding: true })
var handler = new htmlparser2.DomHandler(function(error, dom) {
dom.forEach(item => {
if (item.name) {
var children = item.children
if (children && children.length == 1 && children[0].type == 'text') {
// console.log(uint8array.encode('值编号:SDJL1293780123') + '')
byteArray = byteArray.concat(Array.from(uint8array.encode(children[0].data)))
byteArray = byteArray.concat([10])
}
if (children.length > 1) {
children.forEach(child => {
var childChildren = child.children
if (childChildren && childChildren.length == 1 && childChildren[0].type == 'text') {
var childStyle = child.attribs.style
if (childStyle) {
var styleObject = tools.normalizeStyle(childStyle)
if (styleObject['text-align']) {
console.log(tools.fontAlign(styleObject['text-align']))
byteArray = byteArray.concat(tools.fontAlign(styleObject['text-align']))
}
}
byteArray = byteArray.concat(Array.from(uint8array.encode(childChildren[0].data)))
byteArray = byteArray.concat([10])
}
})
}
}
})
byteArray = byteArray.concat([10, 10, 10, 10])
android.print12(byteArray)
// console.log(byteArray + '')
})
var parser = new htmlparser2.Parser(handler)
// var parser = new htmlparser2.Parser({
// onopentag(name, attributes) {
// console.log(name)
// console.log(attributes)
// // html = html + '<' + name + '>'
// // if (name === 'span') {
// // name = 'p'
// // }
// },
// // ontext(text) {
// // console.log("-->", text);
// // // html = html + '{{value}}'
// // },
// // onclosetag(tagname) {
// // // if (tagname === "script") {
// // // console.log("That's it?!");
// // // }
// // // html = html + '</' + tagname + '>'
// // console.log(tagname, '结尾')
// // },
// });
parser.write(render);
parser.end();
// function transform() {
// return render
// }
// console.log(transform())
// let encoding = require('./encoding')
// let TextEncoder = encoding.TextEncoder
// console.log(TextEncoder)
var uint8array = new TextEncoder(
'gbk', { NONSTANDARD_allowLegacyEncoding: true }).encode('值编号:SDJL1293780123');
// function encodeArrayOfStrings(strings) {
// var encoder, encoded, len, i, bytes, view, offset;
// encoder = new TextEncoder();
// encoded = [];
// len = Uint16Array.BYTES_PER_ELEMENT;
// for (i = 0; i < strings.length; i += 1) {
// len += Uint16Array.BYTES_PER_ELEMENT;
// encoded[i] = new TextEncoder().encode(strings[i]);
// len += encoded[i].byteLength;
// }
// bytes = new Uint16Array(len);
// view = new DataView(bytes.buffer);
// offset = 0;
// view.setUint32(offset, strings.length);
// offset += Uint16Array.BYTES_PER_ELEMENT;
// for (i = 0; i < encoded.length; i += 1) {
// len = encoded[i].byteLength;
// view.setUint32(offset, len);
// offset += Uint16Array.BYTES_PER_ELEMENT;
// bytes.set(encoded[i], offset);
// offset += len;
// }
// return bytes;
// }
// var aa = encodeArrayOfStrings('hello world')
return
}
\ No newline at end of file
var INITBYTE = [0x1b, 0x40];
var LEFT_ALIGIN = [0x1b, 97, 0];
var CENTER_ALIGIN = [0x1b, 97, 1];
var RIGHT_ALIGIN = [0x1b, 97, 2];
var FONT_BOLD = [0x1b, 0x45, 0x01];
var FONT_NORMAL = [0x1b, 0x45, 0x00];
var FONT_TYPE_0 = [0x1d, 0x21, 0x00];
var FONT_TYPE_1 = [0x1d, 0x21, 0x10];
var FONT_TYPE_2 = [0x1d, 0x21, 0x01];
var FONT_TYPE_3 = [0x1d, 0x21, 17];
var PRINT_AND_FEED = [10];
var dataArray = [];
var hockeyTeam = {
dataArray1 : [],
templateJson: "",
dataJson: "",
getPrintData:function(){
writeValue("企迈测试");
return stringToByte("企迈测试");
},
};
function print1(){
var arrays2 =
[27, 64, 10, 27, 51, 20, 27, 97, 1, 29, 33, 17, 27, 69, 0, 83, 97, 109, 112, 108,
101, 10, 10, 27, 51, 20, 27, 97, 0, 29, 33, 0, 27, 69, 0, 50, 51, 51, 51, 51, 10,
10, 27, 51, 20, 27, 97, 0, 29, 33, 0, 27, 69, 0, 50, 51, 51, 51, 51, 10, 10, 27,
51, 20, 27, 97, 0, 29, 33, 0, 27, 69, 0, 50, 51, 50, 51, 51, 51, 10, 10, 27, 51,
20, 27, 97, 0, 29, 33, 0, 27, 69, 0, 50, 50, 51, 51, 51, 51, 10, 10, 27, 51, 20,
27, 97, 0, 29, 33, 0, 27, 69, 0, 49, 50, 49, 51, 51, 51, 51, 10, 10, 27, 51, 20,
27, 97, 1, 29, 33, 17, 27, 69, 0, 45, 45, 45, -46, -47, -42, -89, -72, -74, 45, 45, 45, 10,
10, 27, 51, 20, 27, 97, 0, 29, 33, 0, 27, 69, 0, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46,
46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46,
46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 10, 10, 27, 51, 20, 27, 97, 0, 29, 33, 1, 27,
69, 0, -78, -30, -54, -44, -55, -52, -58, -73, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 42, 49, 32, 32, 32, 32, 32, 32, 49, 50, 46, 48, 48, 10, 10, 27, 51, 20, 27, 97, 0, 29, 33, 1, 27,
69, 0, -78, -30, -54, -44, -55, -52, -58, -73, 50, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, -95, -66, 42, 50, -95, -65, 32, 32, 32, 32, 32, 49, 53, 46, 48, 48, 10, 10, 27, 51, 20, 27, 97, 0, 29, 33, 0, 27, 69, 0, 46,
46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46,
46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 46, 10, 10, 27, 51, 20, 27, 97, 0, 29, 33, 0, 27, 69, 0, -74, -87, -75, -91, -70, -59, 58, 70,
68, 49, 50, 52, 81, 52, 52, 51, 52, 50, 50, 10, 10, 27, 51, 20, 27, 97, 0, 29, 33, 0, 27, 69, 0, -48, -69, -48, -69, -69, -35, -71, -53,
-93, -84, -69, -74, -45, -83, -49, -62, -76, -50, -71, -30, -63, -39, 10, 10, 10, 10, 10];
dataArray = write([0x1b, 0x40]);
return dataArray;
}
function stringToByte(str) {
var bytes = new Array();
var len, c;
len = str.length;
for(var i = 0; i < len; i++) {
c = str.charCodeAt(i);
if(c >= 0x010000 && c <= 0x10FF FF) {
bytes.push(((c >> 18) & 0x07) | 0xF0);
bytes.push(((c >> 12) & 0x3F) | 0x80);
bytes.push(((c >> 6) & 0x3F) | 0x80);
bytes.push((c & 0x3F) | 0x80);
} else if(c >= 0x000800 && c <= 0x00FFFF) {
bytes.push(((c >> 12) & 0x0F) | 0xE0);
bytes.push(((c >> 6) & 0x3F) | 0x80);
bytes.push((c & 0x3F) | 0x80);
} else if(c >= 0x000080 && c <= 0x0007FF) {
bytes.push(((c >> 6) & 0x1F) | 0xC0);
bytes.push((c & 0x3F) | 0x80);
} else {
bytes.push(c & 0xFF);
}
}
return bytes;
}
function write(value) {
dataArray = dataArray.concat(value);
return dataArray;
}
function writeValue(value){
return write(stringToByte(value));
}
function writeAlign(align) {
switch (align) {
case 0:
write(LEFT_ALIGIN);
break;
case 1:
write(CENTER_ALIGIN);
break;
case 2 :
write(RIGHT_ALIGIN);
break;
}
return dataArray;
}
function writePrintAndLineFeed() {
return write(PRINT_AND_FEED);
}
function writeNewLine(num) {
for(var i=0;i<num;i++) {
write([10]);
}
return dataArray;
}
function writelnLineText(value,aligin) {
writePrintAndLineFeed();
writeAlign(aligin);
writeValue(value);
writeNewLine(1);
return dataArray;
}
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = global || self, global.tools = factory());
}(this, (function () { 'use strict';
var tools = {
// 文字大小
fontSize(size) {
var array = []
switch(size) {
case 'normal':
array = [29, 33, 0]
break
case 'doubleHeight':
array = [29, 33, 1]
break
case 'doubleWidth':
array = [29, 33, 16]
break
case 'double':
array = [29, 33, 17]
break
}
return array
},
// 文字加粗
fontBold() {
return [27, 69, 1]
},
// 文字对齐方式
fontAlign(align) {
var array = []
switch(align) {
case 'left':
array = [27, 97, 48]
break
case 'center':
array = [27, 97, 49]
break
case 'right':
array = [27, 97, 50]
break
}
return array
},
normalizeStyle(styleString) {
let styleArray = styleString.split(';')
let styleObject = {}
styleArray.forEach(item => {
if (item) {
var styleItem = item.split(':')
styleObject[styleItem[0]] = styleItem[1]
}
})
return styleObject
}
}
return tools
})))
\ No newline at end of file
......@@ -3,7 +3,7 @@ package com.qimai.android.widget
import android.app.Application
import zs.qimai.com.printer2.utils.PrintManagerUtils
class App: Application() {
class App : Application() {
override fun onCreate() {
super.onCreate()
PrintManagerUtils.getInstance().init(this)
......
......@@ -71,7 +71,7 @@ class BtListActivity : AppCompatActivity(), View.OnClickListener {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_bt_list)
Log.d(TAG, "onCreate: model= ${Build.MODEL} brand = ${Build.BRAND} ")
PrintManagerUtils.getInstance().getSearchBlueToothList(object : BlueToothSearchCallBack {
PrintManagerUtils.getInstance().getSearchBlueToothList(this,object : BlueToothSearchCallBack {
override fun onRefuseGrantPermission() {
}
......
package com.qimai.android.widget
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.qimai.android.widget.databinding.ActivityLogBinding
import com.qimai.log.QLog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LogActivity : AppCompatActivity() {
private lateinit var bind: ActivityLogBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bind = ActivityLogBinding.inflate(layoutInflater)
setContentView(bind.root)
init()
}
private fun init() {
bind.btnLog.setOnClickListener { initLog() }
bind.btnWrite.setOnClickListener {
lifecycleScope.launch(Dispatchers.IO) {
QLog.write("123456", QLog.D)
withContext(Dispatchers.Main) {
QLog.write("123456789", QLog.E)
}
}
}
}
/**
* 初始化LogUp
*/
private fun initLog() {
QLog.init(true)
}
}
\ No newline at end of file
......@@ -9,7 +9,6 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.qimai.android.widgetlib.popup.QmTipOverlayDrawable
import com.qimai.android.widgetlib.toast.HolderContext
import com.qimai.log.QmLog
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
......@@ -17,11 +16,7 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
HolderContext.getInstance().register {
this
}
QmLog.d("test1111","111111")
HolderContext.getInstance().register { this }
findViewById<Button>(R.id.commonPop).setOnClickListener {
// ToastUtils.getInstance().showCenter("测试测试")
......@@ -47,22 +42,22 @@ class MainActivity : AppCompatActivity() {
findViewById<TextView>(R.id.drawer).setOnClickListener {
startActivity(Intent(MainActivity@ this, CommomPopActivity::class.java))
}
findViewById<Button>(R.id.tip).setOnClickListener {
startActivity(Intent(MainActivity@ this, TipActivity::class.java))
}
findViewById<Button>(R.id.topbar).setOnClickListener {
startActivity(Intent(MainActivity@ this, TopBarActivity::class.java))
}
findViewById<Button>(R.id.divide).setOnClickListener {
startActivity(Intent(MainActivity@ this, GridDividerActivity::class.java))
}
keyboard.btnText = "结账"
findViewById<Button>(R.id.btn_printer).setOnClickListener {
startActivity(Intent(this, MainActivityPtinter::class.java))
}
findViewById<Button>(R.id.btn_log).setOnClickListener {
startActivity(Intent(this, LogActivity::class.java))
}
keyboard.btnText = "结账"
keyboard.invalidate()
}
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn_log"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="初始化Log"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_write"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="写日志"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_log" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
......@@ -41,6 +41,16 @@
android:text="drawer"
android:layout_width="match_parent"
android:layout_height="50dp" />
<Button
android:id="@+id/btn_printer"
android:text="打印"
android:layout_width="match_parent"
android:layout_height="50dp" />
<Button
android:id="@+id/btn_log"
android:text="日志"
android:layout_width="match_parent"
android:layout_height="50dp" />
<TextView
android:layout_width="match_parent"
......
......@@ -4,8 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="zs.qimai.com.test.MainActivity">
android:orientation="vertical">
<TextView
android:id="@+id/tv_connect_bt"
......@@ -80,4 +79,20 @@
android:textColor="#000"
/>
<ImageView
android:layout_gravity="center"
android:id="@+id/iv_image"
android:layout_width="100dp"
android:layout_height="100dp"/>
<TextView
android:id="@+id/tv_Log_up"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="10dp"
android:gravity="center"
android:text="日志操作"
android:textColor="#000"
android:textSize="22sp" />
</LinearLayout>
\ No newline at end of file
......@@ -21,7 +21,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:4.0.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath "com.whl:gradle-publish-plugin:0.1.16-SNAPSHOT"
......
......@@ -63,7 +63,7 @@ repositories {
mavenCentral()
}
group 'com.qmai.android.fetch'
version '1.2.57-SNAPSHOT'
version '1.2.58-SNAPSHOT'
gradlePublish {
......
......@@ -137,7 +137,8 @@ class OkHttpConfig private constructor(builder: Builder) {
setHostnameVerifier()
addInterceptors()
setTimeout()
setDebugConfig()
//http log显示信息由外部调用,内部不提供
// setDebugConfig()
return OkHttpConfig(this)
}
......
File mode changed from 100755 to 100644
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: "com.whl.gradle-publish-plugin"
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 29
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
minSdkVersion 16
targetSdkVersion 29
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
......@@ -36,41 +34,33 @@ android {
jniLibs.srcDirs = ['libs']
}
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation files('libs/commons-net-3.7.jar')
implementation files('libs/commons-net-3.7-sources.jar')
implementation files('libs/commons-net-examples-3.7.jar')
testImplementation 'junit:junit:4.+'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
//日志读写
implementation 'com.dianping.android.sdk:logan:1.2.4'
//系统级别浮框
implementation "com.github.princekin-f:EasyFloat:2.0.3"
implementation project(path: ':widgetlib')
//test
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
api 'com.tencent.mars:mars-xlog:1.2.4'
// For Kotlin use kapt instead of annotationProcessor (注意这个注释)
kapt "androidx.room:room-compiler:$lifecycle_version"
implementation "androidx.room:room-runtime:$lifecycle_version"
kapt "androidx.room:room-compiler:$lifecycle_version"
implementation "androidx.room:room-ktx:$lifecycle_version"
api "androidx.lifecycle:lifecycle-extensions:2.2.0"
api "androidx.lifecycle:lifecycle-runtime:2.2.0"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:2.2.0"
kapt "androidx.lifecycle:lifecycle-compiler:2.2.0"
api "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
api "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
}
group 'com.qmai.android.log'
version '1.0.1.21-SNAPSHOT'
version '1.2-SNAPSHOT'
gradlePublish {
......
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.qimai.log">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
>
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.INTERNET" />
<provider
android:name="com.qimai.log.InitContentProvider"
android:authorities="${applicationId}.log.InitContentProvider1"
android:enabled="true"
android:exported="false"/>
<service android:name="com.qimai.log.UploadService"/>
<service android:name=".service.ScheduledExecutionService"/>
<application>
<activity
android:name=".ui.LogUpActivity"
android:exported="false" />
</application>
</manifest>
\ No newline at end of file
package com.qimai.log;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.channels.FileChannel;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DBCopyUtils {
private static final String TAG = "DBCopyUtils";
/* public static void copyLogToFTP(String tag) {
File file = null;
final String SDCARD = Environment.getExternalStorageDirectory().getAbsolutePath();
final String logPath = LogInit.Companion.getInstance().mApplication.getFilesDir().toString() + "/release/xlog";
file = new File(logPath);
try {
// File logFile =
if (!file.exists()) {
return;
}
File[] fileList = file.listFiles();
if (fileList != null && fileList.length > 0) {
for (int i = 0; i < fileList.length; i++) {
file = fileList[i];
Log.d(TAG, "copylogToFTP: file = " + file.getAbsolutePath().toString());
final FileInputStream in = new FileInputStream(file);
final File finalFile = file;
new Thread(new Runnable() {
@Override
public void run() {
boolean isUpload = FtpUtil.uploadFile("119.29.25.121", 221, "dataftp"
, "Data2019@#%", "/", tag, finalFile.getName(), in);
Log.d(TAG, "copyDataBaseToSD: isUpload= " + isUpload + "DeviceUtils.getDeviceSN()= " + tag);
if (isUpload) {
finalFile.delete();
}
}
}).start();
}
} else {
return;
}
} catch (Exception e) {
Log.d(TAG, "copyDataBaseToSD: failed= " + e.getMessage());
//LogUtils.e(TAG, "copy dataBase to SD error.");
e.printStackTrace();
} finally {
}
}*/
/**
* @param file 代表文件不是目录
**/
public static void copyLogToFTP(File file,String tag) {
FileInputStream in = null;
try {
// File logFile =
if (!file.exists()) {
return;
}
in = new FileInputStream(file);
Log.d(TAG, "copyLogToFTP: thread= " + Thread.currentThread().getName());
boolean isUpload = FtpUtil.uploadFile("119.29.25.121", 221, "dataftp"
, "Data2019@#%",
"/", tag, file.getName(), in);
Log.d(TAG, "copyLogToFTP: isUpload= " + isUpload);
/*File []fileList = file.listFiles();
if (fileList!=null&&fileList.length>0){
for (int i = 0; i <fileList.length;i++) {
file = fileList[i];
Log.d(TAG, "copylogToFTP: file = "+file.getAbsolutePath().toString());
FileInputStream in = new FileInputStream(file);
File finalFile = file;
new Thread(new Runnable() {
@Override
public void run() {
boolean isUpload = FtpUtil.uploadFile("119.29.25.121",221,"dataftp"
,"Data2018@#%",
"/",DeviceUtils.getDeviceSN(), finalFile.getName(),in);
Log.d(TAG, "copyDataBaseToSD: isUpload= "+isUpload+"DeviceUtils.getDeviceSN()= "+(DeviceUtils.getDeviceSN()));
if (isUpload){
finalFile.delete();
}
}
}).start();
}
}
else{
return;
}
}
catch (Exception e) {
Log.d(TAG, "copyDataBaseToSD: failed= "+e.getMessage());
//LogUtils.e(TAG, "copy dataBase to SD error.");
e.printStackTrace();
}finally{
}
*/
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package com.qimai.log
import android.util.Log
import com.qimai.log.QmLog.getConfig
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.IOException
import java.util.*
object FtpUpload {
fun upload(config: UploadConfig, uploadCallBack: UploadCallBack? = null) {
GlobalScope.launch(Dispatchers.Main){
uploadCallBack?.onLoading()
val logFiles = getLogFiles(config)
if (logFiles.isNullOrEmpty()) {
uploadCallBack?.onError("暂无可上传日志文件")
} else {
try {
for (i in logFiles.indices) {
val tempFile = logFiles[i]
if (tempFile != null) {
uploadFile(config.tag,tempFile)
}
}
uploadCallBack?.onSuccess()
} catch (e: FileNotFoundException) {
uploadCallBack?.onError(e.toString())
}catch (e: IOException) {
uploadCallBack?.onError(e.toString())
}
}
}
}
suspend fun uploadFile(tag: String, file: File) = withContext(Dispatchers.IO){
var `in`: FileInputStream? = null
try {
// File logFile =
if (!file.exists()) {
return@withContext
}
`in` = FileInputStream(file)
val isUpload = FtpUtil.uploadFile(
"119.29.25.121", 221, "dataftp", "Data2019@#%",
"/", tag, file.getName(), `in`
)
} catch (e: FileNotFoundException) {
throw e
} finally {
if (`in` != null) {
try {
`in`.close()
} catch (e: IOException) {
throw e
}
}
}
}
}
private fun getLogFiles(config: UploadConfig): Array<File?>? {
//DBCopyUtils.copyLogToFTP();
//int requestNum = receiverBean.getNum();
var file: File? = null
// final String logPath = SDCARD + "/ptcy/log";
val logPath: String
logPath = if (BuildConfig.DEBUG) {
getConfig().logDebugPath
} else {
getConfig().logReleasePath
}
file = File(logPath)
if (!file.exists()) {
return null
}
//file.lastModified();
val files: Array<File?>? = convertTimeDesc(file.listFiles())
if (files == null || files.size == 0) {
return null
}
return files
// int maxFile = files.length;
// int endNum = Math.min(maxFile,num);
/*for (i in files.indices) {
val tempFile = files[i]
DBCopyUtils.copyLogToFTP(tempFile, config.tag)
}*/
}
private fun convertTimeDesc(files: Array<File>): Array<File?>? {
if (files == null || files.size == 0) {
return null
}
val endNum = files.size
val filesAll = arrayOfNulls<File>(20)
val longFileTreeMap = TreeMap<Long, File>()
for (i in files.indices) {
val temp = files[i].lastModified()
if (files[i].exists()) {
longFileTreeMap[temp] = files[i]
}
}
val navigableSet = longFileTreeMap.descendingKeySet()
val iterator: Iterator<Long> = navigableSet.iterator()
var index = 0
while (iterator.hasNext()) {
val file = longFileTreeMap[iterator.next()]
// Log.d(UploadService.TAG, "convertTimeDesc: file name= " + file!!.name)
filesAll[index] = file
index++
if (index >= endNum) {
break
}
}
return filesAll
}
fun copyLogToFTP(file: File, tag: String?) {
var `in`: FileInputStream? = null
try {
// File logFile =
if (!file.exists()) {
return
}
`in` = FileInputStream(file)
val isUpload = FtpUtil.uploadFile(
"119.29.25.121", 221, "dataftp", "Data2019@#%",
"/", tag, file.name, `in`
)
} catch (e: FileNotFoundException) {
e.printStackTrace()
} finally {
if (`in` != null) {
try {
`in`.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
package com.qimai.log;
import android.util.Log;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketException;
public class FtpUtil {
private static final String TAG = "FtpUtil";
/**
* Description: 向FTP服务器上传文件
*
* @param host FTP服务器hostname
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param basePath FTP服务器基础目录
* @param filePath FTP服务器文件存放路径。例如分日期存放:/2017/08/06。文件的路径为basePath+filePath
* @param filename 上传到FTP服务器上的文件名
* @param input 输入流
* @return 成功返回true,否则返回false
*/
public static boolean uploadFile(String host, int port, String username, String password, String basePath,
String filePath, String filename, InputStream input)
{
Log.d(TAG, "uploadFile: thread= "+Thread.currentThread().getName());
boolean result = false;
FTPClient ftp = new FTPClient();
try
{
int reply;
ftp.connect(host, port);// 连接FTP服务器
// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
ftp.login(username, password);// 登录
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply))
{
ftp.disconnect();
return result;
}
// 切换到上传目录
String onepath=new String((basePath + filePath).getBytes("GBK"),"iso-8859-1");
if (!ftp.changeWorkingDirectory(onepath))
{
ftp.makeDirectory(onepath);
ftp.changeWorkingDirectory(onepath);
/* Log.d(TAG, "uploadFile: 切换到上传目录");
// 如果目录不存在创建目录
String[] dirs = filePath.split("/");
String tempPath = basePath;*/
/*for (String dir : dirs)
{
if (null == dir || "".equals(dir))
continue;
tempPath += "/" + dir;
if (!ftp.changeWorkingDirectory(tempPath))
{
if (!ftp.makeDirectory(tempPath))
{
return result;
}
else
{
ftp.changeWorkingDirectory(tempPath);
}
}
}*/
}
// 设置上传文件的类型为二进制类型
ftp.setFileType(FTP.BINARY_FILE_TYPE);
// 上传文件
ftp.enterLocalPassiveMode();
if (!ftp.storeFile(new String(filename.getBytes("GBK"),"iso-8859-1"), input)) {
Log.d(TAG, "uploadFile: upload fail");
return result;
}
Log.d(TAG, "uploadFile: 上传成功");
input.close();
ftp.logout();
result = true;
}
catch (IOException e)
{
e.printStackTrace();
Log.d(TAG, "uploadFile: e= "+e.getMessage());
}
finally
{
if (ftp.isConnected())
{
try
{
ftp.disconnect();
}
catch (IOException ioe)
{
}
}
}
return result;
}
/**
* Description: 向FTP服务器上传文件
*
* @param host FTP服务器hostname
* @param port FTP服务器端口
* @param username FTP登录账号
* @param password FTP登录密码
* @param basePath FTP服务器基础目录
* @param filePath FTP服务器文件存放路径。例如分日期存放:/2015/01/01。文件的路径为basePath+filePath
* @param filename 上传到FTP服务器上的文件名
* @param input 输入流
* @return 成功返回true,否则返回false
*/
public static boolean uploadFile1(String host, int port, String username, String password, String basePath,
String filePath, String filename, InputStream input) {
boolean result = false;
FTPClient ftp = new FTPClient();
try {
int reply;
ftp.connect(host, port);// 连接FTP服务器
// 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
ftp.login(username, password);// 登录
ftp.setBufferSize(1024);
ftp.setControlEncoding("UTF-8");
ftp.enterLocalPassiveMode();
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
return result;
}
//切换到上传目录
if (!ftp.changeWorkingDirectory(basePath + filePath)) {
//如果目录不存在创建目录
//String[] dirs = filePath.split("/");
String tempPath = basePath;
/* for (String dir : dirs) {
if (null == dir || "".equals(dir)) continue;
tempPath += "/" + dir;
if (!ftp.changeWorkingDirectory(tempPath)) {
if (!ftp.makeDirectory(tempPath)) {
return result;
} else {
ftp.changeWorkingDirectory(tempPath);
}
}
}*/
ftp.changeWorkingDirectory(tempPath);
}
//创建目录
if (!ftp.changeWorkingDirectory(filePath)){
ftp.makeDirectory(filePath);
}
//设置上传文件的类型为二进制类型
ftp.setFileType(FTP.BINARY_FILE_TYPE);
//上传文件
if (!ftp.storeFile(filePath+"/"+filename, input)) {
return result;
}
input.close();
ftp.logout();
result = true;
} catch (IOException e) {
Log.d(TAG, "uploadFile: e= "+e.getMessage());
e.printStackTrace();
e.printStackTrace();
} finally {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
}
}
}
return result;
}
}
package com.qimai.log
import android.content.ContentProvider
import android.content.ContentValues
import android.database.Cursor
import android.net.Uri
class InitContentProvider : ContentProvider() {
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
throw UnsupportedOperationException("Not yet implemented")
}
override fun getType(uri: Uri): String? {
throw UnsupportedOperationException("Not yet implemented")
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
throw UnsupportedOperationException("Not yet implemented")
}
override fun onCreate(): Boolean {
context?.let { LogInit.getInstance().init(it) }
return true
}
override fun query(
uri: Uri, projection: Array<String>?, selection: String?,
selectionArgs: Array<String>?, sortOrder: String?
): Cursor? {
throw UnsupportedOperationException("Not yet implemented")
}
override fun update(
uri: Uri, values: ContentValues?, selection: String?,
selectionArgs: Array<String>?
): Int {
throw UnsupportedOperationException("Not yet implemented")
}
}
\ No newline at end of file
package com.qimai.log
import android.app.Activity
import android.app.Application
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Environment
import com.qimai.log.service.ScheduledExecutionService
import com.tencent.mars.xlog.Log
import com.tencent.mars.xlog.Xlog
class LogInit private constructor() {
private lateinit var mLogConfig: QmLogConfig
@JvmOverloads
fun init(application: Context) {
init(QmLogConfig(application))
}
@JvmOverloads
fun init(
config: QmLogConfig
) {
this.mLogConfig = config
//addObserver(config.app)
System.loadLibrary("c++_shared")
System.loadLibrary("marsxlog")
var logPath = ""
if (BuildConfig.DEBUG) {
logPath = config.logDebugPath
} else {
logPath = config.logReleasePath
}
val logConfig = Xlog.XLogConfig()
logConfig.mode = Xlog.AppednerModeAsync
logConfig.logdir = logPath
logConfig.nameprefix = "log"
logConfig.pubkey = ""
logConfig.compressmode = Xlog.ZLIB_MODE
logConfig.compresslevel = 0
logConfig.cachedays = 3
logConfig.cachedir = ""
// if (BuildConfig.DEBUG) {
logConfig.level = Xlog.LEVEL_DEBUG
Xlog.setConsoleLogOpen(true)
// } else {
// logConfig.level = Xlog.LEVEL_ALL
// Xlog.setConsoleLogOpen(true)
//}
Xlog.appenderOpen(logConfig)
Log.setLogImp(Xlog())
//检查是否有过期的通知数据
// val intent = Intent(getConfig().app, ScheduledExecutionService::class.java)
// getConfig().app.startService(intent)
}
private fun addObserver(mApplication: Context) {
if (mApplication is Application) {
mApplication.registerActivityLifecycleCallbacks(object :
Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
}
override fun onActivityStarted(activity: Activity) {
}
override fun onActivityResumed(activity: Activity) {
}
override fun onActivityPaused(activity: Activity) {
}
override fun onActivityStopped(activity: Activity) {
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
}
override fun onActivityDestroyed(activity: Activity) {
Log.appenderClose()
Log.appenderFlush(false)
}
})
}
}
companion object {
private val mInstance: LogInit = LogInit()
fun getInstance(): LogInit {
return mInstance
}
}
fun getConfig() = mLogConfig
}
\ No newline at end of file
package com.qimai.log;/*
* Copyright (c) 2018-present, 美团点评
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.zip.GZIPInputStream;
import java.util.zip.InflaterInputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class LoganParser {
private static final String ALGORITHM = "AES";
private static final String ALGORITHM_TYPE = "AES/CBC/NoPadding";
private Cipher mDecryptCipher;
private byte[] mEncryptKey16; //128位ase加密Key
private byte[] mEncryptIv16; //128位aes加密IV
public LoganParser(byte[] encryptKey16, byte[] encryptIv16) {
mEncryptKey16 = encryptKey16;
mEncryptIv16 = encryptIv16;
initEncrypt();
}
private void initEncrypt() {
SecretKeySpec secretKeySpec = new SecretKeySpec(mEncryptKey16, ALGORITHM);
try {
mDecryptCipher = Cipher.getInstance(ALGORITHM_TYPE);
mDecryptCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(mEncryptIv16));
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
}
public void parse(InputStream is, OutputStream os) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int t = 0;
try {
while ((t = is.read(buffer)) >= 0) {
bos.write(buffer, 0, t);
bos.flush();
}
byte[] content = bos.toByteArray();
for (int i = 0; i < content.length; i++) {
byte start = content[i];
if (start == '\1') {
i++;
int length = (content[i] & 0xFF) << 24 |
(content[i + 1] & 0xFF) << 16 |
(content[i + 2] & 0xFF) << 8 |
(content[i + 3] & 0xFF);
i += 3;
int type;
if (length > 0) {
int temp = i + length + 1;
if (content.length - i - 1 == length) { //异常
type = 0;
} else if (content.length - i - 1 > length && '\0' == content[temp]) {
type = 1;
} else if (content.length - i - 1 > length && '\1' == content[temp]) { //异常
type = 2;
} else {
i -= 4;
continue;
}
byte[] dest = new byte[length];
System.arraycopy(content, i + 1, dest, 0, length);
ByteArrayOutputStream uncompressBytesArray = new ByteArrayOutputStream();
InflaterInputStream inflaterOs = null;
byte[] uncompressByte;
try {
uncompressBytesArray.reset();
inflaterOs = new GZIPInputStream(new CipherInputStream(new ByteArrayInputStream(dest), mDecryptCipher));
int e = 0;
while ((e = inflaterOs.read(buffer)) >= 0) {
uncompressBytesArray.write(buffer, 0, e);
}
} catch (Exception e) {
e.printStackTrace();
}
uncompressByte = uncompressBytesArray.toByteArray();
uncompressBytesArray.reset();
os.write(uncompressByte);
if (inflaterOs != null)
inflaterOs.close();
i += length;
if (type == 1) {
i++;
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.qimai.log
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
\ No newline at end of file
package com.qimai.log
import com.blankj.utilcode.util.*
import com.dianping.logan.Logan
import com.dianping.logan.LoganConfig
import com.qimai.log.bean.LogUpBean
import com.qimai.log.ui.LogUpFloatView
import kotlinx.coroutines.*
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileInputStream
/**
* 上传日志相关操作
*/
object QLog {
//region ------ 日志类型 ------
//调试信息
const val D: Int = 2
//提示性的消息
const val I: Int = 3
//错误信息
const val E: Int = 4
//警告信息
const val W: Int = 5
//endregion
//ase加密Key
private const val encryptKey16 = "0123456789012345"
//aes加密IV
private const val encryptIv16 = "0123456789012345"
// 到天的日期格式
private const val formatDay = "yyyy-MM-dd"
//每天的毫秒数
private const val millisDay = 86_400_000
//零点字符串
private const val zero = "00:00:00"
//log 文件路径
private var filePath: String? = null
/**
* 初始化
*/
fun init(debug: Boolean = false) {
filePath =
"${Utils.getApp().getExternalFilesDir(null)?.absolutePath}${File.separator}Logan_v1"
val config: LoganConfig = LoganConfig.Builder()
.setCachePath(Utils.getApp().applicationContext.filesDir.absolutePath)
.setEncryptKey16(encryptKey16.toByteArray())
.setEncryptIV16(encryptIv16.toByteArray())
.setPath(filePath)
.build()
Logan.init(config)
Logan.setDebug(debug)
Logan.setOnLoganProtocolStatus { cmd, code ->
LogUtils.d("clogan > cmd : $cmd | code : $code")
}
GlobalScope.launch(Dispatchers.IO) {
delay(1_000)
withContext(Dispatchers.Main) {
LogUpFloatView.show()
}
}
}
/**
* 写入日志
* @param log String - 日志内容
* @param tag Int - 日志类型
* @param now Boolean - 是否立即写入
*/
fun write(log: String, tag: Int = D, now: Boolean = false) {
Logan.w(log, tag)
if (now) Logan.f()
}
/**
* 立即写入
*/
fun f() = Logan.f()
/**
* 获取日志文件保存路径
*/
fun getLogFilePath(): String? = filePath
/**
* 按日期获取日志文件,日期格式 yyyy-MM-dd
*/
fun getLogFile(dateStr: String): File? {
val timeStamp = TimeUtils.string2Millis("$dateStr $zero")
return FileUtils.listFilesInDir(filePath).find { it.name == "$timeStamp" }
}
/**
* 获取一段时间内所有日志文件
*
* @param startDate String 开始时间,格式为:yyyy-MM-dd
* @param endDate String 结束时间,格式为:yyyy-MM-dd
* @return MutableList<File> 文件列表
*/
fun getLogFileByPeriod(startDate: String, endDate: String): MutableList<File> {
val dateList = getPeriodDateList(startDate, endDate)
return FileUtils.listFilesInDirWithFilter(filePath) {
dateList.any { a -> "${TimeUtils.string2Millis("$a $zero")}" == it.name }
}
}
/**
*获取所有日志文件
*/
fun getAllLogFile(): MutableList<File> = FileUtils.listFilesInDir(filePath)
/**
* 按日期获取日志文件内容,日期格式 yyyy-MM-dd
*/
fun getLogFileContent(dateStr: String): String {
val file = getLogFile(dateStr)
return if (file != null) getLogFileContent(file) else ""
}
/**
* 获取一天内一段时间内的日志内容
*
* @param startDate String 开始时间,格式为 yyyy-MM-dd HH:mm:ss
* @param endDate String 结束时间,格式为 yyyy-MM-dd HH:mm:ss
* @return List<LogUpBean> 日志内容
*/
fun getLogListByPeriod(startDate: String, endDate: String): List<LogUpBean?> {
//开始时间戳
val startMillis: Long = TimeUtils.string2Millis(startDate)
//结束时间戳
val endMillis: Long = TimeUtils.string2Millis(endDate)
//当天时间字符串,用于查找文件,格式:yyyy-MM-dd
val dateStr = TimeUtils.millis2String(startMillis, formatDay)
return getLogFileContent(dateStr)
.trim()
.split("\n")
.map { GsonUtils.fromJson(it, LogUpBean::class.java) ?: null }
.filter { it?.l in startMillis..endMillis }
.sortedByDescending { it?.l }
}
/**
* 获取一天内一段时间内的日志内容
*
* @param startDate String 开始时间,格式为 yyyy-MM-dd HH:mm:ss
* @param endDate String 结束时间,格式为 yyyy-MM-dd HH:mm:ss
* @return String 日志内容
*/
fun getLogContentByPeriod(startDate: String, endDate: String): String {
val list = getLogListByPeriod(startDate, endDate)
return GsonUtils.toJson(list)
}
/**
* 获取所有文件内容
*/
fun getAllLogFileContent(): String {
return getAllLogFile()
.fold("") { total, next ->
val nextContent = getLogFileContent(next)
return if (total.isEmpty()) nextContent else "${total}\n${nextContent}"
}
}
/**
* 解析日志文件内容
*/
fun getLogFileContent(file: File): String {
val uncompressBytesArray = ByteArrayOutputStream()
val loganParser = LoganParser(encryptKey16.toByteArray(), encryptIv16.toByteArray())
loganParser.parse(FileInputStream(file), uncompressBytesArray)
return String(uncompressBytesArray.toByteArray())
}
/**
* 获取一段时间内时间列表
*
* @param startDate String 开始时间,格式为 yyyy-MM-dd
* @param endDate String 结束时间,格式为 yyyy-MM-dd
* @return MutableList<String> 时间列表 [yyyy-MM-dd,yyyy-MM-dd]
*/
private fun getPeriodDateList(startDate: String, endDate: String): MutableList<String> {
val start: Long = TimeUtils.string2Millis("$startDate $zero")
var end: Long = TimeUtils.string2Millis("$endDate $zero")
val dateList = mutableListOf<String>()
while (end >= start) {
dateList.add(TimeUtils.millis2String(end, formatDay))
end -= millisDay
}
return dateList
}
/**
* 按名称删除文件
*/
fun deleteLogByName(name: String) {
FileUtils.delete("$filePath/$name")
}
/**
* 按日期删除日志
* @param dateStr String
*/
fun deleteLogByDate(dateStr: String) {
getLogFile(dateStr)?.run { FileUtils.delete(this) }
}
/**
* 获取所有日志文件信息
*/
fun getAllFileInfo(): Map<String, Long> {
return Logan.getAllFilesInfo()
}
/**
* 清空所有日志文件
*/
fun clear() {
FileUtils.deleteAllInDir(filePath)
}
}
\ No newline at end of file
package com.qimai.log
import android.content.Intent
import com.tencent.mars.xlog.Log
object QmLog {
fun d(tag: String, message: String) {
Log.d(tag, message)
}
fun e(tag: String, message: String) {
Log.e(tag, message)
}
fun v(tag: String, message: String) {
Log.v(tag, message)
}
fun w(tag: String, message: String) {
Log.w(tag, message)
}
fun i(tag: String, message: String) {
Log.i(tag, message)
}
fun flush() {
Log.appenderClose()
}
/**
* 上传log
*/
fun uploadLog(config: UploadConfig) {
//刷新下
Log.appenderFlush(false)
val intent = Intent(getConfig().app, UploadService::class.java)
intent.putExtra("upload", config)
getConfig().app.startService(intent)
}
fun uploadLog(config: UploadConfig,callBack: UploadCallBack) {
//刷新下
Log.appenderFlush(false)
FtpUpload.upload(config,callBack)
}
fun getConfig(): QmLogConfig {
val config: QmLogConfig? = LogInit.getInstance().getConfig()
if (config == null) {
throw IllegalArgumentException("please call LogInit.init() first")
} else {
return config
}
}
//上传log
/***
* filePath 保存logo的路径
*/
fun uploadXog(config: (filePath: String) -> Unit) {
//刷新下
Log.appenderFlush(false)
// final String logPath = SDCARD + "/ptcy/log";
val logPath: String = if (BuildConfig.DEBUG) {
getConfig().logDebugPath
} else {
getConfig().logReleasePath
}
config(logPath)
// val intent = Intent(getConfig().app, UploadService::class.java)
// getConfig().app.startService(intent)
}
}
\ No newline at end of file
package com.qimai.log
import android.content.Context
/**
* 基本参数配置
*/
class QmLogConfig(val app: Context, var logDebugPath: String = app.filesDir.toString() + "/debug/xlog",
var logReleasePath: String = app.filesDir.toString() + "/release/xlog",
var expiredDays: Int = 10) {
}
\ No newline at end of file
package com.qimai.log
interface UploadCallBack {
fun onLoading()
fun onError(e:String)
fun onSuccess()
}
\ No newline at end of file
package com.qimai.log
import android.os.Parcelable
/**
* @tag 文件名字
*/
class UploadConfig(var tag: String): java.io.Serializable {
}
\ No newline at end of file
package com.qimai.log;
import android.app.IntentService;
import android.content.Intent;
import android.os.Environment;
import android.util.Log;
import java.io.File;
import java.util.Iterator;
import java.util.NavigableSet;
import java.util.TreeMap;
import androidx.annotation.Nullable;
/**
* Created by wanglei on 18-7-16.
* <p>
* 根据通知去上传日志
*/
public class UploadService extends IntentService {
public UploadService() {
super("upload");
}
private static final String TAG = "UploadService";
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public UploadService(String name) {
super(name);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
Log.d(TAG, "onHandleIntent: ");
// updateOldOrder();
try {
UploadConfig receiverBean = (UploadConfig) intent.getSerializableExtra("upload");
if (receiverBean != null) {
uploadLog(receiverBean);
}
} catch (Exception e) {
}
}
private void uploadLog(UploadConfig config) {
//DBCopyUtils.copyLogToFTP();
//int requestNum = receiverBean.getNum();
File file = null;
// final String logPath = SDCARD + "/ptcy/log";
final String logPath;
if (BuildConfig.DEBUG) {
logPath = QmLog.INSTANCE.getConfig().getLogDebugPath();
} else {
logPath = QmLog.INSTANCE.getConfig().getLogReleasePath();
}
file = new File(logPath);
if (!file.exists()) {
return;
}
//file.lastModified();
File[] files = convertTimeDesc(file.listFiles());
if (files == null || files.length == 0) {
return;
}
// int maxFile = files.length;
// int endNum = Math.min(maxFile,num);
for (int i = 0; i < files.length; i++) {
File tempFile = files[i];
DBCopyUtils.copyLogToFTP(tempFile,config.getTag());
}
}
private File[] convertTimeDesc(File[] files) {
if (files == null || files.length == 0) {
return null;
}
int endNum = files.length;
File[] filesAll = new File[20];
TreeMap<Long, File> longFileTreeMap = new TreeMap<>();
for (int i = 0; i < files.length; i++) {
Long temp = files[i].lastModified();
if (files[i].exists()) {
longFileTreeMap.put(temp, files[i]);
}
}
NavigableSet<Long> navigableSet = longFileTreeMap.descendingKeySet();
Iterator<Long> iterator = navigableSet.iterator();
int index = 0;
while (iterator.hasNext()) {
File file = longFileTreeMap.get(iterator.next());
Log.d(TAG, "convertTimeDesc: file name= " + file.getName());
filesAll[index] = file;
index++;
if (index >= endNum) {
break;
}
}
return filesAll;
}
}
package com.qimai.log
import android.view.View
/**
* Created by chuxiaoshan on 2021/11/15 20:25
*
* Description:
*/
/**
* 扩展属性参数:用于标记是否可点击
*/
private var <T : View> T.viewClickable: Boolean
get() = if (getTag(1 + 2 shl 24) != null) getTag(1 + 2 shl 24) as Boolean else true
set(value) {
setTag(1 + 2 shl 24, value)
}
/**
* 设置点击监听, 并自定义防抖时间
*
* @receiver T View
* @param time Long 防抖间隔时长 默认300
* @param action Function1<[@kotlin.ParameterName] View, Unit> 点击事件
*/
fun <T : View> T.click(time: Long = 300, action: (T: View) -> Unit) {
setOnClickListener {
if (viewClickable) {
viewClickable = false
action(it)
postDelayed({ viewClickable = true }, time)
}
}
}
\ No newline at end of file
package com.qimai.log.bean
/**
* Created by chuxiaoshan on 2021/11/11 14:46
*
* Description:
*/
data class LogUpBean(
//{
// "c": "hello world!!!!",
// "f": 312,
// "l": 1636363201246,
// "n": "main",
// "i": 2,
// "m": true
//}
//日志内容
var c: String? = null,
//日志类型
var f: Int,
//日志记录时间
var l: Long,
//:线程名称
var n: String? = null,
// 线程id
var i: Int,
//是否是主线程
var m: Boolean
)
\ No newline at end of file
package com.qimai.log.database
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
/**
* 获取数据库操作类
*/
class PushDataBaseVm(application: Application) : AndroidViewModel(application) {
private val mDataBse: PushNotifiInfoDao by lazy {
PushNotifiDataBase.getDatabase(application).pushInfoDao()
}
fun insertPushInfo(notifiInfo: PushNotifiInfo) {
viewModelScope.launch(Dispatchers.IO) {
mDataBse.insert(notifiInfo)
}
}
fun updatePushInfo(msg_id: String, status: Boolean, errorMsg: String? = null) {
viewModelScope.launch(Dispatchers.IO) {
mDataBse.updateStatus(msg_id, status, errorMsg)
}
}
}
\ No newline at end of file
package com.qimai.log.database
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
/**
*
*/
@Database(entities = [PushNotifiInfo::class], version = 1, exportSchema = false)
abstract class PushNotifiDataBase : RoomDatabase() {
abstract fun pushInfoDao(): PushNotifiInfoDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
@Volatile
private var INSTANCE: PushNotifiDataBase? = null
fun getDatabase(context: Context): PushNotifiDataBase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
PushNotifiDataBase::class.java,
"push_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
\ No newline at end of file
package com.qimai.log.database
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "push_info")
data class PushNotifiInfo @JvmOverloads
constructor(
@PrimaryKey @ColumnInfo(name = "msg_id") val msg_id: String, //消息id
@ColumnInfo(name = "status") var status: Boolean, // 状态
@ColumnInfo(name = "error_msg") var errorMsg: String? = null, //错误原因
@ColumnInfo(name = "push_type") val pushType: String, //用的什么推送
@ColumnInfo(name = "rawText") var rawText: String? = null, //原始信息
@ColumnInfo(name = "time") var time: String? = null, //格式化时间
@ColumnInfo(name = "device_id") var deviceId: String? = null, //推送的devicedId
@ColumnInfo(name = "model") var model: String? = null, //手机型号
@ColumnInfo(name = "app_version") var version: String? = null, //app版本
@ColumnInfo(name = "time_stamp") var timeStamp: Long? = null //时间戳
) {
}
\ No newline at end of file
package com.qimai.log.database
import androidx.room.*
import java.sql.Timestamp
/**
* 数据库操作类
*/
@Dao
interface PushNotifiInfoDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(word: PushNotifiInfo)
@Query("update push_info set status= :status ,error_msg = :errorMsg where msg_id=:msg_id")
suspend fun updateStatus(msg_id: String, status: Boolean, errorMsg: String? = null)
//删除过期数据
@Query("delete from push_info where time_stamp < :timestamp")
fun deleteExpiredItems(timestamp: Long)
}
\ No newline at end of file
package com.qimai.log.service
import android.app.IntentService
import android.content.Intent
import com.qimai.log.QmLog
import com.qimai.log.database.PushNotifiDataBase
import com.qimai.log.database.PushNotifiInfoDao
import com.qimai.log.QmLogConfig
/**
* 定时执行任务
*/
class ScheduledExecutionService(name: String = "schedule") : IntentService(name) {
private val mDataBse: PushNotifiInfoDao by lazy {
PushNotifiDataBase.getDatabase(application).pushInfoDao()
}
override fun onHandleIntent(intent: Intent?) {
val totalTime: Long = (QmLog.getConfig().expiredDays * 24 * 60 * 60 * 1000).toLong()
mDataBse.deleteExpiredItems((System.currentTimeMillis() - totalTime))
}
}
\ No newline at end of file
package com.qimai.log.ui
import android.annotation.SuppressLint
import android.graphics.Color
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.blankj.utilcode.util.*
import com.qimai.log.QLog
import com.qimai.log.databinding.ActivityLogUpBinding
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LogUpActivity : AppCompatActivity() {
private lateinit var bind: ActivityLogUpBinding
private val mAdapter by lazy { LogUpAdapter() }
override fun onCreate(savedInstanceState: Bundle?) {
BarUtils.setStatusBarColor(this, Color.WHITE)
BarUtils.setStatusBarLightMode(this, true)
super.onCreate(savedInstanceState)
bind = ActivityLogUpBinding.inflate(layoutInflater)
setContentView(bind.root)
init()
}
@SuppressLint("SetTextI18n")
private fun init() {
val startDate = TimeUtils.getNowString(TimeUtils.getSafeDateFormat("yyyy-MM-dd 00:00:00"))
val endDate = TimeUtils.getNowString(TimeUtils.getSafeDateFormat("yyyy-MM-dd HH:mm:ss"))
bind.etDate.setText("$startDate -- $endDate")
bind.rv.layoutManager = LinearLayoutManager(this)
bind.rv.adapter = mAdapter
bind.btnSearch.setOnClickListener { getLogData() }
getLogData()
}
private fun getLogData() {
lifecycleScope.launch(Dispatchers.IO) {
val timeStr = bind.etDate.text.toString()
val timeSplit = timeStr.split(" -- ")
val startTime = timeSplit[0]
val endTime = timeSplit[1]
QLog.f()
val list = QLog.getLogListByPeriod(startTime, endTime)
withContext(Dispatchers.Main) {
if (list.isNullOrEmpty()) {
ToastUtils.showShort("未查询到日志")
return@withContext
}
KeyboardUtils.hideSoftInput(bind.btnSearch)
bind.etDate.clearFocus()
mAdapter.setNewData(list)
}
}
}
}
\ No newline at end of file
package com.qimai.log.ui
import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.blankj.utilcode.util.ClipboardUtils
import com.blankj.utilcode.util.TimeUtils
import com.blankj.utilcode.util.ToastUtils
import com.qimai.log.R
import com.qimai.log.bean.LogUpBean
/**
* Created by chuxiaoshan on 2021/11/15 14:42
*
* Description: 日志渲染adapter
*/
class LogUpAdapter(
private var dataSet: List<LogUpBean?> = listOf()
) : RecyclerView.Adapter<LogUpAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_log, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = dataSet[position]
val content = "时间:${TimeUtils.millis2String(item?.l ?: 0, "yyyy-MM-dd HH:mm:ss")}\n" +
"类型:${item?.f}\n" +
"线程:${item?.i} ${item?.n}\n" +
"内容:\n${item?.c}\n"
holder.tvContent.text = content
holder.btnCopy.setOnClickListener {
ClipboardUtils.copyText(content)
ToastUtils.showShort("内容已复制到粘贴板")
}
}
override fun getItemCount() = dataSet.size
@SuppressLint("NotifyDataSetChanged")
fun setNewData(data: List<LogUpBean?>) {
if (dataSet === data) return
dataSet = data
notifyDataSetChanged()
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val tvContent: TextView = view.findViewById(R.id.tv_content)
val btnCopy: TextView = view.findViewById(R.id.btn_copy)
}
}
\ No newline at end of file
package com.qimai.log.ui
import android.content.Intent
import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.os.Build
import androidx.constraintlayout.widget.ConstraintLayout
import com.blankj.utilcode.util.*
import com.lzf.easyfloat.EasyFloat
import com.lzf.easyfloat.enums.ShowPattern
import com.lzf.easyfloat.enums.SidePattern
import com.qimai.log.R
import com.qimai.log.click
/**
* Created by chuxiaoshan on 2021/5/27 14:10
*
* Description: 浮框
*/
object LogUpFloatView {
//标识浮框的tag
private const val FLOAT_VIEW_TAG = "tag_float_view"
/**
* 浮框权限判断与申请
*/
private fun requestPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!PermissionUtils.isGrantedDrawOverlays()) {
PermissionUtils.requestDrawOverlays(object :
PermissionUtils.SimpleCallback {
override fun onGranted() {
LogUtils.d("---1111--->onGranted")
initFloat()
}
override fun onDenied() {
LogUtils.d("---1111--->onDenied")
ToastUtils.showShort("浮框权限未打开")
}
})
} else {
initFloat()
LogUtils.d("---1111--->浮框权限本来就是打开的")
}
} else {
LogUtils.d("---1111--->低版本")
initFloat()
}
}
private fun initFloat() {
try {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) {
EasyFloat.dismiss(FLOAT_VIEW_TAG)
}
EasyFloat.with(Utils.getApp())
.setTag(FLOAT_VIEW_TAG)
.setShowPattern(ShowPattern.FOREGROUND)
.setLocation(
ScreenUtils.getScreenWidth() - ConvertUtils.dp2px(58f),
ScreenUtils.getScreenHeight() - ConvertUtils.dp2px(200f)
)
.setSidePattern(SidePattern.RESULT_HORIZONTAL)
.setImmersionStatusBar(true)
.setFilter(LogUpActivity::class.java)
.setLayout(R.layout.float_logup) {
it.findViewById<ConstraintLayout>(R.id.cl_float_view).click {
Utils.getApp().startActivity(Intent(
Utils.getApp(),
LogUpActivity::class.java
).apply {
flags = FLAG_ACTIVITY_NEW_TASK
})
}
}.show()
} catch (e: Exception) {
LogUtils.e("---e--->${e.printStackTrace()}")
}
}
/**
* 初始化并显示全局浮框
*/
fun show() {
requestPermission()
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#ffee58" />
<corners android:radius="100dp" />
</shape>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<EditText
android:id="@+id/et_date"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginVertical="16dp"
android:layout_marginLeft="16dp"
android:text="2021-11-11 15:21:56 -- 2021-11-11 15:34:56"
android:textSize="14sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/btn_search"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_search"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="16dp"
android:text="查询"
app:layout_constraintBottom_toBottomOf="@+id/et_date"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@+id/et_date" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/et_date"
tools:listitem="@layout/item_log" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cl_float_view"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_margin="4dp"
android:background="@drawable/circle_log"
android:clickable="true"
android:clipToPadding="false"
android:elevation="2dp"
android:focusable="true">
<TextView
android:id="@+id/tv_clear_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LogUp"
android:textColor="#666666"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:background="@color/white"
android:elevation="2dp">
<TextView
android:id="@+id/tv_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:padding="16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="时间:2021-11-15 12:34:56\n类型:错误\n线程:123 Thread-5\n内容:\ngheioirnerinewrpewrgheioirnerinewrpewrgheioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewrioirnerinewrpewr" />
<TextView
android:id="@+id/btn_copy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"
android:text="复制"
android:textColor="#FF9919"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
package com.bigkoo.pickerview.adapter;
import com.contrarywind.adapter.WheelAdapter;
import java.util.List;
/**
* The simple Array wheel adapter
* @param <T> the element type
*/
public class ArrayWheelAdapter<T> implements WheelAdapter {
// items
private List<T> items;
/**
* Constructor
* @param items the items
*/
public ArrayWheelAdapter(List<T> items) {
this.items = items;
}
@Override
public Object getItem(int index) {
if (index >= 0 && index < items.size()) {
return items.get(index);
}
return "";
}
@Override
public int getItemsCount() {
return items.size();
}
@Override
public int indexOf(Object o){
return items.indexOf(o);
}
}
package com.bigkoo.pickerview.adapter;
import com.contrarywind.adapter.WheelAdapter;
import java.util.List;
/**
* The simple Array wheel adapter
* @param <T> the element type
*/
public class ArrayWheelAdapter<T> implements WheelAdapter {
// items
private List<T> items;
/**
* Constructor
* @param items the items
*/
public ArrayWheelAdapter(List<T> items) {
this.items = items;
}
@Override
public Object getItem(int index) {
if (index >= 0 && index < items.size()) {
return items.get(index);
}
return "";
}
@Override
public int getItemsCount() {
return items.size();
}
@Override
public int indexOf(Object o){
return items.indexOf(o);
}
}
package com.bigkoo.pickerview.adapter;
import com.contrarywind.adapter.WheelAdapter;
/**
* Numeric Wheel adapter.
*/
public class NumericWheelAdapter implements WheelAdapter {
private int minValue;
private int maxValue;
/**
* Constructor
* @param minValue the wheel min value
* @param maxValue the wheel max value
*/
public NumericWheelAdapter(int minValue, int maxValue) {
this.minValue = minValue;
this.maxValue = maxValue;
}
@Override
public Object getItem(int index) {
if (index >= 0 && index < getItemsCount()) {
int value = minValue + index;
return value;
}
return 0;
}
@Override
public int getItemsCount() {
return maxValue - minValue + 1;
}
@Override
public int indexOf(Object o){
try {
return (int)o - minValue;
} catch (Exception e) {
return -1;
}
}
}
package com.bigkoo.pickerview.adapter;
import com.contrarywind.adapter.WheelAdapter;
/**
* Numeric Wheel adapter.
*/
public class NumericWheelAdapter implements WheelAdapter {
private int minValue;
private int maxValue;
/**
* Constructor
* @param minValue the wheel min value
* @param maxValue the wheel max value
*/
public NumericWheelAdapter(int minValue, int maxValue) {
this.minValue = minValue;
this.maxValue = maxValue;
}
@Override
public Object getItem(int index) {
if (index >= 0 && index < getItemsCount()) {
int value = minValue + index;
return value;
}
return 0;
}
@Override
public int getItemsCount() {
return maxValue - minValue + 1;
}
@Override
public int indexOf(Object o){
try {
return (int)o - minValue;
} catch (Exception e) {
return -1;
}
}
}
This diff is collapsed.
File mode changed from 100755 to 100644
File mode changed from 100755 to 100644
......@@ -51,7 +51,7 @@ repositories {
}
group 'com.qmai.android.print'
version '1.1.45-SNAPSHOT'
version '1.1.75-SNAPSHOT'
gradlePublish {
......
package zs.qimai.com.printer2.callback
import zs.qimai.com.printer2.PrintDeviceStatus
/**
* @author: leiwang
* @time: 2021/12/9 3:22 下午
* @desc:
*/
interface PrintStatusChangeCallBack {
fun onChange(printDeviceStatus: PrintDeviceStatus)
}
\ No newline at end of file
......@@ -73,7 +73,7 @@ open abstract class PrintExecutor {
}
}
private fun goToPrint(it: DeviceManager, data: ByteArray) = GlobalScope.async(Dispatchers.IO) {
fun goToPrint(it: DeviceManager, data: ByteArray) = GlobalScope.async(Dispatchers.IO) {
it.writeData(data)
}
}
\ No newline at end of file
......@@ -7,6 +7,9 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.util.Log
import zs.qimai.com.printer2.PrintDeviceStatus
import zs.qimai.com.printer2.PrintDeviceStatus.Companion.BLUETOOTH_CLOSED_RECEIVER
import zs.qimai.com.printer2.manager.BlueDeviceManager
import zs.qimai.com.printer2.manager.DeviceManagerUtils
class BlueToothStatusReceiver : BroadcastReceiver() {
......@@ -20,10 +23,26 @@ class BlueToothStatusReceiver : BroadcastReceiver() {
}
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
var bluetoothDevice = intent.getParcelableExtra<BluetoothDevice?>(EXTRA_DEVICE)
bluetoothDevice?.let {
val bluetoothDevice = intent.getParcelableExtra<BluetoothDevice?>(EXTRA_DEVICE)
bluetoothDevice?.let { bluetoothDevice ->
//如果检测到设备已经断开连接,就从保存的集合中删除
DeviceManagerUtils.getInstance().removeBtDevice(it)
var deviceManager = DeviceManagerUtils.getInstance()
.getDevice { it.deviceId == bluetoothDevice.address }
if (deviceManager == null) {
deviceManager = BlueDeviceManager().apply {
address = bluetoothDevice.address
name = bluetoothDevice.name
}
}
val printDeviceStatus = PrintDeviceStatus(
BLUETOOTH_CLOSED_RECEIVER, deviceManager, Log.getStackTraceString(
Throwable()
)
)
DeviceManagerUtils.getInstance()
.notifyPrintStatusChangeCallBack(printDeviceStatus)
DeviceManagerUtils.getInstance().removeBtDevice(bluetoothDevice)
}
}
BluetoothAdapter.ACTION_STATE_CHANGED -> {
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment