Dispatch user action to activities in backstack

时间:2015-10-06 08:13:46

标签: android

I'm developing social app. Let's assume I have a stack of activities A -> B -> C -> D. D is in foreground and user presses "like" button to something there (post, comment, user etc.) What is the best way to notify all other activities about this action in order to refresh their data? I see 3 options here:

  1. Use local database and some loaders to automatically refresh the data. However, it requires a lot of code if we have different data-models with shared data (for instance BasicUserInfo, UserInfo, DetailedUserInfo).
  2. Use EventBus with sticky events (producers for Otto). In this case I must notify ONLY backstack activities and ignore those that will be created. Also I have to manage events overriding.
  3. Use a simple observer pattern with WeakReferences to backstack activities. But then I have a problem with killed activities that are going to be re-instantiated.

Real example:

In Instagram: I open some specific user's profile (A), there I open some specific post (B), again profile (A) and so on A -> B -> A -> B -> A .... So it loads data from the web everytime. On the step "n+1" a new comment to the post appears. If I start going back through my backstack I will see that instagram has dispatched this "new" comment to all B activities without reloading any data from web. So I'm interesting how do they do it.

7 个答案:

答案 0 :(得分:5)

通知系统(事件,观察者,BroadcastReceiver,...)的主要用例是当您希望收件人在事情发生时或多或少立即采取行动时。

我认为这不是这种情况:后台活动不需要立即采取行动,因为它们不可见。此外,他们甚至可能不再存在(杀死/冻结)。他们实际需要的是在他们回到前台时(可能在重新创建之后)获取最新数据。

为什么不简单地触发onStart()onResume()中的刷新(使用Loader或您已经使用的任何内容)?
如果需要保留“已赞”状态,则可以在D的{​​{1}}中执行此操作。 如果没有,喜欢的对象可以存储在全局变量中(实际上是粘性事件)

答案 1 :(得分:3)

您可以使用LocalBroadcastManager通知您的堆积活动您的喜欢活动已经发生

假设你的活动D:

private void liked() {
  Log.d("liked", "Broadcasting message");
  Intent intent = new Intent("like-event");
  // You can also include some extra data.
  intent.putExtra("message", "my like event occurs!");
  LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}

现在它将通知所有使用此广播接收器注册的活动

例如在您的活动A,B,C:

@Override
public void onCreate(Bundle savedInstanceState) {

  ...

  // Register to receive messages.
  // We are registering an observer (mMessageReceiver) to receive Intents
  // with actions named "custom-event-name".
  LocalBroadcastManager.getInstance(this).registerReceiver(mLikeEventReceiver ,
      new IntentFilter("like-event"));
}

// Our handler for received Intents. This will be called whenever an Intent
// with an action named "like-event" is broadcasted.
private BroadcastReceiver mLikeEventReceiver = new BroadcastReceiver() {
  @Override
  public void onReceive(Context context, Intent intent) {
    // Get extra data included in the Intent
    String message = intent.getStringExtra("message");
    Log.d("receiver", "Got message: " + message);
  }
};

@Override
protected void onDestroy() {
  // Unregister since the activity is about to be closed.
  LocalBroadcastManager.getInstance(this).unregisterReceiver(mLikeEventReceiver );
  super.onDestroy();
}

参考: [http://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html][1] [how to use LocalBroadcastManager? [https://androidcookbook.com/Recipe.seam?recipeId=4547][3]

  1. [1]: http://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html

  2. [2]:     how to use LocalBroadcastManager?

  3. [3]:https://androidcookbook.com/Recipe.seam?recipeId=4547

答案 2 :(得分:1)

处理此类事情的经典方法是使用BroadcastReceivers

这是一个示例接收器:

public class StuffHappenedBroadcastReciever extends BroadcastReceiver {


    private static final String ACTION_STUFF_HAPPENED = "stuff happened";
    private final StuffHappenedListener stuffHappenedListener;

    public StuffHappenedBroadcastReciever(@NonNull Context context, @NonNull StuffHappenedListener stuffHappenedListener) {

        this.stuffHappenedListener = stuffHappenedListener;
        context.registerReceiver(this, new IntentFilter(ACTION_STUFF_HAPPENED));
    }

    public static void notifyStuffHappened(Context context, Bundle data) {

        Intent intent = new Intent(ACTION_STUFF_HAPPENED);
        intent.putExtras(data);

        context.sendBroadcast(intent);

    }

    @Override
    public void onReceive(Context context, Intent intent) {

        stuffHappenedListener.onStuffHappened(intent.getExtras());

    }


    public interface StuffHappenedListener {

        void onStuffHappened(Bundle extras);
    }
}

如何将其附加到活动中:

public class MainActivity extends AppCompatActivity implements StuffHappenedBroadcastReciever.StuffHappenedListener {

    private StuffHappenedBroadcastReciever mStuffHappenedReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mStuffHappenedReceiver = new StuffHappenedBroadcastReciever(this, this);
    }

    @Override
    protected void onDestroy() {
        unregisterReceiver(mStuffHappenedReceiver);

        super.onDestroy();
    }

    @Override
    public void onStuffHappened(Bundle extras) {
        // do stuff here
    }
}
只要活动存活,

“onStuffHappened”就会被调用。

答案 3 :(得分:1)

我同意@bwt的方法。当您想立即通知某些内容时,这些通知系统应该很重要。

我的方法是缓存系统。您无需处理“如果活动已重新堆叠或新创建”,您始终需要在活动的onResume中查询所需内容。因此,您将始终获得最新的数据。

从服务器获取数据时,还需要在本地数据库中复制数据模型。在ping服务器之前,例如在用户帖子中有类似内容,您需要在本地数据库中将其设置为Flag,并将您的Http请求发送到您的服务器,说“嘿,这个帖子很受欢迎”。稍后当您收到此请求的响应时,如果成功与否,请尝试再次修改此标记。

因此,在您的“活动”中,如果从onResume中的本地数据库进行查询,您将获得最新的数据。对于其他用户帖子的更改,您可以BroadcastReceiver反映您的可见活动中的更改。

P.S:您可以检查Realm.io以获得相对较快的查询,因为您需要更快地进行本地数据库调用。

答案 4 :(得分:1)

正如许多其他答案所示,这是一个典型的例子,BroadcastReceiver很容易完成工作。

我还建议将LocalBroadcastManager类与BroadcastReceiver结合使用。来自BroadcastReceiver的文档:

  

如果您不需要跨应用程序发送广播,请考虑将此类与LocalBroadcastManager一起使用,而不是使用下面描述的更一般的工具。这将为您提供更高效的实施(无需跨进程通信),并允许您避免考虑与其他应用程序能够接收或发送您的广播相关的任何安全问题。

答案 5 :(得分:1)

  

为了刷新他们的数据?

这就是答案。活动不应拥有数据。活动提供数据并允许用户对其采取行动。数据本身应该位于一个单独的实体中,可以通过活动实例(模型)与其进行交互。

此外,不应该假设在后端堆栈中始终存在活动实例。当用户导航回来时,系统可以销毁这些活动,然后通过系统将其重新创建为不同的对象。在创建整个活动时,数据始终刷新。

将数据处理分离到专门的类,可以通过活动轻松访问,并可以按需提供数据/事件绑定到活动。绑定Service是一个很好的候选人。

如果不从Web加载数据,您可以设置最近访问的数据的本地缓存(缓存,因为移动设备有严格的存储限制,服务器和数据库不是这样)。因此,用户端的任何更改也会在传播到服务器的同时提交到此缓存。所有这些都更好地由专门的数据类封装,而不是依赖于活动类中的后栈或特殊代码。

作为一种模式,您可以为所涉及的数据实体构建Model类。编写Web API接口实现以与服务器通信。然后,在API接口之前放置一个缓存层。缓存将保留对API层的传出更改和传入更新,并在不需要服务器调用时简单地反映数据请求。

Cache主要做三件事:

  1. Evict:随着新数据的到来,删除最不重要的数据,因此缓存仍然是固定大小的。大多数缓存实现(like this one)会自动执行此操作。
  2. 无效:有时,由于用户操作或服务器端的外部事件,某些数据必须刷新。
  3. 过期:数据可以设置时间限制并自动驱逐。这在强制定期刷新数据时很有用。
  4. 现在大多数缓存实现处理原始字节。我建议使用类似Realm之类的东西,一个对象数据库并将其包装在类似缓存的功能中。因此,请求用户推文的典型流程是:

    1. 显示活动。
    2. 活动绑定到数据服务,并表达了对" tweets"。
    3. 的兴趣。
    4. 数据服务在缓存数据库的Tweets表中查找最后提取的推文列表,并立即返回。
    5. 但是,数据服务还会调用服务器在db的本地最新推文的时间戳后给出推文。
    6. 服务器返回最新的推文。
    7. 数据服务使用新的传入数据更新所有对推文感兴趣的绑定活动。此数据也在缓存db中本地复制。此时,推文表也通过删除旧记录进行优化。
    8. 活动取消绑定数据服务,因为活动正在消失,停止,销毁等。
    9. 如果没有绑定活动对" tweets"感兴趣,数据服务会停止加载更多推文。
    10. 通过维护与服务器的套接字连接并实时接收有趣的更改,您的数据实现可以更进一步。只要有新数据传入,服务器就会调用上面的第5步。

      TLDR; 将数据管理与“活动”分开,然后您可以独立于UI问题进行演变。

答案 6 :(得分:-1)

如果您必须对某些数据进行所有活动的更改,您可以遵循界面模式。例如,您有一个自定义类类ActivityData,其中包含您需要在所有活动中更新的内容

第1步:

按如下方式创建界面

public interface ActivityEventListener
{


    ActivityData getData( Context context,
            Activity activity );


}

步骤2:

创建一个BaseActivity,用于引用您在应用程序中拥有的所有活动,并按如下方式实现接口

public class BaseActivity extends Activity
{
    protected AcitivityData mData= null;


    @Override
    protected void onCreate( Bundle savedInstanceState )
    {
        super.onCreate( savedInstanceState );
    }

    protected ActivityEventListener mListener = new ActivityEventListener()
    {


        @Override
        public ActivityData getData( Context context,
                Activity activity )
        {
            // TODO Auto-generated method stub
            return mData;
        }


    };
}

第3步: 使用BaseActivity扩展您自己的活动,例如A或B或C .......

公共类A扩展了BaseActivity {

  ActivityData mData;
@Override
protected void onCreate( Bundle savedInstanceState )
{

     mData = mListener.getData(this,this);

     updateWidgets(mData);

}

}

其中updateWidgets是一个函数,您可以在其中定义UI元素并使用与接口相关的数据

由于您的所有活动B / C /等都可以获得ActivityData的参考。活动表格backstack将通过onStart()开始执行用户可以处理活动相同的活动,ActivityData中存在详细信息

在您喜欢上次活动的情况下,您可以更新ActivtyData的对象,当后台堆栈活动恢复或启动时,您可以获得更新的数据,因为您的Backstack活动通过具有接口的BaseActiivty扩展。