首页 > 解决方案 > 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);
        }
    });

有没有推荐的方法来打破这个?我想为此编写一个单元测试套件,但是许多计算重用相同的变量,这增加了很多复杂性。

标签: javaandroidandroid-fragmentsrefactoring

解决方案


我推荐使用 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


推荐阅读