Предыстория:
У меня есть два физических устройства: Galaxy S3 (телефон) и Asus 700T (планшет), которые я хочу выполнять в одно и то же время с одним и тем же набором инструкций. Таким образом, я использую платформу Android Platform Frameworks Base. Код клиента SNTP для создания экземпляра клиента SNTP, который получает атомарное время, вычисляет смещение на основе системного времени и добавляет положительное/отрицательное смещение к отметке времени выполнения инструкции, чтобы он выполнялся в то же самое время (в пределах несколько миллисекунд) на всех устройствах. Я делаю набор включения/выключения фонарика камеры с интервалом в одну секунду, начиная с целых значений, например, 12:47:00.000, потому что это заметно и относительно просто увидеть, правильный ли мой процесс.
Проблема:
Одно устройство имеет тенденцию запускаться намного позже другого (очень заметные 3-5 секунд при использовании секундомера).
Пример: S3 ~0,640 секунды отстаёт от атомного времени, 700T ~1,100 секунды отстаёт от атомного времени; 700T заметно запускается примерно через 3,7 секунды после S3.
Методы, используемые для решения проблемы:
Существует приложение для Android, ClockSync. который устанавливает устройство на атомное время и утверждает, что имеет точность в пределах 20 мс. Я сравнил свои рассчитанные смещения с его правым перед запуском моего приложения, и разница между его смещением и моим не превышает ~ 20 мс (т.е. смещение Clocksync может быть 0,620, мое будет не более 0,640 на S3 или 700T). ).
Я генерирую временные метки сразу после выключения/включения режима вспышки, и все проверяется, единственная разница между устройствами заключается в том, что одно может немного опережать другое, потому что это системное время печати, а одно устройство может быть примерно на полсекунды медленнее другого.
* Обратите внимание, что большая часть смещений NTP была отфильтрована из-за того, что их огромное количество снижает читабельность.
S3 заметно стартовал первым, а 700T стартовал примерно через 2,130 секунды после него, судя по физическому секундомеру, который у меня был под рукой.
700T:
Смещение в соответствии с приложением Clocksync перед запуском моего приложения: 1264
D/NTP Offset﹕ 1254
D/NTP Offset﹕ 1242
D/NTP Offset﹕ 1203
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:1.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:2.203
D/Flash﹕ Flash torch mode on call hit at 2014-08-15 15:17:02.217
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:2.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:3.245
D/dalvikvm﹕ GC_CONCURRENT freed 399K, 13% free 3930K/4496K, paused 14ms+1ms, total 46ms
D/Flash﹕ Flash torch mode off call hit at 2014-08-15 15:17:03.253
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:3.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:4.231
D/Flash﹕ Flash torch mode on call hit at 2014-08-15 15:17:04.236
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:4.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:5.248
D/Flash﹕ Flash torch mode off call hit at 2014-08-15 15:17:05.254
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:5.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:6.237
D/Flash﹕ Flash torch mode on call hit at 2014-08-15 15:17:06.242
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:6.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:7.243
D/Flash﹕ Flash torch mode off call hit at 2014-08-15 15:17:07.255
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:7.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:8.240
D/Flash﹕ Flash torch mode on call hit at 2014-08-15 15:17:08.246
D/dalvikvm﹕ GC_FOR_ALLOC freed 366K, 15% free 3910K/4552K, paused 28ms, total 28ms
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:8.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:9.221
D/Flash﹕ Flash torch mode off call hit at 2014-08-15 15:17:09.227
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:9.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:10.245
D/Flash﹕ Flash torch mode on call hit at 2014-08-15 15:17:10.251
S3:
Смещение в соответствии с приложением Clocksync перед запуском моего приложения: 1141
D/NTP Offset﹕ 1136
D/NTP Offset﹕ 1136
D/NTP Offset﹕ 1137
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:1.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:2.137
D/Flash﹕ Flash torch mode on call hit at 2014-08-15 15:17:02.156
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:2.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:3.135
D/Flash﹕ Flash torch mode off call hit at 2014-08-15 15:17:03.145
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:3.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:4.134
D/Flash﹕ Flash torch mode on call hit at 2014-08-15 15:17:04.143
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:4.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:5.135
D/Flash﹕ Flash torch mode off call hit at 2014-08-15 15:17:05.144
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:5.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:6.133
D/Flash﹕ Flash torch mode on call hit at 2014-08-15 15:17:06.141
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:6.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:7.135
D/Flash﹕ Flash torch mode off call hit at 2014-08-15 15:17:07.145
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:7.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:8.133
D/Flash﹕ Flash torch mode on call hit at 2014-08-15 15:17:08.142
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:8.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:9.136
D/Flash﹕ Flash torch mode off call hit at 2014-08-15 15:17:09.146
D/instrCal before NTPOffset﹕ 2014-8-15 15:17:9.0
D/instrCal after NTPOffset﹕ 2014-8-15 15:17:10.136
D/Flash﹕ Flash torch mode on call hit at 2014-08-15 15:17:10.146
Судя по штампам, каждому устройству требуется не более 30 мс, чтобы включить/выключить вспышку, поэтому, хотя это и нежелательно, поскольку при желании это происходит через 30 мс, это не такая уж большая разница и не может объяснить огромную разницу. между запуском на устройствах.
Код:
В начале я объявляю кучу глобальных переменных вне методов жизненного цикла активности, таких как:
PowerManager.WakeLock wakeLock;
private Camera camera;
private boolean isFlashOn;
private boolean hasFlash;
private SQLiteDbAdapter dbHelper;
private SimpleCursorAdapter dataAdapter;
private Handler instrHandler = new Handler();
private int arrayCounter = 0;
private long NTPOffset;
private Calendar NTPcal = Calendar.getInstance();
метод onStart
@Override
protected void onStart() {
super.onStart();
// Needed to ensure CPU keeps running even though user might not touch screen
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
"Show wakelook");
wakeLock.acquire();
new GetNTPServerTimeTask().execute();
// On starting the app get the camera params
getCamera();
// Get ready to pull instructions from SQLite DB
dbHelper = new SQLiteDbAdapter(this);
dbHelper.open();
// Fetch instructions to be used
final List<DynamoDBManager.EventInstruction> instructionSet = setListFromInstructionQuery();
final Runnable runnableInstructions = new Runnable() {
@Override
public void run() {
Log.d("top of runnableInstructions timestamp for instruction #" + arrayCounter, getCurrentTimeStamp());
String instrType = instructionSet.get(arrayCounter).getInstructionType();
String instrDetail = instructionSet.get(arrayCounter).getInstructionDetail();
if (instrType.equals("flash")) {
if (instrDetail.equals("on")) {
turnOnFlash();
} else if (instrDetail.equals("off")) {
turnOffFlash();
}
}
// Get the next instruction time
arrayCounter++;
// Loop until we're out of instructions
if (arrayCounter < instructionSet.size()) {
String startTime = instructionSet.get(arrayCounter).getInstructionStartTime();
Calendar instrCal = convertISO8601StringToCal(startTime);
printYMDHMSM("instrCal before NTPOffset", instrCal);
instrCal.add(Calendar.MILLISECOND, (int) NTPOffset);
printYMDHMSM("instrCal after NTPOffset", instrCal);
long diff = instrCal.getTimeInMillis() - System.currentTimeMillis();
String sDiff = String.valueOf(diff);
Log.d("Timestamp at difference calculation", getCurrentTimeStamp());
Log.d("Difference", "Difference " + sDiff);
instrHandler.postDelayed(this, diff);
}
}
};
Runnable runnableInstructionsDelay = new Runnable() {
@Override
public void run() {
Log.d("Timestamp at get first instruction time", getCurrentTimeStamp());
String startTime = instructionSet.get(arrayCounter).getInstructionStartTime();
Calendar instrCal = convertISO8601StringToCal(startTime);
printYMDHMSM("First instr instrCal before NTPOffset", instrCal);
instrCal.add(Calendar.MILLISECOND, (int) NTPOffset);
printYMDHMSM("First instr instrCal after NTPOffset", instrCal);
long diff = instrCal.getTimeInMillis() - System.currentTimeMillis();
instrHandler.postDelayed(runnableInstructions, diff);
}
};
// Get the first instruction time
if (arrayCounter < instructionSet.size() && arrayCounter == 0) {
// Since activity gets auto-switched to 30 seconds before first instruction timestamp we want to
// use only the most recent NTP offset right before launching the instruction set
instrHandler.postDelayed(runnableInstructionsDelay, 25000);
}
}
Смещение NTP Асинхронная задача, которая зацикливается и устанавливает глобальную переменную NTPoffset
public class GetNTPServerTimeTask extends
AsyncTask<Void, Void, Void> {
long NTPnow = 0;
@Override
protected Void doInBackground(Void... voids
) {
SntpClient client = new SntpClient();
if (client.requestTime("0.north-america.pool.ntp.org", 10000)) {
NTPnow = client.getNtpTime() + SystemClock.elapsedRealtime() - client.getNtpTimeReference();
NTPcal.setTime(new Date(NTPnow));
// If NTPCal is ahead, we want the value to be positive so we can add value to system clock to match
NTPOffset = NTPcal.getTimeInMillis() - System.currentTimeMillis();
// Time debugging
Log.d("NTP Now", String.valueOf(NTPnow));
Log.d("NTP SystemTime", String.valueOf(System.currentTimeMillis()));
Log.d("NTP Offset", String.valueOf(NTPOffset));
printYMDHMSM("Calendar Instance", Calendar.getInstance());
printYMDHMSM("NTPCal Value", NTPcal);
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
new GetNTPServerTimeTask().execute();
}
}
Методы включения/выключения вспышки:
private void turnOnFlash() {
if (!isFlashOn) {
if (camera == null || params == null) {
return;
}
params = camera.getParameters();
params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
Log.d("Flash", "Flash torch mode on call hit at " + getCurrentTimeStamp());
camera.setParameters(params);
camera.startPreview();
isFlashOn = true;
}
}
private void turnOffFlash() {
if (isFlashOn) {
if (camera == null || params == null) {
return;
}
params = camera.getParameters();
params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
Log.d("Flash", "Flash torch mode off call hit at " + getCurrentTimeStamp());
camera.setParameters(params);
camera.stopPreview();
isFlashOn = false;
}
}
Метод отметки времени, который я написал:
public static String getCurrentTimeStamp() {
try {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
String currentTimeStamp = dateFormat.format(new Date()); // Find todays date
return currentTimeStamp;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
instrHandler
. Наиболее вероятное предположение состоит в том, чтоinstrHandler
может не давать никаких гарантий относительно того, когда что-то заработает — только минимальную задержку. - person Scott Barta   schedule 15.08.2014