Android时间设置的3个小彩蛋分享

目录
  • 问题现象
  • 源码分析
  • 实践验证
  • 结论

问题现象

最近处理了一个非常有意思的系统bug,修改系统时间,重启后居然没有生效

注意要关闭使用网络提供的时间和使用网络提供的时区这两个开关。

重启后显示的时间日期为

显示的时间既不是我设置的时间,也不是当前时间(当前时间为2023-03-20 15:49),那么显示的这个时间到底是什么时间呢?

为了弄清楚这个问题,我研究了一下Android设置时间的逻辑,研究过程中还发现了一些彩蛋。

源码分析

首先是设置时间的逻辑,源码位于packages/apps/Settings/src/com/android/settings/datetime/DatePreferenceController.java

public class DatePreferenceController extends AbstractPreferenceController
        implements PreferenceControllerMixin, DatePickerDialog.OnDateSetListener {
    //省略部分代码
    private final DatePreferenceHost mHost;

	@Override
    public boolean handlePreferenceTreeClick(Preference preference) {
        //点击日期后处理
        if (!TextUtils.equals(preference.getKey(), KEY_DATE)) {
            return false;
        }
        //显示日期选择框
        mHost.showDatePicker();
        return true;
    }
    //省略部分代码
}

mHostDatePreferenceHost接口,接口实现在packages/apps/Settings/src/com/android/settings/DateTimeSettings.java中,因此,showDatePicker()的逻辑位于该实现类中

@SearchIndexable
public class DateTimeSettings extends DashboardFragment implements
        TimePreferenceController.TimePreferenceHost, DatePreferenceController.DatePreferenceHost {
    //省略部分代码
	@Override
    public void showDatePicker() {
        //显示日期选择对话框
        showDialog(DatePreferenceController.DIALOG_DATEPICKER);
    }
    //省略部分代码
}

showDialog()定义在父类packages/apps/Settings/src/com/android/settings/SettingsPreferenceFragment.java

public abstract class SettingsPreferenceFragment extends InstrumentedPreferenceFragment
        implements DialogCreatable, HelpResourceProvider, Indexable {
	protected void showDialog(int dialogId) {
        if (mDialogFragment != null) {
            Log.e(TAG, "Old dialog fragment not null!");
        }
        //创建SettingsDialogFragment并进行show
        mDialogFragment = SettingsDialogFragment.newInstance(this, dialogId);
        mDialogFragment.show(getChildFragmentManager(), Integer.toString(dialogId));
    }
}

showDialog()中就是创建了SettingsDialogFragment然后显示,SettingsDialogFragmentSettingsPreferenceFragment的一个内部类,看一下SettingsDialogFragment的定义

    public static class SettingsDialogFragment extends InstrumentedDialogFragment {
        private static final String KEY_DIALOG_ID = "key_dialog_id";
        private static final String KEY_PARENT_FRAGMENT_ID = "key_parent_fragment_id";

        private Fragment mParentFragment;

        private DialogInterface.OnCancelListener mOnCancelListener;
        private DialogInterface.OnDismissListener mOnDismissListener;

        public static SettingsDialogFragment newInstance(DialogCreatable fragment, int dialogId) {
            if (!(fragment instanceof Fragment)) {
                throw new IllegalArgumentException("fragment argument must be an instance of "
                        + Fragment.class.getName());
            }

            final SettingsDialogFragment settingsDialogFragment = new SettingsDialogFragment();
            settingsDialogFragment.setParentFragment(fragment);
            settingsDialogFragment.setDialogId(dialogId);

            return settingsDialogFragment;
        }

        @Override
        public int getMetricsCategory() {
            if (mParentFragment == null) {
                return Instrumentable.METRICS_CATEGORY_UNKNOWN;
            }
            final int metricsCategory =
                    ((DialogCreatable) mParentFragment).getDialogMetricsCategory(mDialogId);
            if (metricsCategory <= 0) {
                throw new IllegalStateException("Dialog must provide a metrics category");
            }
            return metricsCategory;
        }

        @Override
        public void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            if (mParentFragment != null) {
                outState.putInt(KEY_DIALOG_ID, mDialogId);
                outState.putInt(KEY_PARENT_FRAGMENT_ID, mParentFragment.getId());
            }
        }

        @Override
        public void onStart() {
            super.onStart();

            if (mParentFragment != null && mParentFragment instanceof SettingsPreferenceFragment) {
                ((SettingsPreferenceFragment) mParentFragment).onDialogShowing();
            }
        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            if (savedInstanceState != null) {
                mDialogId = savedInstanceState.getInt(KEY_DIALOG_ID, 0);
                mParentFragment = getParentFragment();
                int mParentFragmentId = savedInstanceState.getInt(KEY_PARENT_FRAGMENT_ID, -1);
                if (mParentFragment == null) {
                    mParentFragment = getFragmentManager().findFragmentById(mParentFragmentId);
                }
                if (!(mParentFragment instanceof DialogCreatable)) {
                    throw new IllegalArgumentException(
                            (mParentFragment != null
                                    ? mParentFragment.getClass().getName()
                                    : mParentFragmentId)
                                    + " must implement "
                                    + DialogCreatable.class.getName());
                }
                // This dialog fragment could be created from non-SettingsPreferenceFragment
                if (mParentFragment instanceof SettingsPreferenceFragment) {
                    // restore mDialogFragment in mParentFragment
                    ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = this;
                }
            }
            //通过DialogCreatable接口剥离了dialog的创建
            return ((DialogCreatable) mParentFragment).onCreateDialog(mDialogId);
        }

        @Override
        public void onCancel(DialogInterface dialog) {
            super.onCancel(dialog);
            if (mOnCancelListener != null) {
                mOnCancelListener.onCancel(dialog);
            }
        }

        @Override
        public void onDismiss(DialogInterface dialog) {
            super.onDismiss(dialog);
            if (mOnDismissListener != null) {
                mOnDismissListener.onDismiss(dialog);
            }
        }

        public int getDialogId() {
            return mDialogId;
        }

        @Override
        public void onDetach() {
            super.onDetach();

            // This dialog fragment could be created from non-SettingsPreferenceFragment
            if (mParentFragment instanceof SettingsPreferenceFragment) {
                // in case the dialog is not explicitly removed by removeDialog()
                if (((SettingsPreferenceFragment) mParentFragment).mDialogFragment == this) {
                    ((SettingsPreferenceFragment) mParentFragment).mDialogFragment = null;
                }
            }
        }

        private void setParentFragment(DialogCreatable fragment) {
            mParentFragment = (Fragment) fragment;
        }

        private void setDialogId(int dialogId) {
            mDialogId = dialogId;
        }
    }

很标准的自定义DialogFragment的模板代码,核心代码在onCreateDialog()方法当中,但此方法通过DialogCreatable接口剥离了dialog的创建,这里也很好理解,因为不仅有设置日期的Dialog,还有设置时间的Dialog,如果写死的话,那么就需要定义两个DialogFragment,所以这里它给抽象出来了,DialogCreatable接口的实现仍然在DateTimeSettings当中,它的父类SettingsPreferenceFragment实现了DialogCreatable

@SearchIndexable
public class DateTimeSettings extends DashboardFragment implements
        TimePreferenceController.TimePreferenceHost, DatePreferenceController.DatePreferenceHost {
    //省略部分代码
	@Override
    public Dialog onCreateDialog(int id) {
        //根据选项创建对应的dialog
        switch (id) {
            case DatePreferenceController.DIALOG_DATEPICKER:
                return use(DatePreferenceController.class)
                        .buildDatePicker(getActivity());
            case TimePreferenceController.DIALOG_TIMEPICKER:
                return use(TimePreferenceController.class)
                        .buildTimePicker(getActivity());
            default:
                throw new IllegalArgumentException();
        }
    }
    //省略部分代码
}

根据用户选择的操作(设置日期or设置时间),创建对应的dialog,最终的创建过程由DatePreferenceController来完成

public class DatePreferenceController extends AbstractPreferenceController
        implements PreferenceControllerMixin, DatePickerDialog.OnDateSetListener {
    //省略部分代码
	public DatePickerDialog buildDatePicker(Activity activity) {
        final Calendar calendar = Calendar.getInstance();
        //创建DatePickerDialog
        final DatePickerDialog d = new DatePickerDialog(
                activity,
                this,
                calendar.get(Calendar.YEAR),
                calendar.get(Calendar.MONTH),
                calendar.get(Calendar.DAY_OF_MONTH));
        // The system clock can't represent dates outside this range.
        calendar.clear();
        calendar.set(2007, Calendar.JANUARY, 1);
        //设置最小时间为2007-01-01
        d.getDatePicker().setMinDate(calendar.getTimeInMillis());
        calendar.clear();
        calendar.set(2037, Calendar.DECEMBER, 31);
        //设置最大时间为2037-12-31
        d.getDatePicker().setMaxDate(calendar.getTimeInMillis());
        return d;
    }
    //省略部分代码
}

这里可以看到,系统限制了可选的日期范围为2007-01-01至2037-12-31,实际操作也确实是这样子的(开发板和小米手机都是),此为彩蛋1。

看一下DatePickerDialog的定义

public class DatePickerDialog extends AlertDialog implements OnClickListener,
        OnDateChangedListener {
    private static final String YEAR = "year";
    private static final String MONTH = "month";
    private static final String DAY = "day";

    @UnsupportedAppUsage
    private final DatePicker mDatePicker;

    private OnDateSetListener mDateSetListener;

    //省略部分代码

    private DatePickerDialog(@NonNull Context context, @StyleRes int themeResId,
            @Nullable OnDateSetListener listener, @Nullable Calendar calendar, int year,
            int monthOfYear, int dayOfMonth) {
        super(context, resolveDialogTheme(context, themeResId));

        final Context themeContext = getContext();
        final LayoutInflater inflater = LayoutInflater.from(themeContext);
        //初始化Dialog的View
        final View view = inflater.inflate(R.layout.date_picker_dialog, null);
        setView(view);

        setButton(BUTTON_POSITIVE, themeContext.getString(R.string.ok), this);
        setButton(BUTTON_NEGATIVE, themeContext.getString(R.string.cancel), this);
        setButtonPanelLayoutHint(LAYOUT_HINT_SIDE);

        if (calendar != null) {
            year = calendar.get(Calendar.YEAR);
            monthOfYear = calendar.get(Calendar.MONTH);
            dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
        }

        mDatePicker = (DatePicker) view.findViewById(R.id.datePicker);
        mDatePicker.init(year, monthOfYear, dayOfMonth, this);
        mDatePicker.setValidationCallback(mValidationCallback);

        mDateSetListener = listener;
    }

    //省略部分代码

    /**
     * Sets the listener to call when the user sets the date.
     *
     * @param listener the listener to call when the user sets the date
     */
    public void setOnDateSetListener(@Nullable OnDateSetListener listener) {
        mDateSetListener = listener;
    }

    @Override
    public void onClick(@NonNull DialogInterface dialog, int which) {
        switch (which) {
            case BUTTON_POSITIVE:
                if (mDateSetListener != null) {
                    // Clearing focus forces the dialog to commit any pending
                    // changes, e.g. typed text in a NumberPicker.
                    mDatePicker.clearFocus();
                    //设置完成回调
                    mDateSetListener.onDateSet(mDatePicker, mDatePicker.getYear(),
                            mDatePicker.getMonth(), mDatePicker.getDayOfMonth());
                }
                break;
            case BUTTON_NEGATIVE:
                cancel();
                break;
        }
    }

    //省略部分代码

    /**
     * The listener used to indicate the user has finished selecting a date.
     */
    public interface OnDateSetListener {
        /**
         * @param view the picker associated with the dialog
         * @param year the selected year
         * @param month the selected month (0-11 for compatibility with
         *              {@link Calendar#MONTH})
         * @param dayOfMonth the selected day of the month (1-31, depending on
         *                   month)
         */
        void onDateSet(DatePicker view, int year, int month, int dayOfMonth);
    }
}

可以看到也是标准的自定义Dialog,不过它是继承的AlertDialog,设置完成后通过OnDateSetListener进行回调,而DatePreferenceController实现了该接口

public class DatePreferenceController extends AbstractPreferenceController
        implements PreferenceControllerMixin, DatePickerDialog.OnDateSetListener {
	//省略部分代码
    @Override
    public void onDateSet(DatePicker view, int year, int month, int day) {
        //设置日期
        setDate(year, month, day);
        //更新UI
        mHost.updateTimeAndDateDisplay(mContext);
    }
    //省略部分代码

    @VisibleForTesting
    void setDate(int year, int month, int day) {
        Calendar c = Calendar.getInstance();

        c.set(Calendar.YEAR, year);
        c.set(Calendar.MONTH, month);
        c.set(Calendar.DAY_OF_MONTH, day);
        //设置日期与定义的最小日期取最大值,也就意味着设置的日期不能小于定义的最小日期
        long when = Math.max(c.getTimeInMillis(), DatePreferenceHost.MIN_DATE);

        if (when / 1000 < Integer.MAX_VALUE) {
            //设置系统时间
            ((AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE)).setTime(when);
        }
    }
}

可以看到系统定义了一个最小日期DatePreferenceHost.MIN_DATE,其值为2007-11-05 0:00

public interface UpdateTimeAndDateCallback {
    // Minimum time is Nov 5, 2007, 0:00.
    long MIN_DATE = 1194220800000L;

    void updateTimeAndDateDisplay(Context context);
}

最终显示日期会在目标日期和最小日期中取最大值,也就是说设定的日期不能小于最小日期,而上文说到,选择的日期范围为2007-01-01至2037-12-31,因此,如果你设置的日期在2007-01-01至2007-11-05之间,最终都会显示2007-11-05,实际测试也是如此(开发板和小米手机都是),此为彩蛋2。

选择完时间后,最后通过AlarmManagerService来设置系统内核的时间,此处涉及到跨进程通信,使用的通信方式是AIDL,直接到AlarmManagerService看看如何设置内核时间的

class AlarmManagerService extends SystemService {
    //省略部分代码
	/**
     * Public-facing binder interface
     */
    private final IBinder mService = new IAlarmManager.Stub() {
        //省略部分代码
		@Override
        public boolean setTime(long millis) {
            //先授权
            getContext().enforceCallingOrSelfPermission(
                    "android.permission.SET_TIME",
                    "setTime");
			//然后设置系统内核时间
            return setTimeImpl(millis);
        }
        //省略部分代码
    }

    //省略部分代码

    boolean setTimeImpl(long millis) {
        if (!mInjector.isAlarmDriverPresent()) {
            Slog.w(TAG, "Not setting time since no alarm driver is available.");
            return false;
        }

        synchronized (mLock) {
            final long currentTimeMillis = mInjector.getCurrentTimeMillis();
            //设置系统内核时间
            mInjector.setKernelTime(millis);
            final TimeZone timeZone = TimeZone.getDefault();
            final int currentTzOffset = timeZone.getOffset(currentTimeMillis);
            final int newTzOffset = timeZone.getOffset(millis);
            if (currentTzOffset != newTzOffset) {
                Slog.i(TAG, "Timezone offset has changed, updating kernel timezone");
                //设置系统内核时区
                mInjector.setKernelTimezone(-(newTzOffset / 60000));
            }
            // The native implementation of setKernelTime can return -1 even when the kernel
            // time was set correctly, so assume setting kernel time was successful and always
            // return true.
            return true;
        }
    }

    //省略部分代码

    @VisibleForTesting
    static class Injector {
    	//省略部分代码
        void setKernelTime(long millis) {
            Log.d("jasonwan", "setKernelTime: "+millis);
            if (mNativeData != 0) {
                //在native层完成内核时间的设置
                AlarmManagerService.setKernelTime(mNativeData, millis);
            }
        }
        //省略部分代码
    }

    //native层完成
    private static native int setKernelTime(long nativeData, long millis);
    private static native int setKernelTimezone(long nativeData, int minuteswest);
    //省略部分代码
}

可以看到最终是在native层完成内核时间的设置,这也理所当然,毕竟java是应用层,触及不到kernel层。

回到最开始的问题,为啥开机之后却不是我们设置的时间呢,这就要看看开机之后系统是怎么设置时间的。同样在AlarmManagerService里面,因为它是SystemService的子类,所以会随着开机启动而启动,而Service启动后必定会执行它的生命周期方法,设置时间的逻辑就是在onStart()生命周期方法里面

class AlarmManagerService extends SystemService {
    //省略部分代码

	@Override
    public void onStart() {
        mInjector.init();

        synchronized (mLock) {
            //省略部分代码

            // We have to set current TimeZone info to kernel
            // because kernel doesn't keep this after reboot
            //设置时区,从SystemProperty中读取
            setTimeZoneImpl(SystemProperties.get(TIMEZONE_PROPERTY));

            // Ensure that we're booting with a halfway sensible current time.  Use the
            // most recent of Build.TIME, the root file system's timestamp, and the
            // value of the ro.build.date.utc system property (which is in seconds).
            //设置时区
            //先读取系统编译时间
            long utc = 1000L * SystemProperties.getLong("ro.build.date.utc", -1L);
            //再读取根目录最近的修改的时间
            long lastModified = Environment.getRootDirectory().lastModified();
            //然后读取系统构建时间,三个时间取最大值
            final long systemBuildTime =  Long.max(
                    utc,
                    Long.max(lastModified, Build.TIME));
            //代码1
            Log.d("jasonwan", "onStart: utc="+utc+", lastModified="+lastModified+", BuildTime="+Build.TIME+", currentTimeMillis="+mInjector.getCurrentTimeMillis());
            //设置的时间小于最大值,则将最大值设置为系统内核的时间,注意,因为我们刚刚已经设置了内核时间,所以重启后通过System.currentTimeMillis()得到的时间戳为我们设置的时间,此判断意味着,系统编译时间、根目录最近修改时间、系统构建时间、设置的时间,这四者当中取最大值作为重启后的内核时间
            if (mInjector.getCurrentTimeMillis() < systemBuildTime) {
                //这里mInjector.getCurrentTimeMillis()其实就是System.currentTimeMillis()
                Slog.i(TAG, "Current time only " + mInjector.getCurrentTimeMillis()
                        + ", advancing to build time " + systemBuildTime);
                mInjector.setKernelTime(systemBuildTime);
            }
            //省略部分代码

    }
    //省略部分代码

    @VisibleForTesting
    static class Injector {
        //省略部分代码
        void setKernelTimezone(int minutesWest) {
            AlarmManagerService.setKernelTimezone(mNativeData, minutesWest);
        }

        void setKernelTime(long millis) {
            //代码2
            Log.d("jasonwan", "setKernelTime: "+millis);
            if (mNativeData != 0) {
                AlarmManagerService.setKernelTime(mNativeData, millis);
            }
        }

        //省略部分代码
        long getElapsedRealtime() {
            return SystemClock.elapsedRealtime();
        }

        long getCurrentTimeMillis() {
            return System.currentTimeMillis();
        }
        //省略部分代码
    }
}

实践验证

根据源码分析得知,系统最终会在系统编译时间、根目录最近修改时间、系统构建时间、设置的时间,这四者当中取最大值作为重启后的内核时间,这里我在代码1和代码2处埋下了log,看看四个时间的值分别是多少,以及最终设置的内核时间是多少,我在设置中手动设置的日期为2022-10-01,重启后的日志如下

四个值分别为:

  • 系统编译时间:1669271830000,格式化后为2022-11-24 14:37:10
  • 根目录最近修改时间:1678865533000,格式化后为2023-03-15 15:32:13
  • 构建时间:1669271830000,同系统编译时间
  • 设置的时间:1664609754998,格式化后为2022-10-01 15:35:54

注意,我们只需要注意日期,不需要关注时分秒,可以看到四个时间当中,最大的为根目录最近修改时间,所以最终显示的日期为2023-03-15,此为彩蛋3。

我在开发板和小米手机上测试的结果相同,说明MIUI保留了这一块的逻辑,但是MIUI也有一个bug,就是明明我关闭了使用网络提供的时间和使用网络提供的时区,它还是给我自动更新了日期和时间,除非开启飞行模式之后才不自动更新。

同时我们还注意到,系统编译时间ro.build.date.utc跟系统构建时间Build.TIME是相同的,这很好理解,编译跟构建是一个意思,而且Build.TIME的取值其实也来自于ro.build.date.utc

/**
 * Information about the current build, extracted from system properties.
 */
public class Build {
    //省略部分代码
	/** The time at which the build was produced, given in milliseconds since the UNIX epoch. */
    public static final long TIME = getLong("ro.build.date.utc") * 1000;
    //省略部分代码
}

我也搞不懂Google为什么要设计两个概念,搞得我一开始还去研究这两个概念的区别,结果没区别,数据源是一样的,尴尬。

结论

设置系统时间必须大于系统编译时间和根目录最近修改时间才会生效。

最后我在想,MIUI是不是可以在这一块优化一下,直接设置里面告诉用户我能设置的时间区域岂不是更人性化,毕竟细节决定成败。

到此这篇关于Android时间设置的3个小彩蛋的文章就介绍到这了,更多相关Android时间设置彩蛋内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android闹钟启动时间设置无效问题的解决方法

    Android开发中,alarmManager在5.0以上系统,启动时间设置无效的问题 做一个app,需要后台保持发送心跳包.由于锁屏后CPU休眠,导致心跳包线程被挂起,所以尝试使用alarmManager定时唤醒Service发送心跳包. 以下是开启alarmManager的代码 //开启轮询服务 public static void startPollingService(Context context, int seconds, Class<?> cls,String action) {

  • Android中日期与时间设置控件用法实例

    本文实例讲述了Android中日期与时间设置控件用法.分享给大家供大家参考.具体如下: 1.日期设置控件:DatePickerDialog 2.时间设置控件:TimePickerDialog 实例代码: 页面添加两个Button,单击分别显示日期设置控件和时间设置控件,还是有TextView控件,用于显示设置后的系统时间 main.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout x

  • Android时间设置的3个小彩蛋分享

    目录 问题现象 源码分析 实践验证 结论 问题现象 最近处理了一个非常有意思的系统bug,修改系统时间,重启后居然没有生效 注意要关闭使用网络提供的时间和使用网络提供的时区这两个开关. 重启后显示的时间日期为 显示的时间既不是我设置的时间,也不是当前时间(当前时间为2023-03-20 15:49),那么显示的这个时间到底是什么时间呢? 为了弄清楚这个问题,我研究了一下Android设置时间的逻辑,研究过程中还发现了一些彩蛋. 源码分析 首先是设置时间的逻辑,源码位于packages/apps/

  • Android字体设置及Roboto字体使用方法

    本文实例讲述了Android字体设置及Roboto字体使用方法.分享给大家供大家参考.具体分析如下: 一.自定义字体 1.android Typeface使用TTF字体文件设置字体 我们可以在程序中放入ttf字体文件,在程序中使用Typeface设置字体. 第一步,在assets目录下新建fonts目录,把ttf字体文件放到这. 第二步,程序中调用: 复制代码 代码如下: AssetManager mgr=getAssets();//得到AssetManager Typeface tf=Type

  • setTimeout时间设置为0详细解析

    前言 本文主要给大家介绍了关于setTimeout时间设置为0的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 1.开胃菜,setTimeout为何物 首先看一下w3school上面对于setTimeout的解释 setTimeout(fn,millisec) 方法用于在指定的毫秒数后调用函数或计算表达式. 很简单,setTimeout() 只执行 fn 一次,到底什么时候执行取决于第二个参数millisec设定的毫秒数,所以很多人习惯上称之为延迟,无非就是延迟一段时

  • php4的彩蛋

    今天看了一些代码,看到了一个小彩蛋. <? /* env.php */ phpinfo(); ?> 用浏览器访问 http://ipaddress/dirname/env.php?=PHPE9568F36-D428-11d2-A769-00AA001ACF42 你将看到!    一个胖子嘴中刁着两支烟!:-) 秘密在logos.h中用数组定义的图像数据. unsigned char php_egg_logo[] = {      71, 73, 70, 56, 57, 97, 130,  0,

  • 学习Java中的日期和时间处理及Java日历小程序的编写

    Java 在 java.util 包中提供了 Date 类,这个类封装了当前的日期和时间. Date 类支持两种构造函数.第一个构造函数初始化对象的当前日期和时间. Date( ) 下面的构造函数接收一个参数等于自1970年1月1日午夜起已经过的毫秒数 Date(long millisec) 一旦有一个可用的日期对象,可以调用以下任何一种支持的方法使用时间: SN 方法和描述 1 boolean after(Date date) 如果调用Date对象包含或晚于指定的日期则返回true,否则,返回

  • Linux下date命令,格式化输出,时间设置方法

    date命令的帮助信息 [root@localhost source]# date --help 用法:date [选项]... [+格式] 或:date [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]] 以给定的格式显示当前时间,或是设置系统日期. -d,--date=字符串 显示指定字符串所描述的时间,而非当前时间 -f,--file=日期文件 类似--date,从日期文件中按行读入时间描述 -r, --reference=文件 显示文件指定文件的

  • android动态设置app当前运行语言的方法

    android开发中有时候碰到切换语言的需求,这时候需要通过代码动态改变当前运行语言. package com.example.androidtest; import java.util.Locale; import android.os.Bundle; import android.app.Activity; import android.content.Intent; import android.content.res.Configuration; import android.conte

  • Android RadioGroup 设置某一个选中或者不可选中的方法

    如题目的要求,可以参考如下代码: public void generAeidLength(RadioGroup radGroup) { if (ClientAPI.getAeid().length() == 10) { System.out.println(ClientAPI.getAeid()); type_kyc.getChildAt(1).setEnabled(false); } else { System.out.println(ClientAPI.getAeid()); type_ky

  • Android TextView 设置字体大小的方法

    废话不多说了,直接给大家贴代码了,具体代码如下所示: package com.example.yanlei.yl4; import android.graphics.Color; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.text.Spannable; import android.text.style.AbsoluteSizeSpan; import and

随机推荐