如何在Android中显示对话框而不需要Activity上下文?

43

这似乎应该很简单,但我无法找到答案。 我有一个在后台执行网络任务的Android应用程序。 如果返回错误,我想显示错误对话框。 当任务返回时,我不知道哪个Activity处于前台。 根据这篇帖子,看起来我们不能使用应用程序上下文来显示对话框(如果我尝试,确实会崩溃)。

那么我怎样才能获取当前活动的上下文? 再次强调,网络任务的接收器在应用程序上下文中运行,而不是特定的Activity中运行。 有其他的想法吗?

编辑:我应该澄清一下。 如果我们不是前台应用程序,我不想显示错误对话框。 我现在只对应用程序在前台的情况感兴趣。


你可以使用 Toast。据我所知,没有办法在不是活动前台应用程序的情况下显示对话框。 - zapl
可能的解决方案:https://dev59.com/VGTWa4cB1Zd3GeqPHd__#11324877 - Mike
11个回答

27
如果出现错误,我希望显示一个错误对话框。只有在您知道用户正在积极使用应用程序时才执行此操作。如果在进行其他操作(玩游戏、观看电影、阅读书籍)时中断用户,用户将非常烦恼。
那么如何获取当前活动的上下文?你不能。最多,你让当前活动知道它需要做些什么。
还有其他想法吗?
一种可能的方法是使用有序广播,因此如果您有前台活动,则它会获得控制权,否则您可以提醒用户通过引发一个通知而不弹出对话框来了解问题。接收有序广播的活动可以显示AlertDialog或以其他方式通知用户有关问题的信息。我在博客文章(以及书籍章节)中详细介绍了如何实现这一点,并且这里有一个演示该技术的示例应用程序
或者,服务可以调用startActivity()启动一个对话框主题的活动

如果他没有上下文,他怎么能创建并触发通知呢?据我所知,通知需要一个上下文来创建。我有一个后台线程在运行,它没有绑定到任何活动 - 因此我无法获得活动上下文来首先启动通知。 - AgentKnopf
@Zainodis: "我有一个后台线程正在运行,它没有绑定到任何活动" -- 这是一个错误。你应该让它由某个组件管理,比如一个服务。然后,Service 就是你在我的答案中所写的 Context - CommonsWare
谢谢你的提示 - 我们曾经为此提供了一个服务(后台线程处理服务器连接)- 然后我们又回到了后台线程,因为偶尔我们的服务会被杀死(我们认为是由于操作系统,因为没有异常或类似的东西导致突然失败),原因不明(在我们的情况下是不可接受的)。 - AgentKnopf
为什么API允许在只能与Activity一起使用时创建具有上下文的对话框? - klimat

8

虽然这个问题已经很老了,但现在有一个不错的解决方案,可以让你的代码与任何活动的Activity一起使用。所以您不需要像其他答案中提出的那样注册单独的Activities

我想强调的是,通常应该尽量避免在不知道视图上下文的情况下创建对话框,但特殊情况下可能会有用。

使用ActivityLifecycleCallbacks的这个解决方案,您始终可以在Application级别上知道活动的状态,然后可以使用此活动打开对话框或其他Activities,例如在只能访问Application类的网络代码中:

class YourApplication : Application() {

    private val activeActivityCallbacks = ActiveActivityLifecycleCallbacks()

    override fun onCreate() {
        super.onCreate()
        registerActivityLifecycleCallbacks(activeActivityCallbacks)
    }

    override fun onTerminate() {
       unregisterActivityLifecycleCallbacks(activeActivityCallbacks)
       super.onTerminate()
    }

    fun getActiveActivity(): Activity? = activeActivityCallbacks.getActiveActivity()
// ...
}


class ActiveActivityLifecycleCallbacks : Application.ActivityLifecycleCallbacks {

   private var activeActivity: Activity? = null

   fun getActiveActivity(): Activity? = activeActivity

   override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
      activeActivity = activity
   }

   override fun onActivityDestroyed(activity: Activity) {
      if (activity === activeActivity) {
         activeActivity = null
      }
   }
// ...
}

从任何地方:

YourApplication.get().getActiveActivity()?.let { activity ->
    activity.runOnUiThread {
       AlertDialog.Builder(activity).setMessage("test").show()
    }
}

请查看其他SO帖子,了解如何为Application实现getter:例如Kotlin singleton application class

6
我创建了一个帮助类,实现了CommonsWare的想法。希望显示警报的活动只需要调用Alerts.register()和Alerts.unregister()。然后任何人都可以调用Alerts.displayError()。
欢迎评论。
public class Alerts {

    private static class AlertReceiver extends BroadcastReceiver {

        private static HashMap<Activity, AlertReceiver> registrations;
        private Context activityContext;

        static {
            registrations = new HashMap<Activity, AlertReceiver>();
        }

        static void register(Activity activity) {
            AlertReceiver receiver = new AlertReceiver(activity);
            activity.registerReceiver(receiver, new IntentFilter(MyApplication.INTENT_DISPLAYERROR));
            registrations.put(activity, receiver);
        }

        static void unregister(Activity activity) {
            AlertReceiver receiver = registrations.get(activity);
            if(receiver != null) {
                activity.unregisterReceiver(receiver);
                registrations.remove(activity);
            }
        }

        private AlertReceiver(Activity activity) {
            activityContext = activity;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            abortBroadcast();
            String msg = intent.getStringExtra(Intent.EXTRA_TEXT);
            displayErrorInternal(activityContext, msg);
        }
    }

    public static void register(Activity activity) {
        AlertReceiver.register(activity);
    }

    public static void unregister(Activity activity) {
        AlertReceiver.unregister(activity);
    }

    public static void displayError(Context context, String msg) {
        Intent intent = new Intent(MyApplication.INTENT_DISPLAYERROR);
        intent.putExtra(Intent.EXTRA_TEXT, msg);
        context.sendOrderedBroadcast(intent, null);
    }

    private static void displayErrorInternal(Context context, String msg) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("Error")
               .setMessage(msg)
               .setCancelable(false)
               .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
                   public void onClick(DialogInterface dialog, int id) {
                       dialog.cancel();
                   }
               });
        final AlertDialog alert = builder.create();

        alert.show();
    }

}

你好, 我该如何使用这个类,我遇到了一个问题。 Myapplication是什么? - MRT
MyApplication.INTENT_DISPLAYERROR是ManicBlowfish的应用程序中表示字符串的常量。您可以将其替换为您选择的字符串。 - Jon
1
我建议不要使用这个解决方案。它保留了对活动的静态引用,这是 Android 中内存泄漏最常见的原因之一。如果这些引用已注册/注销,则可以正常使用。Lint 工具也可能因此失败。 - Patrick Jackson

4
我将使用自定义对话框。即使当前活动失去焦点,它也将被服务或处理程序调用。 我的自定义对话框的活动:
public class AlertDialogue extends AppCompatActivity {

    Button btnOk;
    TextView textDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE); //comment this line if you need to show Title.
        setContentView(R.layout.activity_alert_dialogue);

        textDialog = (TextView)findViewById(R.id.text_dialog) ;
        textDialog.setText("Hello, I'm the dialog text!");

        btnOk = (Button) findViewById(R.id.button_dialog);
        btnOk.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
}

您可以使用以下方法调用此对话框:

Intent intent = new Intent(this, AlertDialogue.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

在清单文件中:

<activity android:name=".AlertDialogue"
    android:theme="@style/AlertDialogMy">

</activity>

样式:

<resources>

    <style name="AlertDialogMy" parent="Theme.AppCompat.Light.Dialog">
        <item name="android:windowNoTitle">true</item> //delete this line if you need to show Title.
    </style>

</resources>

这是这个示例的完整代码。

当在视图之外点击时,活动会消失,如何解决? - Sattar
如果你想从外部活动完成这个活动,那么这不是解决方案。 - famfamfam

2
为什么不使用事件总线(Event Bus)(https://github.com/greenrobot/EventBus)?
  1. Define events:

    public class ShowErrorMessageEvent {
    
        public String errorMessage;
    
        public ShowErrorMessageEvent(String errorMessage) {
            this.errorMessage = errorMessage;
        }
        public String getErrorMessage() {
            return this.errorMessage;
        }
    }
    
  2. Prepare subscribers for all activities you needed:

    @Override
    public void onStart() { 
        super.onStart(); 
        EventBus.getDefault().register(this);
    }
    
    @Override
    public void onStop() {
        EventBus.getDefault().unregister(this);
        super.onStop();
    }
    
  3. In all activities, show the dialog if the event received

     public void onEvent(ShowErrorMessageEvent event) {
         /* Show dialog with event.getErrorMessage() from background thread */
     };
    
  4. In your background thread, post the error event:

    EventBus.getDefault().post(new ShowErrorMessageEvent("This is an error message!"));
    

0
这是一个实现,可以在当前活动的活动上方显示AlertDialog(这是消息对话框的示例,但也可用于警报)。
public class AlertsDialogue
{
    private AlertDialog.Builder alertDialogBuilder;
    private AlertDialog alert;

    public AlertsDialogue(Context context, String title, String message)
    {
        alertDialogBuilder = new AlertDialog.Builder(context);
        alertDialogBuilder.setTitle(title);
        alertDialogBuilder.setIcon(R.drawable.ic_launcher);
        alertDialogBuilder.setMessage(message)
            .setCancelable(false)
            .setPositiveButton(context.getString(R.string.text_ok), new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which)
                {
                    alert.dismiss();
                }
            });

        alert = alertDialogBuilder.create();
        Window window = alert.getWindow();
        if (window != null)
        {
            // the important stuff..
            window.setType(WindowManager.LayoutParams.TYPE_TOAST);
            alert.show();
        }
        else
            Toast.makeText(context, message, Toast.LENGTH_LONG).show();
    }
}

即使上下文不再活动,对话框仍将显示,就像 Toast 一样。
使用 new AlertsDialogue(MyActivity.this, "title", "message"); 进行调用。
AndroidManifest 文件中不需要额外的权限。

0

您需要重置对话框所附加的窗口类型,如下所示: dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);

不要忘记在清单文件中声明“android.permission.SYSTEM_ALERT_WINDOW”权限。


0

后台正在进行什么类型的网络任务?我建议重新考虑设计。也许通知会更好?或者是一个“结果摘要”屏幕。作为用户,如果我没有在积极等待任务完成,我宁愿得到一个不显眼的错误信号。


0
最好的做法是在Activity / Fragment中显示对话框和其他与视图相关的工作,而不是在其他没有上下文概念的类中进行。
但是,在视图之外想要这样做有一些有效的原因。例如,我想在模拟构建中显示一些警报对话框来帮助测试应用程序。为了提供活动上下文,您可以做几件事情,我将介绍其中两个:
  1. 将活动上下文与活动绑定的范围注入。这是最好的解决方案,但需要正确实现架构并考虑作用域。我不会在这里描述如何做到这一点,可以在教程中找到。

  2. 我创建了一些帮助类,通过活动扩展来获取上下文。它不存储上下文,而是提供上下文的函数,因此我认为它不应该导致内存泄漏(?未经测试,它是为测试人员创建的模拟版本,因此没有花费太多时间检查)

    object ActivityContextProviderForMockBuilds {
    
        private val contextProvider = MutableSharedFlow<(Context) -> Unit>(extraBufferCapacity = 1)
    
        fun getContext(contextCallback: (Context) -> Unit) = contextProvider.tryEmit(contextCallback)
    
        fun AppCompatActivity.setContextProviderForMockBuild() {
            if (BuildConfig.MOCKS_ENABLED) {
                contextProvider.onEach {
                    it.invoke(this)
                }.launchIn(lifecycleScope)
            }
        }
    }
    

使用方法:

ActivityContextProviderForMockBuilds.getContext { context -> ... }

但是正如之前所说,大多数情况下不应该在视图之外使用活动上下文,如果确实需要,则通过依赖注入来使用 :)


0

我也遇到了这个问题。我找到了一个简单而有效的解决方案。通常,我们有一个基础活动用于处理一些常见逻辑。所以可以这样做:

public class BaseActionBarActivity extends ActionBarActivity{  //this BaseActionBarActivity is the Base Act

   private static BaseActionBarActivity current;

   @Override
   protected void onStart() {
     super.onStart();
     current=this;
   }


   public static BaseActionBarActivity getCurrentContext() {
     return current;
   }
}

当前字段是当前上下文活动。我相信没有内存泄漏的问题。对我来说它运行良好!希望有所帮助。


1
不要将上下文设为静态。 - Orri

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