java - Android/java - 分解一大类计算的最佳实践
问题描述
我正在开发一个简单的助眠应用程序,它允许用户选择每分钟的起始呼吸次数、每分钟的目标呼吸次数和总持续时间。然后,该应用程序会根据他们选择的每分钟呼吸次数来闪烁一盏昏暗的灯,在 5 分钟内减慢到他们的目标,然后继续运行直到选择的持续时间。
为了做到这一点,我只是在扩展 android.app.Activity 的类的 OnCreate 方法中编写了一大堆计算,但几个月后回来我现在担心可读性/最佳实践,所以现在我想我应该要么打破将其分解为多个函数/类,或者创建一个由易于测试的函数组成的 utils 类,每个函数都执行计算的一部分。
这是 onCreate:
public class LightPulse extends Activity {
public LightPulse(){
}
//Hides the status bar to allow true black background
public void hideStatusBar() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
getWindow().getDecorView()
.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
);
}
}
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.light_pulse);
//prevent the screen from automatically locking before the light pulse duration is over
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
hideStatusBar();
//Enable stop button
Button stopButton = findViewById(R.id.stop_button);
stopButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
//Get data passed to LightPulse activity from home UI
Intent intent = getIntent();
//Set background to selected colour
final ImageView lightPulseImage = findViewById(R.id.image_pulsing_light);
lightPulseImage.setBackgroundColor(intent.getIntExtra("colour", 0));
//Get total duration, start breath duration, and goal breath duration in milliseconds.
int duration = Integer.valueOf(intent.getStringExtra("duration").substring(0,2))*60000;
Integer startBreathDuration = 60000/intent.getIntExtra("startBPM", 0);
Integer goalBreathDuration = 60000/intent.getIntExtra("goalBPM", 0);
//Calculate difference in milliseconds between start and goal duration.
Integer BreathDelta = goalBreathDuration - startBreathDuration;
//Calculate the average breath length when moving from start to goal. This is just the midpoint.
int averageBreathDuration = (startBreathDuration + goalBreathDuration)/2;
Log.i("debug", "onCreate: Start Breath duration: " + startBreathDuration + " Goal Breath duration: " + goalBreathDuration + " Breath Delta: " + BreathDelta + " Average Breath DUration: " + averageBreathDuration + " total duration: " + duration);
//Transition period is 5 mins, need to calculate the number of breaths in 5 mins if all breaths were at average
Integer breathsInFiveMinutesAverage = 300000/averageBreathDuration;
Log.i("debug", "onCreate: Average breaths in 5 minutes: " + breathsInFiveMinutesAverage);
//Calculate how much to decrease breath duration by each time, in order to be consistent and move to goal breath duration in 5 mins
Integer breathDurationShift = BreathDelta/breathsInFiveMinutesAverage;
Log.i("debug", "onCreate: Breath duration shift: " + breathDurationShift);
//Instantiate animatorSet for playing sequential animators
AnimatorSet pulseSet = new AnimatorSet();
//Instantiate animator list to store pulsing light animators
List<Animator> animations = new ArrayList<>();
//Integer for tracking how long the transition set of animators takes in milliseconds
Integer totalTimeForFirstAnimations = 0;
//Mutable breath duration integer for transition from start to goal BPM in for loop
int breathDuration = startBreathDuration;
//For loop to create the initial set of animators for the transition between start and goal BPM, over 5 minutes
for(int i = 0; i<breathsInFiveMinutesAverage; i++){
Log.i("debug", "onCreate: " + breathDuration);
Log.i("debug", "onCreate colour is: " + intent.getIntExtra("colour", 0));
//Create the animators to fade in and out one time and add them to the list. Exhale/inhale duration is split 60/40
final ObjectAnimator fadeInAnimator = ObjectAnimator
.ofFloat(lightPulseImage, View.ALPHA, 0f, 1f)
.setDuration(Math.round(breathDuration*0.8));
animations.add(fadeInAnimator);
final ObjectAnimator fadeOutAnimator = ObjectAnimator
.ofFloat(lightPulseImage, View.ALPHA, 1f, 0f)
.setDuration(Math.round(breathDuration*1.2));
animations.add(fadeOutAnimator);
//update the duration for the next animation by adding on the previously calculated shift
breathDuration = breathDuration + breathDurationShift;
//update total time for transition animations
totalTimeForFirstAnimations = totalTimeForFirstAnimations + breathDuration;
}
Log.i("debug", "onCreate: Total time for first set of animations: " + totalTimeForFirstAnimations);
//Calculate remaining time after transition is done, for debugging
int remainingDuration = duration-totalTimeForFirstAnimations;
//Calculate how many breaths can be done in the remaining time at the goal breath duration
int numberOfRepeatsRemaining = remainingDuration/goalBreathDuration;
Log.i("debug", "onCreate: remaining duration: " + remainingDuration + " Number of repeats: " + numberOfRepeatsRemaining);
Log.i("debug", "onCreate: Goal breath duration: " + goalBreathDuration);
long totalTimeOfRemainingAnimations = 0;
//Create breathe in and out animators which repeat for the remaining number of breaths, with 5 extra of each added to allow the screen to automatically lock before the process is finished
for(int i = 0; i<(numberOfRepeatsRemaining)+5; i++){
final ObjectAnimator fadeInAnimator = ObjectAnimator
.ofFloat(lightPulseImage, View.ALPHA, 0f, 1f)
.setDuration(Math.round(goalBreathDuration*0.8));
animations.add(fadeInAnimator);
final ObjectAnimator fadeOutAnimator = ObjectAnimator
.ofFloat(lightPulseImage, View.ALPHA, 1f, 0f)
.setDuration(Math.round(goalBreathDuration*1.2));
animations.add(fadeOutAnimator);
totalTimeOfRemainingAnimations = totalTimeOfRemainingAnimations + Math.round(goalBreathDuration*0.8) + Math.round(goalBreathDuration*1.2);
}
Log.i("debug", "onCreate: total time of final animations " + totalTimeOfRemainingAnimations);
Log.i("debug", "onCreate: animations list size: " + animations.size());
//Calculate the total time of animations in milliseconds, for debugging
int totalAnimationTime = totalTimeForFirstAnimations + (numberOfRepeatsRemaining*goalBreathDuration);
Log.i("debug", "onCreate: animations total time in milliseconds: " + totalAnimationTime);
//After the chosen duration is over, allow the screen to lock.
final Handler screenOffHandler = new Handler();
screenOffHandler.postDelayed(new Runnable() {
@Override
public void run() {
Log.i("debug", "times up");
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
//End the activity once the screen has been given time to lock
final Handler screenOffHandler = new Handler();
screenOffHandler.postDelayed(new Runnable() {
@Override
public void run() {
Log.i("debug", "Exiting light pulse activity");
finish();
}
}, 30000);
}
}, duration);
//Add the animator list to the animatorSet, and play them sequentially
pulseSet.playSequentially(animations);
pulseSet.start();
}
}
在 HomeFragment 类中,这里是调用该活动的地方:
//set up start button
final Button startButton = root.findViewById(R.id.start_button);
startButton.setOnClickListener(new View.OnClickListener() {
//when pressed, LightPulse activity is started, and is provided with the selected values for duration, start BPM, goal BPM, and light colour
@Override
public void onClick(View v) {
Intent intent = new Intent(getActivity(), LightPulse.class);
intent.putExtra("startBPM", homeViewModel.getStartingBPM().getValue());
intent.putExtra("goalBPM", homeViewModel.getGoalBPM().getValue());
intent.putExtra("colour", homeViewModel.getColour().getValue());
intent.putExtra("duration", spinner.getSelectedItem().toString());
startActivity(intent);
}
});
有没有推荐的方法来打破这个?我想为此编写一个单元测试套件,但是许多计算重用相同的变量,这增加了很多复杂性。
解决方案
我推荐使用 MVVM 架构模式。您正在主线程上进行大量计算。这是科特林。就像是:
class LightPulseViewModel: ViewModel() {
fun calculateBreathsDuration(duration: Int, startBreathsPerMinute: Int, goalBreathsPerMinute: Int): Int {
var value = 0
//do your work
return value
}
fun calculateAnimationTime(): Long {
var value = 0L
//do your work
return value
}
}
然后在 LightPulse 中:
public class LightPulse extends Activity {
public LightPulse(){
}
LightPulseViewModel viewModel;
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.light_pulse);
viewModel = ViewModelProvider(this).get(LightPulseViewModel::class.java);
///whereever you need to do calculations
Integer breathsduration = viewModel.calculateBreathsDuration(Integer.valueOf(intent.getStringExtra("duration").substring(0,2))*60000, 60000/intent.getIntExtra("startBPM", 0), 60000/intent.getIntExtra("goalBPM", 0))
//do the same for animation duration calculations here.
}
}
有关 MVVM 模式的更多信息: https ://www.journaldev.com/20292/android-mvvm-design-pattern
推荐阅读
- floating-point - 浮点溢出和不精确
- mysql - 修复 MySQL 服务器数据目录
- javascript - 如何使用离子应用程序将记录添加到 Firebase 集合?
- fullcalendar - 在 FileMaker 中使用 FullCalendar 完成任务时是否有任何指示?
- java - 检查 LocalDateTIme 在条件中是否为空
- assembly - armv7-m 裸机 ldr/str 符号内存
- python - 使用 Flask SQLAlchemy 连接 3 个表关系
- visual-studio - 如何将当前项目属性保存为属性表
- java - 如果前一个路由的执行没有完成就不要启动路由
- java - 将 ISO 8601 转换为日期