赞
踩
通过代码实现时间、时区的相关设置。在公司的一个android设备中,经常会出现时间不准,比如重启后时间变成1970年,只要设备连上网,会自动同步时间为正确的时间,但是这个同步有时候也没能同步成功,所以需要我们可以自行设置系统时间,或者同步我们自己服务器的时间,因为有些登录操作要求设备的时间和服务器的时间相差不能超过5分钟,一旦超过5分钟则不给登录。
界面如下:
布局代码:
<?xml version="1.0" encoding="utf-8"?> <androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:orientation="vertical" android:gravity="center"> <androidx.appcompat.widget.LinearLayoutCompat android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:id="@+id/timeText" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" tools:text="2023-08-07 14:44:30"/> <CheckBox android:id="@+id/autoDateAndTimeCheckBox" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="自动确定日期和时间" /> <CheckBox android:id="@+id/autoTimeZoneTimeCheckBox" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="自动确定时区" /> <CheckBox android:id="@+id/use24HourCheckBox" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="使用24小时格式" /> <Button android:id="@+id/setDateButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="设置日期"/> <Button android:id="@+id/setTimeButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="设置时间"/> <Button android:id="@+id/setTimeZoneButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="设置时区"/> <Button android:id="@+id/setUseNetTimeButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="网络时间"/> </androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>
代码实现如下:
清单文件如下:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:sharedUserId="android.uid.system" android:sharedUserMaxSdkVersion="32" tools:targetApi="tiramisu"> <uses-permission android:name="android.permission.SET_TIME" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.SET_TIME_ZONE" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.INTERNET"/> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.SetTimeDemo" tools:targetApi="31" android:name=".App"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
这里主要的设置是android:sharedUserId="android.uid.system"
和设置权限,另外打包时需要使用系统签名文件进行打包,这样才有权限修改时间。
代码实现如下:
import android.app.AlarmManager import android.content.Context import android.os.SystemClock import android.provider.Settings import android.text.format.DateFormat import androidx.annotation.RequiresPermission import java.util.* import kotlin.concurrent.thread class TimeUtil { companion object { /** 判断系统的时间是否自动获取的 */ fun isDateTimeAuto(context: Context): Boolean { return Settings.Global.getInt(context.contentResolver, Settings.Global.AUTO_TIME, 1) == 1 } /** 判断系统的时区是否是自动获取的 */ fun isTimeZoneAuto(context: Context): Boolean { return Settings.Global.getInt(context.contentResolver, Settings.Global.AUTO_TIME_ZONE, 1) == 1 } /** 系统时间是否是使用24小时制 */ fun is24HourFormat(context: Context): Boolean { return DateFormat.is24HourFormat(context) // 下面的方式有问题,比如第一次获取时,如果没有设置过,则会使用默认值24,但其实可能当前是使用的12小时制的,好像一般的手机默认都是使用12小时制的 // return Settings.System.getInt(context.contentResolver, Settings.System.TIME_12_24, 24) == 24 } /** 设置系统的时间是否需要自动获取 */ fun setDateTimeAuto(context: Context, autoEnabled: Boolean) { Settings.Global.putInt(context.contentResolver, Settings.Global.AUTO_TIME, if (autoEnabled) 1 else 0) } /** 设置系统的时区是否自动获取 */ fun setTimeZoneAuto(context: Context, autoEnabled: Boolean) { Settings.Global.putInt(context.contentResolver, Settings.Global.AUTO_TIME_ZONE, if (autoEnabled) 1 else 0) } /** 设置时间是否使用24小时制 */ fun set24HourFormat(context: Context, is24HourFormat: Boolean) { Settings.System.putInt(context.contentResolver, Settings.System.TIME_12_24, if (is24HourFormat) 24 else 12) } /** 设置系统日期,需要有系统签名才可以 */ @RequiresPermission(android.Manifest.permission.SET_TIME) fun setDate(context: Context, year: Int, month: Int, day: Int) { thread { val calendar = Calendar.getInstance() calendar[Calendar.YEAR] = year calendar[Calendar.MONTH] = month // 注意:月份从0开始的 calendar[Calendar.DAY_OF_MONTH] = day val timeInMillis = calendar.timeInMillis if (timeInMillis / 1000 < Int.MAX_VALUE) { (context.getSystemService(Context.ALARM_SERVICE) as AlarmManager).setTime(timeInMillis) } } } /** 设置系统时间,需要有系统签名才可以 */ @RequiresPermission(android.Manifest.permission.SET_TIME) fun setTime(context: Context, hour: Int, minute: Int) { thread { val calendar = Calendar.getInstance() calendar[Calendar.HOUR_OF_DAY] = hour calendar[Calendar.MINUTE] = minute calendar[Calendar.SECOND] = 0 calendar[Calendar.MILLISECOND] = 0 val timeInMillis = calendar.timeInMillis if (timeInMillis / 1000 < Int.MAX_VALUE) { (context.getSystemService(Context.ALARM_SERVICE) as AlarmManager).setTime(timeInMillis) } } } /** * 设置系统时区 * 获取以及设置时区用到的都是TimezoneID,它们以字符串的形式存在。 * 可以用诸如"GMT+05:00", "GMT+0500", "GMT+5:00","GMT+500","GMT+05", and"GMT+5","GMT-05:00"的ID * Android系统用的ID一般为: * <timezone id="Asia/Shanghai">中国标准时间 (北京)</timezone> * <timezone id="Asia/Hong_Kong">香港时间 (香港)</timezone> * <timezone id="Asia/Taipei">台北时间 (台北)</timezone> * <timezone id="Asia/Seoul">首尔</timezone> * <timezone id="Asia/Tokyo">日本时间 (东京)</timezone> * */ @RequiresPermission(android.Manifest.permission.SET_TIME_ZONE) fun setTimeZone(context: Context, timeZoneId: String) { thread { (context.getSystemService(Context.ALARM_SERVICE) as AlarmManager).setTimeZone(timeZoneId) } //DO not need send Intent.ACTION_TIMEZONE_CHANGED //Because system will send itself, and we do not have permission } /** 设置时区为上海 */ @RequiresPermission(android.Manifest.permission.SET_TIME_ZONE) fun setAsChinaTimeZone(context: Context) { val chinaTimeZoneId = "Asia/Shanghai" if (getTimeZoneId() != chinaTimeZoneId) { setTimeZone(context, chinaTimeZoneId) } } /** 获取系统当前的时区 */ fun getTimeZoneId(): String = TimeZone.getDefault().id /** 设置系统日期和时间,需要有系统签名才可以 */ fun setDateAndTime(millis: Long) { thread { // 据说AlarmManager.setTime()检测权限之后也是调用SystemClock.setCurrentTimeMillis(millis)来设置时间的 SystemClock.setCurrentTimeMillis(millis) } } } }
class App : Application() {
override fun onCreate() {
super.onCreate()
Timber.init(this, BuildConfig::class.java)
if (!TimeUtil.isDateTimeAuto(this)) {
TimeUtil.setDateTimeAuto(this, true)
}
if (!TimeUtil.isTimeZoneAuto(this)) {
TimeUtil.setTimeZoneAuto(this, true)
}
}
}
import android.os.SystemClock import android.text.format.DateFormat import com.evendai.loglibrary.Timber import okhttp3.Interceptor import okhttp3.Response import java.util.Calendar import kotlin.math.abs /** * 时间同步拦截器。主要功能为获取响应头中的时间,然后与本地时间对比,相差超过1分钟的则进行同步。 */ class TimeSynchronizationInterceptor: Interceptor { private val oneMinute = 1000 * 60 override fun intercept(chain: Interceptor.Chain): Response { val response = chain.proceed(chain.request()) val webServerDate = response.headers.getDate("Date") val is1970 = Calendar.getInstance().get(Calendar.YEAR) == 1970 // 如果当前时间为1970年或者当前时间和服务器时间相差大于1分钟则同步服务器时间 if (webServerDate != null && (is1970 || abs(webServerDate.time - System.currentTimeMillis()) > oneMinute)) { Timber.fi("当前系统时间为:${DateFormat.format("yyyy-MM-dd HH:mm:ss", Calendar.getInstance())}") SystemClock.setCurrentTimeMillis(webServerDate.time) Timber.fi("更新系统时间为:${DateFormat.format("yyyy-MM-dd HH:mm:ss", webServerDate)}") Timber.fi("当前系统时间为:${DateFormat.format("yyyy-MM-dd HH:mm:ss", Calendar.getInstance())}") } return response } }
class MainActivity : AppCompatActivity(), Handler.Callback { private val binding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) } private val handler: Handler = Handler(Looper.getMainLooper(), this) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) binding.autoDateAndTimeCheckBox.setOnCheckedChangeListener { _, isChecked -> TimeUtil.setDateTimeAuto(this, isChecked) } binding.autoTimeZoneTimeCheckBox.setOnCheckedChangeListener { _, isChecked -> TimeUtil.setTimeZoneAuto(this, isChecked) } binding.use24HourCheckBox.setOnCheckedChangeListener { _, isChecked -> TimeUtil.set24HourFormat(this, isChecked) } binding.setDateButton.setOnClickListener { TimeUtil.setDate(this, 2008, 11, 31) } binding.setTimeButton.setOnClickListener { TimeUtil.setTime(this, 12, 30) } binding.setTimeZoneButton.setOnClickListener { TimeUtil.setTimeZone(this, "Asia/Seoul") } binding.setUseNetTimeButton.setOnClickListener { thread { // val url = "https://www.baidu.com" val url = "https://10.238.113.50" val okHttpClient = OkHttpClientBuilder.createOkHttpClientBuilder() .addInterceptor(TimeSynchronizationInterceptor()) .callTimeout(1, TimeUnit.SECONDS) .build() okHttpClient.newCall(Request.Builder().url(url).build()).enqueue(object: Callback { override fun onFailure(call: Call, e: IOException) { } override fun onResponse(call: Call, response: Response) { Timber.fi("responseCode = ${response.code}") } }) } } Timber.fi("当前年份:${Calendar.getInstance().get(Calendar.YEAR)}") } override fun onResume() { super.onResume() handler.removeMessages(0) handler.sendEmptyMessage(0) binding.autoDateAndTimeCheckBox.isChecked = TimeUtil.isDateTimeAuto(this) binding.autoTimeZoneTimeCheckBox.isChecked = TimeUtil.isTimeZoneAuto(this) binding.use24HourCheckBox.isChecked = TimeUtil.is24HourFormat(this) } override fun onStop() { super.onStop() handler.removeMessages(0) } override fun handleMessage(msg: Message): Boolean { val timeFormat = if (TimeUtil.is24HourFormat(this)) "HH:mm:ss" else "hh:mm:ss a" val dateTimeFormat = "yyyy-MM-dd $timeFormat" val dateTime = DateFormat.format(dateTimeFormat, System.currentTimeMillis()) binding.timeText.text = String.format("%s, %s", dateTime, TimeUtil.getTimeZoneId()) handler.sendEmptyMessageDelayed(0, 1000) return true } }
这里我用到了自己定义的日志库和OkHttp库,在使用时大家可以改成使用Android标准的日志和标准的OkHttp即可。
2023-10-24续:
今天我在另一台球机设备上运行代码,由于没有系统签名,调用各种获取状态的方法似乎都不会有问题(比如TimeUtil.isDateTimeAuto()
),只有调用设置函数时才会出异常。
在调用TimeUtil.setDateTimeAuto()
或TimeUtil.setTimeZoneAuto()
时都报异常提示需要android.permission.WRITE_SECURE_SETTINGS
权限,把这个权限加上之后,虽然我没有系统签名,但是也可以正常调用这两个函数了,而且可以正常修改设置,而且这个权限也不需要动态申请。
在调用TimeUtil.set24HourFormat()
时报缺少android.permission.WRITE_SETTINGS
,跟WRITE_SECURE_SETTINGS
权限不太一样,在清单文件上看它也是ProtectedPermissions类型的,但是我加上这个权限后还是会挂,提示如下:
SecurityException: cn.android666.settimedemo was not granted this permission: android.permission.WRITE_SETTINGS.
查看此系统权限的官方文档声明如下:
WRITE_SETTINGS 允许应用程序读取或写入系统设置。注意:如果应用程序以 API 级别 23
或更高级别为目标,则应用程序用户必须通过权限管理屏幕明确向应用程序授予此权限。该应用程序通过发送 intent with action
Settings.ACTION_MANAGE_WRITE_SETTINGS来请求用户的批准 。应用可以通过调用
来检查自己是否有这个权限Settings.System.canWrite()。
按照文档声明去申请权限之后就可以正常修改了。
在调用SystemClock.setCurrentTimeMillis()
或mAlarmManager.setTime()
来设置时间会报异常,但是程序不会崩,异常如下:
SecurityException: setTime: Neither user 10154 nor current process has android.permission.SET_TIME.
这就有点奇怪, 因为SET_TIME
权限和WRITE_SECURE_SETTINGS
我都声明有了,而且看类型他们是一个类型的,都是ProtectedPermissions
,为什么一个声明了有用,一个没用,搞不懂。对应的TimeUtil.setTimeZone()
需要SET_TIME_ZONE
权限,和SET_TIME
一样,声明了也不管用,一样会挂。
简单总结下:
总结一下,如上图,在没有系统签名的情况下,上图中4个按钮的功能都无法使用,程序会崩,3个复选框的功能则可以正常使用。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。