通过受保护功能设置变量 - 丢失参考

时间:2015-06-08 02:43:34

标签: java android variables

2017年更新(正在进行中......)

总之,情况现在看起来像这样。 我点击B(android片段)的实例,并期望调用该实例的onContextItemSelected。确实调用了onContextItemSelected,但事实证明这是类C实例的方法。

我被要求展示这个项目。在大约2年后查看此代码之后,我决定首先对它进行更多的澄清,因为上面评论的事件可能不是5%。我认为今天我不能做更多的事情购买我已经对整个项目进行了一些概述,所以你知道在哪里看。它没有完全完成,可能包含一些错误,但更多的是它的样子:

enter image description here

我将尝试明确如何重现它的明确步骤。但是如果你想看它,我还是要包含已经here

的文件

ABaseCustomFragment

BC是扩展它的类。

我有这样的事情:

public class A extends Fragment implements OnItemClickListener, OnItemLongClickListener{
    protected D loc;

    protected void setContext(D l){
        Log.d("A", "setContext :" + String.valueOf(l));
        loc = l;
        Log.d("A", "setContext2 :" + String.valueOf(loc));
    }

    public boolean onContextItemSelected(MenuItem item) {
        Log.d("A", "itemSelected :" + String.valueOf(loc));
    }
}

public class B extends A{

    public boolean onItemLongClick(AdapterView<?> pr, View view,int p, long id) {
        D d = (D) pr.getItemAtPosition(p);
        Log.d("B", "longClick :" + String.valueOf(d));
        setContext(d);
        return false;
    }
}

并且日志看起来像这样:

  

B longClick:数据

     

setContext:data

     

setContext2:data

     

itemSelected:null

我在其他任何地方都没有触及locsetContex。我完全不知道发生了什么。怎么可能?

我将A类设置为ListView侦听器。我在ViewPager中使用此片段。在onItemSelected之后立即调用setContext。 不知道该怎么说更多。

编辑:

因为挥发性没有修复但是...... 甚至更奇怪的东西 - 在班级A我得到了:

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    lv = (ListView) inflater.inflate(layoutResourceId, container, false);
    lv.setOnItemClickListener(this);
    lv.setOnItemLongClickListener(this);
    lv.setAdapter(adapter);
    //this.registerForContextMenu(lv);
    return lv;
}

我现在也加入了课程B

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    super.onCreateView(inflater, container, savedInstanceState);
    this.registerForContextMenu(lv);
    return lv;
}
@Override
public boolean onContextItemSelected(MenuItem item) {
    //Never called!
    Log.d("Loc", "selected :" + name);
    return super.onContextItemSelected(item);
}

魔法发生的地方是我还有C类,它与B基本相同,只是我没有覆盖上面那些额外的方法:

public class C extends A {      
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        ...
    }   
    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view,int position, long id) {
        ...
    }       
}

日志显示:

  

B longClick:数据 - 来自B

的实例      

setContext:data - 来自B

的实例      

setContext2:data - 来自B

的实例      

itemSelected:null - 来自C !!!

的实例

怎么样?我只在课堂上发送registerForContext B有什么想法吗?

2 个答案:

答案 0 :(得分:1)

如果正在从多个线程访问您的A对象(看起来可能是您的事件监听器),您可以看到这样的问题(它的一般术语是"stale data")。要快速检查这是否是线程问题,请将您的成员变量声明为protected volatile D loc;,然后查看问题是否消失(请参阅this resource about volatile)。如果这确实解决了问题,那么您将需要实施更高级的线程保护,以确保您不会遇到任何更恶毒/细微的线程错误。

答案 1 :(得分:1)

更新

出了什么问题?

您将ViewPagerFragmentPagerAdapter结合使用。这种组合的一个重要实现是你有几个Fragment附加到Activity。这需要ViewPager才能正常工作,因为它至少需要下一个FragmentView的{​​{1}} s才能进行交换。

当您仅使用标题发送ViewPager时,您不会将MenuItem的这种行为考虑在内,这是错误的。这通常是一个可怕的想法,这仅仅是为什么它是错误的例子。当显示上下文菜单并且用户选择某个项目时,首先MainActivity有机会处理它,然后事件转到FragmentManager,该事件将事件发送到 所有 附加Fragment s(因为如果你考虑的话,没有其他合理的选择)。因此,Fragment每个onMenuItemSelected都会收到MenuItem来电并试图处理它,因为Fragment标题是相同的。当然,你有一个例外,因为对于其他MenuItem s&#34; context&#34;当前所选项目未设置。

如何解决?

显然,不要仅使用标题为您的groupId发送活动。实际上,您可以为每个itemId生成唯一的Fragmentmenus。但我更喜欢类似OOP的方式。首先删除所有原始上下文菜单管理代码(mpossetMenuaddMenuItemremoveItemMenuonMenuItemSelectedpublic class BaseCustomFragment extends Fragment implements OnItemClickListener, OnItemLongClickListener { private List<MenuItemAction> menus = new ArrayList<MenuItemAction>(); // base class for all actions for context-menu in BaseCustomFragment abstract class MenuItemAction implements MenuItem.OnMenuItemClickListener { private final String title; public MenuItemAction(String title) { this.title = title; } public String getTitle() { return title; } public final boolean isVisible() { return isVisibleImpl(conCon, locCon); } @Override public final boolean onMenuItemClick(MenuItem item) { AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); Log.d("MenuItem", "menu item '" + title + "' for #" + info.position + " of " + BaseCustomFragment.this.getClass().getSimpleName()); handleActionImpl(conCon, locCon); return true; } protected final void startActivity(Uri uri) { startActivity(Intent.ACTION_VIEW, uri); } protected final void startActivity(String action, Uri uri) { startActivity(new Intent(action, uri)); } protected final void startActivity(Class<? extends Activity> activityClass) { startActivity(new Intent(getActivity(), activityClass)); } protected final void startActivity(Intent intent) { getActivity().startActivity(intent); } protected abstract boolean isVisibleImpl(Customer conCon, Localization locCon); protected abstract void handleActionImpl(Customer conCon, Localization locCon); } protected void setContext(Customer c, Localization l) { Log.d("BaseFrag", "setContext class:" + String.valueOf(this.getClass())); Log.d("BaseFrag", "setContext :" + String.valueOf(l)); conCon = c; locCon = l; Log.d("BaseFrag", "setContext2 :" + String.valueOf(locCon)); updateContextMenu(); } protected void setMenus(MenuItemAction... menuItems) { this.menus = Arrays.asList(menuItems); updateContextMenu(); } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo info) { super.onCreateContextMenu(menu, v, info); for (MenuItemAction menuItemAction : menus) { if (menuItemAction.isVisible()) { MenuItem menuItem = menu.add(menuItemAction.getTitle()); menuItem.setOnMenuItemClickListener(menuItemAction); } } } private void updateContextMenu() { if (lv == null) return; boolean hasVisibleItems = false; for (MenuItemAction menuItemAction : menus) { if (menuItemAction.isVisible()) { hasVisibleItems = true; break; } } if (hasVisibleItems) { Log.d(getClass().getSimpleName(), "Attaching context menu for " + getClass().getSimpleName()); this.registerForContextMenu(lv); } else { Log.d(getClass().getSimpleName(), "Detaching context menu for " + getClass().getSimpleName()); this.unregisterForContextMenu(lv); } } 等。)并用这样的东西替换它们

MenuItemAction

此处Fragment是所有&#34;内部&#34;的基类。每个MenuItem独立持有的菜单项。我打电话给他们&#34;内部&#34;因为&#34;真实&#34; onCreateContextMenu是实施细节,您无法从中继承,因此我们会创建&#34;真实&#34;来自我们&#34;内部&#34;那些。此转换/创建在MenuItemAction内完成。请注意,startActivity会调度已提供您的&#34;上下文&#34;数据到需要的地方。它还包含一些BaseCustomFragment辅助方法来简化代码。

现在我们可以添加 protected MenuItemAction createNavigateToLocationMenuItem() { return new MenuItemAction("Navigate to location") { @Override protected boolean isVisibleImpl(Customer conCon, Localization locCon) { return (locCon != null); } @Override protected void handleActionImpl(Customer conCon, Localization locCon) { startActivity(Uri.parse("google.navigation:q=" + Uri.encode(locCon.noZipString()))); } }; } protected MenuItemAction createCallMenuItem() { return new MenuItemAction("Call") { @Override protected boolean isVisibleImpl(Customer conCon, Localization locCon) { return (conCon != null) && (conCon.getPhoneNumbers().size() > 0); } @Override protected void handleActionImpl(Customer conCon, Localization locCon) { startActivity(Intent.ACTION_DIAL, Uri.parse("tel:" + Uri.encode(BaseCustomFragment.this.conCon.getPhoneNumbers().get(0).toString()))); } }; } protected MenuItemAction createSmsMenuItem() { return new MenuItemAction("SMS") { @Override protected boolean isVisibleImpl(Customer conCon, Localization locCon) { return (conCon != null) && (conCon.getPhoneNumbers().size() > 0); } @Override protected void handleActionImpl(Customer conCon, Localization locCon) { startActivity(Uri.parse("sms:" + Uri.encode(BaseCustomFragment.this.conCon.getPhoneNumbers().get(0).toString()))); } }; } protected MenuItemAction createEmailMenuItem() { return new MenuItemAction("Email") { @Override protected boolean isVisibleImpl(Customer conCon, Localization locCon) { return (conCon != null) && (conCon.getEmail().length() > 0); } @Override protected void handleActionImpl(Customer conCon, Localization locCon) { startActivity(Uri.parse("mailto:" + Uri.encode(conCon.getEmail()))); } }; } protected MenuItemAction createNewOrderMenuItem() { return new MenuItemAction("New Order") { @Override protected boolean isVisibleImpl(Customer conCon, Localization locCon) { return true; } @Override protected void handleActionImpl(Customer conCon, Localization locCon) { startActivity(OrderActivity.class); } }; } 一堆辅助方法来创建典型的菜单项:

public class CustomersFrag extends BaseCustomFragment
{
    public CustomersFrag()
    {

        ... 

        setMenus(createCallMenuItem(),
                 createSmsMenuItem(),
                 createEmailMenuItem(),
                 createNewOrderMenuItem(),
                 createNavigateToLocationMenuItem());
    }

    ... 

    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
    {
        Customer c = (Customer) parent.getItemAtPosition(position);
        setContext(c, c.getLocalization());
        return super.onItemLongClick(parent, view, position, id);
    }

所以剩下的就是使用这种基础设施特定的片段,例如

public class LocalizationsFrag extends BaseCustomFragment
{
    public LocalizationsFrag()
    {

        ... 

        setMenus(
                createNavigateToLocationMenuItem(),
                createNewOrderMenuItem());
    }

    ... 

    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id)
    {
        Localization o = (Localization) parent.getItemAtPosition(position);
        setContext(null, o);
        return super.onItemLongClick(parent, view, position, id);
    }

if

注意代码中的菜单项标题不再有else / switchaddMenuItem。此外,根据特定片段中的数据,您不必在每个removeMenuItem中执行奇怪的onItemLongClick / MenuItemAction,因为:

  1. 每个isVisibleImpl现在都拥有自己的handleActionImplsetContext方法
  2. updateContextMenu来电MenuItemAction
  3. 所有菜单操作似乎都是通用的,适合简单的&#34; context&#34;你已经有了。因此,我使Fragment只是一个非静态内部类,可以使用BaseCustomFragment.this获取它的父BaseCustomFragment。如果在某些时候这成为一个限制,你想创建一个特定于public class OrdersFragment extends BaseCustomFragment { ... private Order currentOrder; public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { Order o = (Order) parent.getItemAtPosition(position); currentOrder = o; setContext(o.getCustomer(), o.getLocalization()); return super.onItemLongClick(parent, view, position, id); } protected MenuItemAction createNewOrderMenuItem() { return new MenuItemAction("Copy Order") { @Override protected boolean isVisibleImpl(Customer conCon, Localization locCon) { return (OrdersFragment.this.currentOrder != null); } @Override protected void handleActionImpl(Customer conCon, Localization locCon) { Intent intent = new Intent(getActivity(), OrderActivity.class); intent.putExtra("copySrcId", OrdersFragment.this.currentOrder.getId()); startActivity(intent); } }; } 子子类的动作,并且你希望它使用该片段中的数据,你只需要在相应的子代中创建菜单项类。想象一下,你想要添加一个&#34;复制订单&#34;菜单项。你做这样的事情:

    MenuItemAction

    现在你的&#34;复制订单&#34; currentOrder可以访问OrdersFragment内定义的java.util

    附注

    看看你的代码,我发现了一些我认为值得一提的事情:

    • 对电话号码使用整数类型是一个坏主意。电话号码可能包含其他符号,例如&#34; +&#34;或&#34; *&#34;或&#34;#&#34;这将不适合基于整数的类型。电话号码可能以&#34; 0&#34;并且你将失去转换为基于整数的类型。
    • 我认为你应该更好地学习contains包。例如,java.util.List及其子类中有java.sql.Timestamp方法。
    • 当您可以使用超类/接口时,不清楚为什么在声明中使用非常特定的类型。例如,你的所有Model类似乎都没有明显的原因使用ArrayList作为日期字段(你真的需要纳秒吗?)。或者您在任何地方都使用List,但有时仅MainActivity就足够了
    • prepareTabs adapter.notifyDataSetChanged(); this中,在您添加标签后,请不要致电Log。实际上,这会导致新的支持库崩溃。

    希望这有帮助

    旧答案(最可能无关紧要)

    为每个Log电话添加onPause。我怀疑答案是在

      

    B longClick:数据

         

    setContext:data

         

    setContext2:data

         

    itemSelected:null

    不知何故,最后一个onResume是针对第一个不同的对象完成的3.同样,问题每次都不可重现吗?如果是这样,它可能与设备轮换有关,或者在事件处理过程中触发活动重新创建。因此,您可以将日志添加到{{1}}和{{1}},然后查看是否属实。