WindowManager addView - Android 7.1.1

时间:2017-03-07 23:26:12

标签: android android-7.0-nougat

当应用程序离线时,我在应用程序底部添加了一个小TextView。所以我有BroadcastReceiver来监控网络连接的变化,在onReceive中,我会显示横幅。这是横幅类,它在现有视图的顶部添加TextView

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("Offline");
            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;
        }
    }

以下是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;
    }

此代码适用于除7.1.1设备之外的所有设备。在7.1.1设备中,TextView显示一段时间然后消失。在7.1.1设备上只有一个空白空格而不是TextView。知道为什么会这样吗?

编辑:在评论中提到,这里是我如何获得TextView:这是MyApplication类扩展应用程序:

TextView offlineTv = null;
/** Get the TextView to show the offline message */
    public TextView getOfflineTv() {
        if (offlineTv == null) {
            offlineTv = new TextView(this);
        }
        return offlineTv;
    }

    /** Clear the offline TextView once we are done showing it */
    public void clearOfflineTv() {
        if (offlineTv != null) {
            offlineTv = null;
        }
    }

这是我的BroadcastReceiver,我在其中显示/隐藏它:

public class DSConnectionChangeReceiver extends BroadcastReceiver {

    /**
     * Connection-changed callback
     * @param context Context
     * @param intent Intent
     */
    @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();
        }
    }
}

3 个答案:

答案 0 :(得分:2)

问题是由您添加android.R.style.Animation_Toast windowAnimation引起的。当动画在实际的Toast上结束时,整个吐司将消失。在这种情况下,您的视图位于层次结构中,因此它不会消失,而是变为空白。

您应该做的是让layoutParams.windowAnimations离开参数,然后创建并附加视图,并将可见性设置为View.GONE,然后手动将视图设置到屏幕上

使用以下实用程序可以手动设置视图动画:

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

Snackbar Alternative:

public final class ConnectionBar {

     private static boolean mIsConnected = true; //static to preserve state
     private static ConnectionReceiver mReceiver; //static to detect leaks
     private static SnackBar mSnack; 

     private ConnectionBar() { /* required */ )

     public static void prepare(Context ctx) {
         if (mReceiver != null) {
             Log.e(TAG, "WARNING previous ConnectionBar was leaked");
         }
         mReceiver = new ConnectionReceiver();
         ctx.registerBroadcastReceiver(mReceiver);
         if (!mIsConnected) { //static so will remember from last screen
             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(); //check this, might need to wrap in runOnUiThread
         } else {
             ConnectionBar.showBar(context);
         }
    }
}

然后在你的活动中:

public void onResume() {
    ConnectionBar.prepare(this); //takes care of setting br too
}  

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

答案 1 :(得分:2)

如果你想在WindowManager中查看仍然不仅仅是 BroadcastReciever生命周期,你需要在类扩展服务中做到这一点。看看this tutorial

我认为Lifecycle仍然存在问题。您如何使用它以及系统如何处理它。 如果你想强迫系统不要杀死你的服务(而不是删除WindowManager),你有3个选择。

  1. 使用proper flag返回OnStartCommand。

    返回Service.START_REDELIVER_INTENT;

  2. 添加前景通知

    startForeground(123,NotificationFunction());

  3. 并且如果您有许多进程要添加辅助功能服务。 check out this

答案 2 :(得分:1)

这是Android 7.1以来的一种行为,旨在阻止应用程序使用Toast视图无限期地覆盖其他应用程序。每当您使用TYPE_TOAST视图时,系统将为您的视图显示最多3.5秒(即LONG Toast的视图)(并且还将视图动画更改为内部Toast样式),之后您的Toast视图将被隐藏, EXCEPT 您的应用是当前关注的应用。

为避免应用程序崩溃,您的视图仍保留在视图层次结构中。换句话说,在系统隐藏后,您仍然可以在其上调用removeView,而不会导致非法状态异常。

(参考:请参阅Android源的提交消息: https://github.com/aosp-mirror/platform_frameworks_base/commit/aa07653d2eea38a7a5bda5944c8a353586916ae9

要在Android 7.1或更高版本上显示其他应用的视图,您可能需要请求SYSTEM_ALERT_WINDOW权限,提示用户获取Draw Over Apps权限,并使用其他视图类型,例如{{1 }}