当我点击日期选择器时(在我的个人应用中),它会导致设备崩溃。

42

编辑:我检查了一下,发现这个问题只出现在手机设置为“法语”语言时。

我正在构建自己的应用程序,在我的手机上(调试模式手机:三星S5)使用DatePicker小部件时遇到了问题。

收集到的错误是:java.util.IllegalFormatConversionException

我在logcat中添加了更多关于该问题的详细信息:

02-20 10:37:18.255  13029-13029/avappmobile.mytasks E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: avappmobile.mytasks, PID: 13029
    java.util.IllegalFormatConversionException: %d can't format java.lang.String arguments
            at java.util.Formatter.badArgumentType(Formatter.java:1489)
            at java.util.Formatter.transformFromInteger(Formatter.java:1689)
            at java.util.Formatter.transform(Formatter.java:1461)
            at java.util.Formatter.doFormat(Formatter.java:1081)
            at java.util.Formatter.format(Formatter.java:1042)
            at java.util.Formatter.format(Formatter.java:1011)
            at java.lang.String.format(String.java:1803)
            at android.content.res.Resources.getString(Resources.java:1453)
            at android.content.Context.getString(Context.java:397)
            at android.widget.SimpleMonthView$MonthViewTouchHelper.getItemDescription(SimpleMonthView.java:684)
            at android.widget.SimpleMonthView$MonthViewTouchHelper.onPopulateNodeForVirtualView(SimpleMonthView.java:628)
            at com.android.internal.widget.ExploreByTouchHelper.createNodeForChild(ExploreByTouchHelper.java:377)
            at com.android.internal.widget.ExploreByTouchHelper.createNode(ExploreByTouchHelper.java:316)
            at com.android.internal.widget.ExploreByTouchHelper.access$100(ExploreByTouchHelper.java:50)
            at com.android.internal.widget.ExploreByTouchHelper$ExploreByTouchNodeProvider.createAccessibilityNodeInfo(ExploreByTouchHelper.java:711)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfVirtualNode(AccessibilityInteractionController.java:1179)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1091)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchDescendantsOfRealNode(AccessibilityInteractionController.java:1087)
            at android.view.AccessibilityInteractionController$AccessibilityNodePrefetcher.prefetchAccessibilityNodeInfos(AccessibilityInteractionController.java:888)
            at android.view.AccessibilityInteractionController.findAccessibilityNodeInfoByAccessibilityIdUiThread(AccessibilityInteractionController.java:155)
            at android.view.AccessibilityInteractionController.access$400(AccessibilityInteractionController.java:53)
            at android.view.AccessibilityInteractionController$PrivateHandler.handleMessage(AccessibilityInteractionController.java:1236)
            at android.os.Handler.dispatchMessage(Handler.java:102)
            at android.os.Looper.loop(Looper.java:145)
            at android.app.ActivityThread.main(ActivityThread.java:5834)
            at java.lang.reflect.Method.invoke(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:372)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1388)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1183)

这是我的DialogFragment代码:

public class DatePFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener {

    DatePicker dp;
    OnDatePickedListener mCallback;
    Integer mLayoutId = null;

    // Interface definition
    public interface OnDatePickedListener {
        public void onDatePicked(int textId, int year, int month, int day);
    }

    // make sure the Activity implement the OnDatePickedListener
    public void onAttach(Activity activity) {
        super.onAttach(activity);

        try {
            mCallback = (OnDatePickedListener)activity;
        }
        catch (final ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnDatePickedListener");
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState){
        mCallback = (OnDatePickedListener)getActivity();

        Calendar cal = Calendar.getInstance();
        Bundle bundle = this.getArguments();
        mLayoutId = bundle.getInt("layoutId");
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH);
        int day = cal.get(Calendar.DAY_OF_MONTH);

        View view = getActivity().getLayoutInflater().inflate(R.layout.datepfragment, null);

        dp = (DatePicker) view.findViewById(R.id.datePicker);

        // Create a new instance of DatePickerDialog and return it
        return new DatePickerDialog(getActivity(),this,year, month, day);
    }

    @Override
    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
        if (mCallback != null) {
            // Use the date chosen by the user.

            mCallback.onDatePicked(mLayoutId, year, monthOfYear+1, dayOfMonth);
        }
    }
}

这里是相关的XML布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/txtDPTitle"
        android:id="@+id/txtDPTitle"
        android:layout_gravity="center_horizontal"
        android:gravity="center"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true"
        android:layout_marginTop="35dp" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/btnDPValidate"
        android:id="@+id/btnDPValidate"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="35dp" />

    <DatePicker
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/datePicker"
        android:layout_gravity="center_horizontal"
        android:layout_marginLeft="0dp"
        android:datePickerMode="spinner"
        android:calendarViewShown="false"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

事实上,我有两种问题(导致应用程序崩溃):
  1. 当我进入我的应用程序并直接进入“DatePicker”小部件时,日历正确显示,但一旦我选择了一个日期(我说一个日期,因为更改年份等没有问题),应用程序就会崩溃。
  2. 如果我在其他功能上单击以显示“DatePicker”,它将立即崩溃。
我看到了其他线程,例如:DatePicker crash in samsung with android 5.0
在此线程中,我没有获得足够的信息,因为给定的解决方案只允许显示日历的微调视图,而且所显示的微调器不像我的居中和呈现(直接放在屏幕顶部)。
如果您需要更多细节,请问我。
谢谢。 Alex.

你到底期望什么?三星破解了 Material 日期选择器。解决方法是不要使用 Material 日期选择器。 - alanv
好的,这意味着我的问题没有任何解决方案。我只想让我的日历正确显示。 - Alexandre
就记录而言,除了法语之外,这个三星的错误在许多其他语言中也发生,甚至在英语中的"en-PH"版本也有同样的问题。 - sorianiv
7个回答

93

这是三星在其Lollipop用户界面实现中的一个漏洞。它影响Galaxy S4、S5、Note 3等多种设备。对我们而言,它出现在德国(de_DE)和奥地利(de_AT)语言上,但似乎是影响多种语言的问题。

我通过强制三星设备使用Holo主题来修复它的日期选择器问题。在我们的DatePickerFragment中:

@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    /* calendar code here */

    Context context = getActivity();
    if (isBrokenSamsungDevice()) {
        context = new ContextThemeWrapper(getActivity(), android.R.style.Theme_Holo_Light_Dialog);
    }
    return new DatePickerDialog(context, this, year, month, day);
}

private static boolean isBrokenSamsungDevice() {
    return (Build.MANUFACTURER.equalsIgnoreCase("samsung")
            && isBetweenAndroidVersions(
            Build.VERSION_CODES.LOLLIPOP,
            Build.VERSION_CODES.LOLLIPOP_MR1));
}

private static boolean isBetweenAndroidVersions(int min, int max) {
    return Build.VERSION.SDK_INT >= min && Build.VERSION.SDK_INT <= max;
}

多亏了三星,他们的用户将不会得到漂亮的材料设计日期选择器。


请注意,当用户启用Google TalkBack时会出现此问题。 - prcaen
4
@mreichelt的解决方案非常好,只针对出问题的设备隔离了对话框的问题,而且没有使用第三方库,完全符合我的需求。这应该被采纳为最佳答案。 - androidseb
3
有没有办法在模拟器中重现这个错误?三星开放他们的修改吗? - Felipe Jun
1
@FelipeJun,您可以通过在安装有Android 5.x的S4或A5上启用TalkBack并设置为法语区域来可靠地重现此问题。 - Nachi
2
根据以下答案创建了SupportDatePickerDialog:https://gist.github.com/tobiasschuerg/5dd116e1e0bb874038d84fd98cc333c3 - Tobias
显示剩余3条评论

19

我有一个可能的修复补丁,可以让用户继续使用Lollipop库。

我只能在启用TalkBack的Android 5.0.1法语版S4上复现崩溃,似乎三星将整个日期传递给用于可访问性的项目选择字符串,而不是日期编号。

在@mreichelt的解决方案基础上,我们可以覆盖getString(id,args)函数并实际修复此问题。

context = new ContextWrapper(getActivity()) {

    private Resources wrappedResources;

    @Override
    public Resources getResources() {
        Resources r = super.getResources();
        if(wrappedResources == null) {
            wrappedResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration()) {
                @NonNull
                @Override
                public String getString(int id, Object... formatArgs) throws NotFoundException {
                    try {
                        return super.getString(id, formatArgs);
                    } catch (IllegalFormatConversionException ifce) {
                        Log.e("DatePickerDialogFix", "IllegalFormatConversionException Fixed!", ifce);
                        String template = super.getString(id);
                        template = template.replaceAll("%" + ifce.getConversion(), "%s");
                        return String.format(getConfiguration().locale, template, formatArgs);
                    }
                }
            };
        }
        return wrappedResources;
    }
};

所以如果我们将这个上下文提供给DatePickerDialog构造函数,它将解决这个问题。

更新:当网页包含日期输入字段时,这个问题也会出现在webview中。我发现解决这两个问题(对话框和webview)最好的方法是像这样覆盖Activity getResources函数:

@Override
public Resources getResources() {
    if (wrappedResources == null) {
        wrappedResources = wrapResourcesFixDateDialogCrash(super.getResources());
    }
    return wrappedResources;
}

public Resources wrapResourcesFixDateDialogCrash(Resources r) {
    return new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration()) {
        @NonNull
        @Override
        public String getString(int id, Object... formatArgs) throws NotFoundException {
            try {
                return super.getString(id, formatArgs);
            } catch (IllegalFormatConversionException ifce) {
                Log.i("DatePickerDialogFix", "IllegalFormatConversionException Fixed!", ifce);
                String template = super.getString(id);
                template = template.replaceAll("%" + ifce.getConversion(), "%s");
                return String.format(getConfiguration().locale, template, formatArgs);
            }
        }
    };
}

注意:该解决方案包括已缓存的资源 - 这意味着如果您的应用程序中有更改资源(如语言更改)的事件,那么您可能需要更新已缓存的资源。


1
完美地工作。修复了问题,并让我们保持标准日期选择器。对这个优雅的修复表示赞许。 - Moonbloom
1
你太棒了!我也遇到了Android 5.0.1启用TalkBack和德语的同样问题。谢谢! - Bona Fide
1
太好了!你救了我的一天。谢谢你。 - UFreedom
构造函数 Resources (AssetManager assets, DisplayMetrics metrics, Configuration config) 在 API 25 中已被弃用,请参见此处。不确定在 API 25 中是否仍存在此问题,但是否有其他方法? - user1480019
1
这个问题是针对特定的三星设备在Lollipop上报告的日历对话框,因此理论上您可以有条件地应用它(即不适用于API级别24以上)。请参见此答案此处 - Raanan
通过编写new ContextWrapper()而不是像@mreichelt一样使用new ContextThemeWrapper()来丢弃主题信息,可能会有任何影响吗? - Manish Kumar Sharma

2
我找到了一个解决方法,通过配置和Locale变量进行操作。如果检测到是法语("fr")语言,则将其修改为英语("en")语言,在退出DatePicker DialogFragment后再改回来。请保留HTML标签。
public void showDatePickerDialog(int layoutId) {
        Integer year = cal.get(Calendar.YEAR);
        Integer month = cal.get(Calendar.MONTH);
        Integer day = cal.get(Calendar.DAY_OF_MONTH);

        // Due to an issue with DatePicker and fr language (locale), if locale language is french, this is changed to us to make the DatePicker working
        if(Locale.getDefault().getLanguage().toString().equals(FR_LANG_CONTEXT)){
            Configuration config = new Configuration();
            config.locale = new Locale(US_LANG_CONTEXT);
            getApplicationContext().getResources().updateConfiguration(config, null);
        }

        Bundle bundle = createDatePickerBundle(layoutId, year, month, day);
        DialogFragment newFragment = new DatePFragment();
        newFragment.setArguments(bundle);
        newFragment.show(fm, Integer.toString(layoutId));
    }

一旦我离开

@Override
    public void onDatePicked(int LayoutId, int year, int month, int day){
        // Once out from the DatePicker, if we were working on a fr language, we update back the configuration with fr language.
        if(Locale.getDefault().getLanguage().toString().equals(FR_LANG_CONTEXT)){
            Configuration config = new Configuration();
            config.locale = new Locale(FR_LANG_CONTEXT);
            getApplicationContext().getResources().updateConfiguration(config, null);
        }

        String date = day + "/" + month + "/" + year;
        txtTaskDate.setText(date);
    }

5
收到来自波兰用户的报告,反映遇到了相同的问题。因此这个问题不仅限于法语本地化。 - Marten
我还要补充一点,我在Agenda应用程序(官方Android应用程序)中遇到了同样的问题。 - Alexandre
非常糟糕的解决方案。 - Simon
看看日期,现在问题已经不存在了。你可以正确使用日期选择器。 - Alexandre

1

0

我注意到@mreichelt的解决方案存在一个小问题。对话框没有显示当前选定日期的标题(包括星期几)。显式设置标题,然后立即再次设置日期可以解决这个问题。

Context context = getActivity();
if (isBrokenSamsungDevice()) {
    context = new ContextThemeWrapper(getActivity(), android.R.style.Theme_Holo_Light_Dialog);
}
DatePickerDialog datePickerDialog = new DatePickerDialog(context, this, year, month, day);
if (isBrokenSamsungDevice()) {
    datePickerDialog.setTitle("");
    datePickerDialog.updateDate(year, month, day);
}
return datePickerDialog;

0

当您以编程方式定义最小日期超出最大日期时,也会发生这种情况。

datePicker.getDatePicker().setMaxDate(15000000);

datePicker.getDatePicker().setMinDate(16000000);

0

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