From 6d60d71991e81bfd4b9b626bf9d0c69925000ca6 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Mon, 22 Dec 2025 10:01:13 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E9=A1=B5=E5=90=AF=E5=8A=A8=E7=AA=97?= =?UTF-8?q?=E5=8F=A3=E6=94=B9=E7=94=A8=E8=A7=86=E5=9B=BE=E6=8E=A7=E4=BB=B6?= =?UTF-8?q?=E7=BC=93=E5=AD=98=E6=9B=BF=E4=BB=A3=E4=BD=8D=E5=9B=BE=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- powerbell/build.properties | 4 +- .../java/cc/winboll/studio/powerbell/App.java | 155 +++---- .../studio/powerbell/MainActivity.java | 1 + .../powerbell/views/MainContentView.java | 13 +- .../views/MemoryCachedBackgroundView.java | 172 ++++++-- .../src/main/res/layout/activity_main.xml | 410 +++++++++--------- 6 files changed, 430 insertions(+), 325 deletions(-) diff --git a/powerbell/build.properties b/powerbell/build.properties index 1e82604..e114d69 100644 --- a/powerbell/build.properties +++ b/powerbell/build.properties @@ -1,8 +1,8 @@ #Created by .winboll/winboll_app_build.gradle -#Mon Dec 22 00:53:45 GMT 2025 +#Mon Dec 22 01:57:09 GMT 2025 stageCount=18 libraryProject= baseVersion=15.14 publishVersion=15.14.17 -buildCount=7 +buildCount=18 baseBetaVersion=15.14.18 diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/App.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/App.java index 459ff8b..8655249 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/App.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/App.java @@ -15,6 +15,7 @@ import cc.winboll.studio.powerbell.utils.AppCacheUtils; import cc.winboll.studio.powerbell.utils.AppConfigUtils; import cc.winboll.studio.powerbell.utils.BitmapCacheUtils; import cc.winboll.studio.powerbell.utils.NotificationManagerUtils; +import cc.winboll.studio.powerbell.views.MemoryCachedBackgroundView; import java.io.File; import java.util.concurrent.TimeUnit; @@ -40,7 +41,7 @@ public class App extends GlobalApplication { private static final String TRIM_MEMORY_NOTIFY_CONTENT = "由于本应用使用时,系统通知内存紧张程度级别较高,图片缓存功能暂时不启用。"; // 定时任务间隔常量(分钟) - private static final long TIMER_INTERVAL_MINUTES = 1; + //private static final long TIMER_INTERVAL_MINUTES = 1; // ===================== 静态属性区 ===================== // 数据配置工具 @@ -48,14 +49,16 @@ public class App extends GlobalApplication { private static AppCacheUtils sAppCacheUtils; // 全局Bitmap缓存工具 public static BitmapCacheUtils sBitmapCacheUtils; + // 全局视图控件缓存工具 + public static MemoryCachedBackgroundView sMemoryCachedBackgroundView; // 临时文件夹路径 private static String sTempDirPath = ""; // 定时任务静态属性(全局唯一) - private static Handler sTimerHandler; - private static Runnable sTimerRunnable; - private static boolean sIsTimerRunning = false; - +// private static Handler sTimerHandler; +// private static Runnable sTimerRunnable; +// private static boolean sIsTimerRunning = false; +// // ===================== 成员属性区 ===================== // 全局广播接收器 private GlobalApplicationReceiver mGlobalReceiver; @@ -121,7 +124,7 @@ public class App extends GlobalApplication { // 初始化广播接收器 initReceiver(); // 启动定时任务 - initTimerTask(); + //initTimerTask(); LogUtils.d(TAG, "onCreate() 应用初始化完成"); } @@ -136,7 +139,7 @@ public class App extends GlobalApplication { // 释放通知工具 releaseNotificationManager(); // 停止定时任务 - stopTimerTask(); + //stopTimerTask(); LogUtils.d(TAG, "onTerminate() 应用资源释放完成"); } @@ -145,20 +148,22 @@ public class App extends GlobalApplication { public void onTrimMemory(int level) { super.onTrimMemory(level); LogUtils.d(TAG, "onTrimMemory() 调用,内存等级level:" + level); - - // 初始化通知工具(若未初始化) - if (mNotificationManager == null) { - mNotificationManager = new NotificationManagerUtils(this); - LogUtils.d(TAG, "onTrimMemory():NotificationManagerUtils实例已初始化"); - } - - // 内存紧张等级判断 - if (level > ComponentCallbacks2.TRIM_MEMORY_MODERATE) { - sendTrimMemoryNotification(level); - } else { - sBitmapCacheUtils = BitmapCacheUtils.getInstance(); - LogUtils.d(TAG, "onTrimMemory():Bitmap缓存已启用"); - } + sMemoryCachedBackgroundView.clearAllCache(); + sMemoryCachedBackgroundView.getLastInstance(this); +// +// // 初始化通知工具(若未初始化) +// if (mNotificationManager == null) { +// mNotificationManager = new NotificationManagerUtils(this); +// LogUtils.d(TAG, "onTrimMemory():NotificationManagerUtils实例已初始化"); +// } +// +// // 内存紧张等级判断 +// if (level > ComponentCallbacks2.TRIM_MEMORY_MODERATE) { +// sendTrimMemoryNotification(level); +// } else { +// sBitmapCacheUtils = BitmapCacheUtils.getInstance(); +// LogUtils.d(TAG, "onTrimMemory():Bitmap缓存已启用"); +// } } // ===================== 私有初始化方法区 ===================== @@ -212,65 +217,65 @@ public class App extends GlobalApplication { /** * 初始化定时任务(全局唯一实例) */ - private void initTimerTask() { - LogUtils.d(TAG, "initTimerTask() 开始初始化定时任务,当前运行状态:" + sIsTimerRunning); - - // 已运行则直接返回 - if (sIsTimerRunning) { - LogUtils.d(TAG, "initTimerTask() 定时任务已在运行,无需重复启动"); - return; - } - - // 初始化Handler - if (sTimerHandler == null) { - sTimerHandler = new Handler(Looper.getMainLooper()); - LogUtils.d(TAG, "initTimerTask() 定时任务Handler已初始化"); - } - - // 初始化Runnable - if (sTimerRunnable == null) { - sTimerRunnable = new Runnable() { - @Override - public void run() { - try { - LogUtils.d(TAG, "定时任务执行,间隔:" + TIMER_INTERVAL_MINUTES + "分钟"); - sBitmapCacheUtils = BitmapCacheUtils.getInstance(); - LogUtils.d(TAG, "定时任务:Bitmap缓存已重新初始化"); - } catch (Exception e) { - LogUtils.e(TAG, "定时任务执行异常:" + e.getMessage()); - } finally { - if (sIsTimerRunning) { - long delayMillis = TimeUnit.MINUTES.toMillis(TIMER_INTERVAL_MINUTES); - sTimerHandler.postDelayed(this, delayMillis); - LogUtils.d(TAG, "定时任务已预约下次执行,延迟:" + delayMillis + "ms"); - } - } - } - }; - LogUtils.d(TAG, "initTimerTask() 定时任务Runnable已初始化"); - } - - // 启动任务 - sTimerHandler.post(sTimerRunnable); - sIsTimerRunning = true; - LogUtils.d(TAG, "initTimerTask() 定时任务已启动,间隔:" + TIMER_INTERVAL_MINUTES + "分钟"); - } +// private void initTimerTask() { +// LogUtils.d(TAG, "initTimerTask() 开始初始化定时任务,当前运行状态:" + sIsTimerRunning); +// +// // 已运行则直接返回 +// if (sIsTimerRunning) { +// LogUtils.d(TAG, "initTimerTask() 定时任务已在运行,无需重复启动"); +// return; +// } +// +// // 初始化Handler +// if (sTimerHandler == null) { +// sTimerHandler = new Handler(Looper.getMainLooper()); +// LogUtils.d(TAG, "initTimerTask() 定时任务Handler已初始化"); +// } +// +// // 初始化Runnable +// if (sTimerRunnable == null) { +// sTimerRunnable = new Runnable() { +// @Override +// public void run() { +// try { +// LogUtils.d(TAG, "定时任务执行,间隔:" + TIMER_INTERVAL_MINUTES + "分钟"); +// sBitmapCacheUtils = BitmapCacheUtils.getInstance(); +// LogUtils.d(TAG, "定时任务:Bitmap缓存已重新初始化"); +// } catch (Exception e) { +// LogUtils.e(TAG, "定时任务执行异常:" + e.getMessage()); +// } finally { +// if (sIsTimerRunning) { +// long delayMillis = TimeUnit.MINUTES.toMillis(TIMER_INTERVAL_MINUTES); +// sTimerHandler.postDelayed(this, delayMillis); +// LogUtils.d(TAG, "定时任务已预约下次执行,延迟:" + delayMillis + "ms"); +// } +// } +// } +// }; +// LogUtils.d(TAG, "initTimerTask() 定时任务Runnable已初始化"); +// } +// +// // 启动任务 +// sTimerHandler.post(sTimerRunnable); +// sIsTimerRunning = true; +// LogUtils.d(TAG, "initTimerTask() 定时任务已启动,间隔:" + TIMER_INTERVAL_MINUTES + "分钟"); +// } // ===================== 私有工具方法区 ===================== /** * 停止定时任务 */ - private void stopTimerTask() { - LogUtils.d(TAG, "stopTimerTask() 开始停止定时任务"); - if (sTimerHandler != null && sTimerRunnable != null) { - sTimerHandler.removeCallbacks(sTimerRunnable); - sIsTimerRunning = false; - LogUtils.d(TAG, "stopTimerTask() 定时任务已停止,运行状态重置为false"); - } else { - LogUtils.d(TAG, "stopTimerTask() 定时任务未初始化,无需停止"); - } - } - +// private void stopTimerTask() { +// LogUtils.d(TAG, "stopTimerTask() 开始停止定时任务"); +// if (sTimerHandler != null && sTimerRunnable != null) { +// sTimerHandler.removeCallbacks(sTimerRunnable); +// sIsTimerRunning = false; +// LogUtils.d(TAG, "stopTimerTask() 定时任务已停止,运行状态重置为false"); +// } else { +// LogUtils.d(TAG, "stopTimerTask() 定时任务未初始化,无需停止"); +// } +// } +// /** * 释放通知管理工具资源 */ diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/MainActivity.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/MainActivity.java index d2d62e2..59c1b84 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/MainActivity.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/MainActivity.java @@ -12,6 +12,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewStub; +import android.widget.LinearLayout; import androidx.appcompat.widget.Toolbar; import cc.winboll.studio.libaes.activitys.AboutActivity; import cc.winboll.studio.libaes.models.APPInfo; diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/views/MainContentView.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/views/MainContentView.java index 94a38d8..3107a41 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/views/MainContentView.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/views/MainContentView.java @@ -7,6 +7,7 @@ import android.content.DialogInterface; import android.graphics.drawable.Drawable; import android.os.Build; import android.view.View; +import android.view.ViewGroup; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.LinearLayout; @@ -14,6 +15,7 @@ import android.widget.RelativeLayout; import android.widget.Switch; import android.widget.TextView; import cc.winboll.studio.libappbase.LogUtils; +import cc.winboll.studio.powerbell.App; import cc.winboll.studio.powerbell.R; import cc.winboll.studio.powerbell.models.ControlCenterServiceBean; import cc.winboll.studio.powerbell.services.ControlCenterService; @@ -81,7 +83,8 @@ public class MainContentView { // 视图控件(按「布局→开关→文本→进度条→图标」功能归类) // 基础布局控件 public RelativeLayout mainLayout; - public BackgroundView backgroundView; + public MemoryCachedBackgroundView backgroundView; + LinearLayout mllBackgroundView; // 容器布局控件 public LinearLayout llLeftSeekBar; public LinearLayout llRightSeekBar; @@ -144,7 +147,13 @@ public class MainContentView { LogUtils.d(TAG, "bindViews() | rootView=" + rootView); // 基础布局绑定 mainLayout = (RelativeLayout) rootView.findViewById(R.id.activitymainRelativeLayout1); - backgroundView = (BackgroundView) rootView.findViewById(R.id.fragmentmainviewBackgroundView1); + //backgroundView = (BackgroundView) rootView.findViewById(R.id.fragmentmainviewBackgroundView1); + mllBackgroundView = (LinearLayout) rootView.findViewById(R.id.ll_backgroundview); + backgroundView = App.sMemoryCachedBackgroundView.getLastInstance(mContext); + if (backgroundView.getParent() != null) { + ((ViewGroup) backgroundView.getParent()).removeView(backgroundView); + } + mllBackgroundView.addView(backgroundView); // 容器布局绑定 llLeftSeekBar = (LinearLayout) rootView.findViewById(R.id.fragmentmainviewLinearLayout1); llRightSeekBar = (LinearLayout) rootView.findViewById(R.id.fragmentmainviewLinearLayout2); diff --git a/powerbell/src/main/java/cc/winboll/studio/powerbell/views/MemoryCachedBackgroundView.java b/powerbell/src/main/java/cc/winboll/studio/powerbell/views/MemoryCachedBackgroundView.java index 1acc66e..123aa01 100644 --- a/powerbell/src/main/java/cc/winboll/studio/powerbell/views/MemoryCachedBackgroundView.java +++ b/powerbell/src/main/java/cc/winboll/studio/powerbell/views/MemoryCachedBackgroundView.java @@ -1,23 +1,27 @@ package cc.winboll.studio.powerbell.views; import android.content.Context; +import android.content.SharedPreferences; import android.text.TextUtils; import android.util.AttributeSet; import cc.winboll.studio.libappbase.LogUtils; import cc.winboll.studio.powerbell.models.BackgroundBean; -import java.util.HashMap; -import java.util.Map; /** * @Author ZhanGSKen&豆包大模型 * @Date 2025/12/21 20:43 - * @Describe 内存缓存版背景视图控件(基于Java7) - * 核心:通过静态Map缓存控件实例,按图片路径唯一标识,支持强制重载图片 + * @Describe 单实例缓存版背景视图控件(基于Java7) + * 核心:通过静态属性保存当前缓存路径和实例,支持强制重载图片 + * 新增:SP持久化最后加载路径、获取最后加载实例功能 */ public class MemoryCachedBackgroundView extends BackgroundView { public static final String TAG = "MemoryCachedBackgroundView"; - // 静态Map:缓存<图片路径, 控件实例>,全局唯一 - private static final Map sViewCacheMap = new HashMap(); + // 静态属性:保存当前缓存的路径和实例(替代原Map,仅维护单实例) + private static String sCachedImagePath; + private static MemoryCachedBackgroundView sCachedView; + // SP相关常量 + private static final String SP_NAME = "MemoryCachedBackgroundView_SP"; + private static final String KEY_LAST_LOAD_IMAGE_PATH = "last_load_image_path"; // ====================================== 构造器(继承并兼容父类) ====================================== private MemoryCachedBackgroundView(Context context) { @@ -39,7 +43,7 @@ public class MemoryCachedBackgroundView extends BackgroundView { /** * 从缓存获取或创建MemoryCachedBackgroundView实例 * @param context 上下文 - * @param imagePath 图片绝对路径(作为缓存Key) + * @param imagePath 图片绝对路径(作为缓存标识) * @param isReload 是否强制重新加载图片(路径匹配时仍刷新) * @return 缓存/新创建的MemoryCachedBackgroundView实例 */ @@ -50,30 +54,99 @@ public class MemoryCachedBackgroundView extends BackgroundView { return new MemoryCachedBackgroundView(context); } - // 1. 从Map缓存中获取实例 - MemoryCachedBackgroundView cachedView = sViewCacheMap.get(imagePath); - // 2. 缓存不存在 → 新建实例并加入Map - if (cachedView == null) { - LogUtils.d(TAG, "getInstance():路径未缓存,新建实例 | " + imagePath); - cachedView = new MemoryCachedBackgroundView(context); - sViewCacheMap.put(imagePath, cachedView); - // 新建实例直接加载图片 - cachedView.loadImage(imagePath); - } else { - // 3. 缓存存在 → 根据isReload判断是否强制重载 + // 1. 路径匹配缓存 → 判断是否强制重载 + if (imagePath.equals(sCachedImagePath) && sCachedView != null) { + LogUtils.d(TAG, "getInstance():路径已缓存,当前缓存实例有效"); if (isReload) { - LogUtils.d(TAG, "getInstance():路径已缓存,强制重载图片 | " + imagePath); - cachedView.loadImage(imagePath); // 调用父类loadImage刷新 + LogUtils.d(TAG, "getInstance():强制重载图片 | " + imagePath); + sCachedView.loadImage(imagePath); } else { LogUtils.d(TAG, "getInstance():使用缓存实例,无需重载 | " + imagePath); } + return sCachedView; } - return cachedView; + + // 2. 路径不匹配/无缓存 → 新建实例并更新静态缓存 + LogUtils.d(TAG, "getInstance():路径未缓存,新建实例 | " + imagePath); + sCachedView = new MemoryCachedBackgroundView(context); + sCachedImagePath = imagePath; + sCachedView.loadImage(imagePath); + return sCachedView; + } + + // ====================================== 新增功能:获取最后加载的实例 ====================================== + /** + * 获取最后一次loadImage的路径对应的实例 + * 无实例则创建并加载图片,同时更新静态缓存 + * @param context 上下文 + * @return 最后加载路径对应的实例 + */ + public static MemoryCachedBackgroundView getLastInstance(Context context) { + LogUtils.d(TAG, "getLastInstance() 调用"); + // 1. 从SP获取最后加载的路径 + String lastPath = getLastLoadImagePath(context); + if (TextUtils.isEmpty(lastPath)) { + LogUtils.e(TAG, "getLastInstance():无最后加载路径,创建空实例"); + return new MemoryCachedBackgroundView(context); + } + + // 2. 路径匹配当前缓存 → 直接返回 + if (lastPath.equals(sCachedImagePath) && sCachedView != null) { + LogUtils.d(TAG, "getLastInstance():使用最后路径缓存实例 | " + lastPath); + return sCachedView; + } + + // 3. 路径不匹配 → 新建实例并更新缓存 + LogUtils.d(TAG, "getLastInstance():最后路径未缓存,新建实例并加载 | " + lastPath); + sCachedView = new MemoryCachedBackgroundView(context); + sCachedImagePath = lastPath; + sCachedView.loadImage(lastPath); + return sCachedView; + } + + // ====================================== 工具方法:SP持久化最后加载路径 ====================================== + /** + * 保存最后一次loadImage的路径到SP + * @param context 上下文 + * @param imagePath 图片路径 + */ + private static void saveLastLoadImagePath(Context context, String imagePath) { + if (TextUtils.isEmpty(imagePath) || context == null) { + return; + } + SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); + sp.edit().putString(KEY_LAST_LOAD_IMAGE_PATH, imagePath).apply(); + LogUtils.d(TAG, "saveLastLoadImagePath():已保存最后路径 | " + imagePath); + } + + /** + * 从SP获取最后一次loadImage的路径 + * @param context 上下文 + * @return 最后加载的图片路径,空则返回null + */ + public static String getLastLoadImagePath(Context context) { + if (context == null) { + return null; + } + SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); + String lastPath = sp.getString(KEY_LAST_LOAD_IMAGE_PATH, null); + LogUtils.d(TAG, "getLastLoadImagePath():获取最后路径 | " + lastPath); + return lastPath; } // ====================================== 工具方法:缓存管理 ====================================== /** - * 清除指定路径的缓存实例 + * 清除当前缓存实例和路径 + */ + public static void clearCache() { + LogUtils.d(TAG, "clearCache() 调用 | 当前缓存路径:" + sCachedImagePath); + sCachedView = null; + sCachedImagePath = null; + LogUtils.d(TAG, "clearCache():已清除当前缓存实例"); + } + + /** + * 清除指定路径的缓存(仅当路径匹配当前缓存时生效) * @param imagePath 图片路径 */ public static void removeCache(String imagePath) { @@ -82,34 +155,63 @@ public class MemoryCachedBackgroundView extends BackgroundView { LogUtils.e(TAG, "removeCache():图片路径为空,清除失败"); return; } - sViewCacheMap.remove(imagePath); - LogUtils.d(TAG, "removeCache():已清除缓存 | " + imagePath); + if (imagePath.equals(sCachedImagePath)) { + clearCache(); + // 同步删除SP中最后路径记录 + clearLastLoadImagePath(getContextFromCache()); + LogUtils.d(TAG, "removeCache():已清除匹配路径的缓存 | " + imagePath); + } else { + LogUtils.d(TAG, "removeCache():路径不匹配当前缓存,无需清除 | " + imagePath); + } } /** - * 清除所有缓存实例 + * 清除所有缓存(同clearCache,保持方法兼容性) */ public static void clearAllCache() { - LogUtils.d(TAG, "clearAllCache() 调用 | 缓存数量:" + sViewCacheMap.size()); - sViewCacheMap.clear(); - LogUtils.d(TAG, "clearAllCache():已清除所有缓存实例"); + LogUtils.d(TAG, "clearAllCache() 调用"); + clearCache(); + clearLastLoadImagePath(getContextFromCache()); + LogUtils.d(TAG, "clearAllCache():已清除所有缓存及最后路径记录"); } /** - * 获取当前缓存数量 - * @return 缓存的实例数量 + * 判断是否存在缓存实例 + * @return 存在返回true,否则返回false */ - public static int getCacheCount() { - int count = sViewCacheMap.size(); - LogUtils.d(TAG, "getCacheCount() 调用 | 当前缓存数量:" + count); - return count; + public static boolean hasCache() { + return sCachedView != null && !TextUtils.isEmpty(sCachedImagePath); } - // ====================================== 重写父类方法:增强日志 ====================================== + /** + * 清除SP中最后加载的路径记录 + * @param context 上下文 + */ + public static void clearLastLoadImagePath(Context context) { + if (context == null) { + return; + } + SharedPreferences sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); + sp.edit().remove(KEY_LAST_LOAD_IMAGE_PATH).apply(); + LogUtils.d(TAG, "clearLastLoadImagePath():已清除最后路径记录"); + } + + // ====================================== 辅助方法:从缓存获取上下文 ====================================== + /** + * 从缓存实例中获取上下文(用于无外部上下文时的SP操作) + * @return 上下文实例,无则返回null + */ + private static Context getContextFromCache() { + return sCachedView != null ? sCachedView.getContext() : null; + } + + // ====================================== 重写父类方法:增强日志+SP持久化 ====================================== @Override public void loadImage(String imagePath) { LogUtils.d(TAG, "loadImage() 重载方法调用 | 图片路径:" + imagePath); super.loadImage(imagePath); + // 保存最后加载路径到SP + saveLastLoadImagePath(getContext(), imagePath); } @Override diff --git a/powerbell/src/main/res/layout/activity_main.xml b/powerbell/src/main/res/layout/activity_main.xml index 9556761..a8b016b 100644 --- a/powerbell/src/main/res/layout/activity_main.xml +++ b/powerbell/src/main/res/layout/activity_main.xml @@ -1,259 +1,247 @@ + 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"> - - + - - + - - + - - + - - + - - + - + - + - + - + - - + - + - - + - + - + - + - + - - + - + - + - + - - + - + - + - + - - + - + - + - + - - + - + - + - + - + - + - + - - + - + - + - + - - - + - + - + + +