单击时,DatePicker在我的设备上崩溃(使用个人应用程序)

时间:2015-02-19 22:38:25

标签: android datepicker crash device

编辑:我检查过该问题仅在手机设置为“法语”时出现。

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

错误收集是: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,它会直接崩溃。
  3. 我看到了其他主题:DatePicker crash in samsung with android 5.0

    在这个帖子中我没有得到足够的信息,因为给定的解决方案只允许显示日历的微调视图,而且显示的微调器不居中并呈现为我的(直接放在屏幕顶部)

    如果您需要更多详细信息,请询问我。

    感谢。 亚历克斯。

7 个答案:

答案 0 :(得分:90)

这是Lollipop UI实施中的三星漏洞。它会影响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;
}

感谢三星,他们的用户将无法获得精心设计的日期选择器。

答案 1 :(得分:18)

我有一个可能的FIX PATCH,它实际上会让用户继续使用Lollipop库。

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

在@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);
            }
        }
    };
}

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

答案 2 :(得分:2)

我找到了一种解决方法,使用配置和区域设置变量。

如果我检测到它是法语(&#34; fr&#34;)语言并用英语(&#34; en&#34;)语言替换它,那么我会修改语言,然后再回到从DatePicker DialogFragment开始正常。

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);
    }

答案 3 :(得分:1)

忘掉内置的日期选择器。恕我直言,切换语言环境不是一个解决方案,因为我希望在当前语言环境中拥有选择器。只有一种方法可以摆脱崩溃:使用提供独立实现的库。

对于日期选择器片段:https://github.com/flavienlaurent/datetimepicker

对于日期选择器窗口小部件:https://github.com/SingleCycleKing/CustomTimePicker(这是一个起点而不是即用型解决方案)

答案 4 :(得分: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;

答案 5 :(得分:0)

基于mreichelts answer我创建了一个SupportDatePickerDialog.java,它包含aroudn DatePickerDialog并自动修复 android 5.0和5.1的上下文。

请参阅gist

答案 6 :(得分:0)

当您以min date

进一步以编程方式定义max date时,也会发生这种情况
datePicker.getDatePicker().setMaxDate(15000000);

datePicker.getDatePicker().setMinDate(16000000);