在Android中从非活动类显示对话框警报

11

我希望能够通过AlertDialogManager类在DeviceAdminReceiverSampleonDisabled方法中展示一个警报对话框,但每当我通过该方法调用alertDialog时,它都会生成以下文本的错误:

错误

06-12 12:01:19.923: E/AndroidRuntime(468): FATAL EXCEPTION: main
06-12 12:01:19.923: E/AndroidRuntime(468): java.lang.RuntimeException: Unable to start           
receiver com.android.remotewipedata.DeviceAdminReceiverSample:   
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not   
for an application

我知道问题出在context这个东西上,但是我不知道应该放什么进去才能让它工作。我试过了thisgetApplicationContext(),但都没有用。我两个类的代码都放在下面了。

AlertDialogManager

public class AlertDialogManager {

public void showAlertDialog(Context context, String title, String message,
        Boolean status) {
    final AlertDialog alertDialog = new AlertDialog.Builder(context).create();
    alertDialog.setTitle(title);
    alertDialog.setMessage(message);

    if (status != null)
        alertDialog.setButton("OK", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                alertDialog.dismiss();
            }
        });
    alertDialog.show();
}

}

DeviceAdminReceiverSample

public class DeviceAdminReceiverSample extends DeviceAdminReceiver {
static final String TAG = "DeviceAdminReceiver";
AlertDialogManager alert = new AlertDialogManager();

/** Called when this application is no longer the device administrator. */
@Override
public void onDisabled(Context context, Intent intent) {
    super.onDisabled(context, intent);
    Toast.makeText(context, R.string.device_admin_disabled,
            Toast.LENGTH_LONG).show();
    // intent.putExtra("dialogMessage", "Device admin has been disabled");
    // intent.setClass(context, DialogActivity.class);
    // intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    // context.startActivity(intent);
    alert.showAlertDialog(context, "Alert",
            "Device admin has been disabled", true);
}

为此创建一个DialogActivity。 - Chintan Rathod
使用Activity对象而不是Context对象。 - Ajay
7个回答

42

alertDialog.show(); 之前加入以下内容即可:

alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);

如果上述方法不起作用,请尝试以下方法:

alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_PANEL); 

并使用此权限:

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

1
谢谢,它正在工作。你的答案应该被点赞。 - VAdaihiep
2
非常好的发现。谢谢你。根据文档,除了允许应用程序使用类型TYPE_SYSTEM_ALERT打开窗口外,还可以在所有其他应用程序之上显示。很少有应用程序需要使用此权限;这些窗口旨在与用户进行系统级交互。因此,在使用此解决方案时,我们也需要注意。 - Pankaj Kumar
遇到了相同的问题,通过这种方式解决了。谢谢。 - darthvading
1
不好的解决方案,因为hardBackButton会关闭活动,但不会关闭对话框。 - Dmitry Nelepov
3
我觉得我们不应该使用这个功能,因为它会在完整的安卓系统中显示警报,无论用户在哪个应用程序中。其次,在点击警报框之外时,它不会消失。如果你想执行任何与你的应用有关的操作,那么可能会导致崩溃。 - Jishant

15
问题是 'You can show AlertDialogs from Activity only'。这不是上下文的问题。 虽然从接收器显示对话框并不是一个好主意(最好使用通知),但如果您想这样做,可以创建一个作为对话框的活动并显示。 (参考链接)

1
是的,确实无法在Activity之外显示AlertDialog,以下是我解决这个问题的方法:https://medium.com/@vapoyan/android-show-allertdialog-before-the-application-starts-80588d6f2dda - Viktor Apoyan

11
如果您希望在应用程序中的任何位置始终获取当前活动,可以在您的应用程序实例上注册ActivityLifecycleCallback。
以下是一个未经测试的实现,可能会让您更接近目标。
public class TestApp extends Application {

    private WeakReference<Activity> mActivity = null;

    @Override
    public void onCreate() {
        super.onCreate();
        registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
                mActivity = new WeakReference<Activity>(activity);
            }

            @Override
            public void onActivityDestroyed(Activity activity) {
                mActivity.clear();
            }

            /** Unused implementation **/
            @Override
            public void onActivityStarted(Activity activity) {}

            @Override
            public void onActivityResumed(Activity activity) {}
            @Override
            public void onActivityPaused(Activity activity) {}

            @Override
            public void onActivityStopped(Activity activity) {}

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
        });
    }

    public Activity getCurrentActivity() {
        return mActivity.get();
    }

}

然后在整个应用程序中使用它,您需要进行一些如下的调用 ...

Activity activity = ((TestApp)getApplicationContext()).getCurrentActivity(); 

优点是您始终可以跟踪当前活动,但对于仅处理Activity内的对话框来说有些过度。

使用单例 WebView 和 MutableContextWrapper。 - norbDEV
1
@Chris Sullivan 当从一个活动转移到另一个活动时,这种方法是不起作用的,因为第二个活动将在第一个活动被清除之前被初始化。 - Sakkeer Hussain
@SakkeerHussain 是的,第一个活动在两个活动都创建后被销毁。因此,它将清除 mActivity。 - StackExploded
@StackExploded 我找到了一个解决方案,通过在现有的活动中添加一个检查,并判断要删除的活动是否相等,如果两者不匹配,则清除操作将被跳过。 - Sakkeer Hussain
是的,我自己做了。可以检查 activity.hashCode() 是否相等。 - StackExploded
@Chris Sullivan,你可以在onActivityResumed方法中声明活动,并在onActivityPaused方法中清除它。这将解决问题。 - Salam El-Banna

1

在activity类中调用此方法

public static void showAlert(Activity activity, String message) {

        TextView title = new TextView(activity);
        title.setText("Title");
        title.setPadding(10, 10, 10, 10);
        title.setGravity(Gravity.CENTER);
        title.setTextColor(Color.WHITE);
        title.setTextSize(20);

        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
        // builder.setTitle("Title");
        builder.setCustomTitle(title);
        // builder.setIcon(R.drawable.alert_36);

        builder.setMessage(message);

        builder.setCancelable(false);
        builder.setNegativeButton("OK", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int id) {
                dialog.cancel();

            }

        });

        AlertDialog alert = builder.create();
        alert.show();
    }

谢谢,这个概念对我很有帮助。 - Atiar Talukdar

0

这是我制作和使用的:

myDialog.java:

import android.app.Activity;
import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;

public class myDialog {
    private Activity mActivity;

    myDialog(Activity a) {
        this.mActivity = a;
    }

    @SuppressWarnings("InflateParams")
    public void build(String title, String msg) {
        LayoutInflater inflater = LayoutInflater.from(mActivity);
        View subView = inflater.inflate(R.layout.dialog_box_text, null);
        final TextView message = subView.findViewById(R.id.message);
        message.setText(msg);
        AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
        builder.setTitle(title);
        builder.setView(subView);
        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
            }
        });
        AlertDialog alert = builder.create();
        alert.show();
    }
}

dialog_box_text.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:weightSum="1"
    android:orientation="horizontal">
    <TextView
        android:id="@+id/message"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="  "
        android:maxLines="1"
        android:textColor="@color/colorBlack" />
</LinearLayout>

示例代码:

public class MainActivity extends AppCompatActivity {
    private myDialog md;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        md = new myDialog(this);

...

        md.build("Title", "Message");

-2

您可以在MainActivity中定义一个公共的Context变量,并赋初值为(this)。如下所示:

public class MainActivity< alertdail > extends AppCompatActivity {

    ////////////////////////////////////////////////////////////
    //Public var refers to Main Activity:
    Context mainActivity = this;
    ////////////////////////////////////////////////////////////

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate ( savedInstanceState );
        setContentView ( R.layout.activity_main );
        AlertDialogManager alert  =new AlertDialogManager ();
alert.showAlertDialog ( this,"Title","Message",true );


     }


    public class AlertDialogManager {

        public void showAlertDialog(Context context, String title, String message,
                                    Boolean status) {
            final AlertDialog alertDialog = new AlertDialog.Builder ( mainActivity ).create ( );
            alertDialog.setTitle ( title );
            alertDialog.setMessage ( message );

            if (status != null)
                alertDialog.setButton ( "OK", new DialogInterface.OnClickListener ( ) {
                    public void onClick(DialogInterface dialog, int which) {
                        alertDialog.dismiss ( );
                    }
                } );
            alertDialog.show ( );
        }

        public void showAlertDialog(Context c) {
        }
    }


    public class DeviceAdminReceiverSample extends DeviceAdminReceiver {
        static final String TAG = "DeviceAdminReceiver";
        AlertDialogManager alert = new AlertDialogManager ( );

        /**
         * Called when this application is no longer the device administrator.
         */
        @Override
        public void onDisabled(Context context, Intent intent) {
            super.onDisabled ( context, intent );
            Toast.makeText ( context, R.string.device_admin_disabled,
                    Toast.LENGTH_LONG ).show ( );
            // intent.putExtra("dialogMessage", "Device admin has been disabled");
            // intent.setClass(context, DialogActivity.class);
            // intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            // context.startActivity(intent);
            alert.showAlertDialog ( context, "Alert",
                    "Device admin has been disabled", true );
        }
    }
}

3
共享活动上下文的这种方式可能会导致内存泄漏。 - arjoan

-3

这是一个快速而正确地执行此任务的方法,对我来说非常有效。基本上,你需要做的就是创建一个新的线程。


  1. 声明一个公共的静态变量,其类型与原始活动类匹配。

    public static Activity1 activity;

Activity1是变量所在的类。


调用方法onCreate();时,将变量设置为活动的上下文,也就是this
例如:
@Override 
    protected void onCreate( Bundle savedInstanceState ) {
    super.onCreate( savedInstanceState );
    activity = this;
}

3. 既然我们现在有了活动的上下文,我们可以使用它来创建一个带有警示对话框的函数,通过在函数内部使用runOnUiThread();方法来调用警示对话框。我们将使用new Runnable()来执行runOnUiThread();所需的可运行操作,并且为了让警示对话框真正打开,我们将重写可运行项的运行函数,并在其中放置警示对话框的代码。
示例函数:
public static void exampleDialog(){
Activity1.activity.runOnUiThread(new Runnable){
@Override
    public void run(){
    //alert dialog code goes here.  For the context, use the activity variable from Activity1.
        }
    }
}

希望这能有所帮助 :)

使用活动的静态变量会导致内存泄漏,这不是解决问题的有效方法。 - Adeel Javed

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接