System.getProperty("microedition.location.version") 確認支援。Criteria 描述需求 (精度、成本、功耗、速度/方向、海拔、地址)。LocationProvider.getInstance(criteria) 取得提供者。LocationListener、ProximityListener 介面。provider.setLocationListener(this, interval, timeout, maxAge)、LocationProvider.addProximityListener(this, coordinates, radius)。Location 物件:取得 QualifiedCoordinates、AddressInfo、速度、時間戳。LandmarkStore CRUD、分類、查詢。Orientation.getOrientation()、getAzimuth()/getPitch()/getRoll()、磁北/真北判斷。public class ConfigurationProvider {
private static final Criteria[] FREE_CRITERIAS = buildFreeCriterias();
private static final Criteria[] COST_CRITERIAS = buildCostCriterias();
private LocationProvider provider;
public void autoSearch(ProviderStatusListener listener) {
// 1. 先嘗試免費準則
for (Criteria c : FREE_CRITERIAS) {
try {
provider = LocationProvider.getInstance(c);
if (provider != null) {
listener.providerSelectedEvent(); // 觸發 UI 進入主畫面
return;
}
} catch (LocationException ignored) {}
}
// 2. 詢問使用者是否接受付費定位
if (queryUI.confirmCostProvider()) {
for (Criteria c : COST_CRITERIAS) {
try {
provider = LocationProvider.getInstance(c);
if (provider != null) {
listener.providerSelectedEvent();
return;
}
} catch (LocationException ignored) {}
}
}
// 3. 完全找不到
queryUI.showNoFreeServiceFound();
}
// 免費準則範例:高精度、不計費、需速度/方向/海拔/地址
private static Criteria[] buildFreeCriterias() {
Criteria c1 = new Criteria();
c1.setHorizontalAccuracy(25); // 25 公尺水平精度
c1.setVerticalAccuracy(25); // 25 公尺垂直精度
c1.setPreferredResponseTime(Criteria.NO_REQUIREMENT);
c1.setPreferredPowerConsumption(Criteria.POWER_USAGE_HIGH);
c1.setCostAllowed(false); // 關鍵:不允許產生費用
c1.setSpeedAndCourseRequired(true);
c1.setAltitudeRequired(true);
c1.setAddressInfoRequired(true);
return new Criteria[]{c1, /* 低精度省電版 c2 */};
}
}
public class TouristData implements LocationListener, ProximityListener {
private TouristUI ui;
public void locationUpdated(LocationProvider provider, final Location location) {
// 必須立即返回,耗時處理另開執行緒
new Thread() {
public void run() {
QualifiedCoordinates qc = location.getQualifiedCoordinates();
double lat = qc.getLatitude();
double lon = qc.getLongitude();
float speed = location.getSpeed(); // 公尺/秒
AddressInfo addr = location.getAddressInfo(); // 結構化地址
long timestamp = location.getTimestamp(); // UTC 毫秒
// 更新 UI (需在事件執行緒)
if (ui != null) ui.updateLocation(lat, lon, speed, addr, timestamp);
}
}.start();
}
public void providerStateChanged(LocationProvider provider, int newState) {
// newState: AVAILABLE, TEMPORARILY_UNAVAILABLE, OUT_OF_SERVICE
// 可顯示「GPS 訊號弱、正在重新搜尋…」等狀態
}
}
關鍵:locationUpdated 在實作執行緒呼叫,**必須在幾毫秒內返回**,任何 UI 更新、資料庫寫入、網路傳輸都要另開執行緒。
public void proximityEvent(Coordinates coordinates, Location location) {
// coordinates 為觸發的地標座標,location 為當前裝置位置
new Thread() {
public void run() {
String landmarkName = findLandmarkName(coordinates); // 從 LandmarkStore 查詢
if (ui != null) ui.showProximityAlert(landmarkName);
}
}.start();
}
public void monitoringStateChanged(boolean isMonitoringActive) {
// 監控啟動/停止狀態 (如 GPS 關閉導致監控失效)
}
註冊範例:
// 針對每個地標註冊半徑 50 公尺的接近偵測
for (Landmark lm : landmarks) {
try {
LocationProvider.addProximityListener(touristData,
lm.getQualifiedCoordinates(), 50); // radius in meters
} catch (LocationException e) { /* 資源不足或不支援 Proximity */ }
}
public class ControlPoints {
private static final String STORE_NAME = "TouristRoute";
private LandmarkStore store;
public void init() {
try {
store = LandmarkStore.getInstance(STORE_NAME);
if (store == null) {
LandmarkStore.createLandmarkStore(STORE_NAME);
store = LandmarkStore.getInstance(STORE_NAME);
}
} catch (Exception e) { /* fallback to default store */ }
}
// 新增地標
public void addLandmark(String name, String desc, QualifiedCoordinates qc, AddressInfo addr, String category) {
Landmark lm = new Landmark(name, desc, qc, addr);
store.addLandmark(lm, category); // category 可為 null
}
// 依區域+類別查詢
public Enumeration findLandmarks(String category, double minLat, double maxLat, double minLon, double maxLon)
throws IOException {
return store.getLandmarks(category, minLat, maxLat, minLon, maxLon);
}
// 更新地標 (需傳入原物件)
public void updateLandmark(Landmark lm) throws LandmarkException {
store.updateLandmark(lm);
}
// 刪除地標
public void deleteLandmark(Landmark lm) throws LandmarkException {
store.deleteLandmark(lm);
}
// 取得所有分類
public Enumeration getCategories() {
return store.getCategories();
}
}
AddressInfo info = new AddressInfo();
info.setField(AddressInfo.COUNTRY, "Taiwan");
info.setField(AddressInfo.STATE, "Taipei");
info.setField(AddressInfo.CITY, "Taipei");
info.setField(AddressInfo.STREET, "Zhongxiao E. Rd.");
info.setField(AddressInfo.BUILDING_NAME, "Taipei 101");
// 其他欄位:POSTAL_CODE, BUILDING_FLOOR, BUILDING_ROOM, DISTRICT, COUNTY...
Landmark lm = new Landmark("Taipei 101", "Landmark building", coords, info);
public boolean initOrientation() {
try {
orientation = Orientation.getOrientation(); // 可能拋 LocationException
return true;
} catch (LocationException e) {
return false; // 裝置無方位感測器
}
}
public void readOrientation() {
if (orientation == null) return;
float azimuth = orientation.getAzimuth(); // 0-360 度,0=北 (磁北或真北視 isOrientationMagnetic())
float pitch = orientation.getPitch(); // -90 ~ +90,正值 = 螢幕朝上傾斜
float roll = orientation.getRoll(); // -180 ~ +180,正值 = 右側傾斜
// 3D 感測器才有 pitch/roll,2D 只回傳 Float.NaN
boolean isMagnetic = orientation.isOrientationMagnetic(); // true=磁北, false=真北
}
| 問題 | 原因 | 解決 |
|---|---|---|
getInstance(criteria) 一直回傳 null | 準則過嚴 (如要求 10 公尺精度卻無 GPS)、裝置無對應 Provider | 放寬 Criteria:NO_REQUIREMENT、setCostAllowed(true)、降低精度要求 |
locationUpdated 完全不觸發 | 未呼叫 setLocationListener、interval 設負值 (預設) 但 Provider 不支援被動推送 | 確認 interval > 0 (如 5000 ms)、或定期主動呼叫 provider.getLocation() |
| Proximity 事件不觸發 | 裝置/模擬器不支援 Proximity、半徑太小 (< 10 m)、座標格式錯誤 | 查閱裝置規格書、改用輪詢 getLocation() 自行判斷距離 |
LandmarkStore getInstance 回傳 null | 存儲名稱錯誤、尚未建立、裝置不支援多存儲 | 先嘗試 createLandmarkStore、失敗則用無參數 getInstance() 取得預設存儲 |
| Orientation 回傳 NaN | 裝置只有 2D 指南針 (僅方位角) | 檢查 Float.isNaN(pitch),僅顯示方位角 UI |
連結前綴:https://code.yunyiwangluo.com/bianchen/UploadFiles_4619/201701/,檔名:2017011209312047.jpg, 2017011209312041.jpg, 2017011209312122.jpg, 2017011209312072.jpg, 2017011209312105.jpg。