Compare commits

...

5 Commits

8 changed files with 415 additions and 605 deletions

View File

@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
#Sun Dec 07 15:19:14 HKT 2025
stageCount=2
#Wed Dec 10 16:20:34 HKT 2025
stageCount=4
libraryProject=
baseVersion=15.12
publishVersion=15.12.1
publishVersion=15.12.3
buildCount=0
baseBetaVersion=15.12.2
baseBetaVersion=15.12.4

View File

@@ -13,13 +13,13 @@ import android.view.Menu;
import android.view.MenuItem;
import android.widget.RelativeLayout;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.libaes.activitys.AboutActivity;
import cc.winboll.studio.libaes.models.APPInfo;
import cc.winboll.studio.libaes.utils.AESThemeUtil;
import cc.winboll.studio.libaes.utils.DevelopUtils;
import cc.winboll.studio.libaes.utils.WinBoLLActivityManager;
import cc.winboll.studio.libaes.views.ADsBannerView;
import cc.winboll.studio.libappbase.LogActivity;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.powerbell.activities.BackgroundSettingsActivity;
import cc.winboll.studio.powerbell.activities.BatteryReportActivity;
@@ -42,38 +42,21 @@ import cc.winboll.studio.powerbell.utils.PermissionUtils;
*/
public class MainActivity extends WinBoLLActivity {
// ======================== 静态常量(首屏排列,统一管理)========================
public static final String TAG = "MainActivity";
private static final int REQUEST_WRITE_STORAGE_PERMISSION = 1001;
// private static final String PRIVACY_FILE = "privacy_pfs";
// private static final String PRIVACY_VALUE = "privacy_value";//0: 拒绝1赞同
//
// private SharedPreferences mSharedPreferences;
//
// private String BANNER_POS_ID = "802e356f1726f9ff39c69308bfd6f06a";
// private String BANNER_POS_ID_WINBOLL_BETA = "d129ee5a263911f981a6dc7a9802e3e7";
// private String BANNER_POS_ID_WINBOLL = "4ec30efdb32271765b9a4efac902828b";
// ======================== 成员属性(按「静态→非静态」「核心→辅助」排序)========================
public static MainActivity _mMainActivity; // 静态Activity实例全局调用
private App mApplication; // 应用Application实例
private Menu mMenu; // 顶部菜单实例
private Fragment mCurrentShowFragment; // 当前显示的Fragment
private MainViewFragment mMainViewFragment; // 主Fragment核心视图
private Toolbar mToolbar; // 顶部工具栏
private ADsBannerView mADsBannerView; // 广告Banner视图
// private BannerAd mBannerAd;
// private List<BannerAd> mAllBanners = new ArrayList<>();
//
// private ViewGroup mContainer;
//
// private boolean mIsBiddingWin = true;
//
// public static final int BACKGROUND_PICTURE_REQUEST_CODE = 0;
public static MainActivity _mMainActivity;
private App mApplication;
private Menu mMenu;
private Fragment mCurrentShowFragment;
private MainViewFragment mMainViewFragment;
private Toolbar mToolbar;
// 新增主线程Handler确保广告操作在主线程执行
//private Handler mMainHandler;
ADsBannerView mADsBannerView;
// ======================== 重写父类抽象方法(优先排列,明确实现)========================
@Override
public Activity getActivity() {
return this;
@@ -89,74 +72,132 @@ public class MainActivity extends WinBoLLActivity {
LogUtils.d(TAG, "onCreate(...)");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mADsBannerView = findViewById(R.id.adsbanner);
// mContainer = findViewById(R.id.ads_container);
//
// // 初始化主线程Handler关键确保广告操作在主线程执行
// mMainHandler = new Handler(Looper.getMainLooper());
//
// // 米盟模块:隐私协议弹窗
// showPrivacy();
// 初始化视图与实例
initView();
initInstance();
initFragment();
_mMainActivity = this;
mApplication = (App) getApplication();
// 初始化工具栏
mToolbar = findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
// 初始化主Fragment
if (mMainViewFragment == null) {
FragmentTransaction tx = getFragmentManager().beginTransaction();
mMainViewFragment = new MainViewFragment();
tx.add(R.id.activitymainFrameLayout1, mMainViewFragment, MainViewFragment.TAG);
tx.commit();
}
showFragment(mMainViewFragment);
PermissionUtils.getInstance().checkAndRequestStoragePermission(this);
// 权限申请
PermissionUtils.getInstance().checkAndRequestStoragePermission(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
// // 修复:释放广告资源,避免内存泄漏
// releaseAdResources();
// 释放广告资源
if (mADsBannerView != null) {
mADsBannerView.releaseAdResources();
}
// 置空静态引用,避免内存泄漏
_mMainActivity = null;
// // 移除Handler回调
// if (mMainHandler != null) {
// mMainHandler.removeCallbacksAndMessages(null);
// }
if (mADsBannerView != null) {
mADsBannerView.releaseAdResources();
}
}
//
// /**
// * 释放广告资源关键避免内存泄漏和空Context调用
// */
// private void releaseAdResources() {
// LogUtils.d(TAG, "releaseAdResources()");
// // 销毁所有广告实例
// if (mAllBanners != null && !mAllBanners.isEmpty()) {
// for (BannerAd ad : mAllBanners) {
// if (ad != null) {
// ad.destroy();
// }
// }
// mAllBanners.clear();
// }
// // 置空当前广告引用
// mBannerAd = null;
// // 移除广告容器中的视图
// if (mContainer != null) {
// mContainer.removeAllViews();
// }
// }
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
reloadBackground();
}
@Override
protected void onResume() {
super.onResume();
reloadBackground();
setBackgroundColor();
// 恢复广告
if (mADsBannerView != null) {
mADsBannerView.resumeADs(MainActivity.this);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
mMenu = menu;
// 主题菜单
AESThemeUtil.inflateMenu(this, menu);
// 调试工具菜单仅Debug模式显示
if (App.isDebugging()) {
DevelopUtils.inflateMenu(this, menu);
}
// 应用核心菜单
getMenuInflater().inflate(R.menu.toolbar_main, mMenu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int menuItemId = item.getItemId();
// 主题切换菜单
if (AESThemeUtil.onAppThemeItemSelected(this, item)) {
recreate();
return true;
}
// 调试工具菜单
if (DevelopUtils.onDevelopItemSelected(this, item)) {
LogUtils.d(TAG, String.format("onOptionsItemSelected item.getItemId() %d ", item.getItemId()));
return true;
}
// 应用核心菜单
switch (menuItemId) {
case R.id.action_settings:
startActivity(new Intent(this, SettingsActivity.class));
break;
case R.id.action_battery_report:
startActivity(new Intent(this, BatteryReportActivity.class));
break;
case R.id.action_clearrecord:
startActivity(new Intent(this, ClearRecordActivity.class));
break;
case R.id.action_changepicture:
startActivity(new Intent(this, BackgroundSettingsActivity.class));
break;
case R.id.action_unittestactivity:
startActivity(new Intent(this, MainUnitTestActivity.class));
break;
case R.id.action_about:
Intent intent = new Intent(getApplicationContext(), AboutActivity.class);
APPInfo appInfo = genDefaultAPPInfo();
intent.putExtra(AboutActivity.EXTRA_APPINFO, appInfo);
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), intent, AboutActivity.class);
break;
default:
return super.onOptionsItemSelected(item);
}
return true;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onBackPressed() {
if (mCurrentShowFragment != mMainViewFragment) {
showFragment(mMainViewFragment);
} else {
moveTaskToBack(true);
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return super.dispatchKeyEvent(event);
}
@Override
public void setupToolbar() {
super.setupToolbar();
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
}
}
// ======================== 核心业务逻辑方法(按功能优先级排序)========================
/**
* 显示指定Fragment隐藏其他Fragment
*/
void showFragment(Fragment fragment) {
FragmentTransaction tx = getFragmentManager().beginTransaction();
for (Fragment item : getFragmentManager().getFragments()) {
@@ -167,21 +208,34 @@ public class MainActivity extends WinBoLLActivity {
mCurrentShowFragment = fragment;
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
reloadBackground();
}
/**
* 刷新背景(全局静态调用,带生命周期校验)
*/
public static void reloadBackground() {
// 修复添加非空校验避免Activity已销毁时调用
if (_mMainActivity != null && !_mMainActivity.isFinishing() && !_mMainActivity.isDestroyed()) {
_mMainActivity.mMainViewFragment.reloadBackground();
}
}
/**
* 通过Uri获取文件在本地存储的真实路径
* 设置主页面背景颜色从BackgroundBean获取颜色值
*/
void setBackgroundColor() {
// 生命周期校验避免Activity销毁后操作UI
if (isFinishing() || isDestroyed()) {
return;
}
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
BackgroundBean bean = utils.getCurrentBackgroundBean();
int nPixelColor = bean.getPixelColor();
RelativeLayout mainLayout = findViewById(R.id.activitymainRelativeLayout1);
if (mainLayout != null) {
mainLayout.setBackgroundColor(nPixelColor);
}
}
/**
* 通过Uri获取文件本地真实路径兼容图片选择等场景
*/
private String getRealPathFromURI(Uri contentUri) {
String[] proj = {MediaStore.MediaColumns.DATA};
@@ -200,78 +254,10 @@ public class MainActivity extends WinBoLLActivity {
return null;
}
@Override
protected void onResume() {
super.onResume();
reloadBackground();
setBackgroundColor();
if (mADsBannerView != null) {
mADsBannerView.resumeADs(MainActivity.this);
}
// // 修复:优化广告请求逻辑(添加生命周期判断 + 主线程执行)
// if (!isFinishing() && !isDestroyed()) {
// String privacyAgreeValue = getSharedPreferences().getString(PRIVACY_VALUE, null);
// if (TextUtils.equals(privacyAgreeValue, String.valueOf(1))) {
// LogUtils.i(TAG, "已同意隐私协议,开始播放米盟广告...");
// mMainHandler.postDelayed(new Runnable() {
// @Override
// public void run() {
// // 再次校验生命周期避免延迟执行时Activity已销毁
// if (!isFinishing() && !isDestroyed()) {
// fetchAd();
// }
// }
// }, 1000); // 延迟1秒请求广告提升页面加载体验
// }
//
// }
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
mMenu = menu;
// 主题菜单
AESThemeUtil.inflateMenu(this, menu);
// 调试工具菜单
if (App.isDebugging()) {
DevelopUtils.inflateMenu(this, menu);
}
// 应用其他菜单
getMenuInflater().inflate(R.menu.toolbar_main, mMenu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int menuItemId = item.getItemId();
if (AESThemeUtil.onAppThemeItemSelected(this, item)) {
recreate();
} if (DevelopUtils.onDevelopItemSelected(this, item)) {
LogUtils.d(TAG, String.format("onOptionsItemSelected item.getItemId() %d ", item.getItemId()));
} else if (menuItemId == R.id.action_settings) {
startActivity(new Intent(this, SettingsActivity.class));
} else if (menuItemId == R.id.action_battery_report) {
startActivity(new Intent(this, BatteryReportActivity.class));
} else if (menuItemId == R.id.action_clearrecord) {
startActivity(new Intent(this, ClearRecordActivity.class));
} else if (menuItemId == R.id.action_changepicture) {
startActivity(new Intent(this, BackgroundSettingsActivity.class));
} else if (menuItemId == R.id.action_unittestactivity) {
startActivity(new Intent(this, MainUnitTestActivity.class));
} else if (menuItemId == R.id.action_about) {
Intent intent = new Intent(getApplicationContext(), AboutActivity.class);
APPInfo appInfo = genDefaultAPPInfo();
intent.putExtra(AboutActivity.EXTRA_APPINFO, appInfo);
WinBoLLActivityManager.getInstance().startWinBoLLActivity(getApplicationContext(), intent, AboutActivity.class);
} else {
return super.onOptionsItemSelected(item);
}
return true;
}
APPInfo genDefaultAPPInfo() {
/**
* 生成默认APP信息用于关于页面
*/
APPInfo genDefaultAPPInfo() {
String szBranchName = "powerbell";
APPInfo appInfo = new APPInfo();
appInfo.setAppName(getString(R.string.app_name));
@@ -287,372 +273,37 @@ public class MainActivity extends WinBoLLActivity {
return appInfo;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// if (requestCode == BACKGROUND_PICTURE_REQUEST_CODE) {
// if (resultCode == RESULT_OK) {
// Toast.makeText(getApplicationContext(), "OK", Toast.LENGTH_SHORT).show();
// }
// } else {
// String sz = "Unsolved requestCode = " + Integer.toString(requestCode);
// Toast.makeText(getApplicationContext(), sz, Toast.LENGTH_SHORT).show();
// LogUtils.d(TAG, sz);
// }
// ======================== 内部初始化辅助方法(私有,统一放在最后)========================
/**
* 初始化视图控件findViewById
*/
private void initView() {
mADsBannerView = findViewById(R.id.adsbanner);
mToolbar = findViewById(R.id.toolbar);
}
@Override
public void onBackPressed() {
if (mCurrentShowFragment != mMainViewFragment) {
showFragment(mMainViewFragment);
} else {
moveTaskToBack(true);
}
/**
* 初始化实例Application、工具类等
*/
private void initInstance() {
_mMainActivity = this;
mApplication = (App) getApplication();
// 初始化工具栏
setSupportActionBar(mToolbar);
mToolbar.setTitleTextAppearance(this, R.style.Toolbar_TitleText);
}
void setBackgroundColor() {
// 修复添加Activity非空校验
if (isFinishing() || isDestroyed()) {
return;
}
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(this);
BackgroundBean bean = utils.getCurrentBackgroundBean();
int nPixelColor = bean.getPixelColor();
RelativeLayout mainLayout = findViewById(R.id.activitymainRelativeLayout1);
if (mainLayout != null) {
mainLayout.setBackgroundColor(nPixelColor);
/**
* 初始化主Fragment添加并显示
*/
private void initFragment() {
if (mMainViewFragment == null) {
FragmentTransaction tx = getFragmentManager().beginTransaction();
mMainViewFragment = new MainViewFragment();
tx.add(R.id.activitymainFrameLayout1, mMainViewFragment, MainViewFragment.TAG);
tx.commit();
}
showFragment(mMainViewFragment);
}
//
// /**
// * 显示广告核心修复传递安全的Context + 生命周期校验)
// */
// private void showAd() {
// LogUtils.d(TAG, "showAd()");
// // 1. 生命周期校验避免Activity已销毁时操作UI
// if (isFinishing() || isDestroyed()) {
// LogUtils.e(TAG, "showAd: Activity is finishing or destroyed");
// return;
// }
// // 2. 非空校验:广告实例和容器
// if (mBannerAd == null || mContainer == null) {
// LogUtils.e(TAG, "showAd: BannerAd or Container is null");
// return;
// }
// // 3. 创建广告容器使用ApplicationContext避免内存泄漏
// final FrameLayout container = new FrameLayout(getApplicationContext());
// container.setPadding(0, 0, 0, MimoUtils.dpToPx(this, 10));
// mContainer.addView(container, new FrameLayout.LayoutParams(
// FrameLayout.LayoutParams.MATCH_PARENT,
// FrameLayout.LayoutParams.WRAP_CONTENT
// ));
//
// if (mIsBiddingWin) {
// mBannerAd.setPrice(getPrice());
// }
// // 4. 显示广告传递ApplicationContext避免Activity Context失效
// mBannerAd.showAd(MainActivity.this, container, new BannerAd.BannerInteractionListener() {
// @Override
// public void onAdClick() {
// LogUtils.d(TAG, "onAdClick");
// }
//
// @Override
// public void onAdShow() {
// LogUtils.d(TAG, "onAdShow");
// }
//
// @Override
// public void onAdDismiss() {
// LogUtils.d(TAG, "onAdDismiss");
// // 修复移除容器时校验Activity状态
// if (!isFinishing() && !isDestroyed() && mContainer != null) {
// mContainer.removeView(container);
// }
// }
//
// @Override
// public void onRenderSuccess() {
// LogUtils.d(TAG, "onRenderSuccess");
// }
//
// @Override
// public void onRenderFail(int code, String msg) {
// LogUtils.e(TAG, "onRenderFail errorCode " + code + " errorMsg " + msg);
// // 修复:渲染失败时移除容器
// if (!isFinishing() && !isDestroyed() && mContainer != null) {
// mContainer.removeView(container);
// }
// }
// });
// }
//
// /**
// * 请求广告核心修复Context安全校验 + 异常捕获 + 资源管理)
// */
// private void fetchAd() {
// LogUtils.d(TAG, "fetchAd()");
// // 1. 双重校验Activity未销毁 + Context非空
// if (isFinishing() || isDestroyed() || getApplicationContext() == null) {
// LogUtils.e(TAG, "fetchAd: Invalid Context or Activity state");
// return;
// }
// // 2. 释放之前的广告资源,避免内存泄漏
// if (mBannerAd != null) {
// mBannerAd.destroy();
// }
// // 3. 初始化广告使用ApplicationContext避免Activity Context失效
// try {
// mBannerAd = new BannerAd();
// mAllBanners.add(mBannerAd);
// } catch (Exception e) {
// LogUtils.e(TAG, "fetchAd: Init BannerAd failed", e);
// return;
// }
// // 4. 设置下载监听
// mBannerAd.setDownLoadListener(new BannerAd.BannerDownloadListener() {
// @Override
// public void onDownloadStarted() {
// LogUtils.d(TAG, "onDownloadStarted");
// }
//
// @Override
// public void onDownloadPaused() {
// LogUtils.d(TAG, "onDownloadPaused");
// }
//
// @Override
// public void onDownloadFailed(int errorCode) {
// LogUtils.d(TAG, "onDownloadFailed, errorCode = " + errorCode);
// }
//
// @Override
// public void onDownloadFinished() {
// LogUtils.d(TAG, "onDownloadFinished");
// }
//
// @Override
// public void onDownloadProgressUpdated(int progress) {
// LogUtils.d(TAG, "onDownloadProgressUpdated " + progress + "%");
// }
//
// @Override
// public void onInstallFailed(int errorCode) {
// LogUtils.d(TAG, "onInstallFailed, errorCode = " + errorCode);
// }
//
// @Override
// public void onInstallStart() {
// LogUtils.d(TAG, "onInstallStart");
// }
//
// @Override
// public void onInstallSuccess() {
// LogUtils.d(TAG, "onInstallSuccess");
// }
//
// @Override
// public void onDownloadCancel() {
// LogUtils.d(TAG, "onDownloadCancel");
// }
// });
//
// // 5. 构建广告参数并请求
// String currentAD_ID = getAD_ID();
// LogUtils.d(TAG, String.format("currentAD_ID = %s", currentAD_ID));
// ADParams params = new ADParams.Builder().setUpId(currentAD_ID).build();
// mBannerAd.loadAd(params, new BannerAd.BannerLoadListener() {
// @Override
// public void onBannerAdLoadSuccess() {
// LogUtils.d(TAG, "onBannerAdLoadSuccess()");
// // 修复广告加载成功后校验Activity状态
// if (!isFinishing() && !isDestroyed()) {
// showAd();
// }
// }
//
// @Override
// public void onAdLoadFailed(int errorCode, String errorMsg) {
// LogUtils.e(TAG, "onAdLoadFailed: errorCode = " + errorCode + ", errorMsg = " + errorMsg);
// // 修复:加载失败时移除当前广告实例
// if (mAllBanners.contains(mBannerAd)) {
// mAllBanners.remove(mBannerAd);
// }
// mBannerAd.destroy();
// mBannerAd = null;
// }
// });
// }
//
// /**
// * 根据当前秒数获取广告ID原逻辑保留
// */
// private String getAD_ID() {
// long currentSecond = System.currentTimeMillis() / 1000;
// return (currentSecond % 2 == 0) ? BANNER_POS_ID :
// (BuildConfig.DEBUG ? BANNER_POS_ID_WINBOLL_BETA : BANNER_POS_ID_WINBOLL);
// }
//
// /**
// * 获取广告价格(原逻辑保留,添加空指针校验)
// */
// private long getPrice() {
// if (mBannerAd == null) {
// return 0;
// }
// Map<String, Object> map = mBannerAd.getMediaExtraInfo();
// if (map == null || map.isEmpty() || !map.containsKey("price")) {
// LogUtils.w(TAG, "getPrice: media extra info is null or no price key");
// return 0;
// }
// Object priceObj = map.get("price");
// if (priceObj instanceof Long) {
// return (Long) priceObj;
// } else if (priceObj instanceof Integer) {
// return ((Integer) priceObj).longValue();
// } else {
// LogUtils.e(TAG, "getPrice: price type is invalid");
// return 0;
// }
// }
//
// /**
// * 显示隐私协议弹窗原逻辑保留优化Context使用
// */
// private void showPrivacy() {
// // 校验Activity状态避免弹窗泄露
// if (isFinishing() || isDestroyed()) {
// return;
// }
// String privacyAgreeValue = getSharedPreferences().getString(PRIVACY_VALUE, null);
// if (TextUtils.equals(privacyAgreeValue, String.valueOf(0))) {
// LogUtils.i(TAG, "已拒绝隐私协议,广告已处于不可用状态...");
// Toast.makeText(getApplicationContext(), "已拒绝隐私协议,广告已处于不可用状态", Toast.LENGTH_SHORT).show();
// return;
// }
// if (TextUtils.equals(privacyAgreeValue, String.valueOf(1))) {
// LogUtils.i(TAG, "已同意隐私协议开始初始化米盟SDK...");
// initMimoSdk();
// return;
// }
// LogUtils.i(TAG, "开始弹出隐私协议...");
// AlertDialog.Builder builder = new AlertDialog.Builder(this);
// builder.setTitle("用户须知");
// builder.setMessage("小米广告SDK隐私政策: https://dev.mi.com/distribute/doc/details?pId=1688, 请复制到浏览器查看");
// builder.setIcon(R.drawable.ic_launcher);
// builder.setCancelable(false); // 点击对话框以外的区域不消失
// builder.setPositiveButton("同意", new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialog, int which) {
// getSharedPreferences().edit()
// .putString(PRIVACY_VALUE, String.valueOf(1))
// .apply();
// initMimoSdk();
// dialog.dismiss();
// }
// });
// builder.setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
// @Override
// public void onClick(DialogInterface dialog, int which) {
// getSharedPreferences().edit()
// .putString(PRIVACY_VALUE, String.valueOf(0))
// .apply();
// dialog.dismiss();
// }
// });
// AlertDialog dialog = builder.create();
//
// // 配置弹窗位置(底部全屏)
// Window window = dialog.getWindow();
// if (window != null) {
// window.setGravity(Gravity.BOTTOM);
// WindowManager m = getWindowManager();
// Display d = m.getDefaultDisplay();
// WindowManager.LayoutParams p = window.getAttributes();
// p.width = d.getWidth();
// window.setAttributes(p);
// }
// dialog.show();
// }
//
// /**
// * 初始化米盟SDK核心修复传递ApplicationContext + 异常捕获)
// */
// private void initMimoSdk() {
// // 1. 安全获取ApplicationContext避免Activity Context失效
// Context appContext = getApplicationContext();
// if (appContext == null) {
// Log.e(TAG, "initMimoSdk: ApplicationContext is null");
// return;
// }
// // 2. 初始化SDK捕获异常避免崩溃
// try {
// MimoSdk.init(appContext, new MimoCustomController() {
// @Override
// public boolean isCanUseLocation() {
// return true;
// }
//
// @Override
// public MimoLocation getMimoLocation() {
// return null;
// }
//
// @Override
// public boolean isCanUseWifiState() {
// return true;
// }
//
// @Override
// public boolean alist() {
// return true;
// }
// }, new MimoSdk.InitCallback() {
// @Override
// public void success() {
// Log.d(TAG, "MimoSdk init success");
// }
//
// @Override
// public void fail(int code, String msg) {
// Log.e(TAG, "MimoSdk init fail, code=" + code + ",msg=" + msg);
// }
// });
// MimoSdk.setDebugOn(true);
// } catch (Exception e) {
// Log.e(TAG, "initMimoSdk: init failed", e);
// }
// }
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
return super.dispatchKeyEvent(event);
}
@Override
public void setupToolbar() {
super.setupToolbar();
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
}
}
//
// /**
// * 获取SharedPreferences实例原逻辑保留添加空指针校验
// */
// public SharedPreferences getSharedPreferences() {
// if (mSharedPreferences == null) {
// // 修复使用ApplicationContext获取SharedPreferences避免Activity Context泄露
// Context appContext = getApplicationContext();
// if (appContext != null) {
// mSharedPreferences = appContext.getSharedPreferences(PRIVACY_FILE, Context.MODE_PRIVATE);
// } else {
// Log.e(TAG, "getSharedPreferences: ApplicationContext is null");
// // 降级方案若ApplicationContext为空使用Activity Context仅作兼容
// mSharedPreferences = super.getSharedPreferences(PRIVACY_FILE, Context.MODE_PRIVATE);
// }
// }
// return mSharedPreferences;
// }
}

View File

@@ -35,6 +35,8 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import androidx.appcompat.widget.Toolbar;
import cc.winboll.studio.powerbell.utils.UriUtil;
import cc.winboll.studio.powerbell.utils.FileUtils;
public class BackgroundSettingsActivity extends WinBoLLActivity implements BackgroundPicturePreviewDialog.IOnRecivedPictureListener {
@@ -50,6 +52,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
private BackgroundView mBackgroundView;
private File mfTakePhoto;
volatile boolean isCommitSettings = false;
volatile boolean isPreviewBackgroundChanged = false;
@Override
public Activity getActivity() {
@@ -68,34 +71,42 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
mBackgroundView = (BackgroundView) findViewById(R.id.background_view);
mBgSourceUtils = BackgroundSourceUtils.getInstance(this);
mBgSourceUtils.loadSettings();
mPermissionUtils = PermissionUtils.getInstance();
File tempDir = new File(App.getTempDirPath());
if (!tempDir.exists()) {
tempDir.mkdirs();
}
mfTakePhoto = new File(tempDir, "TakePhoto.jpg");
File selectTempDir = new File(mBgSourceUtils.getBackgroundSourceDirPath(), "SelectTemp");
if (!selectTempDir.exists()) {
selectTempDir.mkdirs();
LogUtils.d(TAG, "【选图初始化】选图临时目录创建完成:" + selectTempDir.getAbsolutePath());
}
// File tempDir = new File(App.getTempDirPath());
// if (!tempDir.exists()) {
// tempDir.mkdirs();
// }
// mfTakePhoto = new File(tempDir, "TakePhoto.jpg");
//
// File selectTempDir = new File(mBgSourceUtils.getBackgroundSourceDirPath(), "SelectTemp");
// if (!selectTempDir.exists()) {
// selectTempDir.mkdirs();
// LogUtils.d(TAG, "【选图初始化】选图临时目录创建完成:" + selectTempDir.getAbsolutePath());
// }
initToolbar();
initClickListeners();
initBackgroundViewByPreviewBean();
handleShareIntent();
if (handleShareIntent()) {
ToastUtils.show("handleShareIntent");
} else {
mBgSourceUtils.setCurrentSourceToPreview();
}
Uri uriSelectedImage = UriUtil.getUriForFile(this, new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath()));
// 创建预览数据剪裁环境
mBgSourceUtils.createAndUpdatePreviewEnvironmentForCropping(mBgSourceUtils.getPreviewBackgroundBean());
doubleRefreshPreview();
LogUtils.d(TAG, "【初始化】BackgroundSettingsActivity 初始化完成");
}
private void initBackgroundViewByPreviewBean() {
LogUtils.d(TAG, "【Bean初始化】正式Bean → 预览Bean");
mBgSourceUtils.setCurrentSourceToPreview();
doubleRefreshPreview();
LogUtils.d(TAG, "【Bean初始化】预览Bean初始化完成");
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
doubleRefreshPreview();
}
private void initToolbar() {
mToolbar = findViewById(R.id.toolbar);
@@ -123,7 +134,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
findViewById(R.id.activitybackgroundpictureAButton8).setOnClickListener(onCleanPixelClickListener);
}
private void handleShareIntent() {
private boolean handleShareIntent() {
Intent intent = getIntent();
if (intent != null) {
String action = intent.getAction();
@@ -132,8 +143,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
BackgroundPicturePreviewDialog dlg = new BackgroundPicturePreviewDialog(this);
dlg.show();
LogUtils.d(TAG, "【分享处理】收到分享图片意图");
return true;
}
}
return false;
}
boolean isImageType(String lowerMimeType) {
@@ -478,11 +491,9 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
LogUtils.d(TAG, "【选图权限】已添加持久化权限");
}
mBgSourceUtils.createCropFileProviderBackgroundBean(selectedImage);
LogUtils.d(TAG, "【选图同步】路径绑定完成");
// 选图后启动固定比例裁剪(调用工具类)
mBgSourceUtils.loadSettings();
putUriFileToPreviewSource(selectedImage);
ImageCropUtils.startImageCrop(BackgroundSettingsActivity.this,
mBgSourceUtils.getPreviewBackgroundBean(),
mBackgroundView.getWidth(),
@@ -492,6 +503,17 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
);
}
boolean putUriFileToPreviewSource(Uri srcUriFile) {
File srcFile = new File(UriUtil.getFilePathFromUri(this, srcUriFile));
return putUriFileToPreviewSource(srcFile);
}
boolean putUriFileToPreviewSource(File srcFile) {
mBgSourceUtils.loadSettings();
File dstFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundFilePath());
return FileUtils.copyFile(srcFile, dstFile);
}
private void handleCropImageResult(int requestCode, int resultCode, Intent data) {
LogUtils.d(TAG, "handleCropImageResult: 处理裁剪结果");
File cropTempFile = new File(mBgSourceUtils.getPreviewBackgroundBean().getBackgroundScaledCompressFilePath());
@@ -501,6 +523,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
boolean isCropSuccess = (resultCode == RESULT_OK) && isFileExist && isFileReadable && fileSize > 100;
if (isCropSuccess) {
isPreviewBackgroundChanged = true;
LogUtils.d(TAG, "handleCropImageResult: 裁剪成功");
BackgroundBean previewBean = mBgSourceUtils.getPreviewBackgroundBean();
previewBean.setIsUseBackgroundFile(true);
@@ -677,6 +700,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
private void doubleRefreshPreview() {
LogUtils.d(TAG, "【双重刷新】开始");
if (mBackgroundView != null && !isFinishing()) {
mBgSourceUtils.loadSettings();
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
LogUtils.d(TAG, "【双重刷新】第一重完成");
} else {
@@ -688,6 +712,7 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
@Override
public void run() {
if (mBackgroundView != null && !isFinishing()) {
mBgSourceUtils.loadSettings();
mBackgroundView.loadBackgroundBean(mBgSourceUtils.getPreviewBackgroundBean());
LogUtils.d(TAG, "【双重刷新】第二重完成");
}
@@ -697,9 +722,10 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
private void handleOperationCancelOrFail() {
initBackgroundViewByPreviewBean();
mBgSourceUtils.setCurrentSourceToPreview();
LogUtils.d(TAG, "【操作回调】取消或失败");
ToastUtils.show("操作已取消");
ToastUtils.show("操作回调】取消或失败");
doubleRefreshPreview();
}
@@ -753,20 +779,27 @@ public class BackgroundSettingsActivity extends WinBoLLActivity implements Backg
if (isCommitSettings) {
super.finish();
} else {
YesNoAlertDialog.show(this, "背景更换问题", "是否确定背景图片设置?", new YesNoAlertDialog.OnDialogResultListener(){
@Override
public void onYes() {
mBgSourceUtils.commitPreviewSourceToCurrent();
isCommitSettings = true;
finish();
}
// 如果预览背景改变过就提示是否更换背景
if (isPreviewBackgroundChanged) {
YesNoAlertDialog.show(this, "背景更换问题", "是否确定背景图片设置?", new YesNoAlertDialog.OnDialogResultListener(){
@Override
public void onYes() {
mBgSourceUtils.commitPreviewSourceToCurrent();
isCommitSettings = true;
finish();
}
@Override
public void onNo() {
isCommitSettings = true;
finish();
}
});
@Override
public void onNo() {
isCommitSettings = true;
finish();
}
});
} else {
// 如果预览背景未改变就直接退出
isCommitSettings = true;
finish();
}
}
}
}

View File

@@ -322,7 +322,7 @@ public class MainViewFragment extends Fragment {
public void reloadBackground() {
BackgroundSourceUtils utils = BackgroundSourceUtils.getInstance(getActivity());
mBgSourceUtils.loadSettings();
utils.loadSettings();
BackgroundBean bean = utils.getCurrentBackgroundBean();
mBackgroundView.loadBackgroundBean(bean);
}

View File

@@ -218,7 +218,7 @@ public class BackgroundSourceUtils {
clearCropTempFiles();
LogUtils.d(TAG, "【文件初始化】完成。");
}
// 【核心实现】定义 getFileProviderUri 方法:将 File 转为 ContentUri适配 FileProvider
public Uri getFileProviderUri(File file) {
Log.d("BackgroundSourceUtils", "getFileProviderUri: 生成FileProvider Uri文件路径" + file.getAbsolutePath());
@@ -245,13 +245,16 @@ public class BackgroundSourceUtils {
return contentUri;
}
public boolean createCropFileProviderBackgroundBean(Uri uri) {
/*
* 创建预览数据剪裁环境
*/
public boolean createAndUpdatePreviewEnvironmentForCropping(BackgroundBean oldPreviewBackgroundBean) {
InputStream is = null;
FileOutputStream fos = null;
loadSettings();
try {
clearCropTempFiles();
Uri uri = UriUtil.getUriForFile(mContext, oldPreviewBackgroundBean.getBackgroundFilePath());
//String szType = mContext.getContentResolver().getType(uri);
// 2. 截取MIME类型后缀如从image/jpeg中提取jpeg【核心新增逻辑】
String fileSuffix = FileUtils.getFileSuffix(mContext, uri);
@@ -259,51 +262,63 @@ public class BackgroundSourceUtils {
mCropSourceFile = new File(fCropCacheDir, newCropFileName + "." + fileSuffix);
mCropResultFile = new File(fCropCacheDir, "SelectCompress_" + newCropFileName + "." + fileSuffix);
mCropSourceFile.createNewFile();
mCropResultFile.createNewFile();
// 1. 打开Uri输入流兼容content:///file:// 等多种Uri格式
is = mContext.getContentResolver().openInputStream(uri);
if (is == null) {
LogUtils.e(TAG, "【选图解析】ContentResolver打开Uri失败Uri" + uri.toString());
return false;
if (FileUtils.isFileExists(oldPreviewBackgroundBean.getBackgroundScaledCompressFilePath())) {
FileUtils.copyFile(new File(oldPreviewBackgroundBean.getBackgroundScaledCompressFilePath()), mCropResultFile);
} else {
mCropResultFile.createNewFile();
}
// 2. 初始化选图临时文件输出流Java7 手动创建流不依赖try-with-resources
fos = new FileOutputStream(mCropSourceFile);
byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区平衡读写性能与内存占用
int readLen; // 每次读取的字节长度
if (FileUtils.isFileExists(oldPreviewBackgroundBean.getBackgroundFilePath())) {
FileUtils.copyFile(new File(oldPreviewBackgroundBean.getBackgroundFilePath()), mCropSourceFile);
} else {
mCropSourceFile.createNewFile();
// 1. 打开Uri输入流兼容content:///file:// 等多种Uri格式
is = mContext.getContentResolver().openInputStream(uri);
if (is == null) {
LogUtils.e(TAG, "【选图解析】ContentResolver打开Uri失败Uri" + uri.toString());
return false;
}
// 3. 流复制Java7 标准while循环避免Java8+语法
while ((readLen = is.read(buffer)) != -1) {
fos.write(buffer, 0, readLen); // 精准写入读取到的字节,避免空字节填充
}
// 2. 初始化选图临时文件输出流Java7 手动创建流不依赖try-with-resources
fos = new FileOutputStream(mCropSourceFile);
byte[] buffer = new byte[1024 * 8]; // 8KB缓冲区平衡读写性能与内存占用
int readLen; // 每次读取的字节长度
// 4. 强制同步写入磁盘解决Android 10+ 异步写入导致的文件无效问题
fos.flush();
if (fos != null) {
try {
fos.getFD().sync(); // 确保数据写入物理磁盘,而非缓存
} catch (IOException e) {
LogUtils.w(TAG, "【选图解析】文件同步到磁盘失败用flush()兜底:" + e.getMessage());
fos.flush();
// 3. 流复制Java7 标准while循环避免Java8+语法
while ((readLen = is.read(buffer)) != -1) {
fos.write(buffer, 0, readLen); // 精准写入读取到的字节,避免空字节填充
}
// 4. 强制同步写入磁盘解决Android 10+ 异步写入导致的文件无效问题)
fos.flush();
if (fos != null) {
try {
fos.getFD().sync(); // 确保数据写入物理磁盘,而非缓存
} catch (IOException e) {
LogUtils.w(TAG, "【选图解析】文件同步到磁盘失败用flush()兜底:" + e.getMessage());
fos.flush();
}
}
}
// 加载图片数据模型数据
loadSettings();
// 修改预览数据模型
previewBackgroundBean.setBackgroundFileName(mCropSourceFile.getName());
previewBackgroundBean.setBackgroundFilePath(mCropSourceFile.getAbsolutePath());
previewBackgroundBean.setBackgroundScaledCompressFileName(mCropResultFile.getName());
previewBackgroundBean.setBackgroundScaledCompressFilePath(mCropResultFile.getAbsolutePath());
// 保存数据模型数据更改
saveSettings();
// 6. 解析成功日志(打印文件信息,便于问题排查)
LogUtils.d(TAG, "【选图解析】Uri解析成功");
LogUtils.d(TAG, "→ 原Uri" + uri.toString());
LogUtils.d(TAG, "目标临时文件" + mCropSourceFile.getAbsolutePath());
LogUtils.d(TAG, "目标临时文件大小:" + mCropSourceFile.length() + " bytes");
LogUtils.d(TAG, "目标剪裁临时文件:" + mCropResultFile.getAbsolutePath());
LogUtils.d(TAG, "目标剪裁临时文件大小:" + mCropResultFile.length() + " bytes");
LogUtils.d(TAG, "图片剪裁数据源" + mCropSourceFile.getAbsolutePath());
LogUtils.d(TAG, "图片剪裁数据源文件大小:" + mCropSourceFile.length() + " bytes");
LogUtils.d(TAG, "剪裁结果数据文件:" + mCropResultFile.getAbsolutePath());
LogUtils.d(TAG, "剪裁结果数据文件大小:" + mCropResultFile.length() + " bytes");
return true;
} catch (Exception e) {
@@ -424,7 +439,7 @@ public class BackgroundSourceUtils {
* 保存配置核心将两份独立Bean实例分别写入各自的JSON文件
*/
public void saveSettings() {
if(currentBackgroundBean != null && previewBackgroundBean != null) {
if (currentBackgroundBean != null && previewBackgroundBean != null) {
BackgroundBean.saveBeanToFile(currentBackgroundBeanFile.getAbsolutePath(), currentBackgroundBean); // 正式Bean→正式JSON
BackgroundBean.saveBeanToFile(previewBackgroundBeanFile.getAbsolutePath(), previewBackgroundBean); // 预览Bean→预览JSON
LogUtils.d(TAG, "【配置管理】两份配置保存成功正式JSON=" + currentBackgroundBeanFile.getAbsolutePath() + "预览JSON=" + previewBackgroundBeanFile.getAbsolutePath());
@@ -531,12 +546,12 @@ public class BackgroundSourceUtils {
// 更新当前背景文件路径
currentBackgroundBean.setBackgroundFilePath(currentFile.getAbsolutePath()); // 原图路径BackgroundSource
currentBackgroundBean.setBackgroundScaledCompressFilePath(currentCropFile.getAbsolutePath()); // 压缩图路径BackgroundCrops
saveSettings(); // 分别保存正式Bean→currentJSON预览Bean→previewJSON两份独立
LogUtils.d(TAG, "【配置管理】预览背景深拷贝到正式Bean两份实例独立压缩图统一存储到BackgroundCrops");
ToastUtils.show("背景图片应用成功");
}
/**
* 将正式背景同步到预览背景正式Bean → 预览Bean深拷贝新建预览Bean实例+逐字段拷贝)
* 核心深拷贝后修改预览Bean不会影响正式Bean两份实例完全独立压缩图路径统一指向BackgroundCrops

View File

@@ -0,0 +1,101 @@
package cc.winboll.studio.powerbell.utils;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.util.Log;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* @Author ZhanGSKen&豆包大模型<zhangsken@qq.com>
* @Date 2025/12/08 21:11
* @Describe 把 R.drawable 中的图片保存为 File 对象的工具类
* 适配 PowerBell 项目支持指定保存路径、自动创建目录、处理PNG图片压缩
*/
public class DrawableToFileUtils {
private static final String TAG = "DrawableToFileUtils";
/**
* 核心方法:将 R.drawable 图片保存为 File 对象
* @param context 上下文(用于获取 Resources
* @param drawableResId 图片资源ID如 R.drawable.ic_test_png
* @param fileName 保存的文件名(需带 .png 后缀,如 "test_drawable.png"
* @return 保存成功返回 File 对象,失败返回 null
*/
public static File saveDrawableToFile(Context context, int drawableResId, String filePath) {
// 1. 校验参数(避免空指针/无效参数)
if (context == null || drawableResId == 0 || filePath == null || filePath.isEmpty()) {
LogUtils.e(TAG, "【保存失败】参数无效context为空/资源ID为0/文件名为空)");
return null;
}
if (!filePath.endsWith(".png")) {
filePath += ".png"; // 强制添加 .png 后缀,确保图片格式正确
LogUtils.d(TAG, "【格式适配】自动添加.png后缀最终文件名" + filePath);
}
// 3. 构建目标 File 对象(最终保存的文件路径)
File targetFile = new File(filePath);
LogUtils.d(TAG, "【保存路径】目标文件路径:" + targetFile.getAbsolutePath());
// 4. 读取 drawable 资源为 Bitmap处理高清图/缩放问题)
Bitmap bitmap = null;
try {
// 读取 drawable 资源(适配不同分辨率的图片,避免变形)
bitmap = BitmapFactory.decodeResource(context.getResources(), drawableResId);
if (bitmap == null) {
LogUtils.e(TAG, "【读取失败】无法读取drawable资源资源ID" + drawableResId + "");
return null;
}
LogUtils.d(TAG, "【读取成功】drawable资源转Bitmap成功" + bitmap.getWidth() + ",高:" + bitmap.getHeight() + "");
// 5. 将 Bitmap 写入 FilePNG格式无损保存
FileOutputStream fos = new FileOutputStream(targetFile);
// 压缩参数PNG格式质量100无损写入输出流
boolean isSaved = bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush(); // 刷新输出流
fos.close(); // 关闭输出流
// 6. 校验保存结果(文件是否存在且有效)
if (isSaved && targetFile.exists() && targetFile.length() > 100) {
LogUtils.d(TAG, "【保存成功】drawable图片保存为File" + targetFile.getAbsolutePath());
return targetFile; // 保存成功返回File对象
} else {
LogUtils.e(TAG, "【保存失败】图片写入文件无效(文件大小:" + (targetFile.exists() ? targetFile.length() : 0) + "字节)");
// 保存失败,删除无效文件
if (targetFile.exists()) {
targetFile.delete();
LogUtils.d(TAG, "【清理无效文件】已删除无效文件:" + targetFile.getAbsolutePath());
}
return null;
}
} catch (IOException e) {
LogUtils.e(TAG, "【保存异常】写入文件时出错:" + e.getMessage());
LogUtils.e(TAG, "【异常堆栈】" + Log.getStackTraceString(e));
return null;
} finally {
// 回收Bitmap资源避免内存溢出
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
LogUtils.d(TAG, "【资源回收】Bitmap资源已回收");
}
}
}
/**
* 重载方法:自定义保存路径(灵活适配不同场景)
* @param context 上下文
* @param drawableResId 图片资源ID
* @param saveDirPath 自定义保存目录路径(如 "/storage/emulated/0/PowerBell/custom/"
* @param fileName 保存的文件名(带.png后缀
* @return 保存成功返回File对象失败返回null
*/
public static File saveDrawableToFile(Context context, int drawableResId, String saveDirPath, String fileName) {
File filePath = new File(saveDirPath, fileName);
return saveDrawableToFile(context, drawableResId, filePath.getAbsolutePath());
}
}

View File

@@ -277,5 +277,10 @@ public class FileUtils {
}
return fileSuffix;
}
public static boolean isFileExists(String path) {
File file = new File(path);
return file.exists();
}
}

View File

@@ -103,6 +103,11 @@ public class UriUtil {
return filePath;
}
public static Uri getUriForFile(Context context, String filePath) {
File file = new File(filePath);
return getUriForFile(context, file);
}
public static Uri getUriForFile(Context context, File file) {
//Uri uri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", file);