首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> J2ME

J2ME 定位 API (JSR-179) 實戰:從選擇 Provider 到地標管理與方位感測完整實作

     編輯:J2ME

實作流程總覽

  1. 檢查 System.getProperty("microedition.location.version") 確認支援。
  2. 建立 Criteria 描述需求 (精度、成本、功耗、速度/方向、海拔、地址)。
  3. 呼叫 LocationProvider.getInstance(criteria) 取得提供者。
  4. 實作 LocationListener、ProximityListener 介面。
  5. 註冊監聽器:provider.setLocationListener(this, interval, timeout, maxAge)、LocationProvider.addProximityListener(this, coordinates, radius)。
  6. 在回呼中處理 Location 物件:取得 QualifiedCoordinates、AddressInfo、速度、時間戳。
  7. 地標管理:LandmarkStore CRUD、分類、查詢。
  8. 方位感測:Orientation.getOrientation()、getAzimuth()/getPitch()/getRoll()、磁北/真北判斷。

關鍵程式碼片段 (基於 Nokia TouristRoute 範例重構)

1. 建立 Criteria 並自動搜尋免費/付費 Provider

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 */};
    }
}

2. LocationListener 實作 — 非同步處理位置更新

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 更新、資料庫寫入、網路傳輸都要另開執行緒。

3. ProximityListener 實作 — 進入/離開地標觸發

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 */ }
}

4. LandmarkStore 完整 CRUD 與分類管理

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

5. AddressInfo 結構化地址建構

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);

6. Orientation 方位感測 — 方位角/俯仰角/翻滾角

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

真機測試清單

  • [ ] 室外開闊地 GPS 冷啟動 < 60 秒、熱啟動 < 5 秒
  • [ ] 室內/地下街 Cell-ID/Wi-Fi 定位可用、精度可接受
  • [ ] A-GPS (如有) 能加速首次定位
  • [ ] 移動中位置更新頻率、速度/方向準確度
  • [ ] Proximity 警示在接近/離開地標時正確觸發
  • [ ] LandmarkStore 讀寫跨 MIDlet 共享正常
  • [ ] 指南針方位角與實體指南針一致 (注意磁偏角)
  • [ ] 低電量/來電/簡訊中斷下定位服務自動恢復

圖片資源 (原文保留)

  • 裝置定位原理圖:手機 ↔ 基地台 / 衛星
  • TouristRoute MIDlet 畫面流程圖:啟動 → 搜尋 Provider → Route View → Landmark Editor → Compass
  • 類別關係圖:UI 層 / Model 層 (ControlPoints, TouristData, ConfigurationProvider, ProviderStatusListener)
  • 地標新增流程圖:UI → LandmarkEditorUI → ControlPoints.addLandmark → LandmarkStore
  • 定位工具介面:Route 工具座標編輯、載入/儲存/套用

連結前綴:https://code.yunyiwangluo.com/bianchen/UploadFiles_4619/201701/,檔名:2017011209312047.jpg, 2017011209312041.jpg, 2017011209312122.jpg, 2017011209312072.jpg, 2017011209312105.jpg。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved