This commit is contained in:
ZhanGSKen 2025-02-25 23:47:18 +08:00
commit 820574a4ba
444 changed files with 56705 additions and 447 deletions

3
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name generated Normal file
View File

@ -0,0 +1 @@
appbase

6
.idea/compiler.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

10
.idea/deploymentTargetDropDown.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<value>
<entry key="appbase">
<State />
</entry>
</value>
</component>
</project>

10
.idea/migrations.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

10
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -18,18 +18,18 @@ def genVersionName(def versionName){
} }
android { android {
compileSdkVersion 32 compileSdkVersion 30
buildToolsVersion "33.0.3" buildToolsVersion "30.0.3"
defaultConfig { defaultConfig {
applicationId "cc.winboll.studio.appbase" applicationId "cc.winboll.studio.appbase"
minSdkVersion 21 minSdkVersion 26
targetSdkVersion 30 targetSdkVersion 29
versionCode 1 versionCode 1
// versionName // versionName
// .winboll/winbollBuildProps.properties stageCount=0 // .winboll/winbollBuildProps.properties stageCount=0
// Gradle编译环境下合起来的 versionName "${versionName}.0" // Gradle编译环境下合起来的 versionName "${versionName}.0"
versionName "1.0" versionName "2.0"
if(true) { if(true) {
versionName = genVersionName("${versionName}") versionName = genVersionName("${versionName}")
} }
@ -41,11 +41,6 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
} }
dependencies { dependencies {

View File

@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle #Created by .winboll/winboll_app_build.gradle
#Sun Jan 05 02:30:11 GMT 2025 #Tue Feb 25 16:51:17 HKT 2025
stageCount=0 stageCount=3
libraryProject= libraryProject=libappbase
baseVersion=1.0 baseVersion=2.0
publishVersion=1.0.0 publishVersion=2.0.2
buildCount=2 buildCount=0
baseBetaVersion=1.0.1 baseBetaVersion=2.0.3

View File

@ -21,15 +21,76 @@
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".GlobalApplication$CrashActivity"/>
<service
android:name=".MyTileService"
android:label="@string/tileservice_name"
android:icon="@drawable/ic_launcher"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE"/>
</intent-filter>
</service>
<service
android:name=".services.MainService"
android:exported="true"/>
<service android:name=".services.AssistantService"/>
<receiver android:name="cc.winboll.studio.appbase.receivers.MainReceiver">
<intent-filter>
<action android:name="cc.winboll.studio.appbase.receivers.MainReceiver"/>
</intent-filter>
</receiver>
<receiver
android:name=".widgets.SOSWidget"
android:exported="true">
<intent-filter>
<action android:name="cc.winboll.studio.appbase.widgets.SOSWidget.ACTION_WAKEUP_SERVICE"/>
<action android:name="cc.winboll.studio.appbase.widgets.SOSWidget.ACTION_RELOAD_REPORT"/>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_provider_info_sos"/>
</receiver>
<receiver android:name=".widgets.SOSWidgetClickListener">
<intent-filter>
<action android:name="cc.winboll.studio.appbase.widgets.SOSWidgetClickListener.ACTION_PRE"/>
<action android:name="cc.winboll.studio.appbase.widgets.SOSWidgetClickListener.ACTION_NEXT"/>
</intent-filter>
</receiver>
<meta-data <meta-data
android:name="android.max_aspect" android:name="android.max_aspect"
android:value="4.0"/> android:value="4.0"/>
<activity android:name=".GlobalApplication$CrashActivity"/>
</application> </application>

View File

@ -5,12 +5,23 @@ package cc.winboll.studio.appbase;
* @Date 2025/01/05 09:54:42 * @Date 2025/01/05 09:54:42
* @Describe APPbase 应用类 * @Describe APPbase 应用类
*/ */
import cc.winboll.studio.GlobalApplication; import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.SOSCSBroadcastReceiver;
import android.content.IntentFilter;
public class App extends GlobalApplication { public class App extends GlobalApplication {
public static final String TAG = "App"; public static final String TAG = "App";
SOSCSBroadcastReceiver mSOSCSBroadcastReceiver;
@Override
public void onCreate() {
super.onCreate();
GlobalApplication.setIsDebuging(this, BuildConfig.DEBUG);
mSOSCSBroadcastReceiver = new SOSCSBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(SOSCSBroadcastReceiver.ACTION_SOS);
registerReceiver(mSOSCSBroadcastReceiver, intentFilter);
}
} }

View File

@ -1,15 +1,116 @@
package cc.winboll.studio.appbase; package cc.winboll.studio.appbase;
import android.app.Activity; import android.content.ComponentName;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import android.widget.CheckBox;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.appbase.R;
import cc.winboll.studio.appbase.services.MainService;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.LogView;
import cc.winboll.studio.libappbase.SOS;
import cc.winboll.studio.libappbase.SimpleOperateSignalCenterService;
import cc.winboll.studio.libappbase.bean.APPSOSBean;
import cc.winboll.studio.libappbase.services.TestService;
import cc.winboll.studio.libappbase.widgets.StatusWidget;
import com.hjq.toast.ToastUtils;
public class MainActivity extends Activity { public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
LogView mLogView;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
ToastUtils.show("onCreate");
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.activitymainToolbar1);
setSupportActionBar(toolbar);
CheckBox cbIsDebugMode = findViewById(R.id.activitymainCheckBox1);
cbIsDebugMode.setChecked(GlobalApplication.isDebuging());
mLogView = findViewById(R.id.activitymainLogView1);
if (GlobalApplication.isDebuging()) { mLogView.start(); }
}
@Override
protected void onDestroy() {
super.onDestroy();
Intent intentAPPWidget = new Intent(this, StatusWidget.class);
intentAPPWidget.setAction(StatusWidget.ACTION_STATUS_UPDATE);
sendBroadcast(intentAPPWidget);
}
@Override
protected void onResume() {
LogUtils.d(TAG, "onResume");
super.onResume();
mLogView.start();
}
public void onSwitchDebugMode(View view) {
GlobalApplication.setIsDebuging(this, ((CheckBox)view).isChecked());
}
public void onStartCenter(View view) {
MainService.startMainService(this);
}
public void onStopCenter(View view) {
MainService.stopMainService(this);
}
public void onTestStopWithoutSettingEnable(View view) {
LogUtils.d(TAG, "onTestStopWithoutSettingEnable");
stopService(new Intent(this, SimpleOperateSignalCenterService.class));
}
public void onTestStartWithString(View view) {
LogUtils.d(TAG, "onTestStartWithString");
// 目标服务的包名和类名
String packageName = this.getPackageName();
String serviceClassName = SimpleOperateSignalCenterService.class.getName();
// 构建Intent
Intent intentService = new Intent();
intentService.setComponent(new ComponentName(packageName, serviceClassName));
startService(intentService);
}
public void onSOS(View view) {
Intent intent = new Intent(this, TestService.class);
stopService(intent);
SOS.sosWinBollService(this, new APPSOSBean(getPackageName(), TestService.class.getName()));
}
public void onStartTestService(View view) {
Intent intent = new Intent(this, TestService.class);
intent.setAction(SOS.ACTION_SERVICE_ENABLE);
startService(intent);
} }
public void onStopTestService(View view) {
Intent intent = new Intent(this, TestService.class);
intent.setAction(SOS.ACTION_SERVICE_DISABLE);
startService(intent);
Intent intentStop = new Intent(this, TestService.class);
stopService(intentStop);
}
public void onStopTestServiceNoSettings(View view) {
Intent intent = new Intent(this, TestService.class);
stopService(intent);
}
} }

View File

@ -0,0 +1,79 @@
package cc.winboll.studio.appbase;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/13 19:30:10
*/
import android.content.Context;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
import cc.winboll.studio.appbase.beans.MainServiceBean;
import cc.winboll.studio.appbase.services.MainService;
public class MyTileService extends TileService {
public static final String TAG = "MyTileService";
volatile static MyTileService _MyTileService;
@Override
public void onStartListening() {
super.onStartListening();
_MyTileService = this;
Tile tile = getQsTile();
MainServiceBean bean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (bean != null && bean.isEnable()) {
//MainService.startMainService(context);
tile.setState(Tile.STATE_ACTIVE);
tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud));
} else {
//MainService.stopMainService(context);
tile.setState(Tile.STATE_INACTIVE);
tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud_outline));
}
tile.updateTile();
// Tile tile = getQsTile();
// tile.setState(Tile.STATE_INACTIVE);
// tile.setLabel(getString(R.string.tileservice_name));
// tile.setIcon(android.graphics.drawable.Icon.createWithResource(this, R.drawable.ic_cloud_outline));
// tile.updateTile();
}
@Override
public void onClick() {
super.onClick();
Tile tile = getQsTile();
MainServiceBean bean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (bean == null) {
bean = new MainServiceBean();
}
if (tile.getState() == Tile.STATE_ACTIVE) {
bean.setIsEnable(false);
MainServiceBean.saveBean(this, bean);
MainService.stopMainService(this);
} else if (tile.getState() == Tile.STATE_INACTIVE) {
bean.setIsEnable(true);
MainServiceBean.saveBean(this, bean);
MainService.startMainService(this);
}
updateServiceIconStatus(this);
}
public static void updateServiceIconStatus(Context context) {
if (_MyTileService == null) {
return;
}
Tile tile = _MyTileService.getQsTile();
MainServiceBean bean = MainServiceBean.loadBean(context, MainServiceBean.class);
if (bean != null && bean.isEnable()) {
tile.setState(Tile.STATE_ACTIVE);
tile.setIcon(android.graphics.drawable.Icon.createWithResource(context, R.drawable.ic_cloud));
} else {
tile.setState(Tile.STATE_INACTIVE);
tile.setIcon(android.graphics.drawable.Icon.createWithResource(context, R.drawable.ic_cloud_outline));
}
tile.updateTile();
}
}

View File

@ -0,0 +1,68 @@
package cc.winboll.studio.appbase.beans;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/13 07:06:13
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
public class MainServiceBean extends BaseBean {
public static final String TAG = "MainServiceBean";
boolean isEnable;
public MainServiceBean() {
this.isEnable = false;
}
public void setIsEnable(boolean isEnable) {
this.isEnable = isEnable;
}
public boolean isEnable() {
return isEnable;
}
@Override
public String getName() {
return MainServiceBean.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
MainServiceBean bean = this;
jsonWriter.name("isEnable").value(bean.isEnable());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("isEnable")) {
setIsEnable(jsonReader.nextBoolean());
} else {
return false;
}
}
return true;
}
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
// 结束 JSON 对象
jsonReader.endObject();
return this;
}
}

View File

@ -0,0 +1,72 @@
package cc.winboll.studio.appbase.beans;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/17 10:05:09
* @Describe APPSOSReportBean
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
public class SOSReportBean extends BaseBean {
public static final String TAG = "APPSOSReportBean";
protected String sosReport;
public SOSReportBean() {
this.sosReport = "";
}
public SOSReportBean(String sosReport) {
this.sosReport = sosReport;
}
public void setSosReport(String sosReport) {
this.sosReport = sosReport;
}
public String getSosReport() {
return sosReport;
}
@Override
public String getName() {
return SOSReportBean.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
jsonWriter.name("sosReport").value(getSosReport());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("sosReport")) {
setSosReport(jsonReader.nextString());
} else {
return false;
}
}
return true;
}
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
// 结束 JSON 对象
jsonReader.endObject();
return this;
}
}

View File

@ -0,0 +1,38 @@
package cc.winboll.studio.appbase.handlers;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/14 03:51:40
*/
import android.os.Handler;
import android.os.Message;
import cc.winboll.studio.appbase.services.MainService;
import java.lang.ref.WeakReference;
public class MainServiceHandler extends Handler {
public static final String TAG = "MainServiceHandler";
public static final int MSG_REMINDTHREAD = 0;
WeakReference<MainService> serviceWeakReference;
public MainServiceHandler(MainService service) {
serviceWeakReference = new WeakReference<MainService>(service);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REMINDTHREAD: // 处理下载完成消息更新UI
{
// 显示提醒消息
//
//LogUtils.d(TAG, "显示提醒消息");
MainService mainService = serviceWeakReference.get();
if (mainService != null) {
mainService.appenMessage((String)msg.obj);
}
break;
}
}
}
}

View File

@ -0,0 +1,132 @@
package cc.winboll.studio.appbase.receivers;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/13 06:58:04
* @Describe 主要广播接收器
*/
import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import cc.winboll.studio.appbase.beans.SOSReportBean;
import cc.winboll.studio.appbase.services.MainService;
import cc.winboll.studio.appbase.widgets.SOSWidget;
import cc.winboll.studio.libappbase.AppUtils;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.SOS;
import cc.winboll.studio.libappbase.bean.APPSOSBean;
import com.hjq.toast.ToastUtils;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MainReceiver extends BroadcastReceiver {
public static final String TAG = "MainReceiver";
public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED";
WeakReference<MainService> mwrService;
// 存储电量指示值
// 用于校验电量消息时的电量变化
static volatile int _mnTheQuantityOfElectricityOld = -1;
static volatile boolean _mIsCharging = false;
public MainReceiver(MainService service) {
mwrService = new WeakReference<MainService>(service);
}
@Override
public void onReceive(Context context, Intent intent) {
String szAction = intent.getAction();
if (szAction.equals(ACTION_BOOT_COMPLETED)) {
ToastUtils.show("ACTION_BOOT_COMPLETED");
} else if (szAction.equals(SOS.ACTION_BIND)) {
LogUtils.d(TAG, "ACTION_BIND");
LogUtils.d(TAG, String.format("context.getPackageName() %s", context.getPackageName()));
LogUtils.d(TAG, String.format("intent.getAction() %s", intent.getAction()));
String SOS = intent.getStringExtra("SOS");
LogUtils.d(TAG, String.format("SOS %s", SOS));
if (SOS != null && SOS.equals("Service")) {
String szAPPSOSBean = intent.getStringExtra("APPSOSBean");
LogUtils.d(TAG, String.format("szAPPSOSBean %s", szAPPSOSBean));
if (szAPPSOSBean != null && !szAPPSOSBean.equals("")) {
try {
APPSOSBean bean = APPSOSBean.parseStringToBean(szAPPSOSBean, APPSOSBean.class);
if (bean != null) {
String sosPackage = bean.getSosPackage();
LogUtils.d(TAG, String.format("sosPackage %s", sosPackage));
String sosClassName = bean.getSosClassName();
LogUtils.d(TAG, String.format("sosClassName %s", sosClassName));
mwrService.get().bindSOSConnection(bean);
}
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
}
} else if (intent.getAction().equals(SOS.ACTION_SOS)) {
LogUtils.d(TAG, "ACTION_SOS");
LogUtils.d(TAG, String.format("context.getPackageName() %s", context.getPackageName()));
LogUtils.d(TAG, String.format("intent.getAction() %s", intent.getAction()));
String SOS = intent.getStringExtra("SOS");
LogUtils.d(TAG, String.format("SOS %s", SOS));
if (SOS != null && SOS.equals("Service")) {
String szAPPSOSBean = intent.getStringExtra("APPSOSBean");
LogUtils.d(TAG, String.format("szAPPSOSBean %s", szAPPSOSBean));
if (szAPPSOSBean != null && !szAPPSOSBean.equals("")) {
try {
APPSOSBean bean = APPSOSBean.parseStringToBean(szAPPSOSBean, APPSOSBean.class);
if (bean != null) {
String sosPackage = bean.getSosPackage();
LogUtils.d(TAG, String.format("sosPackage %s", sosPackage));
String sosClassName = bean.getSosClassName();
LogUtils.d(TAG, String.format("sosClassName %s", sosClassName));
Intent intentService = new Intent();
intentService.setComponent(new ComponentName(sosPackage, sosClassName));
context.startService(intentService);
String appName = AppUtils.getAppNameByPackageName(context, sosPackage);
LogUtils.d(TAG, String.format("appName %s", appName));
SOSReportBean appSOSReportBean = new SOSReportBean(appName);
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
String currentTime = sdf.format(new Date());
StringBuilder sbLine = new StringBuilder();
sbLine.append("[");
sbLine.append(currentTime);
sbLine.append("] Power to ");
sbLine.append(appName);
appSOSReportBean.setSosReport(sbLine.toString());
SOSWidget.addAPPSOSReportBean(context, appSOSReportBean);
Intent intentWidget = new Intent(context, SOSWidget.class);
intentWidget.setAction(SOSWidget.ACTION_RELOAD_REPORT);
context.sendBroadcast(intentWidget);
}
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
}
} else {
ToastUtils.show(szAction);
}
}
// 注册 Receiver
//
public void registerAction(MainService service) {
IntentFilter filter=new IntentFilter();
filter.addAction(ACTION_BOOT_COMPLETED);
filter.addAction(SOS.ACTION_SOS);
filter.addAction(SOS.ACTION_BIND);
filter.addAction(SOS.ACTION_SERVICE_ENABLE);
filter.addAction(SOS.ACTION_SERVICE_DISABLE);
//filter.addAction(Intent.ACTION_BATTERY_CHANGED);
service.registerReceiver(this, filter);
}
}

View File

@ -0,0 +1,138 @@
package cc.winboll.studio.appbase.services;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/14 03:38:31
* @Describe 守护进程服务
*/
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import cc.winboll.studio.appbase.beans.MainServiceBean;
import cc.winboll.studio.appbase.services.AssistantService;
import cc.winboll.studio.appbase.services.MainService;
import cc.winboll.studio.libappbase.LogUtils;
import android.os.Binder;
public class AssistantService extends Service {
public static final String TAG = "AssistantService";
MainServiceBean mMainServiceBean;
MyServiceConnection mMyServiceConnection;
MainService mMainService;
boolean isBound = false;
volatile boolean isThreadAlive = false;
public synchronized void setIsThreadAlive(boolean isThreadAlive) {
LogUtils.d(TAG, "setIsThreadAlive(...)");
LogUtils.d(TAG, String.format("isThreadAlive %s", isThreadAlive));
this.isThreadAlive = isThreadAlive;
}
public boolean isThreadAlive() {
return isThreadAlive;
}
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
@Override
public void onCreate() {
LogUtils.d(TAG, "onCreate");
super.onCreate();
//mMyBinder = new MyBinder();
if (mMyServiceConnection == null) {
mMyServiceConnection = new MyServiceConnection();
}
// 设置运行参数
setIsThreadAlive(false);
assistantService();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "call onStartCommand(...)");
assistantService();
return START_STICKY;
}
@Override
public void onDestroy() {
//LogUtils.d(TAG, "onDestroy");
setIsThreadAlive(false);
// 解除绑定
if (isBound) {
unbindService(mMyServiceConnection);
isBound = false;
}
super.onDestroy();
}
// 运行服务内容
//
void assistantService() {
LogUtils.d(TAG, "assistantService()");
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
LogUtils.d(TAG, String.format("mMainServiceBean.isEnable() %s", mMainServiceBean.isEnable()));
if (mMainServiceBean.isEnable()) {
LogUtils.d(TAG, String.format("mIsThreadAlive %s", isThreadAlive()));
if (isThreadAlive() == false) {
// 设置运行状态
setIsThreadAlive(true);
// 唤醒和绑定主进程
wakeupAndBindMain();
}
}
}
// 唤醒和绑定主进程
//
void wakeupAndBindMain() {
LogUtils.d(TAG, "wakeupAndBindMain()");
// 绑定服务的Intent
Intent intent = new Intent(this, MainService.class);
startService(new Intent(this, MainService.class));
bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT);
// startService(new Intent(this, MainService.class));
// bindService(new Intent(AssistantService.this, MainService.class), mMyServiceConnection, Context.BIND_IMPORTANT);
}
// 主进程与守护进程连接时需要用到此类
//
class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LogUtils.d(TAG, "onServiceConnected(...)");
MainService.MyBinder binder = (MainService.MyBinder) service;
mMainService = binder.getService();
isBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtils.d(TAG, "onServiceDisconnected(...)");
mMainServiceBean = MainServiceBean.loadBean(AssistantService.this, MainServiceBean.class);
if (mMainServiceBean.isEnable()) {
wakeupAndBindMain();
}
isBound = false;
mMainService = null;
}
}
// 用于返回服务实例的Binder
public class MyBinder extends Binder {
AssistantService getService() {
LogUtils.d(TAG, "AssistantService MyBinder getService()");
return AssistantService.this;
}
}
}

View File

@ -0,0 +1,326 @@
package cc.winboll.studio.appbase.services;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/13 06:56:41
* @Describe 拨号主服务
* 参考:
* 进程保活-双进程守护的正确姿势
* https://blog.csdn.net/sinat_35159441/article/details/75267380
* Android Service之onStartCommand方法研究
* https://blog.csdn.net/cyp331203/article/details/38920491
*/
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import cc.winboll.studio.appbase.MyTileService;
import cc.winboll.studio.appbase.beans.MainServiceBean;
import cc.winboll.studio.appbase.handlers.MainServiceHandler;
import cc.winboll.studio.appbase.receivers.MainReceiver;
import cc.winboll.studio.appbase.services.AssistantService;
import cc.winboll.studio.appbase.threads.MainServiceThread;
import cc.winboll.studio.appbase.widgets.SOSWidget;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.bean.APPSOSBean;
import java.util.ArrayList;
public class MainService extends Service {
public static final String TAG = "MainService";
public static final int MSG_UPDATE_STATUS = 0;
static MainService _mControlCenterService;
volatile boolean isServiceRunning;
MainServiceBean mMainServiceBean;
MainServiceThread mMainServiceThread;
MainServiceHandler mMainServiceHandler;
MyServiceConnection mMyServiceConnection;
AssistantService mAssistantService;
boolean isBound = false;
MainReceiver mMainReceiver;
ArrayList<SOSConnection> mSOSConnectionList;
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
public MainServiceThread getRemindThread() {
return mMainServiceThread;
}
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(TAG, "onCreate()");
mSOSConnectionList = new ArrayList<SOSConnection>();
_mControlCenterService = MainService.this;
isServiceRunning = false;
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (mMyServiceConnection == null) {
mMyServiceConnection = new MyServiceConnection();
}
mMainServiceHandler = new MainServiceHandler(this);
// 运行服务内容
mainService();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "onStartCommand(...)");
// 运行服务内容
mainService();
return (mMainServiceBean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
}
// 运行服务内容
//
void mainService() {
LogUtils.d(TAG, "mainService()");
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (mMainServiceBean.isEnable() && isServiceRunning == false) {
LogUtils.d(TAG, "mainService() start running");
isServiceRunning = true;
// 唤醒守护进程
wakeupAndBindAssistant();
if (mMainReceiver == null) {
// 注册广播接收器
mMainReceiver = new MainReceiver(this);
mMainReceiver.registerAction(this);
}
// 启动小部件
Intent intentTimeWidget = new Intent(this, SOSWidget.class);
intentTimeWidget.setAction(SOSWidget.ACTION_RELOAD_REPORT);
this.sendBroadcast(intentTimeWidget);
startMainServiceThread();
MyTileService.updateServiceIconStatus(this);
LogUtils.i(TAG, "Main Service Is Start.");
}
}
// 唤醒和绑定守护进程
//
void wakeupAndBindAssistant() {
LogUtils.d(TAG, "wakeupAndBindAssistant()");
// if (ServiceUtils.isServiceAlive(getApplicationContext(), AssistantService.class.getName()) == false) {
// startService(new Intent(MainService.this, AssistantService.class));
// //LogUtils.d(TAG, "call wakeupAndBindAssistant() : Binding... AssistantService");
// bindService(new Intent(MainService.this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT);
// }
Intent intent = new Intent(this, AssistantService.class);
startService(intent);
// 绑定服务的Intent
//Intent intent = new Intent(this, AssistantService.class);
bindService(intent, mMyServiceConnection, Context.BIND_IMPORTANT);
// Intent intent = new Intent(this, AssistantService.class);
// startService(intent);
// LogUtils.d(TAG, "startService(intent)");
// bindService(new Intent(this, AssistantService.class), mMyServiceConnection, Context.BIND_IMPORTANT);
}
// 开启提醒铃声线程
//
public void startMainServiceThread() {
LogUtils.d(TAG, "startMainServiceThread");
if (mMainServiceThread == null) {
mMainServiceThread = new MainServiceThread(this, mMainServiceHandler);
LogUtils.d(TAG, "new MainServiceThread");
} else {
if (mMainServiceThread.isExist() == true) {
mMainServiceThread = new MainServiceThread(this, mMainServiceHandler);
LogUtils.d(TAG, "renew MainServiceThread");
} else {
// 提醒进程正在进行中就更新状态后退出
LogUtils.d(TAG, "A mMainServiceThread running.");
return;
}
}
mMainServiceThread.start();
}
public void stopRemindThread() {
if (mMainServiceThread != null) {
mMainServiceThread.setIsExist(true);
mMainServiceThread = null;
}
}
@Override
public void onDestroy() {
//LogUtils.d(TAG, "onDestroy");
mMainServiceBean = MainServiceBean.loadBean(this, MainServiceBean.class);
if (mMainServiceBean.isEnable() == false) {
// 设置运行状态
isServiceRunning = false;// 解除绑定
if (isBound) {
unbindService(mMyServiceConnection);
isBound = false;
}
// 停止守护进程
Intent intent = new Intent(this, AssistantService.class);
stopService(intent);
// 停止Receiver
if (mMainReceiver != null) {
unregisterReceiver(mMainReceiver);
mMainReceiver = null;
}
// 停止前台通知栏
stopForeground(true);
// 停止消息提醒进程
stopRemindThread();
MyTileService.updateServiceIconStatus(this);
super.onDestroy();
//LogUtils.d(TAG, "onDestroy done");
}
}
public void bindSOSConnection(APPSOSBean bean) {
LogUtils.d(TAG, "bindSOSConnection(...)");
// 清理旧的绑定链接
for (int i = mSOSConnectionList.size() - 1; i > -1; i--) {
SOSConnection item = mSOSConnectionList.get(i);
if (item.isBindToAPPSOSBean(bean)) {
LogUtils.d(TAG, "Bind Servive exist.");
unbindService(item);
mSOSConnectionList.remove(i);
}
}
// 绑定服务
SOSConnection sosConnection = new SOSConnection();
Intent intentService = new Intent();
intentService.setComponent(new ComponentName(bean.getSosPackage(), bean.getSosClassName()));
bindService(intentService, sosConnection, Context.BIND_IMPORTANT);
mSOSConnectionList.add(sosConnection);
Intent intentWidget = new Intent(this, SOSWidget.class);
intentWidget.setAction(SOSWidget.ACTION_WAKEUP_SERVICE);
APPSOSBean appSOSBean = new APPSOSBean(bean.getSosPackage(), bean.getSosClassName());
intentWidget.putExtra("APPSOSBean", appSOSBean.toString());
sendBroadcast(intentWidget);
}
public class SOSConnection implements ServiceConnection {
ComponentName mComponentName;
boolean isBindToAPPSOSBean(APPSOSBean bean) {
return mComponentName != null
&& mComponentName.getClassName().equals(bean.getSosClassName())
&& mComponentName.getPackageName().equals(bean.getSosPackage());
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LogUtils.d(TAG, "onServiceConnected(...)");
mComponentName = name;
LogUtils.d(TAG, String.format("onServiceConnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName()));
}
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtils.d(TAG, "onServiceDisconnected(...)");
LogUtils.d(TAG, String.format("onServiceDisconnected : \ngetClassName %s\ngetPackageName %s", name.getClassName(), name.getPackageName()));
// 尝试无参数启动一下服务
String sosPackage = mComponentName.getPackageName();
LogUtils.d(TAG, String.format("sosPackage %s", sosPackage));
String sosClassName = mComponentName.getClassName();
LogUtils.d(TAG, String.format("sosClassName %s", sosClassName));
Intent intentService = new Intent();
intentService.setComponent(new ComponentName(sosPackage, sosClassName));
startService(intentService);
}
}
// 主进程与守护进程连接时需要用到此类
//
private class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
LogUtils.d(TAG, "onServiceConnected(...)");
AssistantService.MyBinder binder = (AssistantService.MyBinder) service;
mAssistantService = binder.getService();
isBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
LogUtils.d(TAG, "onServiceDisconnected(...)");
if (mMainServiceBean.isEnable()) {
// 唤醒守护进程
wakeupAndBindAssistant();
}
isBound = false;
mAssistantService = null;
}
}
// 用于返回服务实例的Binder
public class MyBinder extends Binder {
MainService getService() {
LogUtils.d(TAG, "MainService MyBinder getService()");
return MainService.this;
}
}
// //
// // 启动服务
// //
// public static void startControlCenterService(Context context) {
// Intent intent = new Intent(context, MainService.class);
// context.startForegroundService(intent);
// }
//
// //
// // 停止服务
// //
// public static void stopControlCenterService(Context context) {
// Intent intent = new Intent(context, MainService.class);
// context.stopService(intent);
// }
public void appenMessage(String message) {
LogUtils.d(TAG, String.format("Message : %s", message));
}
public static void stopMainService(Context context) {
LogUtils.d(TAG, "stopMainService");
MainServiceBean bean = new MainServiceBean();
bean.setIsEnable(false);
MainServiceBean.saveBean(context, bean);
context.stopService(new Intent(context, MainService.class));
}
public static void startMainService(Context context) {
LogUtils.d(TAG, "startMainService");
MainServiceBean bean = new MainServiceBean();
bean.setIsEnable(true);
MainServiceBean.saveBean(context, bean);
context.startService(new Intent(context, MainService.class));
}
}

View File

@ -0,0 +1,54 @@
package cc.winboll.studio.appbase.threads;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/14 03:46:44
*/
import android.content.Context;
import cc.winboll.studio.appbase.handlers.MainServiceHandler;
import cc.winboll.studio.libappbase.LogUtils;
import java.lang.ref.WeakReference;
public class MainServiceThread extends Thread {
public static final String TAG = "MainServiceThread";
Context mContext;
// 控制线程是否退出的标志
volatile boolean isExist = false;
// 服务Handler, 用于线程发送消息使用
WeakReference<MainServiceHandler> mwrMainServiceHandler;
public void setIsExist(boolean isExist) {
this.isExist = isExist;
}
public boolean isExist() {
return isExist;
}
public MainServiceThread(Context context, MainServiceHandler handler) {
mContext = context;
mwrMainServiceHandler = new WeakReference<MainServiceHandler>(handler);
}
@Override
public void run() {
LogUtils.d(TAG, "run()");
while (!isExist()) {
//ToastUtils.show("run()");
//LogUtils.d(TAG, "run()");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
LogUtils.d(TAG, "run() exit.");
}
}

View File

@ -0,0 +1,184 @@
package cc.winboll.studio.appbase.widgets;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/15 14:41:25
* @Describe TimeWidget
*/
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import cc.winboll.studio.appbase.R;
import cc.winboll.studio.appbase.beans.SOSReportBean;
import cc.winboll.studio.libappbase.AppUtils;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.bean.APPSOSBean;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
public class SOSWidget extends AppWidgetProvider {
public static final String TAG = "SOSWidget";
public static final String ACTION_WAKEUP_SERVICE = "cc.winboll.studio.appbase.widgets.SOSWidget.ACTION_WAKEUP_SERVICE";
public static final String ACTION_RELOAD_REPORT = "cc.winboll.studio.appbase.widgets.SOSWidget.ACTION_RELOAD_REPORT";
volatile static ArrayList<SOSReportBean> _SOSReportBeanList;
final static int _MAX_PAGES = 10;
final static int _OnePageLinesCount = 5;
volatile static int _CurrentPageIndex = 0;
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
initAPPSOSReportBeanList(context);
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
initAPPSOSReportBeanList(context);
if (intent.getAction().equals(ACTION_RELOAD_REPORT)) {
LogUtils.d(TAG, "ACTION_RELOAD_REPORT");
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, SOSWidget.class));
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}else if (intent.getAction().equals(ACTION_WAKEUP_SERVICE)) {
LogUtils.d(TAG, "ACTION_WAKEUP_SERVICE");
String szAPPSOSBean = intent.getStringExtra("APPSOSBean");
LogUtils.d(TAG, String.format("szAPPSOSBean %s", szAPPSOSBean));
if (szAPPSOSBean != null && !szAPPSOSBean.equals("")) {
try {
APPSOSBean bean = APPSOSBean.parseStringToBean(szAPPSOSBean, APPSOSBean.class);
if (bean != null) {
String sosPackage = bean.getSosPackage();
LogUtils.d(TAG, String.format("sosPackage %s", sosPackage));
String sosClassName = bean.getSosClassName();
LogUtils.d(TAG, String.format("sosClassName %s", sosClassName));
String appName = AppUtils.getAppNameByPackageName(context, sosPackage);
LogUtils.d(TAG, String.format("appName %s", appName));
SOSReportBean appSOSReportBean = new SOSReportBean(appName);
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
String currentTime = sdf.format(new Date());
StringBuilder sbLine = new StringBuilder();
sbLine.append("[");
sbLine.append(currentTime);
sbLine.append("] Wake up ");
sbLine.append(appName);
appSOSReportBean.setSosReport(sbLine.toString());
addAPPSOSReportBean(context, appSOSReportBean);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, SOSWidget.class));
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
}
}
//
// 加入新报告信息
//
public synchronized static void addAPPSOSReportBean(Context context, SOSReportBean bean) {
initAPPSOSReportBeanList(context);
_SOSReportBeanList.add(0, bean);
// 控制记录总数
while (_SOSReportBeanList.size() > _MAX_PAGES * _OnePageLinesCount) {
_SOSReportBeanList.remove(_SOSReportBeanList.size() - 1);
}
SOSReportBean.saveBeanList(context, _SOSReportBeanList, SOSReportBean.class);
}
synchronized static void initAPPSOSReportBeanList(Context context) {
if (_SOSReportBeanList == null) {
_SOSReportBeanList = new ArrayList<SOSReportBean>();
SOSReportBean.loadBeanList(context, _SOSReportBeanList, SOSReportBean.class);
}
if (_SOSReportBeanList == null) {
_SOSReportBeanList = new ArrayList<SOSReportBean>();
SOSReportBean.saveBeanList(context, _SOSReportBeanList, SOSReportBean.class);
}
}
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
LogUtils.d(TAG, "updateAppWidget(...)");
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_sos);
//设置按钮点击事件
Intent intentPre = new Intent(context, SOSWidgetClickListener.class);
intentPre.setAction(SOSWidgetClickListener.ACTION_PRE);
PendingIntent pendingIntentPre = PendingIntent.getBroadcast(context, 0, intentPre, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.widget_button_pre, pendingIntentPre);
Intent intentNext = new Intent(context, SOSWidgetClickListener.class);
intentNext.setAction(SOSWidgetClickListener.ACTION_NEXT);
PendingIntent pendingIntentNext = PendingIntent.getBroadcast(context, 0, intentNext, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.widget_button_next, pendingIntentNext);
views.setTextViewText(R.id.infoTextView, getPageInfo());
views.setTextViewText(R.id.sosReportTextView, getMessage());
appWidgetManager.updateAppWidget(appWidgetId, views);
}
public static String getMessage() {
ArrayList<String> msgTemp = new ArrayList<String>();
if (_SOSReportBeanList != null) {
int start = _OnePageLinesCount * _CurrentPageIndex;
start = _SOSReportBeanList.size() > start ? start : _SOSReportBeanList.size() - 1;
for (int i = start, j = 0; i < _SOSReportBeanList.size() && j < _OnePageLinesCount && start > -1; i++, j++) {
msgTemp.add(_SOSReportBeanList.get(i).getSosReport());
}
String message = String.join("\n", msgTemp);
return message;
}
return "";
}
public static void prePage(Context context) {
if (_SOSReportBeanList != null) {
if (_CurrentPageIndex > 0) {
_CurrentPageIndex = _CurrentPageIndex - 1;
}
Intent intentWidget = new Intent(context, SOSWidget.class);
intentWidget.setAction(SOSWidget.ACTION_RELOAD_REPORT);
context.sendBroadcast(intentWidget);
}
}
public static void nextPage(Context context) {
if (_SOSReportBeanList != null) {
if ((_CurrentPageIndex + 1) * _OnePageLinesCount < _SOSReportBeanList.size()) {
_CurrentPageIndex = _CurrentPageIndex + 1;
}
Intent intentWidget = new Intent(context, SOSWidget.class);
intentWidget.setAction(SOSWidget.ACTION_RELOAD_REPORT);
context.sendBroadcast(intentWidget);
}
}
String getPageInfo() {
if (_SOSReportBeanList == null) {
return "0/0";
}
int leftCount = _SOSReportBeanList.size() % _OnePageLinesCount;
int currentPageCount = _SOSReportBeanList.size() / _OnePageLinesCount + (leftCount == 0 ?0: 1);
return String.format("%d/%d", _CurrentPageIndex + 1, currentPageCount);
}
}

View File

@ -0,0 +1,36 @@
package cc.winboll.studio.appbase.widgets;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/15 17:20:46
* @Describe WidgetButtonClickListener
*/
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import cc.winboll.studio.libappbase.LogUtils;
public class SOSWidgetClickListener extends BroadcastReceiver {
public static final String TAG = "SOSWidgetClickListener";
public static final String ACTION_PRE = "cc.winboll.studio.appbase.widgets.SOSWidgetClickListener.ACTION_PRE";
public static final String ACTION_NEXT = "cc.winboll.studio.appbase.widgets.SOSWidgetClickListener.ACTION_NEXT";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null) {
LogUtils.d(TAG, String.format("action %s", action));
return;
}
if (action.equals(ACTION_PRE)) {
LogUtils.d(TAG, "ACTION_PRE");
SOSWidget.prePage(context);
} else if (action.equals(ACTION_NEXT)) {
LogUtils.d(TAG, "ACTION_NEXT");
SOSWidget.nextPage(context);
} else {
LogUtils.d(TAG, String.format("action %s", action));
}
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ff000000"
android:pathData="M6.5,20Q4.22,20 2.61,18.43 1,16.85 1,14.58 1,12.63 2.17,11.1 3.35,9.57 5.25,9.15 5.88,6.85 7.75,5.43 9.63,4 12,4 14.93,4 16.96,6.04 19,8.07 19,11 20.73,11.2 21.86,12.5 23,13.78 23,15.5 23,17.38 21.69,18.69 20.38,20 18.5,20Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ff000000"
android:pathData="M6.5,20Q4.22,20 2.61,18.43 1,16.85 1,14.58 1,12.63 2.17,11.1 3.35,9.57 5.25,9.15 5.88,6.85 7.75,5.43 9.63,4 12,4 14.93,4 16.96,6.04 19,8.07 19,11 20.73,11.2 21.86,12.5 23,13.78 23,15.5 23,17.38 21.69,18.69 20.38,20 18.5,20M6.5,18H18.5Q19.55,18 20.27,17.27 21,16.55 21,15.5 21,14.45 20.27,13.73 19.55,13 18.5,13H17V11Q17,8.93 15.54,7.46 14.08,6 12,6 9.93,6 8.46,7.46 7,8.93 7,11H6.5Q5.05,11 4.03,12.03 3,13.05 3,14.5 3,15.95 4.03,17 5.05,18 6.5,18M12,12Z"/>
</vector>

View File

@ -4,13 +4,146 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"> android:gravity="center">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/activitymainToolbar1"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:gravity="center_horizontal">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Hello, WinBoll!" android:text="Hello, WinBoll!"/>
android:onClick="onHello"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Android版本10的代号是“Q”API级别是29。 Android 10开始谷歌不再公开使用甜品作为版本代号但内部仍保留了大量与“Q”相关的元素。Android 10本身并没有严格对应某个特定的Java版本但在开发Android 10应用时通常可以使用Java 8或更高版本。
Java 8为Android开发带来了诸如Lambda表达式、方法引用等新特性能提高开发效率和代码可读性与Android 10开发适配良好。Java 9及更高版本也可用于Android 10开发能使用一些新的语言特性和API但可能需要注意兼容性和配置问题。"/>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right|center_vertical">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Debug Mode"
android:layout_weight="1.0"
android:onClick="onSwitchDebugMode"
android:id="@+id/activitymainCheckBox1"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test Application CrashReport"
android:textAllCaps="false"
android:onClick="onTestApplicationCrashReport"/>
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="400dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StartTestService"
android:textAllCaps="false"
android:onClick="onStartTestService"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StopTestService"
android:textAllCaps="false"
android:onClick="onStopTestService"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StopTestServiceNoSettings"
android:textAllCaps="false"
android:onClick="onStopTestServiceNoSettings"/>
</LinearLayout>
</HorizontalScrollView>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StartCenter"
android:textAllCaps="false"
android:onClick="onStartCenter"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="StopCenter"
android:textAllCaps="false"
android:onClick="onStopCenter"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TestStopWithoutSettingEnable"
android:textAllCaps="false"
android:onClick="onTestStopWithoutSettingEnable"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TestStartWithString"
android:textAllCaps="false"
android:onClick="onTestStartWithString"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SOS"
android:textAllCaps="false"
android:onClick="onSOS"/>
</LinearLayout>
</ScrollView>
<cc.winboll.studio.libappbase.LogView
android:layout_weight="1.0"
android:layout_height="0dp"
android:layout_width="match_parent"
android:id="@+id/activitymainLogView1"/>
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
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"
android:orientation="vertical"
android:background="#FFFFFFFF">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/infoTextView"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="⇦"
android:id="@+id/widget_button_pre"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="⇨"
android:id="@+id/widget_button_next"/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:id="@+id/sosReportTextView"
android:layout_weight="1.0"/>
</LinearLayout>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="@android:style/Theme.Material.Light.DarkActionBar">
<item name="android:colorPrimary">@color/colorPrimary</item>
<item name="android:colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="android:colorAccent">@color/colorAccent</item>
<item name="android:navigationBarColor">?android:colorPrimary</item>
</style>
</resources>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="colorPrimary">#009688</color> <color name="colorPrimary">#005800FF</color>
<color name="colorPrimaryDark">#00796B</color> <color name="colorPrimaryDark">#005800FF</color>
<color name="colorAccent">#FF9800</color> <color name="colorAccent">#005800FF</color>
</resources> </resources>

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">AppBase</string> <string name="app_name">AppBase</string>
<string name="tileservice_name">WinBoll</string>
</resources> </resources>

View File

@ -1,5 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="AppTheme" parent="@android:style/Theme.Holo.Light.DarkActionBar"> <style name="AppTheme" parent="APPBaseTheme">
<item name="attrColorPrimary">@color/colorPrimary</item>
<item name="themeGlobalCrashActivity">@style/MyGlobalCrashActivityTheme</item>
</style>
<style name="MyGlobalCrashActivityTheme" parent="GlobalCrashActivityTheme">
<item name="colorTittle">#FFFFFFFF</item>
<item name="colorTittleBackgound">#FF00A4B3</item>
<item name="colorText">#FFFFFFFF</item>
<item name="colorTextBackgound">#FF000000</item>
</style> </style>
</resources> </resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="200dp"
android:minHeight="100dp"
android:updatePeriodMillis="1000"
android:initialLayout="@layout/widget_sos">
</appwidget-provider>

View File

@ -1,8 +1,21 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
repositories { repositories {
google() maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/google/' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin/' }
maven { url 'https://dl.bintray.com/ppartisan/maven/' }
maven { url "https://clojars.org/repo/" }
maven { url "https://jitpack.io" }
mavenCentral() mavenCentral()
google()
mavenLocal()
// Nexus Maven
// "WinBoll Release"
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
// "WinBoll Snapshot"
maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.2.1' classpath 'com.android.tools.build:gradle:7.2.1'
@ -13,11 +26,12 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
// Nexus Maven maven {
// "WinBoll Release" url "https://mirrors.tencent.com/repository/maven/tencent_public/"
maven { url "https://nexus.winboll.cc/repository/maven-public/" } }
// "WinBoll Snapshot" maven {
maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" } url "https://mirrors.tencent.com/repository/maven/tencent_public_snapshots"
}
maven { url 'https://maven.aliyun.com/repository/public/' } maven { url 'https://maven.aliyun.com/repository/public/' }
maven { url 'https://maven.aliyun.com/repository/google/' } maven { url 'https://maven.aliyun.com/repository/google/' }
@ -28,6 +42,12 @@ allprojects {
mavenCentral() mavenCentral()
google() google()
mavenLocal() mavenLocal()
// Nexus Maven
// "WinBoll Release"
maven { url "https://nexus.winboll.cc/repository/maven-public/" }
// "WinBoll Snapshot"
maven { url "https://nexus.winboll.cc/repository/maven-snapshots/" }
} }
ext { ext {
// //
@ -69,6 +89,14 @@ allprojects {
} }
} }
} }
subprojects {
tasks.withType(JavaCompile) {
options.compilerArgs << "-parameters"
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
} }
task clean(type: Delete) { task clean(type: Delete) {

View File

@ -6,7 +6,7 @@
# http://www.gradle.org/docs/current/userguide/build_environment.html # http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m org.gradle.jvmargs=-Xmx4096m
# When configured, Gradle will run in incubating parallel mode. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
@ -18,7 +18,9 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX # Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true android.enableJetifier=true
#org.gradle.caching=true org.gradle.caching=true
android.disableAutomaticComponentCreation=true android.disableAutomaticComponentCreation=true
android.injected.testOnly=false

View File

@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl = https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip distributionUrl = https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -4,14 +4,12 @@ apply from: '../.winboll/winboll_lib_build.gradle'
apply from: '../.winboll/winboll_lint_build.gradle' apply from: '../.winboll/winboll_lint_build.gradle'
android { android {
namespace 'cc.winboll.studio' compileSdkVersion 30
buildToolsVersion "30.0.3"
compileSdkVersion 32
buildToolsVersion "33.0.3"
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 26
targetSdkVersion 30 targetSdkVersion 29
} }
buildTypes { buildTypes {
release { release {
@ -19,12 +17,16 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
} }
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
} }
dependencies { dependencies {
api 'com.github.getActivity:ToastUtils:10.5'
api 'androidx.appcompat:appcompat:1.3.1'
api 'androidx.vectordrawable:vectordrawable:1.1.0'
api 'androidx.vectordrawable:vectordrawable-animated:1.1.0'
api 'androidx.fragment:fragment:1.1.0'
api 'com.google.android.material:material:1.1.0'
api fileTree(dir: 'libs', include: ['*.jar']) api fileTree(dir: 'libs', include: ['*.jar'])
} }

View File

@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle #Created by .winboll/winboll_app_build.gradle
#Sun Jan 05 07:55:11 HKT 2025 #Tue Feb 25 16:51:09 HKT 2025
stageCount=17 stageCount=3
libraryProject=winboll-shared libraryProject=libappbase
baseVersion=1.8 baseVersion=2.0
publishVersion=1.8.16 publishVersion=2.0.2
buildCount=0 buildCount=0
baseBetaVersion=1.8.17 baseBetaVersion=2.0.3

View File

@ -1,18 +1,78 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version='1.0' encoding='utf-8'?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest
package="cc.winboll.studio" > xmlns:android="http://schemas.android.com/apk/res/android"
package="cc.winboll.studio.libappbase">
<!-- 拥有完全的网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 发送持久广播 -->
<uses-permission android:name="android.permission.BROADCAST_STICKY"/>
<application> <application>
<activity <activity
android:name=".CrashHandler$CrashActiviy" android:name=".CrashHandler$CrashActivity"
android:label="CrashActiviy" android:label="CrashActivity"
android:launchMode="standard"/> android:launchMode="standard"/>
<activity <activity
android:name=".libappbase.LibraryActivity" android:name=".GlobalCrashActivity"
android:label="@string/lib_name" > android:label="GlobalCrashActivity"
</activity> android:launchMode="standard"/>
<activity android:name=".LogActivity"/>
<service
android:name=".SimpleOperateSignalCenterService"
android:exported="true">
</service>
<service
android:name=".services.TestService"
android:exported="true"/>
<receiver android:name=".receiver.MyBroadcastReceiver">
<intent-filter>
<action android:name="cc.winboll.studio.libappbase.action.SOS"/>
</intent-filter>
</receiver>
<receiver
android:name=".widgets.StatusWidget"
android:exported="true">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
<action android:name="cc.winboll.studio.libappbase.widgets.StatusWidget.ACTION_STATUS_UPDATE"/>
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_provider_info_status"/>
</receiver>
<receiver
android:name=".widgets.StatusWidgetClickListener"
android:exported="true">
<intent-filter>
<action android:name="cc.winboll.studio.libappbase.widgets.StatusWidgetClickListener.ACTION_IVAPP"/>
</intent-filter>
</receiver>
</application> </application>
</manifest> </manifest>

View File

@ -1,215 +0,0 @@
package cc.winboll.studio;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2024/08/12 13:22:12
* @Describe 异常处理类
*/
import android.app.Activity;
import android.app.Application;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
import android.widget.TextView;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public final class CrashHandler {
public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();
public static void init(Application app) {
init(app, null);
}
public static void init(final Application app, final String crashDir) {
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler(){
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
try {
tryUncaughtException(thread, throwable);
} catch (Throwable e) {
e.printStackTrace();
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null)
DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
}
}
private void tryUncaughtException(Thread thread, Throwable throwable) {
final String time = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss", Locale.getDefault()).format(new Date());
File crashFile = new File(TextUtils.isEmpty(crashDir) ? new File(app.getExternalFilesDir(null), "crash")
: new File(crashDir), "crash_" + time + ".txt");
String versionName = "unknown";
long versionCode = 0;
try {
PackageInfo packageInfo = app.getPackageManager().getPackageInfo(app.getPackageName(), 0);
versionName = packageInfo.versionName;
versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode()
: packageInfo.versionCode;
} catch (PackageManager.NameNotFoundException ignored) {}
String fullStackTrace; {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
fullStackTrace = sw.toString();
pw.close();
}
StringBuilder sb = new StringBuilder();
sb.append("************* Crash Head ****************\n");
sb.append("Time Of Crash : ").append(time).append("\n");
sb.append("Device Manufacturer: ").append(Build.MANUFACTURER).append("\n");
sb.append("Device Model : ").append(Build.MODEL).append("\n");
sb.append("Android Version : ").append(Build.VERSION.RELEASE).append("\n");
sb.append("Android SDK : ").append(Build.VERSION.SDK_INT).append("\n");
sb.append("App VersionName : ").append(versionName).append("\n");
sb.append("App VersionCode : ").append(versionCode).append("\n");
sb.append("************* Crash Head ****************\n");
sb.append("\n").append(fullStackTrace);
String errorLog = sb.toString();
try {
writeFile(crashFile, errorLog);
} catch (IOException ignored) {}
gotoCrashActiviy: {
Intent intent = new Intent(app, CrashActiviy.class);
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK
);
intent.putExtra(CrashActiviy.EXTRA_CRASH_INFO, errorLog);
try {
app.startActivity(intent);
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null)
DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
}
}
}
private void writeFile(File file, String content) throws IOException {
File parentFile = file.getParentFile();
if (parentFile != null && !parentFile.exists()) {
parentFile.mkdirs();
}
file.createNewFile();
FileOutputStream fos = new FileOutputStream(file);
fos.write(content.getBytes());
try {
fos.close();
} catch (IOException e) {}
}
});
}
public static final class CrashActiviy extends Activity implements MenuItem.OnMenuItemClickListener {
private static final String EXTRA_CRASH_INFO = "crashInfo";
private static final int MENUITEM_COPY = 0;
private static final int MENUITEM_RESTART = 1;
private String mLog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLog = getIntent().getStringExtra(EXTRA_CRASH_INFO);
setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
setContentView: {
ScrollView contentView = new ScrollView(this);
contentView.setFillViewport(true);
HorizontalScrollView hw = new HorizontalScrollView(this);
hw.setBackgroundColor(Color.GRAY);
TextView message = new TextView(this); {
int padding = dp2px(16);
message.setPadding(padding, padding, padding, padding);
message.setText(mLog);
message.setTextIsSelectable(true);
}
hw.addView(message);
contentView.addView(hw, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
setContentView(contentView);
}
}
@Override
public void onBackPressed() {
restart();
}
private void restart() {
PackageManager pm = getPackageManager();
Intent intent = pm.getLaunchIntentForPackage(getPackageName());
if (intent != null) {
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK
);
startActivity(intent);
}
finish();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
private int dp2px(final float dpValue) {
final float scale = Resources.getSystem().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case MENUITEM_COPY:
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
break;
case MENUITEM_RESTART:
restart();
break;
}
return false;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, MENUITEM_COPY, 0, "Copy").setOnMenuItemClickListener(this)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add(0, MENUITEM_RESTART, 0, "Restart").setOnMenuItemClickListener(this)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
return true;
}
}
}

View File

@ -1,95 +0,0 @@
package cc.winboll.studio;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2025/01/05 10:10:23
* @Describe 全局应用类
*/
import android.app.Activity;
import android.app.Application;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
public class GlobalApplication extends Application {
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
@Override
public void onCreate() {
super.onCreate();
CrashHandler.init(this);
}
public static void write(InputStream input, OutputStream output) throws IOException {
byte[] buf = new byte[1024 * 8];
int len;
while ((len = input.read(buf)) != -1) {
output.write(buf, 0, len);
}
}
public static void write(File file, byte[] data) throws IOException {
File parent = file.getParentFile();
if (parent != null && !parent.exists()) parent.mkdirs();
ByteArrayInputStream input = new ByteArrayInputStream(data);
FileOutputStream output = new FileOutputStream(file);
try {
write(input, output);
} finally {
closeIO(input, output);
}
}
public static String toString(InputStream input) throws IOException {
ByteArrayOutputStream output = new ByteArrayOutputStream();
write(input, output);
try {
return output.toString("UTF-8");
} finally {
closeIO(input, output);
}
}
public static void closeIO(Closeable... closeables) {
for (Closeable closeable : closeables) {
try {
if (closeable != null) closeable.close();
} catch (IOException ignored) {}
}
}
}

View File

@ -0,0 +1,28 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/15 20:05:03
* @Describe AppUtils
*/
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
public class AppUtils {
public static final String TAG = "AppUtils";
public static String getAppNameByPackageName(Context context, String packageName) {
PackageManager packageManager = context.getPackageManager();
try {
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0);
return (String) packageManager.getApplicationLabel(applicationInfo);
} catch (NameNotFoundException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
return "";
}
}
}

View File

@ -0,0 +1,287 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2025/01/15 11:11:52
* @Describe Json Bean 基础类
*/
import android.content.Context;
import android.util.JsonReader;
import android.util.JsonWriter;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
public abstract class BaseBean<T extends BaseBean> {
public static final String TAG = "BaseBean";
static final String BEAN_NAME = "BeanName";
public BaseBean() {}
public abstract String getName();
public String getBeanJsonFilePath(Context context) {
return context.getExternalFilesDir(TAG) + "/" + getName() + ".json";
}
public String getBeanListJsonFilePath(Context context) {
return context.getExternalFilesDir(TAG) + "/" + getName() + "_List.json";
}
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
jsonWriter.name(BEAN_NAME).value(getName());
}
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
return false;
}
abstract public T readBeanFromJsonReader(JsonReader jsonReader) throws IOException;
public static <T extends BaseBean> String checkIsTheSameBeanListAndFile(String szFilePath, Class<T> clazz) {
StringBuilder sbResult = new StringBuilder();
String szErrorInfo = "Check Is The Same Bean List And File Error : ";
try {
int nSameCount = 0;
int nBeanListCout = 0;
T beanTemp = clazz.newInstance();
String szBeanSimpleName = beanTemp.getName();
String szListJson = UTF8FileUtils.readStringFromFile(szFilePath);
StringReader stringReader = new StringReader(szListJson);
JsonReader jsonReader = new JsonReader(stringReader);
jsonReader.beginArray();
while (jsonReader.hasNext()) {
nBeanListCout++;
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (name.equals(BEAN_NAME)) {
if (szBeanSimpleName.equals(jsonReader.nextString())) {
nSameCount++;
}
} else {
jsonReader.skipValue();
}
}
jsonReader.endObject();
}
jsonReader.endArray();
// 返回检查结果
if (nSameCount == nBeanListCout) {
// 检查一致直接返回空串
return "";
} else {
// 检查不一致返回对比信息
sbResult.append("Total : ");
sbResult.append(nBeanListCout);
sbResult.append(" Diff : ");
sbResult.append(nBeanListCout - nSameCount);
}
} catch (InstantiationException e) {
sbResult.append(szErrorInfo);
sbResult.append(e);
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
} catch (IllegalAccessException e) {
sbResult.append(szErrorInfo);
sbResult.append(e);
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
} catch (IOException e) {
sbResult.append(szErrorInfo);
sbResult.append(e);
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
}
return sbResult.toString();
}
public static <T extends BaseBean> T parseStringToBean(String szBean, Class<T> clazz) throws IOException {
// 创建 JsonWriter 对象
StringReader stringReader = new StringReader(szBean);
JsonReader jsonReader = new JsonReader(stringReader);
try {
T beanTemp = clazz.newInstance();
return (T)beanTemp.readBeanFromJsonReader(jsonReader);
} catch (InstantiationException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
} catch (IllegalAccessException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
}
return null;
}
public static <T extends BaseBean> boolean parseStringToBeanList(String szBeanList, ArrayList<T> beanList, Class<T> clazz) {
try {
if(beanList == null) {
beanList = new ArrayList<T>();
} else {
beanList.clear();
}
StringReader stringReader = new StringReader(szBeanList);
JsonReader jsonReader = new JsonReader(stringReader);
jsonReader.beginArray();
while (jsonReader.hasNext()) {
T beanTemp = clazz.newInstance();
T bean = (T)beanTemp.readBeanFromJsonReader(jsonReader);
if (bean != null) {
beanList.add(bean);
//LogUtils.d(TAG, "beanList.add(bean)");
}
}
jsonReader.endArray();
return true;
//LogUtils.d(TAG, "beanList.size() is " + Integer.toString(beanList.size()));
} catch (InstantiationException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
} catch (IllegalAccessException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
} catch (IOException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
}
return false;
}
@Override
public String toString() {
// 创建 JsonWriter 对象
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setIndent(" ");
try {// 开始 JSON 对象
jsonWriter.beginObject();
// 写入键值对
writeThisToJsonWriter(jsonWriter);
// 结束 JSON 对象
jsonWriter.endObject();
return stringWriter.toString();
} catch (IOException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
}
// 获取 JSON 字符串
return "";
}
public static <T extends BaseBean> String toStringByBeanList(ArrayList<T> beanList) {
try {
StringWriter stringWriter = new StringWriter();
JsonWriter jsonWriter = new JsonWriter(stringWriter);
jsonWriter.setIndent(" ");
jsonWriter.beginArray();
for (int i = 0; i < beanList.size(); i++) {
// 开始 JSON 对象
jsonWriter.beginObject();
// 写入键值对
beanList.get(i).writeThisToJsonWriter(jsonWriter);
// 结束 JSON 对象
jsonWriter.endObject();
}
jsonWriter.endArray();
jsonWriter.close();
return stringWriter.toString();
} catch (IOException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
}
return "";
}
public static <T extends BaseBean> T loadBean(Context context, Class<T> clazz) {
try {
T beanTemp = clazz.newInstance();
return loadBeanFromFile(beanTemp.getBeanJsonFilePath(context), clazz);
} catch (InstantiationException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
} catch (IllegalAccessException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
}
return null;
}
public static <T extends BaseBean> T loadBeanFromFile(String szFilePath, Class<T> clazz) {
try {
try {
File fTemp = new File(szFilePath);
if (fTemp.exists()) {
T beanTemp = clazz.newInstance();String szJson = UTF8FileUtils.readStringFromFile(szFilePath);
return beanTemp.parseStringToBean(szJson, clazz);
}
} catch (InstantiationException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
} catch (IllegalAccessException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
}
} catch (IOException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
}
return null;
}
public static <T extends BaseBean> boolean saveBean(Context context, T bean) {
return saveBeanToFile(bean.getBeanJsonFilePath(context), bean);
}
public static <T extends BaseBean> boolean saveBeanToFile(String szFilePath, T bean) {
try {
String szJson = bean.toString();
UTF8FileUtils.writeStringToFile(szFilePath, szJson);
return true;
} catch (IOException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
}
return false;
}
public static <T extends BaseBean> boolean loadBeanList(Context context, ArrayList<T> beanListDst, Class<T> clazz) {
try {
T beanTemp = clazz.newInstance();
return loadBeanListFromFile(beanTemp.getBeanListJsonFilePath(context), beanListDst, clazz);
} catch (InstantiationException e) {} catch (IllegalAccessException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
}
return false;
}
public static <T extends BaseBean> boolean loadBeanListFromFile(String szFilePath, ArrayList<T> beanList, Class<T> clazz) {
try {
File fTemp = new File(szFilePath);
if (fTemp.exists()) {
String szListJson = UTF8FileUtils.readStringFromFile(szFilePath);
return parseStringToBeanList(szListJson, beanList, clazz);
}
} catch (IOException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
}
return false;
}
public static <T extends BaseBean> boolean saveBeanList(Context context, ArrayList<T> beanList, Class<T> clazz) {
try {
T beanTemp = clazz.newInstance();
return saveBeanListToFile(beanTemp.getBeanListJsonFilePath(context), beanList);
} catch (InstantiationException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
} catch (IllegalAccessException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
}
return false;
}
public static <T extends BaseBean> boolean saveBeanListToFile(String szFilePath, ArrayList<T> beanList) {
try {
String szJson = toStringByBeanList(beanList);
UTF8FileUtils.writeStringToFile(szFilePath, szJson);
//LogUtils.d(TAG, "FileUtil.writeFile beanList.size() is " + Integer.toString(beanList.size()));
return true;
} catch (IOException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
}
return false;
}
}

View File

@ -0,0 +1,404 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2024/08/12 13:22:12
* @Describe 异常处理类
*/
import android.app.Activity;
import android.app.Application;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.Menu;
import android.view.MenuItem;
import android.view.ViewGroup;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.Toolbar;
import cc.winboll.studio.libappbase.R;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public final class CrashHandler {
public static final String TAG = "CrashHandler";
public static final String TITTLE = "CrashReport";
public static final String EXTRA_CRASH_INFO = "crashInfo";
final static String PREFS = CrashHandler.class.getName() + "PREFS";
final static String PREFS_CRASHHANDLER_ISCRASHHAPPEN = "PREFS_CRASHHANDLER_ISCRASHHAPPEN";
public static String _CrashCountFilePath;
public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler();
public static void init(Application app) {
_CrashCountFilePath = app.getExternalFilesDir("CrashHandler") + "/IsCrashHandlerCrashHappen.dat";
LogUtils.d(TAG, String.format("_CrashCountFilePath %s", _CrashCountFilePath));
init(app, null);
}
public static void init(final Application app, final String crashDir) {
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler(){
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
try {
tryUncaughtException(thread, throwable);
} catch (Throwable e) {
e.printStackTrace();
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null)
DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
}
}
private void tryUncaughtException(Thread thread, Throwable throwable) {
// 每到这里就燃烧一次保险丝
AppCrashSafetyWire.getInstance().burnSafetyWire();
final String time = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss", Locale.getDefault()).format(new Date());
File crashFile = new File(TextUtils.isEmpty(crashDir) ? new File(app.getExternalFilesDir(null), "crash")
: new File(crashDir), "crash_" + time + ".txt");
String versionName = "unknown";
long versionCode = 0;
try {
PackageInfo packageInfo = app.getPackageManager().getPackageInfo(app.getPackageName(), 0);
versionName = packageInfo.versionName;
versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode()
: packageInfo.versionCode;
} catch (PackageManager.NameNotFoundException ignored) {}
String fullStackTrace; {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
throwable.printStackTrace(pw);
fullStackTrace = sw.toString();
pw.close();
}
StringBuilder sb = new StringBuilder();
sb.append("************* Crash Head ****************\n");
sb.append("Time Of Crash : ").append(time).append("\n");
sb.append("Device Manufacturer : ").append(Build.MANUFACTURER).append("\n");
sb.append("Device Model : ").append(Build.MODEL).append("\n");
sb.append("Android Version : ").append(Build.VERSION.RELEASE).append("\n");
sb.append("Android SDK : ").append(Build.VERSION.SDK_INT).append("\n");
sb.append("App VersionName : ").append(versionName).append("\n");
sb.append("App VersionCode : ").append(versionCode).append("\n");
sb.append("************* Crash Head ****************\n");
sb.append("\n").append(fullStackTrace);
String errorLog = sb.toString();
try {
writeFile(crashFile, errorLog);
} catch (IOException ignored) {}
gotoCrashActiviy: {
Intent intent = new Intent();
LogUtils.d(TAG, "gotoCrashActiviy: ");
if (AppCrashSafetyWire.getInstance().isAppCrashSafetyWireOK()) {
LogUtils.d(TAG, "gotoCrashActiviy: isAppCrashSafetyWireOK");
intent.setClass(app, GlobalCrashActivity.class);
intent.putExtra(EXTRA_CRASH_INFO, errorLog);
// 如果发生了 CrashHandler 内部崩溃 就调用基础的应用崩溃显示类
// intent.setClass(app, GlobalCrashActiviy.class);
// intent.putExtra(GlobalCrashActiviy.EXTRA_CRASH_INFO, errorLog);
} else {
LogUtils.d(TAG, "gotoCrashActiviy: else");
// 正常状态调用进阶的应用崩溃显示页
intent.setClass(app, CrashActivity.class);
intent.putExtra(EXTRA_CRASH_INFO, errorLog);
}
// intent.setClass(app, CrashActiviy.class);
// intent.putExtra(CrashActiviy.EXTRA_CRASH_INFO, errorLog);
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK
);
try {
app.startActivity(intent);
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
} catch (ActivityNotFoundException e) {
e.printStackTrace();
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null)
DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
} catch (Exception e) {
e.printStackTrace();
if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null)
DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable);
}
}
}
private void writeFile(File file, String content) throws IOException {
File parentFile = file.getParentFile();
if (parentFile != null && !parentFile.exists()) {
parentFile.mkdirs();
}
file.createNewFile();
FileOutputStream fos = new FileOutputStream(file);
fos.write(content.getBytes());
try {
fos.close();
} catch (IOException e) {}
}
});
}
//
// 应用崩溃保险丝
//
public static final class AppCrashSafetyWire {
volatile static AppCrashSafetyWire _AppCrashSafetyWire;
volatile Integer currentSafetyLevel; // 熔断值 0 表示熔断了
private static final int _MINI = 1;
private static final int _MAX = 2;
AppCrashSafetyWire() {
LogUtils.d(TAG, "AppCrashSafetyWire()");
currentSafetyLevel = loadCurrentSafetyLevel();
}
public static synchronized AppCrashSafetyWire getInstance() {
if (_AppCrashSafetyWire == null) {
_AppCrashSafetyWire = new AppCrashSafetyWire();
}
return _AppCrashSafetyWire;
}
public void setCurrentSafetyLevel(int currentSafetyLevel) {
this.currentSafetyLevel = currentSafetyLevel;
}
public int getCurrentSafetyLevel() {
return currentSafetyLevel;
}
public void saveCurrentSafetyLevel(int currentSafetyLevel) {
LogUtils.d(TAG, "saveCurrentSafetyLevel()");
this.currentSafetyLevel = currentSafetyLevel;
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(_CrashCountFilePath));
oos.writeInt(currentSafetyLevel);
oos.flush();
oos.close();
LogUtils.d(TAG, String.format("saveCurrentSafetyLevel writeInt currentSafetyLevel %d", currentSafetyLevel));
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
public int loadCurrentSafetyLevel() {
LogUtils.d(TAG, "loadCurrentSafetyLevel()");
try {
File f = new File(_CrashCountFilePath);
if (f.exists()) {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(_CrashCountFilePath));
currentSafetyLevel = ois.readInt();
LogUtils.d(TAG, String.format("loadCurrentSafetyLevel() readInt currentSafetyLevel %d", currentSafetyLevel));
} else {
currentSafetyLevel = _MAX;
LogUtils.d(TAG, String.format("loadCurrentSafetyLevel() currentSafetyLevel init to _MAX->%d", _MAX));
saveCurrentSafetyLevel(currentSafetyLevel);
}
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
return currentSafetyLevel;
}
boolean burnSafetyWire() {
LogUtils.d(TAG, "burnSafetyWire()");
// 崩溃计数进入崩溃保险值
int safeLevel = loadCurrentSafetyLevel();
if (isSafetyWireWorking(safeLevel)) {
// 如果保险丝未熔断, 就增加一次熔断值
LogUtils.d(TAG, "burnSafetyWire() use");
saveCurrentSafetyLevel(safeLevel - 1);
return isSafetyWireWorking(safeLevel - 1);
}
return false;
}
boolean isSafetyWireWorking(int safetyLevel) {
LogUtils.d(TAG, "isSafetyWireOK()");
//safetyLevel = _MINI;
//safetyLevel = _MINI - 1;
//safetyLevel = _MINI + 1;
//safetyLevel = _MAX;
//safetyLevel = _MAX + 1;
LogUtils.d(TAG, String.format("SafetyLevel %d", safetyLevel));
if (safetyLevel >= _MINI && safetyLevel <= _MAX) {
// 如果在保险值之内
LogUtils.d(TAG, String.format("In Safety Level"));
return true;
}
LogUtils.d(TAG, String.format("Out of Safety Level"));
return false;
}
void resumeToMaximumImmediately() {
LogUtils.d(TAG, "resumeToMaximumImmediately() call saveCurrentSafetyLevel(_MAX)");
AppCrashSafetyWire.getInstance().saveCurrentSafetyLevel(_MAX);
}
void off() {
LogUtils.d(TAG, "off()");
saveCurrentSafetyLevel(_MINI);
}
boolean isAppCrashSafetyWireOK() {
LogUtils.d(TAG, "isAppCrashSafetyWireOK()");
currentSafetyLevel = loadCurrentSafetyLevel();
return isSafetyWireWorking(currentSafetyLevel);
}
// 调用函数以启用持续崩溃保险从而调用 CrashHandler 内部崩溃处理窗口
void postResumeCrashSafetyWireHandler(final Context context) {
new Handler(Looper.getMainLooper()).postDelayed(new Runnable(){
@Override
public void run() {
LogUtils.d(TAG, "Handler run()");
if (!AppCrashSafetyWire.getInstance().isSafetyWireWorking(currentSafetyLevel - 1)) {
// 如果下一次应用崩溃时保险丝熔断则先恢复保险丝满能状态
// 进程持续运行时恢复保险丝熔断值
//Resume to maximum
AppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
LogUtils.d(TAG, "postResumeCrashSafetyWireHandler");
}
}
}, 500);
}
}
public static final class CrashActivity extends Activity implements MenuItem.OnMenuItemClickListener {
private static final int MENUITEM_COPY = 0;
private static final int MENUITEM_RESTART = 1;
private String mLog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AppCrashSafetyWire.getInstance().postResumeCrashSafetyWireHandler(getApplicationContext());
mLog = getIntent().getStringExtra(EXTRA_CRASH_INFO);
setTheme(android.R.style.Theme_DeviceDefault_Light_DarkActionBar);
setContentView: {
ScrollView contentView = new ScrollView(this);
contentView.setFillViewport(true);
HorizontalScrollView hw = new HorizontalScrollView(this);
hw.setBackgroundColor(Color.GRAY);
TextView message = new TextView(this); {
int padding = dp2px(16);
message.setPadding(padding, padding, padding, padding);
message.setText(mLog);
message.setTextIsSelectable(true);
}
hw.addView(message);
contentView.addView(hw, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
setContentView(contentView);
getActionBar().setTitle(TITTLE);
getActionBar().setSubtitle(GlobalApplication.class.getSimpleName() + " Error");
}
}
@Override
public void onBackPressed() {
restart();
}
private void restart() {
PackageManager pm = getPackageManager();
Intent intent = pm.getLaunchIntentForPackage(getPackageName());
if (intent != null) {
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK
);
startActivity(intent);
}
finish();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
private int dp2px(final float dpValue) {
final float scale = Resources.getSystem().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case MENUITEM_COPY:
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
Toast.makeText(getApplication(), "The text is copied.", Toast.LENGTH_SHORT).show();
break;
case MENUITEM_RESTART:
AppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
restart();
break;
}
return false;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, MENUITEM_COPY, 0, "Copy").setOnMenuItemClickListener(this)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add(0, MENUITEM_RESTART, 0, "Restart").setOnMenuItemClickListener(this)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
return true;
}
}
}

View File

@ -0,0 +1,119 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2025/01/05 10:10:23
* @Describe 全局应用类
*/
import android.app.Application;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.view.Gravity;
import com.hjq.toast.ToastUtils;
import com.hjq.toast.style.WhiteToastStyle;
public class GlobalApplication extends Application {
public static final String TAG = "GlobalApplication";
final static String PREFS = GlobalApplication.class.getName() + "PREFS";
final static String PREFS_ISDEBUGING = "PREFS_ISDEBUGING";
private static Handler MAIN_HANDLER = new Handler(Looper.getMainLooper());
// 是否处于调试状态
volatile static boolean isDebuging = false;
public static void setIsDebuging(Context context, boolean isDebuging) {
GlobalApplication.isDebuging = isDebuging;
// 获取SharedPreferences实例
SharedPreferences sharedPreferences = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
// 获取编辑器
SharedPreferences.Editor editor = sharedPreferences.edit();
// 保存数据
editor.putBoolean(PREFS_ISDEBUGING, GlobalApplication.isDebuging);
// 提交更改
editor.apply();
}
public static boolean isDebuging() {
return isDebuging;
}
@Override
public Context getApplicationContext() {
return super.getApplicationContext();
}
public Application getApplication() {
return this;
}
@Override
public void onCreate() {
super.onCreate();
//GlobalApplication.isDebuging = true;
//GlobalApplication.setIsDebuging(this, true);
LogUtils.init(this);
//LogUtils.setLogLevel(LogUtils.LOG_LEVEL.Debug);
//LogUtils.setTAGListEnable(GlobalApplication.TAG, true);
//LogUtils.setALlTAGListEnable(true);
//LogUtils.d(TAG, "LogUtils init");
// 设置应用异常处理窗口
CrashHandler.init(this);
// 设置应用调试状态
//SharedPreferences sharedPreferences = getSharedPreferences(PREFS, Context.MODE_PRIVATE);
//GlobalApplication.isDebuging = sharedPreferences.getBoolean(PREFS_ISDEBUGING, GlobalApplication.isDebuging);
// 初始化 Toast 框架
ToastUtils.init(this);
// 设置 Toast 布局样式
//ToastUtils.setView(R.layout.toast_custom_view);
ToastUtils.setStyle(new WhiteToastStyle());
ToastUtils.setGravity(Gravity.BOTTOM, 0, 200);
}
public static String getAppName(Context context) {
PackageManager packageManager = context.getPackageManager();
try {
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(
context.getPackageName(), 0);
return (String) packageManager.getApplicationLabel(applicationInfo);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return null;
}
//
// @Override
// public void helpISOSService(Intent intent) {
// String szServiceName = intent.getStringExtra(EXTRA_SERVICE);
// String szPackageName = intent.getStringExtra(EXTRA_PACKAGE);
// if (szServiceName != null && !szServiceName.equals("")
// && szPackageName != null && !szPackageName.equals("")) {
// LogUtils.d(TAG, "szPackageName " + szPackageName);
// LogUtils.d(TAG, "szServiceName " + szServiceName);
//
// // 目标服务的包名和类名
// //String packageName = this.getPackageName();
// //String serviceClassName = SimpleOperateSignalCenterService.class.getName();
//
// // 构建Intent
// Intent intentService = new Intent();
// intentService.setComponent(new ComponentName(szPackageName, szServiceName));
// intentService.putExtra(ISOSService.EXTRA_ENABLE, true);
// startService(intentService);
// LogUtils.d(TAG, "startService(intentService)");
// }
// LogUtils.d(TAG, "helpISOSService");
// }
}

View File

@ -0,0 +1,119 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/11 00:14:05
*/
import android.app.Activity;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.R;
import androidx.appcompat.app.AppCompatActivity;
public final class GlobalCrashActivity extends AppCompatActivity implements MenuItem.OnMenuItemClickListener {
private static final int MENUITEM_COPY = 0;
private static final int MENUITEM_RESTART = 1;
GlobalCrashReportView mGlobalCrashReportView;
String mLog;
public static final String TAG = "GlobalCrashActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
CrashHandler.AppCrashSafetyWire.getInstance().postResumeCrashSafetyWireHandler(getApplicationContext());
mLog = getIntent().getStringExtra(CrashHandler.EXTRA_CRASH_INFO);
//setTheme(android.R.style.Theme_Holo_Light_NoActionBar);
//setTheme(R.style.APPBaseTheme);
setContentView(R.layout.activity_globalcrash);
mGlobalCrashReportView = findViewById(R.id.activityglobalcrashGlobalCrashReportView1);
mGlobalCrashReportView.setReport(mLog);
setSupportActionBar(mGlobalCrashReportView.getToolbar());
getSupportActionBar().setTitle(CrashHandler.TITTLE);
getSupportActionBar().setSubtitle(GlobalApplication.getAppName(getApplicationContext()));
}
@Override
public void onBackPressed() {
restart();
}
private void restart() {
PackageManager pm = getPackageManager();
Intent intent = pm.getLaunchIntentForPackage(getPackageName());
if (intent != null) {
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK
);
startActivity(intent);
}
finish();
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case MENUITEM_COPY:
ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
cm.setPrimaryClip(ClipData.newPlainText(getPackageName(), mLog));
Toast.makeText(getApplication(), "The text is copied.", Toast.LENGTH_SHORT).show();
break;
case MENUITEM_RESTART:
CrashHandler.AppCrashSafetyWire.getInstance().resumeToMaximumImmediately();
restart();
break;
}
return false;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, MENUITEM_COPY, 0, "Copy").setOnMenuItemClickListener(this)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
menu.add(0, MENUITEM_RESTART, 0, "Restart").setOnMenuItemClickListener(this)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
// 更新菜单文字风格
mGlobalCrashReportView.updateMenuStyle();
return true;
}
void joinQQGroup(String key) {
// 创建Intent
Intent intent = new Intent();
// 设置动作
intent.setAction("android.intent.action.VIEW");
// 设置数据为网址的URI
Uri content_url = Uri.parse("https://www.winboll.cc");
intent.setData(content_url);
// 添加标志
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 设置类名和活动名
intent.setClassName("com.android.browser", "com.android.browser.BrowserActivity");
// 启动Activity
startActivity(intent);
}
}

View File

@ -0,0 +1,138 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/11 20:18:30
* @Describe 应用崩溃报告视图
*/
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.R;
public class GlobalCrashReportView extends LinearLayout {
public static final String TAG = "GlobalCrashReportView";
Context mContext;
Toolbar mToolbar;
int colorTittle;
int colorTittleBackground;
int colorText;
int colorTextBackground;
TextView mtvReport;
public GlobalCrashReportView(Context context) {
super(context);
mContext = context;
//initView();
}
public GlobalCrashReportView(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
initView(attrs);
}
public GlobalCrashReportView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mContext = context;
//initView();
}
public GlobalCrashReportView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
mContext = context;
//initView();
}
public void setColorTittle(int colorTittle) {
this.colorTittle = colorTittle;
}
public int getColorTittle() {
return colorTittle;
}
public void setColorTittleBackground(int colorTittleBackground) {
this.colorTittleBackground = colorTittleBackground;
}
public int getColorTittleBackground() {
return colorTittleBackground;
}
public void setColorText(int colorText) {
this.colorText = colorText;
}
public int getColorText() {
return colorText;
}
public void setColorTextBackground(int colorTextBackground) {
this.colorTextBackground = colorTextBackground;
}
public int getColorTextBackground() {
return colorTextBackground;
}
void initView(AttributeSet attrs) {
TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.GlobalCrashActivity, R.attr.themeGlobalCrashActivity, 0);
this.colorTittle = a.getColor(R.styleable.GlobalCrashActivity_colorTittle, Color.WHITE);
this.colorTittleBackground = a.getColor(R.styleable.GlobalCrashActivity_colorTittleBackgound, Color.BLACK);
this.colorText = a.getColor(R.styleable.GlobalCrashActivity_colorText, Color.BLACK);
this.colorTextBackground = a.getColor(R.styleable.GlobalCrashActivity_colorTextBackgound, Color.WHITE);
// 返回一个绑定资源结束的信号给资源
a.recycle();
/*this.colorTittle = Color.WHITE;
this.colorTittleBackground = Color.BLACK;
this.colorText = Color.BLACK;
this.colorTextBackground = Color.WHITE;
*/
inflate(mContext, R.layout.view_globalcrashreport, this);
LinearLayout llMain = findViewById(R.id.viewglobalcrashreportLinearLayout1);
llMain.setBackgroundColor(this.colorTextBackground);
mToolbar = findViewById(R.id.viewglobalcrashreportToolbar1);
mToolbar.setBackgroundColor(this.colorTittleBackground);
mToolbar.setTitleTextColor(this.colorTittle);
mToolbar.setSubtitleTextColor(this.colorTittle);
mtvReport = findViewById(R.id.viewglobalcrashreportTextView1);
mtvReport.setTextColor(this.colorText);
mtvReport.setBackgroundColor(this.colorTextBackground);
}
public void setReport(String report) {
mtvReport.setText(report);
}
public Toolbar getToolbar() {
return mToolbar;
}
//
// 更新菜单文字风格
//
public void updateMenuStyle() {
// 设置菜单文本颜色
Menu menu = mToolbar.getMenu();
for (int i = 0; i < menu.size(); i++) {
MenuItem item = menu.getItem(i);
SpannableString spanString = new SpannableString(item.getTitle().toString());
spanString.setSpan(new ForegroundColorSpan(this.colorTittle), 0, spanString.length(), 0);
item.setTitle(spanString);
}
}
}

View File

@ -0,0 +1,24 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2025/02/04 10:20:16
* @Describe WinBoll Activity 接口
*/
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.bean.APPInfo;
public interface IWinBollActivity {
public static final String TAG = "IWinBollActivity";
// 获取当前具有 IWinBoll 特征的 AppCompatActivity 活动窗口
AppCompatActivity getActivity();
abstract public APPInfo getAppInfo();
abstract public String getTag();
abstract Toolbar initToolBar();
abstract boolean isEnableDisplayHomeAsUp();
abstract boolean isAddWinBollToolBar();
}

View File

@ -1,15 +0,0 @@
package cc.winboll.studio.libappbase;
import android.app.*;
import android.os.*;
import cc.winboll.studio.R;
public class LibraryActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.library);
}
}

View File

@ -0,0 +1,68 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2024/08/12 15:07:58
* @Describe WinBoll 应用日志窗口
*/
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.bean.APPInfo;
import cc.winboll.studio.libappbase.R;
public class LogActivity extends AppCompatActivity implements IWinBollActivity {
public static final String TAG = "LogActivity";
LogView mLogView;
@Override
public AppCompatActivity getActivity() {
return this;
}
@Override
public APPInfo getAppInfo() {
return null;
}
@Override
public String getTag() {
return TAG;
}
@Override
public Toolbar initToolBar() {
return null;
}
@Override
public boolean isEnableDisplayHomeAsUp() {
return false;
}
@Override
public boolean isAddWinBollToolBar() {
return false;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
LogUtils.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_log);
mLogView = findViewById(R.id.logview);
if (GlobalApplication.isDebuging()) { mLogView.start(); }
}
@Override
protected void onResume() {
LogUtils.d(TAG, "onResume");
super.onResume();
mLogView.start();
}
}

View File

@ -0,0 +1,370 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2024/08/12 13:44:06
* @Describe LogUtils
* @Describe 应用日志类
*/
import android.content.Context;
import cc.winboll.studio.libappbase.GlobalApplication;
import dalvik.system.DexFile;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
public class LogUtils {
public static final String TAG = "LogUtils";
public static enum LOG_LEVEL { Off, Error, Warn, Info, Debug, Verbose }
static volatile boolean _IsInited = false;
static Context _mContext;
// 日志显示时间格式
static SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat("[yyyyMMdd_HHmmSS]", Locale.getDefault());
// 应用日志文件夹
static File _mfLogCacheDir;
static File _mfLogDataDir;
// 应用日志文件
static File _mfLogCatchFile;
static File _mfLogUtilsBeanFile;
static LogUtilsBean _mLogUtilsBean;
public static Map<String, Boolean> mapTAGList = new HashMap<String, Boolean>();
//
// 初始化函数
//
public static void init(Context context) {
_mContext = context;
init(context, LOG_LEVEL.Off);
}
//
// 初始化函数
//
public static void init(Context context, LOG_LEVEL logLevel) {
if (GlobalApplication.isDebuging()) {
// 初始化日志缓存文件路径
_mfLogCacheDir = new File(context.getApplicationContext().getExternalCacheDir(), TAG);
if (!_mfLogCacheDir.exists()) {
_mfLogCacheDir.mkdirs();
}
_mfLogCatchFile = new File(_mfLogCacheDir, "log.txt");
// 初始化日志配置文件路径
_mfLogDataDir = context.getApplicationContext().getExternalFilesDir(TAG);
if (!_mfLogDataDir.exists()) {
_mfLogDataDir.mkdirs();
}
_mfLogUtilsBeanFile = new File(_mfLogDataDir, TAG + ".json");
} else {
// 初始化日志缓存文件路径
_mfLogCacheDir = new File(context.getApplicationContext().getCacheDir(), TAG);
if (!_mfLogCacheDir.exists()) {
_mfLogCacheDir.mkdirs();
}
_mfLogCatchFile = new File(_mfLogCacheDir, "log.txt");
// 初始化日志配置文件路径
_mfLogDataDir = new File(context.getApplicationContext().getFilesDir(), TAG);
if (!_mfLogDataDir.exists()) {
_mfLogDataDir.mkdirs();
}
_mfLogUtilsBeanFile = new File(_mfLogDataDir, TAG + ".json");
}
// Toast.makeText(context,
// "_mfLogUtilsBeanFile : " + _mfLogUtilsBeanFile
// + "\n_mfLogCatchFile : " + _mfLogCatchFile,
// Toast.LENGTH_SHORT).show();
//
_mLogUtilsBean = LogUtilsBean.loadBeanFromFile(_mfLogUtilsBeanFile.getPath(), LogUtilsBean.class);
if (_mLogUtilsBean == null) {
_mLogUtilsBean = new LogUtilsBean();
_mLogUtilsBean.saveBeanToFile(_mfLogUtilsBeanFile.getPath(), _mLogUtilsBean);
}
// 加载当前应用下的所有类的 TAG
addClassTAGList();
loadTAGBeanSettings();
_IsInited = true;
LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString()));
}
public static Map<String, Boolean> getMapTAGList() {
return mapTAGList;
}
static void loadTAGBeanSettings() {
ArrayList<LogUtilsClassTAGBean> list = new ArrayList<LogUtilsClassTAGBean>();
LogUtilsClassTAGBean.loadBeanList(_mContext, list, LogUtilsClassTAGBean.class);
for (int i = 0; i < list.size(); i++) {
LogUtilsClassTAGBean beanSetting = list.get(i);
for (Map.Entry<String, Boolean> entry : mapTAGList.entrySet()) {
if (entry.getKey().equals(beanSetting.getTag())) {
entry.setValue(beanSetting.getEnable());
}
}
}
}
static void saveTAGBeanSettings() {
ArrayList<LogUtilsClassTAGBean> list = new ArrayList<LogUtilsClassTAGBean>();
for (Map.Entry<String, Boolean> entry : mapTAGList.entrySet()) {
list.add(new LogUtilsClassTAGBean(entry.getKey(), entry.getValue()));
}
LogUtilsClassTAGBean.saveBeanList(_mContext, list, LogUtilsClassTAGBean.class);
}
static void addClassTAGList() {
//ClassLoader classLoader = getClass().getClassLoader();
try {
//String packageName = context.getPackageName();
String packageNamePrefix = "cc.winboll.studio";
List<String> classNames = new ArrayList<>();
String apkPath = _mContext.getPackageCodePath();
//Log.d("APK_PATH", "The APK path is: " + apkPath);
LogUtils.d(TAG, String.format("apkPath : %s", apkPath));
//String apkPath = "/data/app/" + packageName + "-";
//DexFile dexfile = new DexFile(apkPath + "1/base.apk");
DexFile dexfile = new DexFile(apkPath);
int countTemp = 0;
Enumeration<String> entries = dexfile.entries();
while (entries.hasMoreElements()) {
countTemp++;
String className = entries.nextElement();
if (className.startsWith(packageNamePrefix)) {
classNames.add(className);
}
}
LogUtils.d(TAG, String.format("countTemp : %d\nClassNames size : %d", countTemp, classNames.size()));
for (String className : classNames) {
try {
Class<?> clazz = Class.forName(className);
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers()) && field.getType() == String.class && "TAG".equals(field.getName())) {
String tagValue = (String) field.get(null);
//Log.d("TAG_INFO", "Class: " + className + ", TAG value: " + tagValue);
//LogUtils.d(TAG, String.format("Tag Value : %s", tagValue));
//mapTAGList.put(tagValue, true);
mapTAGList.put(tagValue, false);
}
}
} catch (NoClassDefFoundError | ClassNotFoundException | IllegalAccessException e) {
LogUtils.d(TAG, e.getMessage(), Thread.currentThread().getStackTrace());
//LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
//Toast.makeText(context, TAG + " : " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
//Toast.makeText(context, TAG + " : " + e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
public static void setTAGListEnable(String tag, boolean isEnable) {
Iterator<Map.Entry<String, Boolean>> iterator = mapTAGList.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Boolean> entry = iterator.next();
if (tag.equals(entry.getKey())) {
entry.setValue(isEnable);
//System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
break;
}
}
saveTAGBeanSettings();
LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString()));
}
public static void setALlTAGListEnable(boolean isEnable) {
Iterator<Map.Entry<String, Boolean>> iterator = mapTAGList.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Boolean> entry = iterator.next();
entry.setValue(isEnable);
//System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
saveTAGBeanSettings();
LogUtils.d(TAG, String.format("mapTAGList : %s", mapTAGList.toString()));
}
public static void setLogLevel(LOG_LEVEL logLevel) {
LogUtils._mLogUtilsBean.setLogLevel(logLevel);
_mLogUtilsBean.saveBeanToFile(_mfLogUtilsBeanFile.getPath(), _mLogUtilsBean);
}
public static LOG_LEVEL getLogLevel() {
return LogUtils._mLogUtilsBean.getLogLevel();
}
static boolean isLoggable(String tag, LOG_LEVEL logLevel) {
return _IsInited && mapTAGList.get(tag) && isInTheLevel(logLevel);
}
static boolean isInTheLevel(LOG_LEVEL logLevel) {
return (LogUtils._mLogUtilsBean.getLogLevel().ordinal() == logLevel.ordinal()
|| LogUtils._mLogUtilsBean.getLogLevel().ordinal() > logLevel.ordinal());
}
//
// 获取应用日志文件夹
//
public static File getLogCacheDir() {
return _mfLogCacheDir;
}
//
// 调试日志写入函数
//
public static void e(String szTAG, String szMessage) {
if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Error)) {
saveLog(szTAG, LogUtils.LOG_LEVEL.Error, szMessage);
}
}
//
// 调试日志写入函数
//
public static void w(String szTAG, String szMessage) {
if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Warn)) {
saveLog(szTAG, LogUtils.LOG_LEVEL.Warn, szMessage);
}
}
//
// 调试日志写入函数
//
public static void i(String szTAG, String szMessage) {
if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Info)) {
saveLog(szTAG, LogUtils.LOG_LEVEL.Info, szMessage);
}
}
//
// 调试日志写入函数
//
public static void d(String szTAG, String szMessage) {
if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) {
saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, szMessage);
}
}
//
// 调试日志写入函数
// 包含线程调试堆栈信息
//
public static void d(String szTAG, String szMessage, StackTraceElement[] listStackTrace) {
if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) {
StringBuilder sbMessage = new StringBuilder(szMessage);
sbMessage.append(" \nAt ");
sbMessage.append(listStackTrace[2].getMethodName());
sbMessage.append(" (");
sbMessage.append(listStackTrace[2].getFileName());
sbMessage.append(":");
sbMessage.append(listStackTrace[2].getLineNumber());
sbMessage.append(")");
saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, sbMessage.toString());
}
}
//
// 调试日志写入函数
// 包含异常信息和线程调试堆栈信息
//
public static void d(String szTAG, Exception e, StackTraceElement[] listStackTrace) {
if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Debug)) {
StringBuilder sbMessage = new StringBuilder(e.getClass().toGenericString());
sbMessage.append(" : ");
sbMessage.append(e.getMessage());
sbMessage.append(" \nAt ");
sbMessage.append(listStackTrace[2].getMethodName());
sbMessage.append(" (");
sbMessage.append(listStackTrace[2].getFileName());
sbMessage.append(":");
sbMessage.append(listStackTrace[2].getLineNumber());
sbMessage.append(")");
saveLog(szTAG, LogUtils.LOG_LEVEL.Debug, sbMessage.toString());
}
}
//
// 调试日志写入函数
//
public static void v(String szTAG, String szMessage) {
if (isLoggable(szTAG, LogUtils.LOG_LEVEL.Verbose)) {
saveLog(szTAG, LogUtils.LOG_LEVEL.Verbose, szMessage);
}
}
//
// 日志文件保存函数
//
static void saveLog(String szTAG, LogUtils.LOG_LEVEL logLevel, String szMessage) {
try {
BufferedWriter out = null;
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(_mfLogCatchFile, true), "UTF-8"));
out.write("[" + logLevel + "] " + mSimpleDateFormat.format(System.currentTimeMillis()) + " [" + szTAG + "]\n" + szMessage + "\n");
out.close();
} catch (IOException e) {
LogUtils.d(TAG, "IOException : " + e.getMessage());
}
}
//
// 历史日志加载函数
//
public static String loadLog() {
if (_mfLogCatchFile.exists()) {
StringBuffer sb = new StringBuffer();
try {
BufferedReader in = null;
in = new BufferedReader(new InputStreamReader(new FileInputStream(_mfLogCatchFile), "UTF-8"));
String line = "";
while ((line = in.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
} catch (IOException e) {
LogUtils.d(TAG, "IOException : " + e.getMessage());
}
return sb.toString();
}
return "";
}
//
// 清理日志函数
//
public static void cleanLog() {
if (_mfLogCatchFile.exists()) {
try {
UTF8FileUtils.writeStringToFile(_mfLogCatchFile.getPath(), "");
//LogUtils.d(TAG, "cleanLog");
} catch (IOException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
}
}

View File

@ -0,0 +1,70 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2024/08/23 15:39:07
* @Describe LogUtils 数据配置类
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import java.io.IOException;
public class LogUtilsBean extends BaseBean {
public static final String TAG = "LogUtilsBean";
LogUtils.LOG_LEVEL logLevel;
public LogUtilsBean() {
this.logLevel = LogUtils.LOG_LEVEL.Off;
}
public LogUtilsBean(LogUtils.LOG_LEVEL logLevel) {
this.logLevel = logLevel;
}
public void setLogLevel(LogUtils.LOG_LEVEL logLevel) {
this.logLevel = logLevel;
}
public LogUtils.LOG_LEVEL getLogLevel() {
return logLevel;
}
@Override
public String getName() {
return LogUtilsBean.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
LogUtilsBean bean = this;
jsonWriter.name("logLevel").value(bean.getLogLevel().ordinal());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("logLevel")) {
setLogLevel(LogUtils.LOG_LEVEL.values()[jsonReader.nextInt()]);
} else {
return false;
}
}
return true;
}
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
// 结束 JSON 对象
jsonReader.endObject();
return this;
}
}

View File

@ -0,0 +1,87 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2025/01/04 14:17:02
* @Describe 日志类class TAG 标签数据类
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import java.io.IOException;
public class LogUtilsClassTAGBean extends BaseBean {
public static final String TAG = "LogUtilsClassTAGBean";
// 标签名
String tag;
// 是否启用
Boolean enable;
public LogUtilsClassTAGBean() {
this.tag = TAG;
this.enable = true;
}
public LogUtilsClassTAGBean(String tag, Boolean enable) {
this.tag = tag;
this.enable = enable;
}
public void setTag(String tag) {
this.tag = tag;
}
public String getTag() {
return tag;
}
public void setEnable(Boolean enable) {
this.enable = enable;
}
public Boolean getEnable() {
return enable;
}
@Override
public String getName() {
return LogUtilsClassTAGBean.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
LogUtilsClassTAGBean bean = this;
jsonWriter.name("tag").value(bean.getTag());
jsonWriter.name("enable").value(bean.getEnable());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("tag")) {
setTag(jsonReader.nextString());
} else if (name.equals("enable")) {
setEnable(jsonReader.nextBoolean());
} else {
return false;
}
}
return true;
}
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
// 结束 JSON 对象
jsonReader.endObject();
return this;
}
}

View File

@ -0,0 +1,387 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2024/08/12 14:36:18
* @Describe 日志视图类继承 RelativeLayout
*/
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.Spinner;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.R;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
public class LogView extends RelativeLayout {
public static final String TAG = "LogView";
public volatile boolean mIsHandling;
public volatile boolean mIsAddNewLog;
Context mContext;
ScrollView mScrollView;
TextView mTextView;
CheckBox mSelectableCheckBox;
CheckBox mSelectAllTAGCheckBox;
TAGListAdapter mTAGListAdapter;
LogViewThread mLogViewThread;
LogViewHandler mLogViewHandler;
Spinner mLogLevelSpinner;
ArrayAdapter<CharSequence> mLogLevelSpinnerAdapter;
// 标签列表
RecyclerView recyclerView;
public LogView(Context context) {
super(context);
initView(context);
}
public LogView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public LogView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
public LogView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView(context);
}
public void start() {
mLogViewThread = new LogViewThread(LogView.this);
mLogViewThread.start();
// 显示日志
showAndScrollLogView();
}
public void scrollLogUp() {
mScrollView.post(new Runnable() {
@Override
public void run() {
mScrollView.fullScroll(ScrollView.FOCUS_DOWN);
// 日志显示结束
mLogViewHandler.setIsHandling(false);
// 检查是否添加了新日志
if (mLogViewHandler.isAddNewLog()) {
// 有新日志添加先更改新日志标志
mLogViewHandler.setIsAddNewLog(false);
// 再次发送显示日志的显示
Message message = mLogViewHandler.obtainMessage(LogViewHandler.MSG_LOGVIEW_UPDATE);
mLogViewHandler.sendMessage(message);
}
}
});
}
void initView(Context context) {
mContext = context;
mLogViewHandler = new LogViewHandler();
// 加载视图布局
addView(inflate(mContext, cc.winboll.studio.libappbase.R.layout.view_log, null));
// 初始化日志子控件视图
//
mScrollView = findViewById(cc.winboll.studio.libappbase.R.id.viewlogScrollViewLog);
mTextView = findViewById(cc.winboll.studio.libappbase.R.id.viewlogTextViewLog);
// 获取Log Level spinner实例
mLogLevelSpinner = findViewById(cc.winboll.studio.libappbase.R.id.viewlogSpinner1);
(findViewById(cc.winboll.studio.libappbase.R.id.viewlogButtonClean)).setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
LogUtils.cleanLog();
LogUtils.d(TAG, "Log is cleaned.");
}
});
(findViewById(cc.winboll.studio.libappbase.R.id.viewlogButtonCopy)).setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
ClipboardManager cm = (ClipboardManager) mContext.getSystemService(Context.CLIPBOARD_SERVICE);
cm.setPrimaryClip(ClipData.newPlainText(mContext.getPackageName(), LogUtils.loadLog()));
LogUtils.d(TAG, "Log is copied.");
}
});
mSelectableCheckBox = findViewById(cc.winboll.studio.libappbase.R.id.viewlogCheckBoxSelectable);
mSelectableCheckBox.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
if (mSelectableCheckBox.isChecked()) {
setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
} else {
setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
}
}
});
// 设置日志级别列表
ArrayList<String> adapterItems = new ArrayList<>();
for (LogUtils.LOG_LEVEL e : LogUtils.LOG_LEVEL.values()) {
adapterItems.add(e.name());
}
// 假设你有一个字符串数组作为选项列表
//String[] options = {"Option 1", "Option 2", "Option 3"};
// 创建一个ArrayAdapter来绑定数据到spinner
mLogLevelSpinnerAdapter = ArrayAdapter.createFromResource(
context, cc.winboll.studio.libappbase.R.array.enum_loglevel_array, android.R.layout.simple_spinner_item);
mLogLevelSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
// 设置适配器并将它应用到spinner上
mLogLevelSpinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); // 设置下拉视图样式
mLogLevelSpinner.setAdapter(mLogLevelSpinnerAdapter);
// 为Spinner添加监听器
mLogLevelSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
//String selectedOption = mLogLevelSpinnerAdapter.getItem(position);
// 处理选中的选项...
LogUtils.setLogLevel(LogUtils.LOG_LEVEL.values()[position]);
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// 如果没有选择则执行此操作...
}
});
// 获取默认值的索引
int defaultValueIndex = LogUtils.getLogLevel().ordinal();
if (defaultValueIndex != -1) {
// 如果找到了默认值设置默认选项
mLogLevelSpinner.setSelection(defaultValueIndex);
}
// 加载标签列表
Map<String, Boolean> mapTAGList = LogUtils.getMapTAGList();
boolean isAllSelect = true;
for (Map.Entry<String, Boolean> entry : mapTAGList.entrySet()) {
if (entry.getValue() == false) {
isAllSelect = false;
break;
}
}
CheckBox cbALLTAG = findViewById(cc.winboll.studio.libappbase.R.id.viewlogCheckBox1);
cbALLTAG.setChecked(isAllSelect);
// 加载标签表
recyclerView = findViewById(cc.winboll.studio.libappbase.R.id.viewlogRecyclerView1);
LinearLayoutManager layoutManager = new LinearLayoutManager(mContext, LinearLayoutManager.HORIZONTAL, false);
recyclerView.setLayoutManager(layoutManager);
mTAGListAdapter = new TAGListAdapter(mapTAGList);
recyclerView.setAdapter(mTAGListAdapter);
// 可以添加点击监听器来处理勾选框状态变化后的逻辑比如获取当前勾选情况等
mTAGListAdapter.notifyDataSetChanged();
mSelectAllTAGCheckBox = findViewById(cc.winboll.studio.libappbase.R.id.viewlogCheckBox1);
mSelectAllTAGCheckBox.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
LogUtils.setALlTAGListEnable(mSelectAllTAGCheckBox.isChecked());
//LogUtils.setALlTAGListEnable(false);
//mTAGListAdapter.notifyDataSetChanged();
mTAGListAdapter.reload();
//ToastUtils.show(String.format("onClick\nmSelectAllTAGCheckBox.isChecked() : %s", mSelectAllTAGCheckBox.isChecked()));
}
});
// 设置滚动时不聚焦日志
setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
}
public void updateLogView() {
if (mLogViewHandler.isHandling() == true) {
// 正在处理日志显示
// 就先设置一个新日志标志位
// 以便日志显示完后再次显示新日志内容
mLogViewHandler.setIsAddNewLog(true);
} else {
//LogUtils.d(TAG, "LogListener showLog(String path)");
Message message = mLogViewHandler.obtainMessage(LogViewHandler.MSG_LOGVIEW_UPDATE);
mLogViewHandler.sendMessage(message);
mLogViewHandler.setIsAddNewLog(false);
}
}
void showAndScrollLogView() {
mTextView.setText(LogUtils.loadLog());
scrollLogUp();
}
class LogViewHandler extends Handler {
final static int MSG_LOGVIEW_UPDATE = 0;
volatile boolean isHandling;
volatile boolean isAddNewLog;
public LogViewHandler() {
setIsHandling(false);
setIsAddNewLog(false);
}
public void setIsHandling(boolean isHandling) {
this.isHandling = isHandling;
}
public boolean isHandling() {
return isHandling;
}
public void setIsAddNewLog(boolean isAddNewLog) {
this.isAddNewLog = isAddNewLog;
}
public boolean isAddNewLog() {
return isAddNewLog;
}
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_LOGVIEW_UPDATE:{
if (isHandling() == false) {
setIsHandling(true);
showAndScrollLogView();
}
break;
}
default:
break;
}
super.handleMessage(msg);
}
}
public class TAGItemModel {
private String tag;
private boolean isChecked;
public TAGItemModel(String tag, boolean isChecked) {
this.tag = tag;
this.isChecked = isChecked;
}
public String getTag() {
return tag;
}
public void setTag(String tag) {
this.tag = tag;
}
public boolean isChecked() {
return isChecked;
}
public void setChecked(boolean checked) {
isChecked = checked;
}
}
public class TAGListAdapter extends RecyclerView.Adapter<TAGListAdapter.ViewHolder> {
private Map<String, Boolean> mapOrigin;
private List<TAGItemModel> itemList;
public TAGListAdapter(Map<String, Boolean> map) {
mapOrigin = map;
loadMap(mapOrigin);
}
void loadMap(Map<String, Boolean> map) {
itemList = new ArrayList<TAGItemModel>();
for (Map.Entry<String, Boolean> entry : map.entrySet()) {
itemList.add(new TAGItemModel(entry.getKey(), entry.getValue()));
}
// 添加排序功能按照tag进行升序排序
Collections.sort(itemList, new SortMapEntryByKeyString(true));
//Collections.sort(itemList, new SortMapEntryByKeyString(false));
}
public void reload() {
loadMap(mapOrigin);
super.notifyDataSetChanged();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_logtag, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
final TAGItemModel item = itemList.get(position);
holder.tvText.setText(item.getTag());
holder.cbChecked.setChecked(item.isChecked());
holder.cbChecked.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
LogUtils.setTAGListEnable(item.getTag(), ((CheckBox)v).isChecked());
}
});
}
@Override
public int getItemCount() {
return itemList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
TextView tvText;
CheckBox cbChecked;
public ViewHolder(@NonNull View itemView) {
super(itemView);
tvText = itemView.findViewById(R.id.viewlogtagTextView1);
cbChecked = itemView.findViewById(R.id.viewlogtagCheckBox1);
}
}
}
class SortMapEntryByKeyString implements Comparator<TAGItemModel> {
private boolean mIsDesc = true;
// isDesc 是否降序排列
public SortMapEntryByKeyString(boolean isDesc) {
mIsDesc = isDesc;
}
Collator cmp = Collator.getInstance(java.util.Locale.CHINA);
@Override
public int compare(TAGItemModel o1, TAGItemModel o2) {
if (mIsDesc) {
return o1.getTag().compareTo(o2.getTag());
} else {
return o2.getTag().compareTo(o1.getTag());
}
}
}
}

View File

@ -0,0 +1,80 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2024/08/12 14:43:50
* @Describe 日志视图线程类
*/
import android.os.FileObserver;
import cc.winboll.studio.libappbase.LogUtils;
import java.lang.ref.WeakReference;
public class LogViewThread extends Thread {
public static final String TAG = "LogViewThread";
// 线程退出标志
volatile boolean isExist = false;
// 应用日志文件监听实例
LogListener mLogListener;
// 日志视图弱引用
WeakReference<LogView> mwrLogView;
//
// 构造函数
// @logView : 日志显示输出视图类
public LogViewThread(LogView logView) {
mwrLogView = new WeakReference<LogView>(logView);
}
public void setIsExist(boolean isExist) {
this.isExist = isExist;
}
public boolean isExist() {
return isExist;
}
@Override
public void run() {
String szLogDir = LogUtils.getLogCacheDir().getPath();
mLogListener = new LogListener(szLogDir);
mLogListener.startWatching();
while (isExist() == false) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
mLogListener.stopWatching();
}
//
// 日志文件监听类
//
class LogListener extends FileObserver {
public LogListener(String path) {
super(path);
}
@Override
public void onEvent(int event, String path) {
int e = event & FileObserver.ALL_EVENTS;
switch (e) {
case FileObserver.CLOSE_WRITE:{
if (mwrLogView.get() != null) {
mwrLogView.get().updateLogView();
}
break;
}
case FileObserver.DELETE:{
if (mwrLogView.get() != null) {
mwrLogView.get().updateLogView();
}
break;
}
}
}
}
}

View File

@ -0,0 +1,57 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/13 21:09:36
* @Describe SOS 组件
*/
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import cc.winboll.studio.libappbase.bean.APPSOSBean;
import java.util.ArrayList;
public class SOS {
public static final String TAG = "SOS";
public static final String ACTION_SOS = SOS.class.getName() + ".ACTION_SOS";
public static final String ACTION_BIND = SOS.class.getName() + ".ACTION_BIND";
public static final String ACTION_SERVICE_ENABLE = SOS.class.getName() + ".ACTION_SERVICE_ENABLE";
public static final String ACTION_SERVICE_DISABLE = SOS.class.getName() + ".ACTION_SERVICE_DISENABLE";
public static void sosWinBollService(Context context, APPSOSBean bean) {
Intent intent = new Intent(ACTION_SOS);
intent.putExtra("SOS", "Service");
intent.putExtra("APPSOSBean", bean.toString());
String szToPackage = "";
if (GlobalApplication.isDebuging()) {
szToPackage = "cc.winboll.studio.appbase.beta";
} else {
szToPackage = "cc.winboll.studio.appbase";
}
intent.setPackage(szToPackage);
context.sendBroadcast(intent);
LogUtils.d(TAG, String.format("Send ACTION_SOS To WinBoll. (szToPackage : %s)", szToPackage));
//ToastUtils.show("SOS Send To WinBoll");
}
public static void bindToAPPService(Context context, APPSOSBean bean) {
Intent intent = new Intent(ACTION_BIND);
intent.putExtra("SOS", "Service");
intent.putExtra("APPSOSBean", bean.toString());
String szToPackage = "";
if (GlobalApplication.isDebuging()) {
szToPackage = "cc.winboll.studio.appbase.beta";
} else {
szToPackage = "cc.winboll.studio.appbase";
}
intent.setPackage(szToPackage);
context.sendBroadcast(intent);
LogUtils.d(TAG, String.format("Send ACTION_BIND To WinBoll. (szToPackage : %s)", szToPackage));
}
}

View File

@ -0,0 +1,32 @@
package cc.winboll.studio.libappbase;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/12 23:44:57
* @Describe 简单信号通信中心接收器
*/
public class SOSCSBroadcastReceiver extends BroadcastReceiver {
public static final String TAG = "SOSCSBroadcastReceiver";
public static final String ACTION_SOS = SOSCSBroadcastReceiver.class.getName() + ".ACTION_SOS";
//ISOSAPP mISOSAPP;
public SOSCSBroadcastReceiver() {
//mISOSAPP = iSOSAPP;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action.equals(ACTION_SOS)) {
LogUtils.d(TAG, "ACTION_SOS");
//mISOSAPP.helpISOSService(intent);
} else {
LogUtils.d(TAG, String.format("%s", action));
}
}
}

View File

@ -0,0 +1,187 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/12 11:12:25
* @Describe 简单信号服务中心
*/
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;
import cc.winboll.studio.libappbase.bean.SimpleOperateSignalCenterServiceBean;
import java.io.FileDescriptor;
public class SimpleOperateSignalCenterService extends Service {
public static final String TAG = "SimpleOperateSignalCenterService";
public static final String ACTION_ENABLE = SimpleOperateSignalCenterService.class.getName() + ".ACTION_ENABLE";
public static final String ACTION_DISABLE = SimpleOperateSignalCenterService.class.getName() + ".ACTION_DISABLE";
private final IBinder binder =(IBinder)new SOSBinder();
SimpleOperateSignalCenterServiceBean mSimpleOperateSignalCenterServiceBean;
static MainThread _MainThread;
public static synchronized MainThread getMainThreadInstance() {
if (_MainThread == null) {
_MainThread = new MainThread();
}
return _MainThread;
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public class SOSBinder implements IBinder {
@Override
public void dump(FileDescriptor fileDescriptor, String[] string) throws RemoteException {
}
@Override
public void dumpAsync(FileDescriptor fileDescriptor, String[] string) throws RemoteException {
}
@Override
public String getInterfaceDescriptor() throws RemoteException {
return null;
}
@Override
public boolean isBinderAlive() {
return false;
}
@Override
public void linkToDeath(IBinder.DeathRecipient deathRecipient, int p) throws RemoteException {
}
@Override
public boolean pingBinder() {
return false;
}
@Override
public IInterface queryLocalInterface(String string) {
return null;
}
@Override
public boolean transact(int p, Parcel parcel, Parcel parcel1, int p1) throws RemoteException {
return false;
}
@Override
public boolean unlinkToDeath(IBinder.DeathRecipient deathRecipient, int p) {
return false;
}
public static final String TAG = "SOSBinder";
SimpleOperateSignalCenterService getService() {
return SimpleOperateSignalCenterService.this;
}
}
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(TAG, "onCreate");
mSimpleOperateSignalCenterServiceBean = SimpleOperateSignalCenterServiceBean.loadBean(this, SimpleOperateSignalCenterServiceBean.class);
if(mSimpleOperateSignalCenterServiceBean == null) {
mSimpleOperateSignalCenterServiceBean = new SimpleOperateSignalCenterServiceBean();
SimpleOperateSignalCenterServiceBean.saveBean(this, mSimpleOperateSignalCenterServiceBean);
}
runMainThread();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "onStartCommand");
// if (intent.getBooleanExtra(ISOSService.EXTRA_ENABLE, false)) {
// LogUtils.d(TAG, "onStartCommand enable service");
// mSimpleOperateSignalCenterServiceBean.setIsEnable(true);
// SimpleOperateSignalCenterServiceBean.saveBean(this, mSimpleOperateSignalCenterServiceBean);
// }
runMainThread();
//return super.onStartCommand(intent, flags, startId);
return mSimpleOperateSignalCenterServiceBean.isEnable() ? Service.START_STICKY: super.onStartCommand(intent, flags, startId);
}
void runMainThread() {
mSimpleOperateSignalCenterServiceBean = SimpleOperateSignalCenterServiceBean.loadBean(this, SimpleOperateSignalCenterServiceBean.class);
if (mSimpleOperateSignalCenterServiceBean.isEnable()
&& _MainThread == null) {
getMainThreadInstance().start();
}
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy");
mSimpleOperateSignalCenterServiceBean = SimpleOperateSignalCenterServiceBean.loadBean(this, SimpleOperateSignalCenterServiceBean.class);
if (mSimpleOperateSignalCenterServiceBean.isEnable()) {
LogUtils.d(TAG, "mSimpleOperateSignalCenterServiceBean.isEnable()");
// ISOSAPP iSOSAPP = (ISOSAPP)getApplication();
// iSOSAPP.helpISOSService(getISOSServiceIntentWhichAskForHelp());
}
if (_MainThread != null) {
_MainThread.isExist = true;
_MainThread = null;
}
}
public static void stopISOSService(Context context) {
LogUtils.d(TAG, "stopISOSService");
SimpleOperateSignalCenterServiceBean bean = new SimpleOperateSignalCenterServiceBean();
bean.setIsEnable(false);
SimpleOperateSignalCenterServiceBean.saveBean(context, bean);
context.stopService(new Intent(context, SimpleOperateSignalCenterService.class));
}
public static void startISOSService(Context context) {
LogUtils.d(TAG, "startISOSService");
SimpleOperateSignalCenterServiceBean bean = new SimpleOperateSignalCenterServiceBean();
bean.setIsEnable(true);
SimpleOperateSignalCenterServiceBean.saveBean(context, bean);
context.startService(new Intent(context, SimpleOperateSignalCenterService.class));
}
public String getMessage() {
return "Hello from SimpleOperateSignalCenterService";
}
static class MainThread extends Thread {
volatile boolean isExist = false;
public void setIsExist(boolean isExist) {
this.isExist = isExist;
}
public boolean isExist() {
return isExist;
}
@Override
public void run() {
super.run();
while (!isExist) {
LogUtils.d(TAG, "run");
try {
sleep(1000);
} catch (InterruptedException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
}
}
}

View File

@ -0,0 +1,49 @@
package cc.winboll.studio.libappbase;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2024/07/19 14:30:57
* @Describe UTF-8编码文件工具类
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
public class UTF8FileUtils {
public static final String TAG = "FileUtils";
//
// 把字符串写入文件指定 UTF-8 编码
//
public static void writeStringToFile(String szFilePath, String szContent) throws IOException {
File file = new File(szFilePath);
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
FileOutputStream outputStream = new FileOutputStream(file);
OutputStreamWriter writer = new OutputStreamWriter(outputStream, StandardCharsets.UTF_8);
writer.write(szContent);
writer.close();
}
//
// 读取文件到字符串指定 UTF-8 编码
//
public static String readStringFromFile(String szFilePath) throws IOException {
File file = new File(szFilePath);
FileInputStream inputStream = new FileInputStream(file);
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
StringBuilder content = new StringBuilder();
int character;
while ((character = reader.read()) != -1) {
content.append((char) character);
}
reader.close();
return content.toString();
}
}

View File

@ -0,0 +1,142 @@
package cc.winboll.studio.libappbase.bean;
/**
* @Author ZhanGSKen@QQ.COM
* @Date 2025/01/20 14:19:02
* @Describe 应用信息类
*/
import cc.winboll.studio.libappbase.R;
import java.io.Serializable;
public class APPInfo implements Serializable {
public static final String TAG = "APPInfo";
// 应用名称
String appName;
// 应用图标
int appIcon;
// 应用描述
String appDescription;
// 应用Git仓库地址
String appGitName;
// 应用Git仓库拥有者
String appGitOwner;
// 应用Git仓库分支
String appGitAPPBranch;
// 应用Git仓库子项目文件夹
String appGitAPPSubProjectFolder;
// 应用主页
String appHomePage;
// 应用包名称
String appAPKName;
// 应用包存储文件夹名称
String appAPKFolderName;
public APPInfo(String appName, int appIcon, String appDescription, String appGitName, String appGitOwner, String appGitAPPBranch, String appGitAPPSubProjectFolder, String appHomePage, String appAPKName, String appAPKFolderName) {
this.appName = appName;
this.appIcon = appIcon;
this.appDescription = appDescription;
this.appGitName = appGitName;
this.appGitOwner = appGitOwner;
this.appGitAPPBranch = appGitAPPBranch;
this.appGitAPPSubProjectFolder = appGitAPPSubProjectFolder;
this.appHomePage = appHomePage;
this.appAPKName = appAPKName;
this.appAPKFolderName = appAPKFolderName;
}
public APPInfo() {
this.appName = "WinBoll-APP";
this.appIcon = R.drawable.ic_launcher;
this.appDescription = "WinBoll APP";
this.appGitName = "APP";
this.appGitOwner = "Studio";
this.appGitAPPBranch = "app";
this.appGitAPPSubProjectFolder = "app";
this.appHomePage = "https://www.winboll.cc/studio/details.php?app=APP";
this.appAPKName = "APP";
this.appAPKFolderName = "APP";
}
public void setAppGitOwner(String appGitOwner) {
this.appGitOwner = appGitOwner;
}
public String getAppGitOwner() {
return appGitOwner;
}
public void setAppGitAPPBranch(String appGitAPPBranch) {
this.appGitAPPBranch = appGitAPPBranch;
}
public String getAppGitAPPBranch() {
return appGitAPPBranch;
}
public void setAppGitAPPSubProjectFolder(String appGitAPPSubProjectFolder) {
this.appGitAPPSubProjectFolder = appGitAPPSubProjectFolder;
}
public String getAppGitAPPSubProjectFolder() {
return appGitAPPSubProjectFolder;
}
public void setAppIcon(int appIcon) {
this.appIcon = appIcon;
}
public int getAppIcon() {
return appIcon;
}
public void setAppDescription(String appDescription) {
this.appDescription = appDescription;
}
public String getAppDescription() {
return appDescription;
}
public void setAppAPKFolderName(String appAPKFolderName) {
this.appAPKFolderName = appAPKFolderName;
}
public String getAppAPKFolderName() {
return appAPKFolderName;
}
public void setAppName(String appName) {
this.appName = appName;
}
public String getAppName() {
return appName;
}
public void setAppGitName(String appGitName) {
this.appGitName = appGitName;
}
public String getAppGitName() {
return appGitName;
}
public void setAppHomePage(String appHomePage) {
this.appHomePage = appHomePage;
}
public String getAppHomePage() {
return appHomePage;
}
public void setAppAPKName(String appAPKName) {
this.appAPKName = appAPKName;
}
public String getAppAPKName() {
return appAPKName;
}
}

View File

@ -0,0 +1,87 @@
package cc.winboll.studio.libappbase.bean;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/17 00:29:29
* @Describe APPSOSReportBean
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
import java.io.Serializable;
public class APPSOSBean extends BaseBean {
public static final String TAG = "APPSOSBean";
protected String sosPackage;
protected String sosClassName;
public APPSOSBean() {
this.sosPackage = "";
this.sosClassName = "";
}
public APPSOSBean(String sosPackage, String sosClassName) {
this.sosPackage = sosPackage;
this.sosClassName = sosClassName;
}
public void setSosPackage(String sosPackage) {
this.sosPackage = sosPackage;
}
public String getSosPackage() {
return sosPackage;
}
public void setSosClassName(String sosClassName) {
this.sosClassName = sosClassName;
}
public String getSosClassName() {
return sosClassName;
}
@Override
public String getName() {
return APPSOSBean.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
jsonWriter.name("sosPackage").value(getSosPackage());
jsonWriter.name("sosClassName").value(getSosClassName());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("sosPackage")) {
setSosPackage(jsonReader.nextString());
} else if (name.equals("sosClassName")) {
setSosClassName(jsonReader.nextString());
} else {
return false;
}
}
return true;
}
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
// 结束 JSON 对象
jsonReader.endObject();
return this;
}
}

View File

@ -0,0 +1,66 @@
package cc.winboll.studio.libappbase.bean;
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/13 04:27:42
*/
public class SimpleOperateSignalCenterServiceBean extends BaseBean {
public static final String TAG = "SimpleOperateSignalCenterServiceBean";
boolean isEnable;
public SimpleOperateSignalCenterServiceBean() {
this.isEnable = false;
}
public void setIsEnable(boolean isEnable) {
this.isEnable = isEnable;
}
public boolean isEnable() {
return isEnable;
}
@Override
public String getName() {
return SimpleOperateSignalCenterServiceBean.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
jsonWriter.name("isEnable").value(isEnable());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("isEnable")) {
setIsEnable(jsonReader.nextBoolean());
} else {
return false;
}
}
return true;
}
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
// 结束 JSON 对象
jsonReader.endObject();
return this;
}
}

View File

@ -0,0 +1,67 @@
package cc.winboll.studio.libappbase.bean;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/19 13:34:52
* @Describe TestServiceBean
*/
import android.util.JsonReader;
import android.util.JsonWriter;
import cc.winboll.studio.libappbase.BaseBean;
import java.io.IOException;
public class TestServiceBean extends BaseBean {
public static final String TAG = "TestServiceBean";
boolean isEnable;
public TestServiceBean() {
this.isEnable = false;
}
public void setIsEnable(boolean isEnable) {
this.isEnable = isEnable;
}
public boolean isEnable() {
return isEnable;
}
@Override
public String getName() {
return TestServiceBean.class.getName();
}
@Override
public void writeThisToJsonWriter(JsonWriter jsonWriter) throws IOException {
super.writeThisToJsonWriter(jsonWriter);
jsonWriter.name("isEnable").value(isEnable());
}
@Override
public boolean initObjectsFromJsonReader(JsonReader jsonReader, String name) throws IOException {
if (super.initObjectsFromJsonReader(jsonReader, name)) { return true; } else {
if (name.equals("isEnable")) {
setIsEnable(jsonReader.nextBoolean());
} else {
return false;
}
}
return true;
}
@Override
public BaseBean readBeanFromJsonReader(JsonReader jsonReader) throws IOException {
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (!initObjectsFromJsonReader(jsonReader, name)) {
jsonReader.skipValue();
}
}
// 结束 JSON 对象
jsonReader.endObject();
return this;
}
}

View File

@ -0,0 +1,29 @@
package cc.winboll.studio.libappbase.receiver;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/13 21:19:09
* @Describe MyBroadcastReceiver
*/
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.R;
public class MyBroadcastReceiver extends BroadcastReceiver {
public static final String TAG = "MyBroadcastReceiver";
@Override
public void onReceive(Context context, Intent intent) {
if (context.getString(R.string.action_sos).equals(intent.getAction())) {
String message = intent.getStringExtra("message");
String sosPackage = intent.getStringExtra("sosPackage");
// 处理接收到的广播消息
LogUtils.d(TAG, String.format("MyBroadcastReceiver action %s \n%s\n%s", intent.getAction(), sosPackage, message));
}
}
}

View File

@ -0,0 +1,149 @@
package cc.winboll.studio.libappbase.services;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/15 20:48:36
* @Describe TestService
*/
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.SOS;
import cc.winboll.studio.libappbase.bean.APPSOSBean;
import cc.winboll.studio.libappbase.bean.TestServiceBean;
public class TestService extends Service {
public static final String TAG = "TestService";
volatile static TestThread _TestThread;
volatile static boolean _IsRunning;
public synchronized static void setIsRunning(boolean isRunning) {
_IsRunning = isRunning;
}
public static boolean isRunning() {
return _IsRunning;
}
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
public class MyBinder extends Binder {
public TestService getService() {
return TestService.this;
}
}
@Override
public void onCreate() {
super.onCreate();
LogUtils.d(TAG, "onCreate()");
run();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtils.d(TAG, "onStartCommand(...)");
TestServiceBean bean = TestServiceBean.loadBean(this, TestServiceBean.class);
if (bean == null) {
bean = new TestServiceBean();
}
if (intent.getAction() != null && intent.getAction().equals(SOS.ACTION_SERVICE_ENABLE)) {
bean.setIsEnable(true);
TestServiceBean.saveBean(this, bean);
run();
} else if (intent.getAction() != null && intent.getAction().equals(SOS.ACTION_SERVICE_DISABLE)) {
bean.setIsEnable(false);
TestServiceBean.saveBean(this, bean);
}
LogUtils.d(TAG, String.format("TestServiceBean.saveBean setIsEnable %s", bean.isEnable()));
return (bean.isEnable()) ? START_STICKY : super.onStartCommand(intent, flags, startId);
//return super.onStartCommand(intent, flags, startId);
}
void run() {
LogUtils.d(TAG, "run()");
TestServiceBean bean = TestServiceBean.loadBean(this, TestServiceBean.class);
if (bean == null) {
bean = new TestServiceBean();
TestServiceBean.saveBean(this, bean);
}
if (bean.isEnable()) {
LogUtils.d(TAG, "run() bean.isEnable()");
TestThread.getInstance(this).start();
LogUtils.d(TAG, "_TestThread.start()");
}
}
@Override
public void onDestroy() {
super.onDestroy();
LogUtils.d(TAG, "onDestroy()");
TestThread.getInstance(this).setIsExit(true);
_IsRunning = false;
}
static class TestThread extends Thread {
volatile static TestThread _TestThread;
Context mContext;
volatile boolean isStarted = false;
volatile boolean isExit = false;
TestThread(Context context) {
super();
mContext = context;
}
public static synchronized TestThread getInstance(Context context) {
if (_TestThread != null) {
_TestThread.setIsExit(true);
}
_TestThread = new TestThread(context);
return _TestThread;
}
public synchronized void setIsExit(boolean isExit) {
this.isExit = isExit;
}
public boolean isExit() {
return isExit;
}
@Override
public void run() {
if (isStarted == false) {
isStarted = true;
super.run();
LogUtils.d(TAG, "run() start");
SOS.bindToAPPService(mContext, new APPSOSBean(mContext.getPackageName(), TestService.class.getName()));
while (!isExit()) {
LogUtils.d(TAG, "run()");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
LogUtils.d(TAG, e, Thread.currentThread().getStackTrace());
}
}
LogUtils.d(TAG, "run() exit");
}
}
}
}

View File

@ -0,0 +1,48 @@
package cc.winboll.studio.libappbase.utils;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/17 19:38:20
* @Describe 服务工具集
*/
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.util.Log;
import cc.winboll.studio.libappbase.LogUtils;
import java.util.List;
public class ServiceUtils {
public static final String TAG = "ServiceUtils";
/**
* 检查指定服务是否正在运行
* @param context 上下文
* @param serviceClass 服务类
* @return true 如果服务正在运行否则返回 false
*/
public static boolean isServiceRunning(Context context, String serviceClassName) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (activityManager == null) {
return false;
}
List<ActivityManager.RunningServiceInfo> runningServices;
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Intent intent = new Intent(context, serviceClass);
// runningServices = activityManager.getRunningServices(100, intent);
// } else {
runningServices = activityManager.getRunningServices(100);
//}
for (ActivityManager.RunningServiceInfo serviceInfo : runningServices) {
if (serviceClassName.equals(serviceInfo.service.getClassName())) {
LogUtils.d(TAG, "Service is running: " + serviceInfo.service.getClassName());
return true;
}
}
LogUtils.d(TAG, "Service is not running: " + serviceClassName);
return false;
}
}

View File

@ -0,0 +1,63 @@
package cc.winboll.studio.libappbase.widgets;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/17 20:32:12
*/
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.widget.RemoteViews;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.R;
import cc.winboll.studio.libappbase.utils.ServiceUtils;
import com.hjq.toast.ToastUtils;
import android.content.ServiceConnection;
import android.os.IBinder;
import cc.winboll.studio.libappbase.services.TestService;
public class StatusWidget extends AppWidgetProvider {
public static final String TAG = "StatusWidget";
public static final String ACTION_STATUS_UPDATE = "cc.winboll.studio.libappbase.widgets.APPWidget.ACTION_STATUS_UPDATE";
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (intent.getAction().equals(ACTION_STATUS_UPDATE)) {
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, StatusWidget.class));
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
}
}
}
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_status);
//设置按钮点击事件
Intent intentAppButton = new Intent(context, StatusWidgetClickListener.class);
intentAppButton.setAction(StatusWidgetClickListener.ACTION_IVAPP);
PendingIntent pendingIntentAppButton = PendingIntent.getBroadcast(context, 0, intentAppButton, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(R.id.ivapp, pendingIntentAppButton);
boolean isActive = ServiceUtils.isServiceRunning(context, TestService.class.getName());
if (isActive) {
views.setImageViewResource(R.id.ivapp, cc.winboll.studio.libappbase.R.drawable.ic_launcher);
} else {
views.setImageViewResource(R.id.ivapp, cc.winboll.studio.libappbase.R.drawable.ic_launcher_disable);
}
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}

View File

@ -0,0 +1,33 @@
package cc.winboll.studio.libappbase.widgets;
/**
* @Author ZhanGSKen@AliYun.Com
* @Date 2025/02/17 20:33:53
* @Describe APPWidgetClickListener
*/
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import cc.winboll.studio.libappbase.LogUtils;
import com.hjq.toast.ToastUtils;
public class StatusWidgetClickListener extends BroadcastReceiver {
public static final String TAG = "APPWidgetClickListener";
public static final String ACTION_IVAPP = "cc.winboll.studio.libappbase.widgets.StatusWidgetClickListener.ACTION_IVAPP";
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null) {
LogUtils.d(TAG, String.format("action %s", action));
return;
}
if (action.equals(ACTION_IVAPP)) {
ToastUtils.show("ACTION_LAUNCHER");
} else {
LogUtils.d(TAG, String.format("action %s", action));
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<!-- 阴影部分 -->
<!-- 个人觉得更形象的表达top代表下边的阴影高度left代表右边的阴影宽度。其实也就是相对应的offsetsolid中的颜色是阴影的颜色也可以设置角度等等 -->
<item
android:left="2dp"
android:top="2dp"
android:right="2dp"
android:bottom="2dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="#0F000000"
android:startColor="#0F000000" />
<corners
android:bottomLeftRadius="6dip"
android:bottomRightRadius="6dip"
android:topLeftRadius="6dip"
android:topRightRadius="6dip" />
</shape>
</item>
<!-- 背景部分 -->
<!-- 形象的表达bottom代表背景部分在上边缘超出阴影的高度right代表背景部分在左边超出阴影的宽度相对应的offset -->
<item
android:left="3dp"
android:top="3dp"
android:right="3dp"
android:bottom="5dp">
<shape android:shape="rectangle" >
<gradient
android:angle="270"
android:endColor="@color/colorAccent"
android:startColor="@color/colorAccent" />
<corners
android:bottomLeftRadius="6dip"
android:bottomRightRadius="6dip"
android:topLeftRadius="6dip"
android:topRightRadius="6dip" />
</shape>
</item>
</layer-list>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:clickable="true"
android:layout_width="24dp"
android:layout_height="24dp">
<item android:drawable="@drawable/ic_launcher_background"/>
<item
android:left="0dp"
android:top="0dp"
android:right="0dp"
android:bottom="0dp"
android:drawable="@drawable/ic_launcher_foreground"/>
</layer-list>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#FF005C12"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
android:clickable="true"
android:layout_width="24dp"
android:layout_height="24dp">
<item android:drawable="@drawable/ic_launcher_background"/>
<item
android:left="0dp"
android:top="0dp"
android:right="0dp"
android:bottom="0dp"
android:drawable="@drawable/ic_launcher_foreground_disable"/>
</layer-list>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M16.61,15.15C16.15,15.15 15.77,14.78 15.77,14.32S16.15,13.5 16.61,13.5H16.61C17.07,13.5 17.45,13.86 17.45,14.32C17.45,14.78 17.07,15.15 16.61,15.15M7.41,15.15C6.95,15.15 6.57,14.78 6.57,14.32C6.57,13.86 6.95,13.5 7.41,13.5H7.41C7.87,13.5 8.24,13.86 8.24,14.32C8.24,14.78 7.87,15.15 7.41,15.15M16.91,10.14L18.58,7.26C18.67,7.09 18.61,6.88 18.45,6.79C18.28,6.69 18.07,6.75 18,6.92L16.29,9.83C14.95,9.22 13.5,8.9 12,8.91C10.47,8.91 9,9.24 7.73,9.82L6.04,6.91C5.95,6.74 5.74,6.68 5.57,6.78C5.4,6.87 5.35,7.08 5.44,7.25L7.1,10.13C4.25,11.69 2.29,14.58 2,18H22C21.72,14.59 19.77,11.7 16.91,10.14H16.91Z"/>
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FF808080"
android:pathData="M16.61,15.15C16.15,15.15 15.77,14.78 15.77,14.32S16.15,13.5 16.61,13.5H16.61C17.07,13.5 17.45,13.86 17.45,14.32C17.45,14.78 17.07,15.15 16.61,15.15M7.41,15.15C6.95,15.15 6.57,14.78 6.57,14.32C6.57,13.86 6.95,13.5 7.41,13.5H7.41C7.87,13.5 8.24,13.86 8.24,14.32C8.24,14.78 7.87,15.15 7.41,15.15M16.91,10.14L18.58,7.26C18.67,7.09 18.61,6.88 18.45,6.79C18.28,6.69 18.07,6.75 18,6.92L16.29,9.83C14.95,9.22 13.5,8.9 12,8.91C10.47,8.91 9,9.24 7.73,9.82L6.04,6.91C5.95,6.74 5.74,6.68 5.57,6.78C5.4,6.87 5.35,7.08 5.44,7.25L7.1,10.13C4.25,11.69 2.29,14.58 2,18H22C21.72,14.59 19.77,11.7 16.91,10.14H16.91Z"/>
</vector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="1dp"
android:color="#000000" /> <!-- 这里可调整边框宽度和颜色 -->
<solid android:color="@android:color/transparent" />
</shape>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<cc.winboll.studio.libappbase.GlobalCrashReportView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/activityglobalcrashGlobalCrashReportView1"/>
</LinearLayout>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<cc.winboll.studio.libappbase.LogView
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0"
android:id="@+id/logview"/>
</LinearLayout>

View File

@ -1,11 +0,0 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<TextView
android:text="@string/hello_world"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/viewglobalcrashreportLinearLayout1">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/viewglobalcrashreportToolbar1"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1.0">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFFFFFFF"
android:id="@+id/viewglobalcrashreportTextView1"/>
</HorizontalScrollView>
</ScrollView>
</LinearLayout>

View File

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF000000">
<RelativeLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_alignParentTop="true"
android:background="@drawable/bg_shadow"
android:id="@+id/viewlogRelativeLayoutToolbar">
<Button
android:layout_width="wrap_content"
android:layout_height="36dp"
android:text="Clean"
android:textSize="14dp"
android:layout_centerVertical="true"
android:id="@+id/viewlogButtonClean"
android:layout_marginLeft="5dp"/>
<TextView
android:background="#FF000000"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:text="LV:"
android:layout_toRightOf="@+id/viewlogButtonClean"
android:layout_centerVertical="true"
android:id="@+id/viewlogTextView1"
android:textColor="#FFFFFFFF"/>
<Spinner
android:background="#FFFFFFFF"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_toRightOf="@+id/viewlogTextView1"
android:layout_centerVertical="true"
android:id="@+id/viewlogSpinner1"/>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_toLeftOf="@+id/viewlogButtonCopy"
android:layout_centerVertical="true"
android:text="Selectable"
android:background="#FFFFFFFF"
android:paddingRight="10dp"
android:textSize="16dp"
android:id="@+id/viewlogCheckBoxSelectable"/>
<Button
android:layout_width="wrap_content"
android:layout_height="36dp"
android:text="Copy"
android:layout_alignParentRight="true"
android:textSize="14dp"
android:layout_centerVertical="true"
android:id="@+id/viewlogButtonCopy"
android:layout_marginRight="5dp"/>
</RelativeLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/bg_shadow"
android:layout_below="@+id/viewlogRelativeLayoutToolbar"
android:id="@+id/viewlogLinearLayout1"
android:gravity="center_vertical">
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ALL"
android:id="@+id/viewlogCheckBox1"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/view_border"
android:id="@+id/viewlogRecyclerView1"
android:layout_weight="1.0"
android:layout_marginRight="5dp"
android:padding="2dp"/>
</LinearLayout>
<RelativeLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:layout_alignParentBottom="true"
android:layout_below="@+id/viewlogLinearLayout1">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF000000"
android:id="@+id/viewlogScrollViewLog">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Text"
android:textColor="#FF00FF00"
android:textIsSelectable="true"
android:id="@+id/viewlogTextViewLog"/>
</ScrollView>
</RelativeLayout>
</RelativeLayout>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardBackgroundColor="#F5F5F5"
app:cardElevation="4dp"
app:cardCornerRadius="4dp"
android:id="@+id/listviewauthinfoCardView1"
android:layout_marginLeft="0dp"
android:layout_marginRight="5dp">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="24dp"
android:layout_marginLeft="5dp"
android:id="@+id/viewlogtagTextView1"/>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="24dp"
android:id="@+id/viewlogtagCheckBox1"/>
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="40dp"
android:layout_height="40dp">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:id="@+id/ivapp"/>
</LinearLayout>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="@android:style/Theme.Material.Light">
</style>
</resources>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="enum_loglevel_array">
<item>Off</item>
<item>Error</item>
<item>Warn</item>
<item>Info</item>
<item>Debug</item>
<item>Verbose</item>
</array>
</resources>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="attrColorPrimary" format="color" />
<attr name="themeGlobalCrashActivity" format="reference"/>
<declare-styleable name="GlobalCrashActivity">
<attr name="colorTittle" format="color" />
<attr name="colorTittleBackgound" format="color" />
<attr name="colorText" format="color" />
<attr name="colorTextBackgound" format="color" />
</declare-styleable>
</resources>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#FF00B322</color>
<color name="colorPrimaryDark">#FF005C12</color>
<color name="colorAccent">#FF8DFFA2</color>
<color name="colorText">#FFFFFB8D</color>
</resources>

View File

@ -3,5 +3,5 @@
<string name="lib_name">libappbase</string> <string name="lib_name">libappbase</string>
<string name="hello_world">Hello world!</string> <string name="hello_world">Hello world!</string>
<string name="action_sos">cc.winboll.studio.libappbase.action.SOS</string>
</resources> </resources>

View File

@ -1,5 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="AppTheme" parent="@android:style/Theme.Holo.Light">
<style name="APPBaseTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="themeGlobalCrashActivity">@style/GlobalCrashActivityTheme</item>
</style> </style>
<style name="GlobalCrashActivityTheme" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
<item name="colorTittle">#FFFFF600</item>
<item name="colorTittleBackgound">#FF00B322</item>
<item name="colorText">#FF00B322</item>
<item name="colorTextBackgound">#FF000000</item>
</style>
</resources> </resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="40dp"
android:minHeight="40dp"
android:updatePeriodMillis="1000"
android:initialLayout="@layout/widget_status"
android:resizeMode="none">
</appwidget-provider>

View File

@ -1,10 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- WinBoll 默认方案 -->
<color name="colorPrimary">#FF196ABC</color> <color name="colorPrimary">#FF196ABC</color>
<color name="colorPrimaryDark">#FF002B57</color> <color name="colorPrimaryDark">#FF002B57</color>
<color name="colorAccent">#FF80BFFF</color> <color name="colorAccent">#FF80BFFF</color>
<color name="colorToastFrame">#FFA9A9A9</color>
<color name="colorToastShadow">#FF000000</color>
<color name="colorToastBackgroung">#FFFFFFFF</color>
</resources> </resources>

1
mymessagemanager/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,45 @@
# MyMessageManager
#### 介绍
用正则表达式方法自定义短信过滤和语音播报的短信应用。
#### 软件架构
软件架构说明
#### 安装教程
1. xxxx
2. xxxx
3. xxxx
#### 使用说明
1. xxxx
2. xxxx
3. xxxx
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码ZhanGSKen(ZhanGSKen@QQ.COM)
4. 新建 Pull Request
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
#### 参考文档
使用GitHub Actions实现Android自动打包apk
https://blog.csdn.net/ZZL23333/article/details/115798615?app_version=6.0.0&code=app_1562916241&csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22115798615%22%2C%22source%22%3A%22weixin_38986226%22%7D&uLinkId=usr1mkqgl919blen&utm_source=app
Android中assets的使用用于读取内容
https://blog.csdn.net/qq_27664947/article/details/103924058?app_version=6.0.0&code=app_1562916241&csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22103924058%22%2C%22source%22%3A%22weixin_38986226%22%7D&uLinkId=usr1mkqgl919blen&utm_source=app

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,69 @@
apply plugin: 'com.android.application'
apply from: '../.winboll/winboll_app_build.gradle'
apply from: '../.winboll/winboll_lint_build.gradle'
def genVersionName(def versionName){
//
assert (winbollBuildProps['stageCount'] != null)
assert (winbollBuildProps['baseVersion'] != null)
//
winbollBuildProps.setProperty("baseVersion", "${versionName}");
//
FileOutputStream fos = new FileOutputStream(winbollBuildPropsFile)
winbollBuildProps.store(fos, "${winbollBuildPropsDesc}");
fos.close();
//
return "${versionName}." + winbollBuildProps['stageCount']
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "cc.winboll.studio.mymessagemanager"
minSdkVersion 26
targetSdkVersion 29
versionCode 8
// versionName
// .winboll/winbollBuildProps.properties stageCount=0
// Gradle编译环境下合起来的 versionName "${versionName}.0"
versionName "4.1"
if(true) {
versionName = genVersionName("${versionName}")
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
api 'cc.winboll.studio:winboll-shared:1.6.4'
api 'io.github.medyo:android-about-page:2.0.0'
api 'com.github.getActivity:ToastUtils:10.5'
api 'com.jcraft:jsch:0.1.55'
api 'org.jsoup:jsoup:1.13.1'
api 'com.squareup.okhttp3:okhttp:4.4.1'
api 'androidx.appcompat:appcompat:1.0.0'
api 'androidx.fragment:fragment:1.0.0'
api 'com.google.android.material:material:1.0.0'
// https://github.com/getActivity/XXPermissions
api 'com.github.getActivity:XXPermissions:18.63'
api 'com.baoyz.pullrefreshlayout:library:1.2.0'
api 'androidx.appcompat:appcompat:1.0.0'
api 'androidx.fragment:fragment:1.0.0'
api 'com.google.android.material:material:1.0.0'
api 'cc.winboll.studio:libaes:7.6.0'
api fileTree(dir: 'libs', include: ['*.jar'])
}

View File

@ -0,0 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Tue Feb 25 10:52:41 GMT 2025
stageCount=14
libraryProject=
baseVersion=4.1
publishVersion=4.1.13
buildCount=5
baseBetaVersion=4.1.14

17
mymessagemanager/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in C:\tools\adt-bundle-windows-x86_64-20131030\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" >
<application>
<!-- Put flavor specific code here -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="cc.winboll.studio.mymessagemanager.beta.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider"/>
</provider>
</application>
</manifest>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">我的短信管家 ☆</string>
</resources>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">My Message Manager +</string>
</resources>

Some files were not shown because too many files have changed in this diff Show More