[JAVA] Erstellen Sie eine Android-App für diejenigen, die keine Musik über ihre Lautsprecher abspielen möchten

Im Zug - in einer ruhigen Bibliothek - in einem Konferenzraum - Als ich die App startete, hörte ich ein lautes Geräusch. Hast du so eine Erfahrung? Glücklicherweise habe ich es noch nicht erlebt, aber ich habe die Angewohnheit, die Lautstärke viele Male zu überprüfen.

Ich habe eine App erstellt, um ein solches Problem zu lösen.

DoNotSpeak: Eine App, die keine Musik auf dem Lautsprecher wiedergibt

Download über den unten stehenden Link Get it on Google Play

Es ist Open Source (MIT-Lizenz). Repository

Was ist zu tun

Diese App setzt die Lautstärke des Lautsprechers (STREAM_MUSIC) auf Null, wenn der Kopfhörer nicht angeschlossen ist. Stellen Sie die Lautstärke zum folgenden Zeitpunkt auf Null.

Temporäre Freigabefunktion

Diese App befindet sich im Benachrichtigungsbereich (Vordergrunddienst) und versucht immer, die Lautsprecherlautstärke auf Null zu reduzieren. Es gibt jedoch Situationen, in denen Sie einen Lautsprecher verwenden möchten. Daher haben wir eine Funktion implementiert, um den Stummschaltungsvorgang vorübergehend abzubrechen.

Technische Details

Wenn nur das oben Genannte getan wird, wird es ein Werbeartikel sein, also werde ich den Implementierungsinhalt schreiben.

Lautstärkeeinstellung

Dies ist der Kern dieser App, die die Lautstärke des Lautsprechers auf Null reduziert. Sie kann in der Klasse AudioManager festgelegt werden.

AudioManager audioManager = this.getSystemService(this, AudioManager.class);
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);

Holen Sie sich einfach den "AudioManager" und rufen Sie die "setStreamVolume" -Methode auf. Unter Android sind die Lautstärkeeinstellungen für jeden Stream getrennt, und jede Lautstärke kann unabhängig eingestellt werden. Geben Sie "STREAM_MUSIC" an, um nur die Musik in dieser App auf Null zu setzen.

Es sieht auch so aus, als ob Sie die Berechtigung "android.permission.MODIFY_AUDIO_SETTINGS" benötigen, dies war jedoch nicht der Fall.

Status der Kopfhörerverbindung

Diese App setzt die Lautstärke nicht auf Null, wenn die Kopfhörer angeschlossen sind. Daher müssen Sie den Verbindungsstatus der Kopfhörer überprüfen. Sie können den Verbindungsstatus mit "AudioManager" überprüfen, aber der Beurteilungscode ändert sich je nach Android-Version. Android vor Android 6.0 M verwendet die Methoden "isWiredHeadsetOn", "isBluetoothScoOn" und "isBluetoothA2dpOn", und Android 6.0 M verwendet die Methode "getDevices".

private boolean isHeadsetConnected() {
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        return this.audioManager.isWiredHeadsetOn() || this.audioManager.isBluetoothScoOn() || this.audioManager.isBluetoothA2dpOn();
    } else {
        AudioDeviceInfo[] devices = this.audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
        for (int i = 0; i < devices.length; i++) {
            AudioDeviceInfo device = devices[i];

            int type = device.getType();
            if (type == AudioDeviceInfo.TYPE_WIRED_HEADSET
                    || type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES
                    || type == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP
                    || type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
            ) {
                return true;
            }
        }

        return false;
    }
}

Erkennung von Volumenänderungen

Selbst wenn die Lautstärke auf Null gesetzt ist, ist es bedeutungslos, wenn der Benutzer oder andere Apps die Lautstärke ändern. Daher benötigen wir eine Funktion, um das Volumen auf Null zu setzen, wenn das Volumen geändert wird. Unter Android Content Provider und ContentObserver Sie können die Lautstärkeänderung durch Registrierung erkennen.

public final class DNSContentObserver extends ContentObserver {
    private Runnable callback;

    public DNSContentObserver(Handler handler, Runnable callback) {
        super(handler);
        this.callback = callback;
    }

    @Override
    public boolean deliverSelfNotifications() {
        return false;
    }

    @Override
    public void onChange(boolean selfChange, Uri uri) {
        super.onChange(selfChange, uri);
        this.callback.run();
    }
}

this.getContentResolver().registerContentObserver(
  android.provider.Settings.System.getUriFor("volume_music_speaker"),
  true,
  new DNSContentObserver(new Handler(), new Runnable() {
      @Override
      public void run() {
          //Stummschaltung
      }
  }));

Holen Sie sich die ContentResolver-Klasse mit getContentResolver und registrieren Sie den ContentObserver mit der registerContentObserver-Methode. Zu diesem Zeitpunkt können Sie mithilfe der als Inhalts-URI bezeichneten Zeichenfolge angeben, welche Daten überwacht werden sollen. In dieser App möchten wir Änderungen in der Lautstärke des Lautsprechers erkennen, daher geben wir "content: // settings / system / volume_music_speaker" an.

Entprellen Sie die Verarbeitung

Wenn bei dem oben genannten Verfahren zur Erkennung von Volumenänderungen das Volumen in kurzer Zeit kontinuierlich geändert wird, tritt möglicherweise nicht "onChange" von "ContentObserver" auf (können mehrere Änderungen zu einem "onChange" kombiniert werden?). Um diesen Effekt abzuschwächen, schalten Sie 1 Sekunde nach dem Auftreten von "onChange" erneut stumm. Ich habe eine Klasse erstellt, um die [Debounce] -Verarbeitung (http://reactivex.io/documentation/operators/debounce.html) durchzuführen.

public final class Debouncer {
    private static final String TAG = "Debouncer";

    private final Handler handler;
    private final int dueTime;
    private final Runnable callback;
    private final Runnable checkRunner = new Runnable() {
        @Override
        public void run() {
            Debouncer.this.check();
        }
    };

    private final AtomicBoolean locked = new AtomicBoolean(false);
    private int elapsedTime;
    private long startTime;

    public Debouncer(Handler handler, int dueTime, Runnable callback) {
        this.handler = handler;
        this.dueTime = dueTime;
        this.callback = callback;
        this.elapsedTime = this.dueTime;
    }

    public void update() {
        for (; ; ) {
            // lock
            if (this.locked.compareAndSet(false, true)) {
                int elapsed = this.elapsedTime;

                // reset time
                Log.d(TAG, "reset");
                this.startTime = System.currentTimeMillis();
                this.elapsedTime = 0;

                // not running?
                if (elapsed >= this.dueTime) {
                    this.start(this.dueTime);
                }

                // unlock
                this.locked.set(false);
                break;
            }
        }
    }

    private void start(int delayTime) {
        Log.d(TAG, "start");
        this.handler.postDelayed(this.checkRunner, delayTime);
    }

    private void check() {
        Log.d(TAG, "check");
        // lock
        if (this.locked.compareAndSet(false, true)) {
            long currentTime = System.currentTimeMillis();
            this.elapsedTime += currentTime - this.startTime;
            boolean over = this.elapsedTime >= this.dueTime;

            // retry
            if (!over) {
                int remainTime = this.dueTime - this.elapsedTime;
                this.startTime = currentTime;
                this.start(remainTime);
            }

            // unlock
            this.locked.set(false);

            // callback
            if (over) this.callback.run();
        }
    }
}

Erkennt, dass der Kopfhörer herausgezogen wurde

Unter Android wird "android.media.AUDIO_BECOMING_NOISY" gesendet, sobald der Kopfhörer herausgezogen und der Ton über den Lautsprecher ausgegeben wird.

public final class DNSReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action == null) return;

        switch (action) {
            case AudioManager.ACTION_AUDIO_BECOMING_NOISY: {
                //Stummschaltung
                break;
            }
        }
    }
}

Ich schreibe auch einen Absichtsfilter in das Manifest.

<intent-filter>
    <action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>

Übrigens, bevor Sie den Verbindungsstatus des Kopfhörers in Android aktualisieren, wird diese Sendung aufgerufen. Sie sollten daher nicht prüfen, ob der Kopfhörer zu diesem Zeitpunkt angeschlossen ist. Diese App wird unabhängig vom Status der Kopfhörerverbindung zwangsweise stummgeschaltet.

Neustart

Der Dienst wird beendet, wenn das Terminal neu gestartet oder die Anwendung aktualisiert wird. Wenn nichts unternommen wird, kann es zu einer Tragödie kommen. Daher muss die App automatisch gestartet werden. Empfangen Sie in einem solchen Fall die Sendung "android.intent.action.BOOT_COMPLETED" und die Sendung "android.intent.action.MY_PACKAGE_REPLACED" und starten Sie den Dienst.

<intent-filter>
    <action android:name="android.intent.action.BOOT_COMPLETED" />
    <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>

Sie benötigen auch Berechtigungen.

<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

Der Code ähnelt "AUDIO_BECOMING_NOISY" und wird daher weggelassen.

Starten Sie den Service vom Launcher

Unter Android können Sie Dienste nicht direkt vom Launcher aus starten. Sie können (wahrscheinlich) nur Aktivitäten über den Launcher starten. Diese App muss jedoch keine Aktivität anzeigen, daher habe ich sie so angepasst, dass keine Aktivität angezeigt wird.

Ändern Sie das AppTheme wie folgt:

<resources>
    <style name="AppTheme" parent="android:Theme.Material">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowNoTitle">true</item>
    </style>
</resources>

Dies ist ein transparentes Thema für Aktivitäten ohne Titelleiste. Sie können den Dienst dann mit "onCreate" in "MainActivity" starten und sofort "finishAndRemoveTask" aufrufen, um die Aktivität vor dem Erscheinungsbild zu verbergen.

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //Dienst starten
    this.finishAndRemoveTask();
    this.overridePendingTransition(0, 0);
}

Es scheint auch, dass die Übergangsanimation durch Aufrufen von "overridePendingTransition" abgebrochen werden kann.

App-Größe minimieren

Als ich eine App in Android Studio erstellt habe, hat die APK-Größe 1 MB überschritten. Für residente Apps ist es definitiv besser, weniger Speicher zu verwenden. Daher haben wir verschiedene Reduzierungen vorgenommen, um die App-Größe zu reduzieren.

Setzen Sie zuerst "minifyEnabled" und "shirinkResources" in ProGuard auf "true". Allein dies machte es ungefähr 700KB.

release {
    minifyEnabled true
    shrinkResources true
    ...
}

Normalerweise glaube ich nicht, dass es ein Problem gibt, wenn es so klein wie das KB-Level ist, aber ich war nicht zufrieden und habe beschlossen, es kleiner zu machen. In dieser App hat die durch "Abhängigkeiten" angegebene Abhängigkeitsbibliothek die APK aufgeblasen.

build.gradle

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.0.0-beta01'
    implementation 'androidx.core:core-ktx:1.1.0-alpha03'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.1.0-alpha4'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4'
}

Dies ist Android X, eine Unterstützungsbibliothek zur Aufrechterhaltung der Abwärtskompatibilität mit Android, und ich denke, dass dies für allgemeine Apps fast unerlässlich ist. Sie können die APK jedoch verkleinern, indem Sie sie wegwerfen. (Nicht empfohlen.)

Ich konnte eine APK mit weniger als 100 KB erstellen, indem ich AndroidX entfernte und meinen eigenen abwärtskompatiblen Code schrieb. Die endgültige Größe beträgt ca. 26 KB. (Übrigens habe ich es zuerst in Kotlin geschrieben, aber ich habe es in Java geändert, weil der generierte Bytecode subtil war. Companion-Objekt ...)

Recommended Posts

Erstellen Sie eine Android-App für diejenigen, die keine Musik über ihre Lautsprecher abspielen möchten
Die Geschichte der ersten Veröffentlichung der Android-App im Play Store.
[Für diejenigen, die Portfolios erstellen] Verwendung von Font-Awesome-Rails
Ich möchte die Protokollausgabe unter Android vereinfachen
Für diejenigen, die MySQL für die Datenbank in der Umgebungskonstruktion von Rails6 ~ verwenden möchten.
VS Code FAQ für diejenigen, die Eclipse entkommen möchten
[Für diejenigen, die Portfolios erstellen] Verwendung von Chart Kick
[Für diejenigen, die Portfolios erstellen] So lassen Sie Zeichenketten weg
[Für diejenigen, die ein unerfahrener Literaturingenieur sein wollen] Möchten Sie sich der entflohenen Mathematik durch Project_Euler stellen?
[Für diejenigen, die Portfolios erstellen] Reduzieren Sie Fehler und machen Sie den Code leichter lesbar -Rubocop Airbnb-
[Für diejenigen, die Portfolios erstellen] Verwendung von binding.pry mit Docker
Erstellen einer App und erstmaliges Bereitstellen mit heroku
Ich möchte eine ios.android App machen
[Android] Ich möchte einen ViewPager erstellen, der für Tutorials verwendet werden kann
Eine Einführung in Java, die Menschen erzählt, die schon lange C-Sprache sprechen
Ich habe eine Android-App für den MiRm-Dienst erstellt
SSH-Login beim App-Server mit Heroku
Erstellen Sie Ihre eigene Android-App für das Java-Lernen
Erstellen Sie eine App, indem Sie die Rails-Version angeben
Wie man einen imposanten Android-Musikplayer macht
[Android Studio] Ich möchte Einschränkungen für die in EditText [Java] registrierten Werte festlegen.
Erstellen Sie eine App, die mithilfe der Wetter-API ermittelt, ob Sie einen Regenschirm benötigen.