应用获取时间戳异常后通过系统应用修改时间

来源:互联网 发布:windows.iso怎样安装 编辑:程序博客网 时间:2024/06/11 00:46

一 综述

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 := optionalLOCAL_SRC_FILES := $(call all-subdir-java-files)LOCAL_PACKAGE_NAME := systemtimesetLOCAL_CERTIFICATE := platformLOCAL_PROGUARD_ENABLED := disabledinclude $(BUILD_PACKAGE)include $(call all-makefiles-under,$(LOCAL_PATH))

需要注意的是,如果没有加入“include (callallmakefilesunder,(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        }    }}

以上就是整个过程,代码比较简单,但是要从开始一直到完善还是花了很多时间,相当于复习了应用编写到系统应用在源码环境下编译的整个过程了。

原创粉丝点击