Android之APP层使用adb命令抓取系统日志

本文介绍了如何在Android应用层通过adb命令抓取系统日志,并将其保存到U盘。关键步骤包括修改相关代码如build.gradle、AndroidManifest.xml等,以及使用系统签名对APK进行签名,因为未签名的APP只能获取到/dev/log/main下的无用日志,而签名后才能访问到包含错误信息的/dev/log/system路径下的日志。在RK3128上的Android 4.4系统测试中,抓取的日志范围约为3个多小时。

Android之APP层使用adb命令抓取系统日志之保存到U盘

代码如下:
build.gradle

plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 28
    buildToolsVersion "29.0.2"

    defaultConfig {
        applicationId "com.yml.savelog"
        minSdkVersion 16
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    signingConfigs {
        lycoo_release {
            keyAlias 'lycoo'
            keyPassword 'lycoomylive'
            storeFile file('E:/Emylive/KeyStore/lycoo.keystore')
            storePassword 'lycoomylive'
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            signingConfig signingConfigs.lycoo_release
        }
    }

    lintOptions {
        checkReleaseBuilds false
        // Or, if you prefer, you can continue to check for errors in release builds,
        // but continue the build even when errors are found:
        abortOnError false
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    android.applicationVariants.all { variant ->
        variant.outputs.all { output ->
            def outputFile = output.outputFile
            if (outputFile != null && outputFile.name.endsWith('.apk')) {
                def date = new Date().format("yyyyMMdd", TimeZone.getTimeZone("GMT+08"))
                if (outputFile.name.contains("-release")) {
                    outputFileName = outputFile.name.replace("-release", "-${variant.versionName}-${date}")
                } else if (outputFile.name.contains("-debug")) {
                    outputFileName = outputFile.name.replace("-debug", "-${variant.versionName}-${date}-debug")
                }
            }
        }
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'com.google.android.material:material:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation files('libs\\commons-lang3-3.5.jar')
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    // rxjava2
    implementation 'io.reactivex.rxjava2:rxjava:2.2.4'
    // rxandroid
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
}

AndroidManifest.xml

<?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.yml.savelog">

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.GET_TASKS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-feature android:name="android.hardware.usb.host"/>

    <uses-permission android:name="android.permission.DELETE_CACHE_FILES"
        tools:ignore="ProtectedPermissions" />
    <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE"
        tools:ignore="ProtectedPermissions" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_LOGS"
        tools:ignore="ProtectedPermissions"
        />
    <application
        android:name=".BaseApplication"
        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/Theme.YMLSaveLog">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".MediaReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.lycoo.action.MEDIA_MOUNTED" />
                <action android:name="android.intent.action.MEDIA_SCANNER_SCAN_FILE" />
                <action android:name="android.intent.action.MEDIA_MOUNTED" />
                <action android:name="android.intent.action.MEDIA_REMOVED" />
                <action android:name="android.intent.action.MEDIA_EJECT" />
                <!--
                <action android:name="android.intent.action.MEDIA_UNMOUNTED"/>
                <action android:name="android.intent.action.MEDIA_BAD_REMOVAL"/>
                -->
                <data android:scheme="file" />
            </intent-filter>
        </receiver>
    </application>

</manifest>

RxBus.java

package com.yml.savelog;

import java.util.HashMap;
import java.util.Map;

import io.reactivex.BackpressureStrategy;
import io.reactivex.Flowable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import io.reactivex.subjects.PublishSubject;
import io.reactivex.subjects.Subject;

/**
 * xxx
 *
 * Created by lancy on 2017/12/20
 */
public class RxBus {
    private static final String TAG = RxBus.class.getSimpleName();

    private Map<String, CompositeDisposable> mDisposableMap;
    private static volatile RxBus mRxBus;
    private final Subject<Object> mSubject;

    public static RxBus getInstance() {
        if (mRxBus == null) {
            synchronized (RxBus.class) {
                if (mRxBus == null) {
                    mRxBus = new RxBus();
                }
            }
        }
        return mRxBus;
    }

    private RxBus() {
        mSubject = PublishSubject.create().toSerialized();
    }

    public void post(Object o) {
        mSubject.onNext(o);
    }

    public boolean hasObservers() {
        return mSubject.hasObservers();
    }

    public void addDisposable(Object obj, Disposable disposable) {
        if (mDisposableMap == null) {
            mDisposableMap = new HashMap<>();
        }
        String key = obj.getClass().getName();
        if (mDisposableMap.get(key) != null) {
            mDisposableMap.get(key).add(disposable);
        } else {
            //一次性容器,可以持有多个并提供 添加和移除。
            CompositeDisposable disposables = new CompositeDisposable();
            disposables.add(disposable);
            mDisposableMap.put(key, disposables);
        }
    }

    public <T> Flowable<T> getObservable(Class<T> type) {
        return mSubject
                .toFlowable(BackpressureStrategy.BUFFER)
                .ofType(type);
    }

    public <T> Disposable registerSubscribe(Class<T> type, Consumer<T> next, Consumer<Throwable> error) {
        return getObservable(type)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(next, error);
    }

    public void unRegisterSubscribe(Object o) {
        if (mDisposableMap == null) {
            return;
        }

        String key = o.getClass().getName();
        if (!mDisposableMap.containsKey(key)) {
            return;
        }
        if (mDisposableMap.get(key) != null) {
            mDisposableMap.get(key).dispose();
        }

        mDisposableMap.remove(key);
    }

}

RootCmd.java

package com.yml.savelog;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import android.util.Log;

/**
 * Android运行linux命令
 */
public final class RootCmd {
    private static final String TAG = "RootCmd";
    private static boolean mHaveRoot = false;
    /**
     *   判断机器Android是否已经root,即是否获取root权限
     */
    public static boolean haveRoot() {
        if (!mHaveRoot) {
            int ret = execRootCmdSilent("echo test"); // 通过执行测试命令来检测
            if (ret != -1) {
                Log.i(TAG, "have root!");
                mHaveRoot = true;
            } else {
                Log.i(TAG, "not root!");
            }
        } else {
            Log.i(TAG, "mHaveRoot = true, have root!");
        }
        return mHaveRoot;
    }

    /**
     * 执行命令并且输出结果
     */
    public static String execRootCmd(String cmd) {
        String result = "";
        DataOutputStream dos = null;
        DataInputStream dis = null;

        try {
            Process p = Runtime.getRuntime().exec("su");// 经过Root处理的android系统即有su命令
            dos = new DataOutputStream(p.getOutputStream());
            dis = new DataInputStream(p.getInputStream());

//            Log.i(TAG, cmd);
            dos.writeBytes(cmd + "\n");
            dos.flush();
            dos.writeBytes("exit\n");
            dos.flush();
            String line = null;
            while ((line = dis.readLine()) != null) {
//                Log.d("result", line);
                result += line;
            }
            p.waitFor();
        } catch (Exception e) {
//            System.out.println("liu::::    报错");
            e.printStackTrace();
        } finally {
            if (dos != null) {
                try {
                    dos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (dis != null) {
                try {
                    dis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }

    /**
     * 执行命令但不关注结果输出
     */
    public static int execRootCmdSilent(String cmd) {
        int result = -1;
        DataOutputStream dos = null;

        try {
            Process p = Runtime.getRuntime().exec("su");
            dos = new DataOutputStream(p.getOutputStream());

            Log.i(TAG, cmd);
            dos.writeBytes(cmd + "\n");
            dos.flush();
            dos.writeBytes("exit\n");
            dos.flush();
            p.waitFor();
            result = p.exitValue();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (dos != null) {
                try {
                    dos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }
}

MediaReceiver.java

package com.yml.savelog;

import android.annotation.SuppressLint;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;

import com.yml.savelog.util.ApplicationUtils;
import com.yml.savelog.util.LogUtils;
import com.yml.savelog.util.SystemPropertiesUtils;

import org.apache.commons.lang3.StringUtils;

/**
 * 外设挂载卸载广播接受者
 */
public class MediaReceiver extends BroadcastReceiver {
    private static final String TAG = MediaReceiver.class.getSimpleName();

    @SuppressWarnings("unchecked")
    @SuppressLint("WrongConstant")
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        String device = intent.getDataString();
        LogUtils.debug(TAG, "action : " + action + ", device : " + device);
        System.out.println("MediaReceiver      device="+device);
        if (StringUtils.isEmpty(action) || StringUtils.isEmpty(device)) {
            return;
        }

        if (device.contains(":///")) {
            device = device.substring(device.indexOf(":///") + 3).trim();   // 截取设备真实路径
        }

        RxBus.getInstance().post(new MediaChangedEvent(action, device));    // 发送外设改变通知


    }
}

MediaChangedEvent

package com.yml.savelog;

/**
 * 外设改变事件
 *
 * Created by lancy on 2018/1/31
 */
public class MediaChangedEvent {

    private String mAction;
    private String mDevice;

    public MediaChangedEvent(String action, String device) {
        this.mAction = action;
        this.mDevice = device;
    }

    public String getDevice() {
        return mDevice;
    }

    public String getAction() {
        return mAction;
    }

    @Override
    public String toString() {
        return "MediaChangedEvent{" +
                "mAction='" + mAction + '\'' +
                ", mDevice='" + mDevice + '\'' +
                '}';
    }
}

CommonConstants

package com.yml.savelog;

/**
 * 常量定义
 * 请不要随意更改,如果必须要改,请同步修改引用
 */
public class CommonConstants {
	public static final String CUSTOM_FONT_FLFBLS                           = "fonts/flfbls.ttf";
	public static final String SP_COMMON                           			= "common";
	public static final String UPDATETIME                           		= "updateTime";
	public static final String DEF_UPDATETIME                       		= "1970-01-01 00:00:00";

	public static final String STYLE_COLOR                           		= "styleColor";
	public static final String STYLE_COLOR_TAG                         		= "styleEnable";
	public static final int STYLE_COLOR_COUNT                          		= 4;
	public static final int STYLE_COLOR_DEFAULT                          	= 0;
	public static final int STYLE_COLOR_ORANGE                          	= 1;
	public static final int STYLE_COLOR_GREEN                          		= 2;
	public static final int STYLE_COLOR_BLUE                          		= 3;

	/** 默认同时下载的最大数 */
	public static final int DEFAULT_DOWNLOAD_COUNT                          = 3;

	public static final String BASE_URL                                     = "http://192.168.1.137:8080/lycoocms/";
	public static final String BASE_URL_DEBUG                               = "http://192.168.1.137:8080/lycoocms/";

	public static final String URL_PREFIX_DEBUG 							= "http://192.168.1.137:8080/lycoocms";
	public static final String URL_PREFIX_RELEASE 							= "http://114.55.115.128/lycoocms";
	
	public static final int DAY_OF_WEEK_MONDAY 								= 1;
	public static final int DAY_OF_WEEK_TUESDAY 							= 2;
	public static final int DAY_OF_WEEK_WEDNESDAY 							= 3;
	public static final int DAY_OF_WEEK_THURDAY  							= 4;
	public static final int DAY_OF_WEEK_FRIDAY 								= 5;
	public static final int DAY_OF_WEEK_SATURDAY  							= 6;
	public static final int DAY_OF_WEEK_SUNDAY 								= 7;

	/* 平台定义 =================================================================================================== */
	public static final int PLATFORM_UNKNOW                           		= 0x00;
	public static final int PLATFORM_V40                           			= 0x01;
	public static final int PLATFORM_H3                           			= 0x02;

	public static final int PLATFORM_RK3128_DEPRECATED                     	= 0x04;
	public static final int PLATFORM_RK3128                        			= 0x40;
	public static final int PLATFORM_RK3128H                        		= 0x41;
	public static final int PLATFORM_RK3229                        			= 0x42;
	public static final int PLATFORM_RK3368                        			= 0x43;

	public static final int PLATFORM_QZ_ANDROID10                        			= 1;

	public static final String BRAND_T361                           		= "T361";

	/* Lycoo property定义================================================================================================== */
	/** 替换开机动画 (自定义) */
	public static final String PROPERTY_REPLACE_BAM							= "sys.lycoo.replace.bam.trigger";
	/** 重置开机动画 (自定义) */
	public static final String PROPERTY_RESET_BAM							= "sys.lycoo.reset.bam.trigger";
	/** 客户编码 (自定义) */
	public static final String PROPERTY_CUSTOMER_CODE						= "persist.sys.customer.code";
	/** 客户编码(默认) */
	public static final String PROPERTY_CUSTOMER_DEFAULT_CODE				= "ro.customer.default.code";
	/** 芯片类型(自定义) */
	public static final String PROPERTY_PLATFORM_TYPE						= "ro.lycoo.platform";
	/** 平台类型(自定义) */
	public static final String PROPERTY_CHIP								= "ro.lycoo.chip";
	/** 固件型号(自定义) */
	public static final String PROPERTY_LYCOO_MODEL							= "ro.lycoo.model";
	/** 固件标识符(自定义) */
	public static final String PROPERTY_FIRMWARE_KEY						= "ro.lycoo.firmware.key";
	/** 系统版本名称(自定义) */
	public static final String PROPERTY_FIRMWARE_VERSION_NAME				= "ro.lycoo.firmware.verion.name";
	/** 系统版本号(自定义) */
	public static final String PROPERTY_FIRMWARE_VERSION_CODE				= "ro.lycoo.firmware.verion.code";
	/** 固件平台 */
	public static final String PROPERTY_PLATFORM							= "ro.board.platform";
    /** 固件模式 (自定义) */
    public static final String PROPERTY_FIRMWARE_MODE						= "persist.sys.firmware.mode";
	/** log打印级别 (自定义) */
	public static final String PROPERTY_LOG_LEVEL							= "persist.sys.log.level";
	/** 蓝牙是否使能(true: 使能, false:使能) */
	public static final String PROPERTY_BLUETOOTH_ENABLE					= "ro.lycoo.bluetooth.enable";
	/** 系统配置默认桌面的包名 */
	public static final String PROPERTY_DEFAULT_LAUNCHER_PACKAGENAME		= "ro.lycoo.defaultlauncherpackage";
	/** 系统配置默认桌面的启动类名 */
	public static final String PROPERTY_DEFAULT_LAUNCHER_CLASSNAME			= "ro.lycoo.defaultlauncherclass";
	/** 系统属性: 允许同时下载的最大数 */
	public static final String PROPERTY_MAX_DOWNLOAD_COUNT                  = "persist.sys.dance.max.count";

	/** 系统属性: 是否支持TP */
	public static final String PROPERTY_TP_ENABLE                  			= "persist.sys.tp.enable";
	/** 系统属性: 是否支持悬浮back */
	public static final String PROPERTY_FLOATED_BACK_ENABLE                 = "persist.sys.floated_back.enable";
	/** 系统属性: MYKTV是否允许运行 */
	public static final String PROPERTY_MYKTV_ENABLE                		= "sys.lycoo.myktv.enable";
	/** 系统序列号 */
	public static final String PROPERTY_DEVICE_SN                			= "ro.boot.serialno";
	/** 系统菜单位置 */
	public static final String PROPERTY_ARC_MENU_LOCATION                	= "persist.sys.arc_menu.location";
	/** 系统属性:是否支持DSP切换左右声道 */
	public static final String PROPERTY_DSP_SET_STEREO_MODE                	= "ro.lycoo.dsp_set_stereo";
	/** 系统属性:是否支持DSP消原音 */
	public static final String PROPERTY_DSP_SET_VOCAL_CUT                	= "ro.lycoo.dsp_set_vocal_cut";

	// 快捷键属性定义=========================================================================================================
	/** 直播(自定义) */
	public static final String PROPERTY_SHORTCUT_KEY_TV						= "persist.sys.key.tv";
	/** 点播(自定义) */
	public static final String PROPERTY_SHORTCUT_KEY_VOD					= "persist.sys.key.vod";
	/** 音乐(自定义) */
	public static final String PROPERTY_SHORTCUT_KEY_MUSIC					= "persist.sys.key.music";
	/** 游戏(自定义) */
	public static final String PROPERTY_SHORTCUT_KEY_GAME					= "persist.sys.key.game";
	/** 应用(自定义) */
	public static final String PROPERTY_SHORTCUT_KEY_APPS					= "persist.sys.key.apps";
	/** 设置(自定义) */
	public static final String PROPERTY_SHORTCUT_KEY_SETUP					= "persist.sys.key.setup";
	/** 文件管理器(自定义) */
	public static final String PROPERTY_SHORTCUT_KEY_FILE_MANAGER			= "persist.sys.key.file_manager";
	/** 一键清理(自定义) */
	public static final String PROPERTY_SHORTCUT_KEY_CLEAR					= "persist.sys.key.clear";
	/** 接口测试 */
	public static final String PROPERTY_SHORTCUT_KEY_INTERFACE_TEST 		= "persist.sys.key.interface_test";
	/** 老化测试 */
	public static final String PROPERTY_SHORTCUT_KEY_AGING_TEST				= "persist.sys.key.aging_test";

	/* 作假属性 ============================================================================================================ */
	/** FLASH 容量是否作假 */
	public static final String PROPERTY_FLASH_FEIGNED						= "ro.lycoo.flash.feigned";
	/** DDR 容量是否作假 */
	public static final String PROPERTY_DDR_FEIGNED							= "ro.lycoo.ddr.feigned";
	/** FLASH 作假容量, 单位G */
	public static final String PROPERTY_FLASH_SIZE							= "ro.lycoo.flash.size";
	/** DDR 作假容量, 单位M */
	public static final String PROPERTY_DDR_SIZE							= "ro.lycoo.ddr.size";

	/** 系统属性(仅用于设置显示):DDR作假*/
	public static final String PROPERTY_SETUP_DDR_FEIGNED         			= "ro.lycoo.setup.ddr.feigned";
	/** 系统属性(仅用于设置显示):DDR作假大小,单位为M */
	public static final String PROPERTY_SETUP_DDR_FEIGNED_SIZE   			= "ro.lycoo.setup.ddr.size";
	/** 系统属性(仅用于设置显示):FLASH作假*/
	public static final String PROPERTY_SETUP_FLASH_FEIGNED       			= "ro.lycoo.setup.flash.feigned";
	/** 系统属性(仅用于设置显示):FLASH作假大小,单位为G */
	public static final String PROPERTY_SETUP_FLASH_FEIGNED_SIZE  			= "ro.lycoo.setup.flash.size";

	/* 固件-应用信息 ========================================================================================================= */
	/** 固件keyCode */
	public static final String FIRMWARE_KEY 								= "firmwareKey";
	/** 应用keyCode,在AndroidManifest.xml中配置 */
	public static final String APP_KEY 										= "appKey";
	/** 设备以太网mac地址 */
	public static final String MAC 											= "mac";
	/** 设备wifi mac地址 */
	public static final String WIFI_MAC 									= "wifiMac";
	/** 设备客户编码 */
	public static final String CUSTOMER_CODE 								= "customerCode";
	/** 版本名称:*/
	public static final String VERSION_NAME 								= "versionName";
	/** 版本号,升级用 */
	public static final String VERSION_CODE 								= "versionCode";
	/** 应用包名 */
	public static final String PACKAGENAME 									= "packageName";
	/** 所属公司编号 */
	public static final String COMPANY_NUMBER 								= "companyNumber";
	/** 序列号 */
	public static final String SN 											= "sn";
	/** 动态授权码 */
	public static final String DYNAMIC_CODE 								= "dynamicCode";
	/** 固件模式-量产模式 */
	public static final int FIRMWARE_MODE_RELEASE                           = 0;
	/** 固件模式-调试模式 */
	public static final int FIRMWARE_MODE_DEBUG                             = 1;
	/** log级别 */
	public static final int DEFAULT_LOG_LEVEL                             	= 3;

	/* LycooPropertyUpdate ===================================================================================================== */
	// 请不要随意更改名称,如果要改必须和LycooPropertyUpdate同步修改
	/** 启动LycooPropertyUpdate action */
	public static final String ACTION_UPDATE_PROPERTY 						= "com.lycoo.ACTION_UPDATE_PROPERTY";
	public static final String PROPERTY_KEY 								= "propertyKey";
	public static final String PROPERTY_VALUE 								= "propertyValue";
	/** 属性名最大长度 */
	public static final int PROPERTY_KEY_MAX_LENGTH 						= 31;
	/** 属性值最大长度 */
	public static final int PROPERTY_VALUE_MAX_LENGTH 						= 91;

	/* LycooPackageInstaller ================================================================================================= */
	/** 广播action: 开始执行 */
	public static final String ACTION_PACKAGEINSTALL               			= "com.lycoo.ACTION_PACKAGEINSTALL";
	/** 执行模式 */
	public static final String EXECUTE_MODE                        			= "executeMode";
	/** 执行数据 */
	public static final String EXECUTE_DATA                        			= "executeData";
	/** 执行模式:安装 */
	public static final int MODE_INSTALL                               		= 0;
	/** 执行模式:卸载 */
	public static final int MODE_UNINSTALL                             		= 1;

	/** 广播action:执行完成发送 */
	public static final String ACTION_PACKAGEINSTALL_COMPLETE          		= "com.lycoo.ACTION_PACKAGEINSTALL_COMPLETE";
	/** 执行结果 */
	public static final String PACKAGEINSTALL_RESULTCODE               		= "resultCode";
	/** 执行应用包名 */
	public static final String PACKAGEINSTALL_PACKAGENAME              		= "packageName";
	/** 安装文件 */
	public static final String PACKAGEINSTALL_FILE                      	= "file";
	/** 安装成功 */
	public static final int INSTALL_SUCCEEDED                          		= 1;
	/** 安装失败 */
	public static final int INSTALL_FAILED_INVALID_APK                      = -2;
	public static final int INSTALL_FAILED_INVALID_URI 						= -3;
	public static final int INSTALL_FAILED_INSUFFICIENT_STORAGE         	= -4;
	/** 卸载成功 */
	public static final int DELETE_SUCCEEDED                           		= 1;
	/** 卸载失败 */
	public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER         	= -2;

	/* preInstaller ======================================================================================================== */
	public static final String PREINSTALL_DIR 								= "/system/preinstall";
	public static final String KEY_PREINSTALL_COMPLETED 					= "persist.sys.lycoo.preinstall";
	public static final String LYCOO_PREINSTALLER_PACKAGENAME 				= "com.lycoo.lancy.preinstaller";
	public static final String LYCOO_PREINSTALLER_LUANCH_CLASSNAME 			= "com.lycoo.lancy.preinstaller.MainActivity";
	public static final String ACTION_PREINSTALL 							= "com.lycoo.ACTION_PREINSTALL";
	public static final String KEY_PREINSTALL 								= "preinstall";
	public static final String EXTRA_PREINSTALL_BEGIN 						= "preinstall_begin";
	public static final String EXTRA_PREINSTALL_COMPLETED 					= "preinstall_completed";
	/*
	public static final String PREINSTALL_DIR 								= "/system/preinstall";
	public static final String KEY_PREINSTALL_COMPLETED 					= "persist.sys.preinstall.ok";
	public static final String PREINSTALL_COMPLETED 						= "ok";
	public static final String LYCOO_PREINSTALLER_PACKAGENAME 				= "com.lycoo.lancy.preinstaller";
	public static final String LYCOO_PREINSTALLER_LUANCH_CLASSNAME 			= "com.lycoo.lancy.preinstaller.MainActivity";
	public static final String ACTION_PREINSTALL 							= "com.lycoo.ACTION_PREINSTALL";
	public static final String KEY_PREINSTALL 								= "preinstall";
	public static final String EXTRA_PREINSTALL_BEGIN 						= "preinstall_begin";
	public static final String EXTRA_PREINSTALL_COMPLETED 					= "preinstall_completed";
	*/
	// PackageInstall
	public static final String LYCOO_PACKAGEINSTALLER_PACKAGENAME 			= "com.lycoo.lancy.packageinstaller";
	public static final String LYCOO_PACKAGEINSTALLER_LUANCH_CLASSNAME 		= "com.lycoo.lancy.packageinstaller.MainActivity";

	// wallPaper
	/** 绝对路径为:/sdcard/Wallpaper */
	public static final String WALLPAPER_DIR_LOCATION_SDCARD				= "Wallpaper";
	public static final String WALLPAPER_DIR_LOCATION_SYSTEM 				= "/system/media/images/wallpapers";

	/* 服务器返回参数 ==================================================================================================== */
	public static final String RESPONSE_STATUS_CODE 						= "statusCode";
	public static final String RESPONSE_MESSAGE 							= "message";
	public static final String RESPONSE_DATA 								= "data";
	public static final int STATUS_CODE_ERROR                               = 0;
	public static final int STATUS_CODE_SUCCESS                             = 1;

	/* 天气预报 ======================================================================================================== */
	public static final String ACTION_GET_WEATHER 							= "com.lycoo.keily.getweather";
	public static final String ACTION_RECEIVE_WEATHER 						= "com.lycoo.receive.weather";
	public static final String KEY_WEATHER                              	= "weather";
	public static final String KEY_WEATHER_CITY 							= "city";
	public static final String KEY_WEATHER_TEMP1 							= "temp1";

	/* LycooOtaUpdate =================================================================================================== */
	public static final String ACTION_INSTALL_OTA_PACKAGE 					= "com.lycoo.ACTION_INSTALL_OTA_PACKAGE";
	public static final String PACKAGE_FILE 								= "packageFile";

	/*  数据库 ============================================================================================================== */
	public static final String DB_NAME 										= "commons.db";
	public static final int DB_VERSION 										= 1;

	/*  应用广告========================================================================================================= */
	public static final int APPVERTISING_IMAGE_SPLASH						= 0;
	public static final int APPVERTISING_IMAGE_EXIT							= 1;
	public static final int APPVERTISING_IMAGE_BUFFERING					= 2;
	public static final int APPVERTISING_IMAGE_PAUSE						= 3;
	public static final int APPVERTISING_IMAGE_PROGRAM_LIST					= 4;
	public static final int APPVERTISING_IMAGE_PROGRAM_EPG					= 5;
	public static final int APPVERTISING_IMAGE_LOGO							= 6;

	/*  MYKTV授权 ========================================================================================================= */
	public static final String AUTHORIZE_APP_PACKAGENAME					= "com.lycoo.lancy.ks.authorizer";
	public static final String ACTION_AUTHORIZE_FAILED 						= "com.lycoo.ACTION_AUTHORIZE_FAILED";
	public static final String ACTION_AUTHORIZE_SUCCEED 					= "com.lycoo.ACTION_AUTHORIZE_SUCCEED";

	/*  ARC MENU ========================================================================================================= */
	public static final int ARC_MENU_LOCATION_LEFT_TOP						= 0;
	public static final int ARC_MENU_LOCATION_CENTER_TOP					= 1;
	public static final int ARC_MENU_LOCATION_RIGHT_TOP						= 2;
	public static final int ARC_MENU_LOCATION_LEFT_BOTTOM					= 3;
	public static final int ARC_MENU_LOCATION_CENTER_BOTTOM					= 4;
	public static final int ARC_MENU_LOCATION_RIGHT_BOTTOM					= 5;
}

DeviceManager


package com.yml.savelog;

import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.os.storage.StorageManager;
import android.text.TextUtils;

import com.yml.savelog.util.CollectionUtils;
import com.yml.savelog.util.DeviceUtils;
import com.yml.savelog.util.FileUtils;
import com.yml.savelog.util.LogUtils;

import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * 设备管理器
 *
 * Created by lancy on 2017/10/28
 */
public class DeviceManager {
    private static final String TAG = DeviceManager.class.getSimpleName();
    // DEBUG-LOG **************************************************
    private static final boolean DEBUG_STATFS = true;
    // DEBUG-LOG **************************************************
    private static final boolean DEBUG_USB = true;

    private static int mPlatformType;

    static {
        // TODO: 2018/5/21 modify when release
        mPlatformType = DeviceUtils.getPlatformType();
    }

    /**
     * 当前平台是否为全志V40
     *
     *
     *
     * @return 如果是V40返回true, 否则返回false
     *
     * Created by lancy on 2018/5/21 11:36
     */
    public static boolean isV40() {
        return mPlatformType == CommonConstants.PLATFORM_V40;
    }

    /**
     * 当前平台是否为全志H3/H2
     *
     * @return 如果是全志H3/H2返回true, 否则返回false
     *
     * Created by lancy on 2018/11/20 18:02
     */
    public static boolean isH3() {
        return mPlatformType == CommonConstants.PLATFORM_H3;
    }


    /**
     * 是否为全志平台
     *
     * @return 如果是全志平台返回true, 否则返回false
     *
     * Created by lancy on 2019/1/8 22:03
     */
    public static boolean isAllwinnerPlatform() {
        return isV40() || isH3();
    }


    /**
     * 当前平台是否为RK3128
     *
     * @return 如果是RK3128返回true, 否则返回false
     *
     * Created by lancy on 2018/5/21 11:36
     */
    public static boolean isRK3128() {

        return mPlatformType == CommonConstants.PLATFORM_RK3128 || mPlatformType == CommonConstants.PLATFORM_RK3128_DEPRECATED;
    }

    /**
     * 当前平台是否为RK3128H
     *
     * @return 如果是RK3128H 返回true, 否则返回false
     *
     * Created by lancy on 2018/6/23 16:15
     */
    public static boolean isRK3128H() {
        return mPlatformType == CommonConstants.PLATFORM_RK3128H;
    }

    /**
     * 当前平台是否为RK3229
     *
     * @return 如果是RK3229 返回true, 否则返回false
     *
     * Created by lancy on 2018/7/19 21:04
     */
    public static boolean isRK3229() {
        return mPlatformType == CommonConstants.PLATFORM_RK3229;
    }

    /**
     * 当前平台是否为RK3368
     *
     * @return 如果是RK3368 返回true, 否则返回false
     *
     * Created by lancy on 2019/2/13 11:03
     */
    public static boolean isRK3368() {
        return mPlatformType == CommonConstants.PLATFORM_RK3368;
    }

    /**
     * 枚举系统所有可用的挂载点
     *
     * @param context
     * @return 当前系统中可用设备挂载点绝对路径
     *
     * Created by lancy on 2017/10/27 14:01
     */
    public static List<String> getDevices(Context context) {
        List<String> devicePaths = null;
        try {
            StorageManager storageManager = (StorageManager) context.getSystemService(Activity.STORAGE_SERVICE);
            Method method = storageManager.getClass().getMethod("getVolumePaths");
            String[] paths = (String[]) method.invoke(storageManager);
            if (paths != null && paths.length > 0) {
                devicePaths = Arrays.asList(paths);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return devicePaths;
    }

    /**
     * 检查设备是否挂载到系统中
     *
     * @param context    上下文
     * @param mountPoint 设备挂载点(绝对路径)
     * @return true: 设备已挂载, false: 设备未挂载
     *
     * Created by lancy on 2017/10/27 14:03
     */
    public static boolean isDeviceMounted(Context context, String mountPoint) {
        if (TextUtils.isEmpty(mountPoint)) {
            return false;
        }
        // RK3128需要判断上一级
        if ((isRK3128() || isRK3128H() || isRK3368()) && isUsb(mountPoint)) {
            mountPoint = mountPoint.substring(0, mountPoint.lastIndexOf("/"));
        }

        try {
            StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
            Method method = storageManager.getClass().getMethod("getVolumeState", String.class);
            String state = (String) method.invoke(storageManager, mountPoint);
            return Environment.MEDIA_MOUNTED.equals(state);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }

    /**
     * 获取系统中已挂载的设备
     *
     * @param context 上下文
     * @return 系统中已挂载设备的绝对路径
     *
     * Created by lancy on 2017/10/27 14:27
     */
    public static List<String> getMountedDevices(Context context) {
        List<String> mountedPaths = new ArrayList<>();
        try {
            StorageManager storageManager = (StorageManager) context.getSystemService(Activity.STORAGE_SERVICE);
            Method getVolumePathsMethod = storageManager.getClass().getMethod("getVolumePaths");
            String[] volumePaths = (String[]) getVolumePathsMethod.invoke(storageManager);
            if (volumePaths != null && volumePaths.length > 0) {
                Method getVolumeStateMethod = storageManager.getClass().getMethod("getVolumeState", String.class);
                for (String path : volumePaths) {
                    LogUtils.debug(TAG, "path : " + path);
                    String state = (String) getVolumeStateMethod.invoke(storageManager, path);
                    if (!TextUtils.isEmpty(path) && state != null && !state.isEmpty() && Environment.MEDIA_MOUNTED.equals(state)) {
                        LogUtils.debug(TAG, "mounted path : " + path);
                        if (isRK3128() || isRK3128H() || isRK3368()) {
                            // USB
                            if (isUsb(path)) {
                                // 3128 7.1
                                if (isRK3128() && Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {
                                    mountedPaths.add(path);
                                } else {
                                    File[] files = new File(path).listFiles();
                                    if (files != null && files.length > 0) {
                                        for (File file : files) {
                                            if (DEBUG_USB) {
                                                LogUtils.debug(TAG, "*******************************************************************************************************");
                                                LogUtils.debug(TAG, "* usb file : " + file.getPath());
                                                LogUtils.debug(TAG, "* canRead : " + file.canRead() + ", canWrite: " + file.canWrite() + ", canExecute : " + file.canExecute());
                                                LogUtils.debug(TAG, "*******************************************************************************************************");
                                            }
                                            String filePath = file.getPath();
                                            if (!TextUtils.isEmpty(filePath)
                                                    && file.isDirectory()
                                                    && file.canRead() && file.canWrite() && file.canExecute()) {
                                                List<String> attributes = FileUtils.getFileAttributes(file);
                                                LogUtils.debug(TAG, "attributes: " + attributes);
                                                if (!CollectionUtils.isEmpty(attributes) && attributes.size() == 3
                                                        && (attributes.get(1).equals("system") || attributes.get(1).equals("root"))) {
                                                    mountedPaths.add(filePath);
                                                }
                                            }
                                        }
                                    }
                                }
                            } else {
                                mountedPaths.add(path);
                            }
                        }
                        else if (isV40() || isH3()) {
                            String name = FileUtils.getName(path, false);
                            System.out.println("DeviceManager      "+name);
                            if (name.contains("udisk5")&&name.length()>6){
                                mountedPaths.add(1,path);
                            }else{
                                mountedPaths.add(path);
                            }

                        } else {
                            mountedPaths.add(path);
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return mountedPaths;
    }

    /**
     * 获取挂载的设备
     * 例如某个硬盘可能有几个分区, 每个厂商的处理方式不同
     * 全志  :每个分区都当做一个外设, 挂载和移除的时候会发对应分区个数的广播
     * 瑞星微: 以3128为例, 它会当做是一个外设, 每个分区对应不同的文件夹, 挂载和移除的时候会只发一次广播
     *
     * @param path 设备挂载点
     * @return 挂载设备的硬盘分区
     *
     * Created by lancy on 2018/5/21 11:52
     */
    @SuppressWarnings("unchecked")
    public static List<String> getMountedDevices(String path) {
        LogUtils.debug(TAG, "getMountedDevices, path : " + path);
        if (TextUtils.isEmpty(path)) {
            return Collections.EMPTY_LIST;
        }

        List<String> partitions = new ArrayList<>();
        if (isRK3128() && DeviceManager.isUsb(path)&& Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {
            partitions.add(path);
            return partitions;
        }
        if ((DeviceManager.isRK3128() || isRK3128H())
                && DeviceManager.isUsb(path)) {
            File file = new File(path);
            if (file.exists() && file.canRead()) {
                File[] files = file.listFiles();
                if (files != null && files.length > 0) {
                    for (File dir : files) {
                        if (DEBUG_USB) {
                            LogUtils.info(TAG, "mounted usb dir : " + dir.getPath());
                            LogUtils.info(TAG, "canRead: " + dir.canRead() + ", canWrite: " + dir.canWrite() + ", canExecute: " + dir.canExecute());
                        }
                        if (dir.isDirectory() && dir.canRead() && dir.canWrite() && dir.canExecute()) {
                            List<String> attributes = FileUtils.getFileAttributes(file);
                            LogUtils.debug(TAG, "attributes: " + attributes);
                            if (!CollectionUtils.isEmpty(attributes) && attributes.size() == 3
                                    && (attributes.get(1).equals("system") || attributes.get(1).equals("root"))) {
                                partitions.add(dir.getPath());
                            }
                        }
                    }
                }
            }
        } else {
            partitions.add(path);
        }

        return partitions;
    }

    /**
     * 检查目标设备剩余空间
     *
     * @param path      目标设备路径
     * @param limitSize 临界值, 例如1G:1 * 1024 * 1024* 1024
     * @return 如果剩余空间 > 临界值, 则返回true, 否则返回false
     *
     * Created by lancy on 2018/4/28 16:46
     */
//    public static boolean checkSpace(String path, long limitSize) {
//        StatFs statFs = new StatFs(path);
//        long availableSize = statFs.getAvailableBytes();
//        if (DEBUG_STATFS) {
//            LogUtils.debug(TAG, "totalSize = " + statFs.getTotalBytes());
//            LogUtils.debug(TAG, "freeBytes = " + statFs.getFreeBytes());
//        }
//        LogUtils.debug(TAG, "availableSize = " + statFs.getAvailableBytes());
//        LogUtils.debug(TAG, "limitSize     = " + limitSize);
//        // 存储空间不足1G不允许下载
//        return availableSize > limitSize;
//    }

    /**
     * 挂载设备是否为本地存储
     *
     * @param mountPoint 挂载点
     * @return 返回true如果为本地设备, 否则返回false
     *
     * Created by lancy on 2018/5/21 11:19
     */
    public static boolean isInternalCard(String mountPoint) {
        if (isRK3128() || isRK3128H() || isRK3229()) {
            return !StringUtils.isEmpty(mountPoint) && mountPoint.startsWith("/storage/emulated");
        } else if (isRK3368()) {
            return !StringUtils.isEmpty(mountPoint) && mountPoint.startsWith("/storage/emulated/0");
        } else if (isH3()) {
            return !StringUtils.isEmpty(mountPoint) && mountPoint.startsWith("/storage/emulated/0");
        } else if (isV40()) {
            // 7.1
            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {
                return !StringUtils.isEmpty(mountPoint) && mountPoint.contains("/storage/emulated/0");
            }
            //android10
            if (Build.VERSION.SDK_INT == 29) {
                return !StringUtils.isEmpty(mountPoint) && mountPoint.startsWith("/storage/emulated/0");
            }

            return !StringUtils.isEmpty(mountPoint) && mountPoint.contains("sdcard");
        }
        return false;
    }
    /**
     * 当前平台是否为全志Android10.0
     *
     * @return 如果是全志返回true, 否则返回false
     *
     * Created by liuyi on 2021/5/17 19:03
     */
    public static boolean isQZ() {
        return mPlatformType == CommonConstants.PLATFORM_QZ_ANDROID10 ;
    }
    /**
     * 挂载设备是否为TF Card
     *
     * @param mountPoint 挂载点
     * @return 返回true如果为TF Card, 否则返回false
     *
     * Created by lancy on 2018/5/21 11:19
     */
    public static boolean isExternalCard(String mountPoint) {
        if (isRK3128() || isRK3128H()) {
            return !StringUtils.isEmpty(mountPoint) && mountPoint.contains("external_sd");
        } else if (isV40() || isH3()) {
            //android10
            if (Build.VERSION.SDK_INT == 29) {
                return !StringUtils.isEmpty(mountPoint) && mountPoint.startsWith("/storage/sdcard");
            }
            return !StringUtils.isEmpty(mountPoint) && !mountPoint.contains("sdcard") && mountPoint.contains("card");
        }

        return false;
    }

    /**
     * 挂载设备是否为USB设备
     *
     * @param mountPoint 挂载点
     * @return 返回true如果为USB设备, 否则返回false
     *
     * Created by lancy on 2018/5/21 11:19
     */
    public static boolean isUsb(String mountPoint) {
        System.out.println("lius:1:::    "+isRK3128()+"   "+mountPoint);
        if (isRK3128()) {
            // 7.1
            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {
                return !StringUtils.isEmpty(mountPoint)
                        && mountPoint.startsWith("/storage") && !mountPoint.startsWith("/storage/emulated");
            }
            return !StringUtils.isEmpty(mountPoint) && mountPoint.contains("usb_storage");
        } else if (isRK3128H() || isRK3368()) {
            return !StringUtils.isEmpty(mountPoint) && mountPoint.contains("usb_storage");
        } else if (isH3()) {
            return !StringUtils.isEmpty(mountPoint) && mountPoint.contains("usbhost");
        } else if (isV40()) {
            // 7.1
            if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {
                return !StringUtils.isEmpty(mountPoint) && mountPoint.startsWith("/storage/udisk");
            }
            //Android10   liuyi
            if (Build.VERSION.SDK_INT == 29) {
                return !StringUtils.isEmpty(mountPoint) && mountPoint.startsWith("/storage/udisk");
            }

            return !StringUtils.isEmpty(mountPoint) && mountPoint.contains("usbhost");
        }

        return false;
    }

    /**
     * 获取usb数量
     *
     * @param context 上下文
     * @return 当前系统挂载的USB设备数量
     *
     * Created by lancy on 2018/11/21 20:11
     */
    public static int getMountedUsbSize(Context context) {
        int size = 0;
        List<String> devices = getMountedDevices(context);
        if (!CollectionUtils.isEmpty(devices)) {
            for (String device : devices) {
                if (isUsb(device)) {
                    size++;
                }
            }
        }

        return size;
    }

    /**
     * 获取当前系统挂载usb设备
     *
     * @param context 上下文
     * @return 系统挂载usb设备
     *
     * Created by lancy on 2018/11/21 20:11
     */
    public static List<String> getMountedUsbDevices(Context context) {
        List<String> devices = getMountedDevices(context);
        List<String> usbDevices = new ArrayList<>();
        if (!CollectionUtils.isEmpty(devices)) {
            for (String device : devices) {
                if (isUsb(device)) {
                    usbDevices.add(device);
                }
            }
        }
        return usbDevices;
    }

    /**
     * 获取挂载的TFCard数量
     *
     * @param context 上下文
     * @return TFCard数量
     *
     * Created by lancy on 2018/12/21 19:49
     */
    public static int getMountedTFCardSize(Context context) {
        int size = 0;
        List<String> devices = getMountedDevices(context);
        if (!CollectionUtils.isEmpty(devices)) {
            for (String device : devices) {
                if (isExternalCard(device)) {
                    size++;
                }
            }
        }
        return size;
    }

}

MainActivity

package com.yml.savelog;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.text.TextUtils;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.yml.savelog.util.FileUtils;
import com.yml.savelog.util.LogUtils;

import org.apache.commons.lang3.StringUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import io.reactivex.disposables.Disposable;

public class MainActivity extends AppCompatActivity {

    private TextView tv_usb;
    private String mPath="";
    private String mSavePath="";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tv_usb=findViewById(R.id.tv_usb);
        mSavePath=Environment.getExternalStorageDirectory()
                .getAbsolutePath() + File.separator + "AndroidLog";
        if (!(new File(mSavePath)).exists())
            (new File(mSavePath)).mkdirs();
        subscribeMeidaChangeEvent();
    }

    private void subscribeMeidaChangeEvent() {
        Disposable disposable = RxBus.getInstance()
                .registerSubscribe(MediaChangedEvent.class,
                        mediaChangedEvent -> {
//                                LogUtils.debug(TAG, "mediaChangedEvent : " + mediaChangedEvent);
                            if (mediaChangedEvent == null
                                    || StringUtils.isEmpty(mediaChangedEvent.getAction())
                                    || StringUtils.isEmpty(mediaChangedEvent.getDevice())) {
                                return;
                            }


                            String path = mediaChangedEvent.getDevice();
                            if (mediaChangedEvent.getAction().equals(Intent.ACTION_MEDIA_MOUNTED)) {
//                                    onMediaMounted(path);
                                List<String> devices = DeviceManager.getMountedDevices(path);
                                System.out.println("liu:::::::     "+devices.size()+"    "+devices.get(0));

                                tv_usb.setText("已插入外设:"+devices.get(0));
                                mPath=devices.get(0);
                            }
                            else if (mediaChangedEvent.getAction().equals(Intent.ACTION_MEDIA_REMOVED)
                                    && (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT)
                                    && DeviceManager.isUsb(path)
                                    && DeviceManager.isRK3128()) {

//                                    onMediaRemoved(path);
                                tv_usb.setText("已拔出外设");
                                mPath="";

                            }
                            else if (mediaChangedEvent.getAction().equals(Intent.ACTION_MEDIA_EJECT)
                            ){

//                                    onMediaRemoved(path);
                                tv_usb.setText("已拔出外设");
                                mPath="";
                            }
                        }, Throwable::printStackTrace);
        RxBus.getInstance().addDisposable(this, disposable);
    }






    public void onBtnClick(View view) {
//        LogcatHelper.getInstance(this).stop();
        if (mPath.equals("")){
            Toast.makeText(this,"请插入外设",Toast.LENGTH_SHORT).show();
            return;
        }
        copyFolder(mSavePath,mPath+"/log");
    }
    /**
     * 复制整个文件夹内容
     * @param oldPath String 原文件路径 如:c:/fqf
     * @param newPath String 复制后路径 如:f:/fqf/ff
     * @return boolean
     */
    public void copyFolder(String oldPath, String newPath) {

        try {
            (new File(newPath)).mkdirs(); //如果文件夹不存在 则建立新文件夹
            System.out.println("liu::::   newPath="+newPath+"   "+(new File(newPath)).exists());
            File a=new File(oldPath);
            String[] file=a.list();
            File temp=null;
            for (int i = 0; i < file.length; i++) {
                if(oldPath.endsWith(File.separator)){
                    temp=new File(oldPath+file[i]);
                }
                else{
                    temp=new File(oldPath+File.separator+file[i]);
                }

                if(temp.isFile()){
                    System.out.println("liu::::    "+(temp.getName()).toString());
                    FileInputStream input = new FileInputStream(temp);
                    FileOutputStream output = new FileOutputStream(newPath + "/" +
                            (temp.getName()).toString());
                    byte[] b = new byte[1024 * 5];
                    int len;
                    while ( (len = input.read(b)) != -1) {
                        output.write(b, 0, len);
                    }
                    output.flush();
                    output.close();
                    input.close();
                }

                if(temp.isDirectory()){//如果是子文件夹
                    System.out.println("liu::::    dfjioadhoi");
                    copyFolder(oldPath+"/"+file[i],newPath+"/"+file[i]);

                }
            }
        }
        catch (Exception e) {
            System.out.println("复制整个文件夹内容操作出错");
            e.printStackTrace();

        }

    }

    /**
     * 复制单个文件
     * @param oldPath String 原文件路径 如:c:/fqf.txt
     * @param newPath String 复制后路径 如:f:/fqf.txt
     * @return boolean
     */
    public void copyFile(String oldPath, String newPath) {
        try {
            int bytesum = 0;
            int byteread = 0;
            File oldfile = new File(oldPath);
            if (oldfile.exists()) { //文件存在时
                InputStream inStream = new FileInputStream(oldPath); //读入原文件
                FileOutputStream fs = new FileOutputStream(newPath);
                byte[] buffer = new byte[1444];
                int length;
                while ( (byteread = inStream.read(buffer)) != -1) {
                    bytesum += byteread; //字节数 文件大小
                    System.out.println(bytesum);
                    fs.write(buffer, 0, byteread);
                }
                inStream.close();
            }
        }
        catch (Exception e) {
            System.out.println("复制单个文件操作出错");
            e.printStackTrace();

        }

    }



    public void onBtnSureClick(View view) {
//        new Handler().postAtTime(new Runnable() {
//            @Override
//            public void run() {
//                finish();
//                System.exit(0);//正常退出
//                android.os.Process.killProcess(android.os.Process.myPid());
//            }
//        },6000);
//        RootCmd.execRootCmd("logcat -v time > "+ mSavePath+"/device_log.txt");
        //mPath
        //adb logcat -b events -b radio -b main -b system -b crash -v time >
//        RootCmd.execRootCmd("logcat -v time > "+ mPath+"/device_log.txt");
//        RootCmd.execRootCmd("logcat -t 1000 > "+ mPath+"/device_log.txt");
        RootCmd.execRootCmd("logcat -v time -b system > "+ mPath+"/device_log.txt");

//        RootCmd.execRootCmd("logcat -b system > "+ mPath+"/device_log.txt");//不行







    }
}

最后打包出来的apk要进行系统签名

1.找到平台签名文件“platform.pk8”和“platform.x509.pem”

文件位置 android/build/target/product/security/

2.签名工具“signapk.jar”

位置:android/prebuilts/sdk/tools/lib

3.签名证书“platform.pk8 ”“platform.x509.pem ”,签名工具“signapk.jar ”放置在同一个文件夹;
4.执行命令

java -jar signapk.jar platform.x509.pem platform.pk8 Demo.apk signedDemo.apk
或者直接在Ubuntu 编译环境下执行
java -jar prebuilts/sdk/tools/lib/signapk.jar  build/target/product/security/platform.x509.pem build/target/product/security/platform.pk8 input.apk output.apk

注意:
Android App层抓取日志,APP不使用系统签名的话是抓取不到系统日志的,它抓取的只是/dev/log/main下的无用日志,必须使用系统签名后才可抓取到有用的日志,是/dev/log/system下的,比如报错信息,但是抓取的日志是在一段时间内的日志,经验证,在RK3128上Android4.4系统上抓取的日志是3个多小时内的日志。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值