Sto postando questa domanda qui dopo che è stato determinato come "off-topic" per lo stackoverflow e "troppo ipotetico" per la visualizzazione del codice.
Sto sperimentando diversi modelli di progettazione in stile singleton per un componente di app che ha a che fare con effetti sonori e parlato in tutta l'app.
La mia teoria è che mentre spesso disapprovato, rendendo questi tipi di oggetti globali / statici / singleton-like avrà vantaggi come:
- L'utilizzo della memoria è più stabile / costante con una previsione di base, rendendo più facili da prevedere gli errori di OOM.
- Inizializzazioni costose all'interno di questi oggetti (come motori TTS e grandi buffer audio) avverranno una volta sola.
- I suoni non vengono interrotti dalle transizioni di attività.
- Perdite di memoria causate da de-e-re-inizializzazioni di oggetti costanti e laggose (ad esempio il motore Google TTS) possono essere evitate avendo un'unica istanza globale.
La mia domanda è: "Il modello di progettazione mostrato nel mio esempio di codice è fattibile / accettabile / solido?" ... e con questo non intendo secondo te - voglio dire c'è qualcuno là fuori con più esperienza di me (non è difficile da trovare) che possa vedere qualche motivo per cui questo codice potrebbe essere soggetto a bug?
Ho usato principalmente l'aiuto di questo articolo .
manifestare:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.boober.speechsingletonsimplifiedunit">
<application
android:name="App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SettingsActivity"
>
</activity>
</application>
<!-- android:screenOrientation="portrait" -->
</manifest>
Classe di applicazione:
package com.example.boober.speechsingletonsimplifiedunit;
import android.app.Application;
import android.content.Context;
import android.util.Log;
import com.squareup.leakcanary.LeakCanary;
public class App extends Application {
private static Context appContext;
public static GodOfSpeechSingleton GOSS;
@Override
public void onCreate() {
super.onCreate();
// BEGIN LEAK CANARY CODE:
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// END LEAK CANARY CODE, PROCEED...
Log.i("XXX", "App.onCreate() called.");
appContext = this;
GOSS = new GodOfSpeechSingleton();
}
public static Context getAppContext() {
return appContext;
}
}
Il "GOSS":
package com.example.boober.speechsingletonsimplifiedunit;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import java.util.HashMap;
import java.util.Locale;
import java.util.Random;
public class GodOfSpeechSingleton { // aka "the GOSS"
private boolean thisClassHasBeenInitAlready = false;
TextToSpeech tts1;
boolean tts1IsInit;
public GodOfSpeechSingleton() {
Log.i("XXX", "GOSS constructor called.");
}
// This method body was created/moved (execution delayed) from the constructor to here
// because when constructor was called from Application.onCreate(),
// the code "android.content.Context.getPackageManager()" inside the TTS
// causes a null pointer exception.
public void initFromMainActivity(String caller) {
if (!caller.equals("MainActivity")) { return; }
if (thisClassHasBeenInitAlready) {
Log.i("XXX", "GOSS init already.");
return; }
thisClassHasBeenInitAlready = true;
tts1 = new TextToSpeech(App.getAppContext(), new TextToSpeech.OnInitListener() {
@Override
public void onInit(int i) {
tts1IsInit = true;
}
}, "com.google.android.tts");
}
public void speak(String stringToSpeak, Locale localeToUseForThisUtterance) {
if (!tts1IsInit) {
Log.i("XXX", "speak() called, but tts1 not init yet!");
return;
}
tts1.setLanguage(localeToUseForThisUtterance);
tts1.speak(stringToSpeak, TextToSpeech.QUEUE_FLUSH, null);
}
}
MainActivity:
package com.example.boober.speechsingletonsimplifiedunit;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import java.util.Locale;
public class MainActivity extends AppCompatActivity {
// LIFECYCLE
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("XXX", "Main.onCreate() called.");
App.GOSS.initFromMainActivity("MainActivity");
}
@Override
protected void onDestroy() {
Log.i("XXX", "Main.onDestroy() called.");
super.onDestroy();
}
// BUTTON PRESSES
public void settingsButtonPressed(View ignored) {
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
}
public void speakButtonPressed(View ignored) {
App.GOSS.speak("you are in the main activity!",new Locale("en"));
}
}
SettingsActivity:
package com.example.boober.speechsingletonsimplifiedunit;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import java.util.Locale;
public class SettingsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
}
// BUTTON PRESSES
public void speakButtonPressed(View ignored) {
App.GOSS.speak("you are in the settings activity!", new Locale("en"));
}
}