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

一 综述

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 (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
        }
    }
}

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

猜你喜欢

转载自blog.csdn.net/zhaoqi2617/article/details/78836288
今日推荐