diff --git a/gitsion/build.properties b/gitsion/build.properties
index de7b289..010c564 100644
--- a/gitsion/build.properties
+++ b/gitsion/build.properties
@@ -1,8 +1,8 @@
#Created by .winboll/winboll_app_build.gradle
-#Thu Jun 04 18:23:05 HKT 2026
+#Thu Jun 04 19:50:05 HKT 2026
stageCount=27
libraryProject=
baseVersion=15.11
publishVersion=15.11.26
-buildCount=47
+buildCount=53
baseBetaVersion=15.11.27
diff --git a/gitsion/src/main/AndroidManifest.xml b/gitsion/src/main/AndroidManifest.xml
index 0dc4dde..e55e48c 100644
--- a/gitsion/src/main/AndroidManifest.xml
+++ b/gitsion/src/main/AndroidManifest.xml
@@ -56,6 +56,7 @@
+
diff --git a/gitsion/src/main/java/cc/winboll/studio/gitsion/App.java b/gitsion/src/main/java/cc/winboll/studio/gitsion/App.java
index ca563a9..e015452 100644
--- a/gitsion/src/main/java/cc/winboll/studio/gitsion/App.java
+++ b/gitsion/src/main/java/cc/winboll/studio/gitsion/App.java
@@ -57,6 +57,7 @@ public class App extends GlobalApplication {
ToastUtils.init(this);
WinBoLLActivityManager.init(this);
AESThemeUtil.init(null);
+ GpsHistoryManager.getInstance().init(this);
} catch (Throwable e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
diff --git a/gitsion/src/main/java/cc/winboll/studio/gitsion/GpsHistoryActivity.java b/gitsion/src/main/java/cc/winboll/studio/gitsion/GpsHistoryActivity.java
new file mode 100644
index 0000000..10a83bb
--- /dev/null
+++ b/gitsion/src/main/java/cc/winboll/studio/gitsion/GpsHistoryActivity.java
@@ -0,0 +1,151 @@
+package cc.winboll.studio.gitsion;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.LinearLayout;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+import cc.winboll.studio.gitsion.db.GpsHistoryDatabaseHelper;
+import cc.winboll.studio.gitsion.model.GpsHistoryRecord;
+
+public class GpsHistoryActivity extends Activity {
+
+ private ListView mListView;
+ private TextView mEmptyHint;
+ private TextView mTvSystemCount;
+ private TextView mTvSimCount;
+ private GpsHistoryAdapter mAdapter;
+ private final Runnable mRefreshRunnable = new Runnable() {
+ @Override
+ public void run() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ refreshData();
+ }
+ });
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_gps_history);
+
+ mListView = findViewById(R.id.lv_gps_history);
+ mEmptyHint = findViewById(R.id.tv_empty_hint);
+ mTvSystemCount = findViewById(R.id.tv_system_count);
+ mTvSimCount = findViewById(R.id.tv_sim_count);
+
+ List records = GpsHistoryManager.getInstance().getRecords();
+ mAdapter = new GpsHistoryAdapter(records);
+ mListView.setAdapter(mAdapter);
+
+ updateEmptyHint(records.isEmpty());
+ updateCounts();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ GpsHistoryManager.getInstance().addListener(mRefreshRunnable);
+ refreshData();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ GpsHistoryManager.getInstance().removeListener(mRefreshRunnable);
+ }
+
+ private void refreshData() {
+ List records = GpsHistoryManager.getInstance().getRecords();
+ mAdapter.setData(records);
+ mAdapter.notifyDataSetChanged();
+ updateEmptyHint(records.isEmpty());
+ updateCounts();
+ }
+
+ private void updateCounts() {
+ mTvSystemCount.setText(String.valueOf(GpsHistoryManager.getInstance().getSystemCount()));
+ mTvSimCount.setText(String.valueOf(GpsHistoryManager.getInstance().getSimCount()));
+ }
+
+ private void updateEmptyHint(boolean empty) {
+ mEmptyHint.setVisibility(empty ? View.VISIBLE : View.GONE);
+ mListView.setVisibility(empty ? View.GONE : View.VISIBLE);
+ }
+
+ private static class GpsHistoryAdapter extends BaseAdapter {
+
+ private List mData;
+ private final SimpleDateFormat mTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
+
+ GpsHistoryAdapter(List data) {
+ mData = data;
+ }
+
+ void setData(List data) {
+ mData = data;
+ }
+
+ @Override
+ public int getCount() {
+ return mData.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mData.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = View.inflate(parent.getContext(), R.layout.list_item_gps_history, null);
+ }
+
+ GpsHistoryRecord item = mData.get(position);
+
+ LinearLayout root = (LinearLayout) convertView.findViewById(R.id.layout_record_root);
+ TextView tvType = (TextView) convertView.findViewById(R.id.tv_record_type);
+ TextView tvTime = (TextView) convertView.findViewById(R.id.tv_record_time);
+ TextView tvCoord = (TextView) convertView.findViewById(R.id.tv_record_coord);
+
+ String timeStr = mTimeFormat.format(new Date(item.getLocationTime()));
+ String coordStr = String.format(Locale.getDefault(),
+ "纬度: %.6f 经度: %.6f SID: %s",
+ item.getLatitude(), item.getLongitude(), item.getSid());
+
+ tvTime.setText(timeStr);
+ tvCoord.setText(coordStr);
+
+ if (item.isSim()) {
+ root.setBackgroundColor(Color.parseColor("#2a3a2a"));
+ tvType.setText("[模拟数据]");
+ tvType.setTextColor(Color.parseColor("#ffb74d"));
+ } else {
+ root.setBackgroundColor(Color.parseColor("#1c1c1c"));
+ tvType.setText("[系统数据]");
+ tvType.setTextColor(Color.parseColor("#4fc3f7"));
+ }
+
+ return convertView;
+ }
+ }
+}
diff --git a/gitsion/src/main/java/cc/winboll/studio/gitsion/GpsHistoryManager.java b/gitsion/src/main/java/cc/winboll/studio/gitsion/GpsHistoryManager.java
new file mode 100644
index 0000000..071cdac
--- /dev/null
+++ b/gitsion/src/main/java/cc/winboll/studio/gitsion/GpsHistoryManager.java
@@ -0,0 +1,150 @@
+package cc.winboll.studio.gitsion;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import cc.winboll.studio.gitsion.db.GpsHistoryDatabaseHelper;
+import cc.winboll.studio.gitsion.model.GpsHistoryRecord;
+import cc.winboll.studio.libgitsion.model.GpsSubscribeResult;
+
+public final class GpsHistoryManager {
+
+ private static final int MAX_RECORDS = 2000;
+ private static final int TRIM_COUNT = 1000;
+ private static final GpsHistoryManager sInstance = new GpsHistoryManager();
+ private final List mListeners = new ArrayList();
+ private GpsHistoryDatabaseHelper mDbHelper;
+
+ private GpsHistoryManager() {}
+
+ public static GpsHistoryManager getInstance() {
+ return sInstance;
+ }
+
+ public void init(Context context) {
+ mDbHelper = new GpsHistoryDatabaseHelper(context.getApplicationContext());
+ }
+
+ public void addRecord(GpsSubscribeResult result, int type) {
+ if (mDbHelper == null) return;
+ SQLiteDatabase db = mDbHelper.getWritableDatabase();
+ ContentValues cv = new ContentValues();
+ cv.put(GpsHistoryDatabaseHelper.COL_LATITUDE, result.getLatitude());
+ cv.put(GpsHistoryDatabaseHelper.COL_LONGITUDE, result.getLongitude());
+ cv.put(GpsHistoryDatabaseHelper.COL_LOCATION_TIME, result.getLocationTime());
+ cv.put(GpsHistoryDatabaseHelper.COL_RECORD_TIME, System.currentTimeMillis());
+ cv.put(GpsHistoryDatabaseHelper.COL_TYPE, type);
+ cv.put(GpsHistoryDatabaseHelper.COL_SID, result.getSubscribeUniqueId());
+ cv.put(GpsHistoryDatabaseHelper.COL_DESC, result.getResultDesc());
+ db.insert(GpsHistoryDatabaseHelper.TABLE_NAME, null, cv);
+
+ trimIfNeeded(db);
+ notifyListeners();
+ }
+
+ public void addSystemRecord(GpsSubscribeResult result) {
+ addRecord(result, GpsHistoryDatabaseHelper.TYPE_SYSTEM);
+ }
+
+ public void addSimRecord(GpsSubscribeResult result) {
+ addRecord(result, GpsHistoryDatabaseHelper.TYPE_SIM);
+ }
+
+ public List getRecords() {
+ List list = new ArrayList();
+ if (mDbHelper == null) return list;
+ SQLiteDatabase db = mDbHelper.getReadableDatabase();
+ Cursor c = db.query(GpsHistoryDatabaseHelper.TABLE_NAME, null, null, null,
+ null, null, GpsHistoryDatabaseHelper.COL_ID + " DESC", null);
+ while (c.moveToNext()) {
+ list.add(parseCursor(c));
+ }
+ c.close();
+ return list;
+ }
+
+ public int getSystemCount() {
+ return countByType(GpsHistoryDatabaseHelper.TYPE_SYSTEM);
+ }
+
+ public int getSimCount() {
+ return countByType(GpsHistoryDatabaseHelper.TYPE_SIM);
+ }
+
+ public void clear() {
+ if (mDbHelper == null) return;
+ mDbHelper.getWritableDatabase().delete(GpsHistoryDatabaseHelper.TABLE_NAME, null, null);
+ notifyListeners();
+ }
+
+ public void addListener(Runnable listener) {
+ synchronized (mListeners) {
+ mListeners.add(listener);
+ }
+ }
+
+ public void removeListener(Runnable listener) {
+ synchronized (mListeners) {
+ mListeners.remove(listener);
+ }
+ }
+
+ private void notifyListeners() {
+ synchronized (mListeners) {
+ for (Runnable r : mListeners) {
+ r.run();
+ }
+ }
+ }
+
+ private int countByType(int type) {
+ if (mDbHelper == null) return 0;
+ SQLiteDatabase db = mDbHelper.getReadableDatabase();
+ Cursor c = db.rawQuery(
+ "SELECT COUNT(*) FROM " + GpsHistoryDatabaseHelper.TABLE_NAME
+ + " WHERE " + GpsHistoryDatabaseHelper.COL_TYPE + "=?",
+ new String[]{String.valueOf(type)});
+ int count = 0;
+ if (c.moveToFirst()) {
+ count = c.getInt(0);
+ }
+ c.close();
+ return count;
+ }
+
+ private void trimIfNeeded(SQLiteDatabase db) {
+ Cursor c = db.rawQuery(
+ "SELECT COUNT(*) FROM " + GpsHistoryDatabaseHelper.TABLE_NAME, null);
+ int total = 0;
+ if (c.moveToFirst()) {
+ total = c.getInt(0);
+ }
+ c.close();
+ if (total > MAX_RECORDS) {
+ db.execSQL("DELETE FROM " + GpsHistoryDatabaseHelper.TABLE_NAME
+ + " WHERE " + GpsHistoryDatabaseHelper.COL_ID + " IN ("
+ + " SELECT " + GpsHistoryDatabaseHelper.COL_ID
+ + " FROM " + GpsHistoryDatabaseHelper.TABLE_NAME
+ + " ORDER BY " + GpsHistoryDatabaseHelper.COL_ID + " ASC"
+ + " LIMIT " + TRIM_COUNT + ")");
+ }
+ }
+
+ private static GpsHistoryRecord parseCursor(Cursor c) {
+ return new GpsHistoryRecord(
+ c.getLong(c.getColumnIndexOrThrow(GpsHistoryDatabaseHelper.COL_ID)),
+ c.getDouble(c.getColumnIndexOrThrow(GpsHistoryDatabaseHelper.COL_LATITUDE)),
+ c.getDouble(c.getColumnIndexOrThrow(GpsHistoryDatabaseHelper.COL_LONGITUDE)),
+ c.getLong(c.getColumnIndexOrThrow(GpsHistoryDatabaseHelper.COL_LOCATION_TIME)),
+ c.getLong(c.getColumnIndexOrThrow(GpsHistoryDatabaseHelper.COL_RECORD_TIME)),
+ c.getInt(c.getColumnIndexOrThrow(GpsHistoryDatabaseHelper.COL_TYPE)),
+ c.getString(c.getColumnIndexOrThrow(GpsHistoryDatabaseHelper.COL_SID)),
+ c.getString(c.getColumnIndexOrThrow(GpsHistoryDatabaseHelper.COL_DESC))
+ );
+ }
+}
diff --git a/gitsion/src/main/java/cc/winboll/studio/gitsion/MainActivity.java b/gitsion/src/main/java/cc/winboll/studio/gitsion/MainActivity.java
index 0a89c1e..9c12e9b 100644
--- a/gitsion/src/main/java/cc/winboll/studio/gitsion/MainActivity.java
+++ b/gitsion/src/main/java/cc/winboll/studio/gitsion/MainActivity.java
@@ -160,6 +160,10 @@ public final class MainActivity extends AppCompatActivity {
startActivity(new Intent(this, AboutActivity.class));
return true;
}
+ if (item.getItemId() == R.id.action_gps_history) {
+ startActivity(new Intent(this, GpsHistoryActivity.class));
+ return true;
+ }
if (item.getItemId() == R.id.action_app_log) {
LogActivity.startLogActivity(this, false);
return true;
@@ -227,6 +231,17 @@ public final class MainActivity extends AppCompatActivity {
@Override
public void onClick(View v) {
saveSimGpsData();
+ GpsHistoryManager.getInstance().addSimRecord(new cc.winboll.studio.libgitsion.model.GpsSubscribeResult(
+ "SIM",
+ cc.winboll.studio.libgitsion.model.GpsSubscribeConst.RESULT_SUCCESS,
+ "模拟GPS定位",
+ cc.winboll.studio.libgitsion.model.GpsSubscribeConst.GPS_STATE_LOCATED,
+ 0,
+ System.currentTimeMillis(),
+ simLat,
+ simLng,
+ System.currentTimeMillis()
+ ));
ToastUtils.show("已设置当前模拟GPS坐标");
}
});
diff --git a/gitsion/src/main/java/cc/winboll/studio/gitsion/MainService.java b/gitsion/src/main/java/cc/winboll/studio/gitsion/MainService.java
index 900d2dd..8a1f655 100644
--- a/gitsion/src/main/java/cc/winboll/studio/gitsion/MainService.java
+++ b/gitsion/src/main/java/cc/winboll/studio/gitsion/MainService.java
@@ -196,6 +196,20 @@ public final class MainService extends Service {
double currentLng = location.getLongitude();
long currentTime = location.getTime();
+ //保存每一次系统原始定位数据到历史记录
+ GpsSubscribeResult rawRecord = new GpsSubscribeResult(
+ "",
+ GpsSubscribeConst.RESULT_SUCCESS,
+ "系统GPS定位",
+ GpsSubscribeConst.GPS_STATE_LOCATED,
+ 0,
+ System.currentTimeMillis(),
+ currentLat,
+ currentLng,
+ currentTime
+ );
+ GpsHistoryManager.getInstance().addSystemRecord(rawRecord);
+
//遍历全部订阅者进行推送规则判断
Map subscribeAllMap = mSubscribeManager.getSubscribeMap();
for (Map.Entry entry : subscribeAllMap.entrySet()) {
@@ -222,6 +236,7 @@ public final class MainService extends Service {
currentTime
);
mSubscribeManager.sendSubscribeResult(result);
+ GpsHistoryManager.getInstance().addSystemRecord(result);
LogUtils.d(TAG, "推送GPS数据至订阅者 SID:" + subscribeSid);
}
}
diff --git a/gitsion/src/main/java/cc/winboll/studio/gitsion/db/GpsHistoryDatabaseHelper.java b/gitsion/src/main/java/cc/winboll/studio/gitsion/db/GpsHistoryDatabaseHelper.java
new file mode 100644
index 0000000..a5137b6
--- /dev/null
+++ b/gitsion/src/main/java/cc/winboll/studio/gitsion/db/GpsHistoryDatabaseHelper.java
@@ -0,0 +1,51 @@
+package cc.winboll.studio.gitsion.db;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+public final class GpsHistoryDatabaseHelper extends SQLiteOpenHelper {
+
+ private static final String DB_NAME = "gps_history.db";
+ private static final int DB_VERSION = 1;
+
+ public static final String TABLE_NAME = "gps_history";
+ public static final String COL_ID = "_id";
+ public static final String COL_LATITUDE = "latitude";
+ public static final String COL_LONGITUDE = "longitude";
+ public static final String COL_LOCATION_TIME = "location_time";
+ public static final String COL_RECORD_TIME = "record_time";
+ public static final String COL_TYPE = "type";
+ public static final String COL_SID = "sid";
+ public static final String COL_DESC = "description";
+
+ public static final int TYPE_SYSTEM = 0;
+ public static final int TYPE_SIM = 1;
+
+ private static final String CREATE_TABLE =
+ "CREATE TABLE " + TABLE_NAME + " ("
+ + COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ + COL_LATITUDE + " REAL NOT NULL, "
+ + COL_LONGITUDE + " REAL NOT NULL, "
+ + COL_LOCATION_TIME + " INTEGER NOT NULL, "
+ + COL_RECORD_TIME + " INTEGER NOT NULL, "
+ + COL_TYPE + " INTEGER NOT NULL DEFAULT " + TYPE_SYSTEM + ", "
+ + COL_SID + " TEXT, "
+ + COL_DESC + " TEXT"
+ + ")";
+
+ public GpsHistoryDatabaseHelper(Context context) {
+ super(context, DB_NAME, null, DB_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL(CREATE_TABLE);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
+ onCreate(db);
+ }
+}
diff --git a/gitsion/src/main/java/cc/winboll/studio/gitsion/model/GpsHistoryRecord.java b/gitsion/src/main/java/cc/winboll/studio/gitsion/model/GpsHistoryRecord.java
new file mode 100644
index 0000000..18da982
--- /dev/null
+++ b/gitsion/src/main/java/cc/winboll/studio/gitsion/model/GpsHistoryRecord.java
@@ -0,0 +1,64 @@
+package cc.winboll.studio.gitsion.model;
+
+import cc.winboll.studio.gitsion.db.GpsHistoryDatabaseHelper;
+
+public final class GpsHistoryRecord {
+
+ private final long id;
+ private final double latitude;
+ private final double longitude;
+ private final long locationTime;
+ private final long recordTime;
+ private final int type;
+ private final String sid;
+ private final String description;
+
+ public GpsHistoryRecord(long id, double latitude, double longitude,
+ long locationTime, long recordTime,
+ int type, String sid, String description) {
+ this.id = id;
+ this.latitude = latitude;
+ this.longitude = longitude;
+ this.locationTime = locationTime;
+ this.recordTime = recordTime;
+ this.type = type;
+ this.sid = sid;
+ this.description = description;
+ }
+
+ public long getId() {
+ return id;
+ }
+
+ public double getLatitude() {
+ return latitude;
+ }
+
+ public double getLongitude() {
+ return longitude;
+ }
+
+ public long getLocationTime() {
+ return locationTime;
+ }
+
+ public long getRecordTime() {
+ return recordTime;
+ }
+
+ public int getType() {
+ return type;
+ }
+
+ public boolean isSim() {
+ return type == GpsHistoryDatabaseHelper.TYPE_SIM;
+ }
+
+ public String getSid() {
+ return sid;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+}
diff --git a/gitsion/src/main/res/layout/activity_gps_history.xml b/gitsion/src/main/res/layout/activity_gps_history.xml
new file mode 100644
index 0000000..fa7603d
--- /dev/null
+++ b/gitsion/src/main/res/layout/activity_gps_history.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gitsion/src/main/res/layout/list_item_gps_history.xml b/gitsion/src/main/res/layout/list_item_gps_history.xml
new file mode 100644
index 0000000..05de355
--- /dev/null
+++ b/gitsion/src/main/res/layout/list_item_gps_history.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/gitsion/src/main/res/menu/menu_main.xml b/gitsion/src/main/res/menu/menu_main.xml
index 71c340e..1fbf8b0 100644
--- a/gitsion/src/main/res/menu/menu_main.xml
+++ b/gitsion/src/main/res/menu/menu_main.xml
@@ -6,6 +6,11 @@
android:title="About"
android:icon="@android:drawable/ic_menu_info_details"
app:showAsAction="ifRoom"/>
+