2 votes

WindowManager addView - Android 7.1.1

Je rajoute un petit TextView en bas de mon application lorsque l'application est hors ligne. Donc j'ai un BroadcastReceiver qui surveille les changements de connectivité réseau et dans la méthode onReceive, j'affiche la bannière. Voici la classe de la bannière qui ajoute le TextView au-dessus de la vue existante :

public static void show() {
        if (!isShowing && !isAppBackgrounded()) {
            MyApplication app = MyApplication.getInstance();
            WindowManager windowManager = (WindowManager) app.getSystemService(Context.WINDOW_SERVICE);
            Resources res = app.getResources();
            TextView offlineTv = app.getOfflineTv();

            if (offlineTv.getWindowToken() != null) {
                return;
            }

            offlineTv.setText("Hors ligne");
            offlineTv.setTextColor(ContextCompat.getColor(app, R.color.yellow));
            offlineTv.setGravity(Gravity.CENTER);
            offlineTv.setBackgroundColor(ContextCompat.getColor(app, R.color.dark_grey));
            offlineTv.setTextSize(TypedValue.COMPLEX_UNIT_SP, app.getResources().getInteger(R.integer.offline_banner_text_size));

            WindowManager.LayoutParams params = createLayoutParams(WindowManager.LayoutParams.TYPE_TOAST, null);
            windowManager.addView(offlineTv, params);
            isShowing = true;
        }
    }

Voici la méthode createLayoutParams

 private static WindowManager.LayoutParams createLayoutParams(int type, @Nullable IBinder windowToken) {
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
        layoutParams.format = PixelFormat.TRANSLUCENT;
        layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
        layoutParams.height = 25;
        layoutParams.gravity = GravityCompat.getAbsoluteGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, ViewCompat.LAYOUT_DIRECTION_LTR);
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        layoutParams.type = type;
        layoutParams.token = windowToken;
        layoutParams.windowAnimations = android.R.style.Animation_Toast;

        return layoutParams;
    }

Ce code fonctionne correctement sur tous les appareils sauf sur les appareils 7.1.1. Sur les appareils 7.1.1, le TextView s'affiche pendant un moment puis disparaît. Il y a juste un espace blanc vide à la place du TextView sur les appareils 7.1.1. Avez-vous une idée de pourquoi cela se produit ?

MODIFICATION : comme demandé dans les commentaires, voici comment j'obtiens le TextView : il s'agit de la classe MyApplication étendant Application :

TextView offlineTv = null;
/** Obtenir le TextView pour afficher le message hors ligne */
    public TextView getOfflineTv() {
        if (offlineTv == null) {
            offlineTv = new TextView(this);
        }
        return offlineTv;
    }

    /** Effacer le TextView hors ligne une fois que nous avons fini de le montrer */
    public void clearOfflineTv() {
        if (offlineTv != null) {
            offlineTv = null;
        }
    }

Et voici mon BroadcastReceiver, où je montre / cache le TextView :

public class DSConnectionChangeReceiver extends BroadcastReceiver {

    /**
     * Callback de changement de connexion
     * @param context Contexte
     * @param intent Intention
     */
    @Override
    public void onReceive(Context context, Intent intent) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
        boolean connected = false;
        boolean isCellularData = false;

        if (activeNetworkInfo != null) {
            connected = activeNetworkInfo.isAvailable() && activeNetworkInfo.isConnected();
            int type = activeNetworkInfo.getType();
            isCellularData = (type == ConnectivityManager.TYPE_MOBILE) || (type == ConnectivityManager.TYPE_MOBILE_DUN);
        }

        if (connected) {
            if (OfflineBanner.isShowing()) {
                OfflineBanner.dismiss();
            }
        } else {
            OfflineBanner.show();
        }
    }
}

2voto

Nick Cardoso Points 4864

Le problème est causé par l'ajout de la fenêtre d'animation android.R.style.Animation_Toast. Lorsque l'animation se termine sur un Toast réel, tout le toast disparaît. Dans ce cas, votre vue est dans la hiérarchie, donc au lieu de disparaître, elle devient vide.

Ce que vous devriez faire est de laisser layoutParams.windowAnimations hors des paramètres et plutôt créer et attacher la vue avec la visibilité définie sur View.GONE, puis animer manuellement la vue sur l'écran

Animer manuellement la vue peut être réalisé avec l'utilitaire suivant:

Animation animIn = AnimationUtils.makeInAnimation(context, true);
textView.setAnimation(animIn);
textView.setVisibility(View.VISIBLE);
textView.animate();

Alternative Snackbar:

public final class ConnectionBar {

     private static boolean mIsConnected = true; //statique pour préserver l'état
     private static ConnectionReceiver mReceiver; //statique pour détecter les fuites
     private static SnackBar mSnack; 

     private ConnectionBar() { /* requis */) 

     public static void prepare(Context ctx) {
         if (mReceiver != null) {
             Log.e(TAG, "ATTENTION l'ancien ConnectionBar a été libéré");
         }
         mReceiver = new ConnectionReceiver();
         ctx.registerBroadcastReceiver(mReceiver);
         if (!mIsConnected) { //statique pour se souvenir de l'écran précédent
             showBar(ctx);
         }
     } 

     private static void showBar(Context ctx) {
         if (mSnack == null) {
             mSnack = Snackbar.make(view, message, SnackBar.LENGTH_INDEFINITE);
             mSnack.show();
         }
     }

     public static void release(Context ctx) {
         if (mReceiver != null) {
             ctx.unregisterBroadcastReceiver(mReceiver);
             mReceiver = null;
         }
         if (mSnack != null) {
             mSnack.dismiss();
         }
     }

     private static class ConnectionReceiver extends BroadcastReceiver {

         @Override
         public void onReceive(Context context, Intent intent) {
             ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
             NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
             boolean isCellularData = false; //migrate this how you want
             if (activeNetworkInfo != null) {
                 ConnectionBar.mIsConnected = activeNetworkInfo.isAvailable() && activeNetworkInfo.isConnected();
                 int type = activeNetworkInfo.getType();
                 isCellularData = (type == ConnectivityManager.TYPE_MOBILE) || (type == ConnectivityManager.TYPE_MOBILE_DUN);
             }  
         }
         if (connected && ConnectionBar.mSnack != null) {
             ConnectionBar.mSnack.dismiss(); //vérifiez cela, il pourrait être nécessaire de l'encapsuler dans runOnUiThread
         } else {
             ConnectionBar.showBar(context);
         }
    }
}

Ensuite, dans votre activité:

public void onResume() {
    ConnectionBar.prepare(this); //s'occupe également de définir br
}  

public void onPause() {
    ConnectionBar.release(this);
}

2voto

Elias Fazel Points 1088

Si vous souhaitez que View in WindowManager reste plus longtemps que le Cycle de vie du BroadcastReciever, vous devez le faire à l'intérieur d'une classe étendue de service. Consultez ce tutoriel

Je pense qu'il y a encore des problèmes avec le Cycle de vie. Comment l'utilisez-vous et comment le système le gère. Si vous voulez forcer le système à ne pas tuer votre service (et à ne pas supprimer WindowManager), vous avez 3 options.

  1. Retournez OnStartCommand avec le drapeau approprié.

    return Service.START_REDELIVER_INTENT;

  2. Ajoutez une notification en premier plan

    startForeground(123, NotificationFunction());

  3. et Si vous avez beaucoup de processus à effectuer, ajoutez un service d'accessibilité. consultez cela

1voto

headuck Points 2258

Ceci est un comportement planifié depuis Android 7.1 pour empêcher les applications d'utiliser une vue toast pour recouvrir indéfiniment d'autres applications. Chaque fois que vous utilisez une vue TYPE_TOAST, le système imposera un maximum de 3,5 secondes (c'est-à-dire celui d'un toast LONG) pour l'affichage de votre vue (et changera également l'animation de la vue pour le style Toast interne), après quoi votre vue de toast sera masquée, SAUF lorsque votre application est celle actuellement au premier plan.

Pour éviter les plantages des applications, votre vue reste toujours dans la hiérarchie des vues. En d'autres termes, vous pouvez toujours appeler removeView sur elle après qu'elle a été masquée par le système, sans causer une exception d'état illégal.

(Référence : Voir le message de validation du code source Android : https://github.com/aosp-mirror/platform_frameworks_base/commit/aa07653d2eea38a7a5bda5944c8a353586916ae9 )

Pour afficher une vue par-dessus d'autres applications sur Android 7.1 ou version ultérieure, vous pouvez avoir besoin de demander l'autorisation SYSTEM_ALERT_WINDOW, inciter l'utilisateur à obtenir l'autorisation Dessiner par-dessus les applications, et utiliser un autre type de vue, tel que TYPE_SYSTEM_OVERLAY.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X