一 综述
Android平台上面一个应用需要获取平台的系统时间作为时间戳,但是有时候系统启动的时候会将时间恢复到1970年~~这个初始时间,因此会导致应用和服务器之间的连接异常,因此有必要通过修改系统时间来解决这种启动异常导致的问题,但是修改Android系统时间是需要系统权限的,一般的应用层APP无法满足这个要求,不过,幸好能够在源码平台进行编译,因此主要的思路就是应用层APP启动异常之后通过广播通知在后台运行的一个系统应用(其实就是一个service)进行系统时间的修改,完成之后再通过广播告知应用层APP,系统时间修复成功,最后再应用层的APP重新获取时间戳登录服务器。整个思路比较明朗的,但是由于涉及到系统应用的编译等问题,结果前后折腾了一天才完成。
二 应用层广播
当系统启动异常之后,登录服务器失败,随即发出广播:
测试工程中的代码
public class MainActivity extends AppCompatActivity {
public static final String ACTION_GET_NET_TIME_TO_SET_SYSTEM_TIM = "ACTION_GET_NET_TIME_TO_SET_SYSTEM_TIM";
private Button send;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
send = (Button)findViewById(R.id.send);
send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
sendBroadcastToSystemTimeSetAPP("TEST");
}
});
}
/**
* function: send broadcast to systemtimeset APP to test.
*
* */
public void sendBroadcastToSystemTimeSetAPP(String message){
//发送广播
String broadcastIntent = ACTION_GET_NET_TIME_TO_SET_SYSTEM_TIM;
Intent intent = new Intent(broadcastIntent);
intent.putExtra("MESSAGE", message);
MainActivity.this.sendBroadcast(intent);
}
}
二 系统应用的编写
由于普通APP并没有修改系统时间的权限,因此专门写了一个应用在源码环境进行编译,主要工作就是在后台运行的service等待应用层广播去进行网络事件的获取和系统时间的修改。
通过一个activity来启动service,使用的是ServiceConnection,应为这样可以直接调用service里面的方法:
public class MainActivity extends Activity {
public static Handler mReceiveTimeSetHandler;
private GetNetTimeToSetSystemTimeService.TimeToSetBinder mTimeToSetBinder;
private Context mContext;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
mTimeToSetBinder = (GetNetTimeToSetSystemTimeService.TimeToSetBinder)iBinder;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
//bind service
Intent bindServiceIntent = new Intent(this, GetNetTimeToSetSystemTimeService.class);
boolean isBind = bindService(bindServiceIntent, mServiceConnection, this.BIND_AUTO_CREATE);
mReceiveTimeSetHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
mTimeToSetBinder.requireSetSystemTime(mContext);
break;
default:
break;
}
}
};
}
@Override
protected void onResume() {
super.onResume();
//启动service之后退至后台
boolean movetoback = moveTaskToBack(true);
}
}
注意在启动成功之后,就将应用的activity退至后台,即在onResume里面进行处理。
关于service的分析,或者service的启动,郭琳大神的博客讲的非常清楚细致。
然后是service部分的代码,这部分里面我通过线程去获取了网络时间,获取和修改成功之后,直接在内部类里面发出了时间修改完成的广播:
public class GetNetTimeToSetSystemTimeService extends Service {
public TimeToSetBinder mTimeToSetBinder = new TimeToSetBinder();
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return mTimeToSetBinder;
}
public class TimeToSetBinder extends Binder{
private Handler mDataHandler;
public void requireSetSystemTime(final Context context){
new Thread(new WebsiteDataDeal()).start();
mDataHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 1:
String mTimeValue = (String) msg.obj;
if (mTimeValue.length() == 19){
String[] temp = mTimeValue.split("\\s+");
String[] mYearMonthData = temp[0].split("-");
int year = Integer.valueOf(mYearMonthData[0]);
int month = Integer.valueOf(mYearMonthData[1]);
int day = Integer.valueOf(mYearMonthData[2]);
String[] mHourMinuteSecond = temp[1].split(":");
int hour = Integer.valueOf(mHourMinuteSecond[0]);
int minute = Integer.valueOf(mHourMinuteSecond[1]);
int second = Integer.valueOf(mHourMinuteSecond[2]);
setSysDate(context, year, month-1, day);
setSysTime(context, hour, minute);
// setSysDate(context, year, 10, 12);
// setSysTime(context, hour, 22);
sendBroadcastToXiaoLeRobot(context, "already set");
}
break;
default:
break;
}
}
};
}
/**
* function: send broadcast to XiaoLeRobot that system time already set.
*
* */
public void sendBroadcastToXiaoLeRobot(Context context, String message){
//发送广播
String broadcastIntent = Constants.ACTION_SYSTEM_TIM_ALREADY_SET;
Intent intent = new Intent(broadcastIntent);
intent.putExtra("MESSAGE", message);
context.sendBroadcastAsUser(intent, UserHandle.ALL);
}
class WebsiteDataDeal implements Runnable{
@Override
public void run() {
String data = getNetWorkTime();
// String websiteData = String.valueOf(data);
mDataHandler.obtainMessage(1, data).sendToTarget();
}
}
public String getNetWorkTime(){
String format = "--";
URL url = null;//取得资源对象
long ld = 0;
try {
url = new URL("http://www.baidu.com");
URLConnection uc = url.openConnection();//生成连接对象
uc.connect(); //发出连接
ld = uc.getDate(); //取得网站日期时间
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(ld);
format = formatter.format(calendar.getTime());
} catch (Exception e) {
e.printStackTrace();
}
return format;
}
/**
* 设置系统日期
* */
public void setSysDate(Context mContext, 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 = c.getTimeInMillis();
if(when / 1000 < Integer.MAX_VALUE){
((AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE)).setTime(when);
}
}
/**
* 设置系统时间
* */
public void setSysTime(Context mContext, int hour, int minute){
Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR_OF_DAY, hour);
c.set(Calendar.MINUTE, minute);
c.set(Calendar.SECOND, 0);
c.set(Calendar.MILLISECOND, 0);
long when = c.getTimeInMillis();
if(when / 1000 < Integer.MAX_VALUE){
((AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE)).setTime(when);
}
}
}
}
这里有几个问题需要注意:1.设置时间的时候,出现一个问题,month的值必须减去1,不知道为啥,参见这篇文章的时候发现的:month要减去1
2.在这个地方发送的广播,由于是Android4.4的系统,sendBroadcast(intent)要写为sendBroadcastAsUser(intetn, UserHandler.ALL);具体情况请参见:sendBroadcastAsUser
好了,最后里面的时间修改参考了文章:
系统时间修改-参考settting源码
再加上一个开机启动的广播:
public class PowerBootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(ACTION_BOOT_COMPLETED)){
// 启动应用首界面
Intent actIntent = new Intent(context.getApplicationContext(), MainActivity.class);
actIntent.setAction("android.intent.action.MAIN");
actIntent.addCategory("android.intent.category.LAUNCHER");
actIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(actIntent);
}
}
}
代码主要逻辑就是上述情况。
三 源码环境下编译配置
在源码下编译首先要在对应方案里面进行APP的配置,在对应方案目录下找到~~~.mk文件,并在里面加入:
PRODUCT_PACKAGES += \
systemtimeset
然后在android/packages/apps/下面建立文件夹(工程包),编写Android.mk文件,由于这个应用很简单,并没有jar包和so等共享库文件,内容如下:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-subdir-java-files)
LOCAL_PACKAGE_NAME := systemtimeset
LOCAL_CERTIFICATE := platform
LOCAL_PROGUARD_ENABLED := disabled
include $(BUILD_PACKAGE)
include $(call all-makefiles-under,$(LOCAL_PATH))
需要注意的是,如果没有加入“include (callall−makefiles−under, (LOCAL_PATH))”则在编译时候没问题,但是在安装的时候会出现“INSTALL_FAILED_SHARED_USER_INCOMPATIBLE”的问题,网络上面是通过其他方式给这个应用进行签名然后安装即可(参考:签名工具签名后安装),但我在出现这个问题之后通过上述的方法解决了。
四 应用层广播接收重新连接服务器
在应用层发出广播之后就等待系统应用返回设置成功后的广播:
public class ReceiveTimeSetInfo extends BroadcastReceiver {
public static final String ACTION_SYSTEM_TIM_ALREADY_SET = "ACTION_SYSTEM_TIM_ALREADY_SET";
@Override
public void onReceive(Context context, Intent intent) {
String receStr = intent.getStringExtra("MESSAGE");
if (intent.getAction().equals(ACTION_SYSTEM_TIM_ALREADY_SET)){
//do something
}
}
}
以上就是整个过程,代码比较简单,但是要从开始一直到完善还是花了很多时间,相当于复习了应用编写到系统应用在源码环境下编译的整个过程了。