Dans le train - dans une bibliothèque tranquille - dans une salle de conférence - Lorsque j'ai lancé l'application, j'ai entendu un son fort. Avez-vous une telle expérience? Heureusement, je ne l'ai pas encore expérimenté, mais j'ai l'habitude de vérifier le volume plusieurs fois.
J'ai créé une application pour résoudre un tel problème.
Téléchargez à partir du lien ci-dessous
Il est open source (licence MIT). Dépôt
Cette application règle le volume du haut-parleur (STREAM_MUSIC) à zéro lorsque l'écouteur n'est pas connecté. Réglez le volume sur zéro au moment suivant.
Cette application réside dans la zone de notification (service de premier plan) et essaie toujours de réduire le volume du haut-parleur à zéro. Cependant, il y a des moments où vous souhaitez utiliser un haut-parleur, nous avons donc implémenté une fonction pour annuler temporairement le processus de mise en sourdine.
Appuyez sur Ne pas parler! Dans la notification pour afficher la boîte de dialogue d'annulation.
Si seulement ce qui précède est fait, ce sera un article publicitaire, donc j'écrirai le contenu de la mise en œuvre.
C'est le cœur de cette application, qui réduit le volume du haut-parleur à zéro. Il peut être défini dans la classe AudioManager.
AudioManager audioManager = this.getSystemService(this, AudioManager.class);
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0);
Récupérez simplement le ʻAudioManager et appelez la méthode
setStreamVolume. Sur Android, les paramètres de volume sont séparés pour chaque flux et chaque volume peut être réglé indépendamment. Spécifiez
STREAM_MUSIC` pour ne mettre à zéro que la musique de cette application.
De plus, il semble que l'autorisation ʻandroid.permission.MODIFY_AUDIO_SETTINGS` soit nécessaire, mais ce n'était pas le cas.
Cette application ne règle pas le volume à zéro lorsque les écouteurs sont connectés, vous devez donc vérifier l'état de connexion des écouteurs. Vous pouvez vérifier l'état de la connexion avec ʻAudioManager, mais le code de jugement change en fonction de la version d'Android. Android avant Android 6.0 M utilise les méthodes ʻisWiredHeadsetOn
, ʻisBluetoothScoOn et ʻisBluetoothA2dpOn
, et Android 6.0 M utilise la méthode 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;
}
}
Bluetooth A2DP semble être utilisé dans les écouteurs Bluetooth courants, mais Bluetooth SCO peut ne pas être nécessaire.
Même si le volume est réglé sur zéro, cela n'a aucun sens si l'utilisateur ou d'autres applications modifient le volume. Par conséquent, nous avons besoin d'une fonction pour rendre le volume nul lorsque le volume est modifié. Sur Android, Content Provider et ContentObserver Vous pouvez détecter le changement de volume en vous enregistrant.
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() {
//Traitement muet
}
}));
Récupérez la classe ContentResolver
avec getContentResolver
et enregistrez ContentObserver avec la méthode registerContentObserver
. À ce stade, vous pouvez spécifier les données à surveiller à l'aide de la chaîne de caractères appelée URI de contenu. Dans cette application, nous voulons détecter les changements de volume du haut-parleur, nous spécifions donc content: // settings / system / volume_music_speaker
.
Dans le processus de détection de changement de volume ci-dessus, si le volume est changé continuellement en peu de temps, ʻonChangede
ContentObserver peut ne pas se produire (plusieurs changements peuvent être combinés en un ʻonChange
?). Pour atténuer cet effet, coupez à nouveau le son 1 seconde après que ʻonChange` se soit produit. J'ai créé une classe pour faire le traitement debounce pour cela.
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();
}
}
}
Sur Android, ʻandroid.media.AUDIO_BECOMING_NOISY` est diffusé au moment où l'écouteur est retiré et le son est émis par le haut-parleur.
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: {
//Traitement muet
break;
}
}
}
}
J'écris également un filtre d'intention dans le manifeste.
<intent-filter>
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
À propos, avant de mettre à jour l'état de connexion de l'écouteur dans Android, cette diffusion est appelée, vous ne devez donc pas vérifier si l'écouteur est connecté à ce moment. Cette application sera désactivée de force quel que soit l'état de la connexion des écouteurs.
Le service sera interrompu lors du redémarrage du terminal ou de la mise à jour de l'application. Si rien n'est fait, une tragédie peut survenir, il est donc nécessaire de démarrer l'application automatiquement. Dans un tel cas, recevez la diffusion ʻandroid.intent.action.BOOT_COMPLETED et la diffusion ʻandroid.intent.action.MY_PACKAGE_REPLACED
et démarrez le service.
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
</intent-filter>
Vous avez également besoin d'autorisations.
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
Le code est similaire à ʻAUDIO_BECOMING_NOISY`, donc il est omis.
Sur Android, vous ne pouvez pas lancer de services directement depuis le lanceur. Vous ne pouvez (probablement) lancer des activités qu'à partir du lanceur. Cependant, cette application n'a pas besoin d'afficher l'activité, je l'ai donc ajustée pour ne pas afficher d'activité.
Remplacez ʻAppTheme` par:
<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>
Ce sera un thème transparent pour les activités sans barre de titre. Vous pouvez alors démarrer le service avec ʻonCreate dans
MainActivityet appeler immédiatement
finishAndRemoveTask` pour masquer l'activité de l'apparence.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Démarrer le service
this.finishAndRemoveTask();
this.overridePendingTransition(0, 0);
}
De plus, il semble que l'animation de transition puisse être annulée en appelant ʻoverridePendingTransition`.
Lorsque j'ai créé une application dans Android Studio, la taille de l'APK dépassait 1 Mo. Pour les applications résidentes, il est certainement préférable d'avoir une utilisation de la mémoire plus petite, nous avons donc effectué diverses réductions pour réduire la taille de l'application.
Tout d'abord, définissez minifyEnabled
et shirinkResources
sur true
dans ProGuard. Juste en faisant cela, cela faisait environ 700 Ko.
release {
minifyEnabled true
shrinkResources true
...
}
Normalement, je ne pense pas qu'il y ait de problème s'il est aussi petit que le niveau KB, mais je n'étais pas satisfait et j'ai décidé de le réduire. Dans cette application, la bibliothèque de dépendances spécifiée par dependencies
a gonflé l'APK.
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'
}
Il s'agit d'Android X, une bibliothèque de support pour maintenir la compatibilité descendante avec Android, et je pense que c'est presque essentiel pour les applications générales. Cependant, vous pouvez réduire la taille de l'APK en les jetant. (Non recommandé.)
J'ai pu créer un APK de moins de 100 Ko en supprimant AndroidX et en écrivant mon propre code rétrocompatible. La taille finale est d'environ 26 Ko. (Au fait, je l'ai écrit dans Kotlin au début, mais je l'ai changé en Java car le code d'octet généré était subtil. Objet compagnon ...)
Recommended Posts