【问题标题】:Android Widget destroyed on orientation changeAndroid Widget在方向改变时被破坏
【发布时间】:2014-04-21 13:31:53
【问题描述】:

应用程序的快速说明。这是一个简单的小部件,其中 4 个值通过 SharedPreferences 保存。唯一的活动视图是设置首选项的地方。完成后,将保存变量,并更新小部件。小部件每 90,000 毫秒自行更新一次。这一切都很好。我遇到的问题是方向改变时。发生这种情况时,小部件将被销毁,然后重新制作。我得到的是原始 XML,其中可变文本字段设置为默认值。在下一次手动或自动更新之前一直保持这种状态。

编辑: 这个问题现在有了很大的改变。我得到的两个回复指出了一些错误的地方。阅读所有 cmets 以查看所有内容。在最后一次尝试之后,即尝试在清单中添加标签后,我得到了更糟糕的结果。该程序根本无法运行。

我将使用提供该想法的同一答案中所说的最后一句话。我的 onUpdate() 一定有问题。这是整个类的代码:

public class WatchWidget extends AppWidgetProvider
{

    public void onCreate(Context context, Bundle savedInstanceState) 
    {

        RemoteViews remoteViews;
        remoteViews = new RemoteViews( context.getPackageName(), R.layout.main );
        remoteViews.setTextViewText( R.id.widget_elapsedtime, "Time");

    }

    @Override
    public void onUpdate( Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
    {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        RemoteViews remoteViews;
        ComponentName watchWidget;
        DateFormat format = SimpleDateFormat.getTimeInstance( SimpleDateFormat.MEDIUM, Locale.getDefault() );

        remoteViews = new RemoteViews( context.getPackageName(), R.layout.main );
        watchWidget = new ComponentName( context, WatchWidget.class );
        //remoteViews.setTextViewText( R.id.widget_textview, "" + format.format( new Date()));

        //widget_date
        Calendar c = Calendar.getInstance();
        SimpleDateFormat df = new SimpleDateFormat("MM/dd/yyyy");
        String formattedDate = df.format(c.getTime());
        //remoteViews.setTextViewText( R.id.widget_date, formattedDate); 

        SharedPreferences prefs = context.getSharedPreferences("qsPrefs", context.MODE_PRIVATE);
        String sdayTextEdit = prefs.getString("dayTextEdit", "23");
        //String sdayTextEdit ="23";
        String smonthTextEdit = prefs.getString("monthTextEdit", "3");
        String syearTextEdit = prefs.getString("yearTextEdit", "2014");
        String scostTextEdit = prefs.getString("costTextEdit", "8.5");
        String spacksTextEdit = prefs.getString("packsTextEdit", ".95");


        //Starting day
        Calendar pastdate = Calendar.getInstance();
        //int inputDay = 25;
        int inputDay = Integer.valueOf(sdayTextEdit);
        int inputMonth = 2;  //starts at 0
        inputMonth = Integer.valueOf(smonthTextEdit);
        int inputMonthAdj = inputMonth-1;
        int inputYear = 2014;
        inputYear = Integer.valueOf(syearTextEdit);
        pastdate.set(Calendar.DAY_OF_MONTH, inputDay);
        pastdate.set(Calendar.MONTH, inputMonthAdj);
        pastdate.set(Calendar.YEAR, inputYear);
        double packsPer = .95;
        packsPer = Double.parseDouble(spacksTextEdit.toString());
        double perPack = 8.57;
        perPack = Double.parseDouble(scostTextEdit.toString());

        inputMonthAdj = inputMonth;
        remoteViews.setTextViewText( R.id.widget_date, "From:  " + inputMonthAdj +"/"+ inputDay +"/"+ inputYear );

        SfinalDate = "From:  " + inputMonthAdj +"/"+ inputDay +"/"+ inputYear;


        Calendar today = Calendar.getInstance();
        DecimalFormat dform = new DecimalFormat("0.00");

        long dateDiff = today.getTimeInMillis() - pastdate.getTimeInMillis();

        long resultDays = dateDiff / (24*60*60*1000);
        remoteViews.setTextViewText( R.id.widget_elapsedtime, ""+ resultDays);

        double savedmoney = packsPer * perPack * resultDays;
        String finalMoney = dform.format(savedmoney);
        remoteViews.setTextViewText( R.id.widget_savedmoney, "$"+ finalMoney);

        double notSmoked = resultDays * 20 * packsPer;
        int finalNotSmoked = (int) Math.round(notSmoked);
        remoteViews.setTextViewText( R.id.widget_cigsnot, ""+ finalNotSmoked);

        SfinalMoney = "$"+ finalMoney;
        SfinalNS = ""+ finalNotSmoked;
        SfinalDays = ""+ resultDays;

        appWidgetManager.updateAppWidget( watchWidget, remoteViews );

        //Show Prefs screen
        Intent intent = new Intent(context, preferences.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
        RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.main);
        views.setOnClickPendingIntent(R.id.LinearLayout01, pendingIntent);
        appWidgetManager.updateAppWidget( watchWidget, views );
    }

}

已经发布... 可能有一些额外的行试图设置不再相关的东西。但是那里的代码可以工作。 onUpdate() 以自动间隔(90000 毫秒)正常触发。启动以设置首选项的活动也会在关闭时进行调用,这也会很好地更新它。这就是让我相信整个虚空一定没问题的原因。但一定有什么不对劲。因为当屏幕方向改变时,onUpdate() 不会运行。而且,再次,根据第二个答案,无论如何都不应该这样做,因为系统应该自己保留 UI。但事实并非如此。

【问题讨论】:

  • 您的小部件提供商的onUpdate() 应该被调用。在onUpdate() 中,您应该根据需要设置视图状态。
  • 我试过了。但我不知道如何让它着火。也就是说,我尝试将其设置为 super.onUpdate,因此我可以随时调用它。但是我遇到了与所有其他超级玩家描述的相同的错误。此外,似乎没有一个我可以知道的地方会被触发来调用它。
  • 你是说改变方向时不会触发onUpdate()?您可以尝试覆盖 onAppWidgetOptionsChanged() 并从那里以及从 onUpdate() 更新小部件状态
  • onUpdate() 不会在方向更改时调用。它仅根据 AppWidgetProvider 中定义的 XML 标签调用。我认为 onCreate() 会触发,但我也没有看到这种情况发生。我没有将 onAppWidgetOptionsChanged() 用于任何事情。我保存的首选项位于自定义活动中。

标签: android android-widget super


【解决方案1】:

您尝试执行的操作适用于 Activity,但不适用于 BroadcastReceiver。 AppWidgetProvider 是一个 BroadcastReceiver,没有您尝试覆盖的方法(onSaveInstanceState、onRestoreInstanceState)。如果您将@Override 注解添加到它告诉您的方法中

YourWidgetProvider 类型的方法 xyz() 必须重写或实现 超类型方法

这是因为 AppWidgetProvider 类没有 onSaveInstanceState 或 onRestoreInstanceState 方法,您无法覆盖不存在的方法。只需删除 @Override 注释即可消除编译错误,但您只需添加一个永远不会调用的方法(当然,除非您自己做,但这是没有意义的)。

解决这个问题的方法是监听Application中的配置变化,并向widget广播一个ACTION_APPWIDGET_UPDATE。

向您的应用添加应用程序类

public class MyApplication extends Application {

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        // create intent to update all instances of the widget
        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, this, MyWidgetProvider.class);

        // retrieve all appWidgetIds for the widget & put it into the Intent
        AppWidgetManager appWidgetMgr = AppWidgetManager.getInstance(this);
        ComponentName cm = new ComponentName(this, MyWidgetProvider.class);
        int[] appWidgetIds = appWidgetMgr.getAppWidgetIds(cm);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds);

        // update the widget
        sendBroadcast(intent);
    }
}

每当发生配置更改时都会调用该方法,其中之一是方向更改。您可以通过仅在方向更改而不是例如方向更改时进行广播来改进该方法系统语言的改变。 该方法基本上向您的小部件的所有实例发送更新广播(将 MyWidgetProvider 替换为您用于小部件提供程序的任何名称)。

在清单中定义 MyApplication

<application
    android:name="yourpackagename.MyApplication"
    android:description="@string/app_name"
    android:label="@string/app_name"
    android:icon="@drawable/app_icon">

    <!-- here go your Activity definitions -->

</application>

重要只有在横向和纵向使用不同的布局时才需要这样做。否则没有必要,因为 Android 负责保存和恢复小部件的 ui 状态。如果你得到一个带有默认值的小部件,那么你在 onUpdate 方法中做错了。 现在我查看了您的 onUpdate 方法,发现有一个主要问题(还有其他问题,但我只指出肯定会破坏您的小部件的问题)。

在使用初始化的 TextViews 调用 updateAppWidget 之后,您创建新的 RemoteViews:

appWidgetManager.updateAppWidget( watchWidget, remoteViews );

//Show Prefs screen
Intent intent = new Intent(context, preferences.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);

// this line gets rid of all the work you have done above
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.main);

views.setOnClickPendingIntent(R.id.LinearLayout01, pendingIntent);
appWidgetManager.updateAppWidget( watchWidget, views );

因此,在创建视图并设置值之后,您将其全部扔掉并创建新的 RemoteViews,这些 RemoteViews 显然具有空/未初始化的 TextView。也不需要调用 appWidgetManager.updateAppWidget 两次,每次 onUpdate 调用一次就足够了。 所以你要做的是:

remoteViews.setTextViewText( R.id.widget_cigsnot, ""+ finalNotSmoked);

SfinalMoney = "$"+ finalMoney;
SfinalNS = ""+ finalNotSmoked;
SfinalDays = ""+ resultDays;

//Show Prefs screen
Intent intent = new Intent(context, preferences.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
remoteViews.setOnClickPendingIntent(R.id.LinearLayout01, pendingIntent);
appWidgetManager.updateAppWidget( appWidgetIds, remoteViews );

请注意,我使用的是 updateAppWidget(int[] appWidgetIds, RemoteViews views) 而不是 updateAppWidget(ComponentName provider, RemoteViews views) 因为这允许您仅更新某些小部件实例并且您不需要保存的 ComponentName你再写一行代码。

【讨论】:

  • 您所听到的一切似乎都很有意义。今天晚些时候我会试一试。这种似乎是另一种建议,例如将其作为服务处理。我也没有运气。至于 onUpdate(),我看不出它有什么问题。如果您查看此线程中的其他答案,那正是我正在做的事情(所以代码就在那里)。它适用于自动和手动呼叫。为什么系统会在不运行更新的情况下将其销毁并默认重建,我不知道。
  • 我尝试按照建议实现这个精确度。编译的代码。但是该应用程序无法运行 - 日志猫中没有任何解释。它根本不运行。所以我更新了原始问题以包括运行 onUpdate() 的整个类。我现在假设您的最后一条评论,即 UI 应该被维护但不是,必须是真实的。因此,我包含该代码,希望您能指出我的缺陷。谢谢!
  • 我更新了我的答案来解释你的 onUpdate 失败的原因
  • 彩蛋!我会试一试。我会告诉你进展如何。
  • 谢谢。毕竟,这确实是我的代码中的一个错误。你赢了赏金!但现在我有一个新问题。偏好更改后,小部件不会更新。如果我无法弄清楚,我将创建一个新问题。
【解决方案2】:

AppWidgetProvideronCreate() 仅在创建第一个小部件时调用。 Widget 应该在onUpdate() 中更新,而不是从 Activity 中更新。

widget 的生命周期与 Activity 的生命周期无关。

@Override
public void onUpdate(final Context context,
        final AppWidgetManager appWidgetManager,
        final int[] appWidgetIds) {

    udpateViews(context, appWidgetManager, appWidgetIds);
}

@Override
public void onAppWidgetOptionsChanged(final Context context,
        final AppWidgetManager appWidgetManager, final int appWidgetId, final Bundle newOptions) {

    updateViews(context, appWidgetManager, appWidgetId);
}

private static void udpateViews(final Context context,
        final AppWidgetManager appWidgetManager,
        final int... appWidgetIds) {

    final String money = // read from shared prefs
    final String ns = // read from shared prefs
    final String days = // read from shared prefs
    final String date = // read from shared prefs

    final RemoteViews views = remoteViews = new RemoteViews(context.getPackageName(), R.layout.main);
    remoteViews.setTextViewText(R.id.widget_cigsnot, ns);
    remoteViews.setTextViewText(R.id.widget_savedmoney, money);
    remoteViews.setTextViewText(R.id.widget_elapsedtime, days);
    remoteViews.setTextViewText(R.id.widget_date, date);

    //always call updateAppWidget whenever RemoteViews had changed
    appWidgetManager.updateAppWidget(appWidgetIds, views);
}

【讨论】:

  • 好的。这看起来至少有几件事我还没有尝试过。而且由于这似乎避免使用“超级”,我可能会取得更好的成功。我今晚回家时会试一试。谢谢!
  • 我按照建议设置了这一切。而且,是的,将所有实际更新移动到它自己的单独空白的想法是一个好主意,并且效果很好 - 它仍然不能解决我的问题。当方向改变时,似乎“void onAppWidgetOptionsChanged”没有触发。我什至设置了一个小小的 Toast 警报,让我知道它是否触发了。它没有。还有其他地方需要声明吗?
  • 四处搜索我发现 onUpdate() 确实没有在主屏幕小部件上调用(我从未在可以改变方向的启动器上尝试过我的小部件)。尝试使用服务然后stackoverflow.com/questions/3503114/…
  • 接下来我会试试的,我猜。但是有谁知道为什么像“void onRestoreInstanceState()”这样的东西在声明 super 的情况下不起作用?我仍然认为这是一个更合理的选择,因为它正是它的用途。但我无法编译它。
  • onRestoreInstanceState 是一个 Activity 组件。它与小部件有什么关系?此外,Activity 的 onRestoreInstanceState 的超级实现什么也不做。如果在 onCreate() 中恢复不是首选,这只是开发人员处理恢复状态的回调。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多