架构
Log框架分析:
1.全局配置类HiLogConfig
2.全局管理类HiLogManager
3. 对外暴露的方法HiLog
4.Log的类型HiLogType
5.格式化接口HiLogFormatter
6.线程格式化HiThreadFormatter ,堆栈格式化HiStackTraceFormatter 实现格式化接口 HiLogFormatter
7.Log打印器接口HiLogPrinter
8.日志打印器HiConsolePrinter , 悬浮窗视图打印器HiViewPrinter
9.HiLogMo模型类
一配置Config配置类
package com.junbao.library.log;
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 1:42 下午
* desc : log配置类
*/
public abstract class HiLogConfig {
//一行最大输出长度
static int MAX_LEN= 512;
HiStackTraceFormatter();
/**
* 配置全局tag
* @return
*/
public String getGlobalTag(){
return "HiLog";
}
/**
* 配置全局启用
* @return
*/
public boolean enable(){
return true;
}
/**
* 是否包含线程信息
* @return
*/
public boolean includeThread(){
return false;
}
/**
* 打印堆栈信息级别 深度
* @return
*/
public int stackTraceDepth(){
return 5;
}
}
二.配置全局管理类
package com.junbao.library.log;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 1:42 下午
* desc : log管理类
*/
public class HiLogManager {
private static HiLogManager instance;
private HiLogConfig config;
private HiLogManager(HiLogConfig config ) {
this.config = config;
}
public static HiLogManager getInstance(){
return instance;
}
public static void init(@NonNull HiLogConfig config){
instance = new HiLogManager(config);
}
/**
* 获取config对象
* @return
*/
public HiLogConfig getConfig(){
return config;
}
}
三.定义日志打印类型
public class HiLogType {
@IntDef({V,D,I,W,E,A})
//保留在源码级别
@Retention(RetentionPolicy.SOURCE)
public @interface TYPE{}
public final static int V = Log.VERBOSE;
public final static int D = Log.DEBUG;
public final static int I = Log.INFO;
public final static int W = Log.WARN;
public final static int E = Log.ERROR;
public final static int A = Log.ASSERT;
}
四.配置HiLog对外暴露的方法
package com.junbao.library.log;
import android.util.Log;
import androidx.annotation.NonNull;
import java.util.Arrays;
import java.util.List;
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 1:41 下午
* desc : Log
*/
public class HiLog {
private static final String HI_LOG_PACKAGE;
static {
String className = HiLog.class.getName();
HI_LOG_PACKAGE = className.substring(0 , className.lastIndexOf(".")+1);
}
public static void v(Object... contents) {
log(HiLogType.V, contents);
}
public static void vt(String tag, Object... contents) {
log(HiLogType.V, tag, contents);
}
public static void d(Object... contents) {
log(HiLogType.D, contents);
}
public static void dt(String tag, Object... contents) {
log(HiLogType.D, tag, contents);
}
public static void i(Object... contents) {
log(HiLogType.I, contents);
}
public static void it(String tag, Object... contents) {
log(HiLogType.I, tag, contents);
}
public static void w(Object... contents) {
log(HiLogType.W, contents);
}
public static void wt(String tag, Object... contents) {
log(HiLogType.W, tag, contents);
}
public static void e(Object... contents) {
log(HiLogType.E, contents);
}
public static void et(String tag, Object... contents) {
log(HiLogType.E, tag, contents);
}
public static void a(Object... contents) {
log(HiLogType.A, contents);
}
public static void at(String tag, Object... contents) {
log(HiLogType.A, tag, contents);
}
public static void log(@HiLogType.TYPE int type, Object... contents) {
log(type, HiLogManager.getInstance().getConfig().getGlobalTag(), contents);
}
public static void log(@HiLogType.TYPE int type, String tag, Object... contents) {
log(HiLogManager.getInstance().getConfig(), type, tag, contents);
}
public static void log(HiLogConfig config, @HiLogType.TYPE int type, String tag, Object... contents) {
//如果false ,则代表 不输出
if (!config.enable()){
return;
}
StringBuilder sb = new StringBuilder();
//1.我们需要先对日志进行格式化
String body = parseBody(contents , config);
sb.append(body);
Log.println(type , tag , sb.toString());
}
/**
* 格式化日志
* @param contents
* @param config
* @return
*/
private static String parseBody(@NonNull Object[] contents , @NonNull HiLogConfig config){
StringBuilder sb = new StringBuilder();
for (Object content : contents) {
sb.append(content.toString()).append(";");
}
if (sb.length() > 0 ){
sb.deleteCharAt(sb.length()-1);
}
return sb.toString();
}
}
测试
package com.junbao.androidlibrary
import android.app.Application
import com.junbao.library.log.HiLogConfig
import com.junbao.library.log.HiLogManager
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 2:04 下午
* desc :
*/
class MyApplication:Application() {
override fun onCreate() {
super.onCreate()
HiLogManager.init(object : HiLogConfig() {
override fun getGlobalTag(): String {
return "liuyi"
}
override fun enable(): Boolean {
return true
}
})
}
}
package com.junbao.androidlibrary
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import com.junbao.library.log.*
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 1:38 下午
* desc :
*/
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.tv_log).setOnClickListener {
printLog()
}
}
private fun printLog() {
HiLog.e("Majunbao Test Log")
}
}
五.定义打印器
package com.junbao.library.log;
import androidx.annotation.NonNull;
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 2:07 下午
* desc : log输出接口
*/
public interface HiLogPrinter {
void print(@NonNull HiLogConfig config , int level , String tag , @NonNull String printString);
}
实现日志打印器
package com.junbao.library.log;
import static com.junbao.library.log.HiLogConfig.MAX_LEN;
import android.util.Log;
import androidx.annotation.NonNull;
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 2:08 下午
* desc : 日志打印器
*/
public class HiConsolePrinter implements HiLogPrinter{
/**
* 分析: 获取日志长度 ,进行分行打印
* HiLogConfig中配置下最大一行显示长度
* @param config
* @param level
* @param tag
* @param printString
*/
@Override
public void print(@NonNull HiLogConfig config, int level, String tag, @NonNull String printString) {
//获取日志长度
int len = printString.length();
//行数 = 总长度 / 一行最大长度
int rowCount = len / MAX_LEN;
//行数不为0时
if (rowCount > 0 ){
StringBuilder sb = new StringBuilder();
int index = 0 ;
//遍历行数
for (int i = 0; i < rowCount; i++) {
sb.append(printString.substring(index , index+MAX_LEN));
index = i + MAX_LEN;
}
if (index!=len){
sb.append(printString.substring(index, len));
}
}
//行数为0 ,输出剩余部分
Log.println(level , tag , printString);
}
}
六.定义格式化接口
package com.junbao.library.log;
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 2:54 下午
* desc : 日志解析器
*/
public interface HiLogFormatter <T>{
String format(T data);
}
实现线程格式化 ,和 堆栈信息格式化
package com.junbao.library.log;
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 2:55 下午
* desc : 线程格式化
*/
public class HiThreadFormatter implements HiLogFormatter<Thread>{
@Override
public String format(Thread data) {
return "Thread:"+data.getName();
}
}
创建堆栈信息工具类
package com.junbao.library.log;
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 4:10 下午
* desc : 堆栈打印工具类
*/
public class HiStackTraceUtil {
public static StackTraceElement[] getCroppedRealStackTrack(StackTraceElement[] stackTrace, String ignorePackage, int maxDepth) {
return cropStackTrace(getRealStackTrack(stackTrace, ignorePackage), maxDepth);
}
private static StackTraceElement[] getRealStackTrack(StackTraceElement[] stackTrace, String ignorePackage) {
int ignoreDepth = 0;
int allDepth = stackTrace.length;
String className;
for (int i = allDepth - 1 ; i >= 0 ; i--){
//获取堆栈包名
className = stackTrace[i].getClassName();
if (ignorePackage !=null && className.startsWith(ignorePackage)){
ignoreDepth= i+1;
break;
}
}
int realDepth = allDepth - ignoreDepth;
StackTraceElement[] realStack = new StackTraceElement[realDepth];
System.arraycopy(stackTrace, ignoreDepth, realStack, 0, realDepth);
return realStack;
}
/**
*
* @param callStack
* @param maxDepth
* @return
*/
public static StackTraceElement[] cropStackTrace(StackTraceElement[] callStack, int maxDepth) {
//获取堆栈的 长度
int realDepth = callStack.length;
if (maxDepth > 0) {
//获取最小深度
realDepth = Math.min(maxDepth, realDepth);
}
//创建新的堆栈
StackTraceElement[] realStack = new StackTraceElement[realDepth];
//copy
System.arraycopy(callStack, 0, realStack, 0, realDepth);
return realStack;
}
}
package com.junbao.library.log;
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 3:53 下午
* desc : 堆栈信息格式化
*/
public class HiStackTraceFormatter implements HiLogFormatter<StackTraceElement[]>{
@Override
public String format(StackTraceElement[] stackTrace) {
StringBuilder sb = new StringBuilder(128);
if (stackTrace == null || stackTrace.length == 0 ){
return null;
}else if (stackTrace.length == 1){
return "\t- "+stackTrace[0].toString();
}else {
//
for (int i = 0, len = stackTrace.length; i < len ; i++) {
if (i == 0 ){
sb.append("stackTrace: \n");
}
// 0!=4
// 1!=4
// 2!=4
// 3!=4
if (i != len-1){
sb.append("\t├ ");
sb.append(stackTrace[i].toString());
sb.append("\n");
}else {
sb.append("\t└ ");
sb.append(stackTrace[i].toString());
}
}
}
return sb.toString();
}
}
HiLogConfig创建静态
static HiThreadFormatter HI_THREAD_FORMATTER = new HiThreadFormatter();
static HiStackTraceFormatter HI_STACK_TRACE_FORMATTER = new HiStackTraceFormatter();
HiLog中输出打印
public static void log(HiLogConfig config, @HiLogType.TYPE int type, String tag, Object... contents) {
//如果false ,则代表 不输出
if (!config.enable()){
return;
}
StringBuilder sb = new StringBuilder();
if (config.includeThread()){
String threadInfo = HiLogConfig.HI_THREAD_FORMATTER.format(Thread.currentThread());
sb.append(threadInfo).append("\n");
}
if (config.stackTraceDepth() > 0 ){
String stackTrace = HiLogConfig.HI_STACK_TRACE_FORMATTER.format(
HiStackTraceUtil.getCroppedRealStackTrack(new Throwable().getStackTrace() , HI_LOG_PACKAGE , config.stackTraceDepth())
);
sb.append(stackTrace).append("\n");
}
//1.我们需要先对日志进行格式化
String body = parseBody(contents , config);
sb.append(body);
Log.println(type , tag , sb.toString());
}
配置日志打印器
HiLogConfig中添加
/**
* 打印器集合
* @return
*/
public HiLogPrinter[] printers() {
return null;
}
/**
* 配置Gson解析器
* @return
*/
public JsonParser injectJsonParser() {
return null;
}
public interface JsonParser{
String toJson(Object src);
}
HiLog中更改代码
public static void log(HiLogConfig config, @HiLogType.TYPE int type, String tag, Object... contents) {
//如果false ,则代表 不输出
if (!config.enable()){
return;
}
StringBuilder sb = new StringBuilder();
if (config.includeThread()){
String threadInfo = HiLogConfig.HI_THREAD_FORMATTER.format(Thread.currentThread());
sb.append(threadInfo).append("\n");
}
if (config.stackTraceDepth() > 0 ){
String stackTrace = HiLogConfig.HI_STACK_TRACE_FORMATTER.format(
HiStackTraceUtil.getCroppedRealStackTrack(new Throwable().getStackTrace() , HI_LOG_PACKAGE , config.stackTraceDepth())
);
sb.append(stackTrace).append("\n");
}
//1.我们需要先对日志进行格式化
String body = parseBody(contents , config);
sb.append(body);
List<HiLogPrinter> printers = config.printers()!=null ? Arrays.asList(config.printers()) : HiLogManager.getInstance().getPrinters();
for (HiLogPrinter printer : printers) {
printer.print(config , type , tag , sb.toString());
}
// Log.println(type , tag , sb.toString());
}
/**
* 格式化日志
* @param contents
* @param config
* @return
*/
private static String parseBody(@NonNull Object[] contents , @NonNull HiLogConfig config){
if (config.injectJsonParser()!=null){
if (contents.length == 1 && contents[0] instanceof String){
return (String) contents[0];
}
return config.injectJsonParser().toJson(contents);
}
StringBuilder sb = new StringBuilder();
for (Object content : contents) {
sb.append(content.toString()).append(";");
}
if (sb.length() > 0 ){
sb.deleteCharAt(sb.length()-1);
}
return sb.toString();
}
MyApplication中配置
class MyApplication:Application() {
override fun onCreate() {
super.onCreate()
HiLogManager.init(object : HiLogConfig() {
override fun getGlobalTag(): String {
return "liuyi"
}
override fun enable(): Boolean {
return true
}
override fun includeThread(): Boolean {
return true
}
override fun injectJsonParser(): JsonParser {
return JsonParser {
src -> Gson().toJson(src)
}
}
} ,HiConsolePrinter())
}
}
测试
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<View>(R.id.tv_log).setOnClickListener {
printLog()
}
}
private fun printLog() {
HiLog.e("Majunbao Test Log")
Thread{
HiLog.e("线程测试")
}.start()
HiLog.log(object : HiLogConfig() {
override fun includeThread(): Boolean {
return false
}
}, HiLogType.E, "---", "5566")
HiLog.a("9900")
}
}
扩展
实现悬浮窗,可在页面上看到控制台信息
1.定义Log模型类
package com.junbao.library.log;
import java.text.SimpleDateFormat;
import java.util.Locale;
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 6:37 下午
* desc : log数据类
*/
public class HiLogMo {
private static SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm:ss", Locale.CHINA);
public long timeMillis;
public int level;
public String tag;
public String log;
public HiLogMo(long timeMillis, int level, String tag, String log) {
this.timeMillis = timeMillis;
this.level = level;
this.tag = tag;
this.log = log;
}
public String flattenedLog() {
return getFlattened() + "\n" + log;
}
public String getFlattened() {
return format(timeMillis) + '|' + level + '|' + tag + "|:";
}
private String format(long timeMillis) {
return sdf.format(timeMillis);
}
}
2.创建HiViewPrinter类
package com.junbao.library.log;
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.junbao.library.R;
import java.util.ArrayList;
import java.util.List;
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 6:34 下午
* desc : view打印器
*/
public class HiViewPrinter implements HiLogPrinter{
private final LogAdapter adapter;
private final HiViewPrinterProvider viewPrinterProvider;
private final RecyclerView recyclerView;
public HiViewPrinter(Activity activity) {
FrameLayout rootView = activity.findViewById(android.R.id.content);
recyclerView = new RecyclerView(activity);
recyclerView.setLayoutManager(new LinearLayoutManager(recyclerView.getContext()));
adapter = new LogAdapter(LayoutInflater.from(recyclerView.getContext()));
recyclerView.setAdapter(adapter);
viewPrinterProvider = new HiViewPrinterProvider(rootView, recyclerView);
}
/**
* 获取
* @return
*/
@NonNull
public HiViewPrinterProvider getViewPrinterProvider() {
return viewPrinterProvider;
}
@Override
public void print(@NonNull HiLogConfig config, int level, String tag, @NonNull String printString) {
adapter.addItem(new HiLogMo(System.currentTimeMillis() , level , tag , printString));
// 滚动到对应的位置
recyclerView.smoothScrollToPosition(adapter.getItemCount() - 1);
}
public static class LogAdapter extends RecyclerView.Adapter<LogViewHolder>{
private final LayoutInflater inflater;
List<HiLogMo> logs = new ArrayList<>();
/**
* 添加新数据
* @param log
*/
public void addItem(HiLogMo log){
logs.add(log);
notifyItemInserted(logs.size()-1);
}
public LogAdapter(LayoutInflater inflater) {
this.inflater = inflater;
}
@NonNull
@Override
public LogViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = inflater.inflate(R.layout.hilog_item , parent , false);
return new LogViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull LogViewHolder holder, int position) {
HiLogMo logItem = logs.get(position);
int color = getHighlightColor(logItem.level);
holder.tagView.setTextColor(color);
holder.messageView.setTextColor(color);
holder.tagView.setText(logItem.getFlattened());
holder.messageView.setText(logItem.log);
}
@Override
public int getItemCount() {
return logs==null ? 0 : logs.size();
}
/**
* 跟进log级别获取不同的高了颜色
*
* @param logLevel log 级别
* @return 高亮的颜色
*/
private int getHighlightColor(int logLevel) {
int highlight;
switch (logLevel) {
case HiLogType.V:
highlight = 0xffbbbbbb;
break;
case HiLogType.D:
highlight = 0xffffffff;
break;
case HiLogType.I:
highlight = 0xff6a8759;
break;
case HiLogType.W:
highlight = 0xffbbb529;
break;
case HiLogType.E:
highlight = 0xffff6b68;
break;
default:
highlight = 0xffffff00;
break;
}
return highlight;
}
}
public static class LogViewHolder extends RecyclerView.ViewHolder{
TextView tagView;
TextView messageView;
public LogViewHolder(@NonNull View itemView) {
super(itemView);
tagView = itemView.findViewById(R.id.tag);
messageView = itemView.findViewById(R.id.message);
}
}
}
package com.junbao.library.log;
import android.graphics.Color;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 6:41 下午
* desc : logcat悬浮实现
*/
public class HiViewPrinterProvider {
private FrameLayout rootView;
private RecyclerView recyclerView;
private View floatingView;
private boolean isOpen;
private FrameLayout logView;
public HiViewPrinterProvider(FrameLayout rootView, RecyclerView recyclerView) {
this.rootView = rootView;
this.recyclerView = recyclerView;
}
private static final String TAG_FLOATING_VIEW = "TAG_FLOATING_VIEW";
private static final String TAG_LOG_VIEW = "TAG_LOG_VIEW";
/**
* 展示悬浮
*/
public void showFloatingView(){
if (rootView.findViewWithTag(TAG_FLOATING_VIEW) != null){
return;
}
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.BOTTOM | Gravity.END;
View floatingView = getFloatingView();
floatingView.setTag(TAG_FLOATING_VIEW);
floatingView.setBackgroundColor(Color.BLACK);
floatingView.setAlpha(0.8f);
params.bottomMargin = HiDisplayUtil.dp2px(100, rootView.getResources());
rootView.addView(floatingView , params);
}
/**
* 绘制悬浮
* @return
*/
private View getFloatingView() {
if (floatingView !=null){
return floatingView;
}
TextView textView = new TextView(rootView.getContext());
textView.setOnClickListener(view -> {
if (!isOpen){
showLogView();
}
});
textView.setText("HiLog");
return floatingView = textView;
}
/**
* 展示logCat
*/
private void showLogView() {
if (rootView.findViewWithTag(TAG_LOG_VIEW) !=null){
return;
}
FrameLayout.LayoutParams params =
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, HiDisplayUtil.dp2px(160, rootView.getResources()));
params.gravity = Gravity.BOTTOM;
View logView = genLogView();
logView.setTag(TAG_LOG_VIEW);
rootView.addView(genLogView() , params);
isOpen = true;
}
/**
* 绘制loacat
* @return
*/
private View genLogView() {
if (logView!=null){
return logView;
}
FrameLayout logView = new FrameLayout(rootView.getContext());
logView.setBackgroundColor(Color.BLACK);
logView.addView(recyclerView);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.END;
TextView closeView = new TextView(rootView.getContext());
closeView.setOnClickListener(v ->{
closeLogView();
});
closeView.setText("Close");
logView.addView(closeView , params);
return this.logView = logView;
}
/**
* 关闭logcat窗口
*/
private void closeLogView() {
isOpen = false;
rootView.removeView(genLogView());
}
}
使用
package com.junbao.androidlibrary
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import com.junbao.library.log.*
/**
* author : Majunbao
* github : https://github.com/MaJunBaox
* time : 2022/5/13 1:38 下午
* desc :
*/
class MainActivity : AppCompatActivity() {
var viewPrinter : HiViewPrinter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewPrinter = HiViewPrinter(this)
findViewById<View>(R.id.tv_log).setOnClickListener {
printLog()
}
viewPrinter!!.viewPrinterProvider.showFloatingView()
}
private fun printLog() {
HiLogManager.getInstance().addPrinter(viewPrinter)
HiLog.e("Majunbao Test Log")
Thread{
HiLog.e("线程测试")
}.start()
HiLog.log(object : HiLogConfig() {
override fun includeThread(): Boolean {
return false
}
}, HiLogType.E, "---", "5566")
HiLog.a("9900")
}
}
到此Log框架封装完毕
码云仓库:AndroidLibrary: 安卓通用组件库