diff --git a/app/build.gradle b/app/build.gradle index cdfbf9a..ce067eb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,6 +22,9 @@ android { // App启动页厂商名称 buildConfigField "String", "APP_VENDOR", "\"${project.properties.appVendor}\"" + // App图标 + buildConfigField "String", "APP_ICON", "\"${project.properties.appIconKey}\"" + // AndroidManifest.xml占位符 manifestPlaceholders = [ // App名称 @@ -52,12 +55,14 @@ android { resValue("string", "app_home_url", "\"${project.properties.appHomeUrl}\"") resValue("string", "app_copyright", "\"${project.properties.appCopyright}\"") resValue("string", "app_vendor", "\"${project.properties.appVendor}\"") + resValue("string", "app_name2", "\"${project.properties.appName}\"") } debug { signingConfig signingConfigs.release resValue("string", "app_home_url", "\"${project.properties.appHomeUrl}\"") resValue("string", "app_copyright", "\"${project.properties.appCopyright}\"") resValue("string", "app_vendor", "\"${project.properties.appVendor}\"") + resValue("string", "app_name2", "\"${project.properties.appName}\"") } } compileOptions { @@ -78,4 +83,9 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' implementation 'com.github.Justson.AgentWeb:agentweb-core:v4.1.9-androidx' // (必选) implementation 'com.github.Justson.AgentWeb:agentweb-filechooser:v4.1.9-androidx' -} \ No newline at end of file + implementation 'androidx.preference:preference:1.1.1' + implementation 'cn.hutool:hutool-all:5.5.4' + + compile fileTree(include: ['*.jar'], dir: 'libs') + //compile 'com.amap.api:location:latest.integration' +} diff --git a/app/libs/AMap_Location_V6.4.9_20241226.jar b/app/libs/AMap_Location_V6.4.9_20241226.jar new file mode 100644 index 0000000..648692e Binary files /dev/null and b/app/libs/AMap_Location_V6.4.9_20241226.jar differ diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e3487cd..54bf096 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,6 +15,16 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/nsgk/ruralWeb/location/NSAMapLocation.java b/app/src/main/java/com/nsgk/ruralWeb/location/NSAMapLocation.java new file mode 100644 index 0000000..9e4af21 --- /dev/null +++ b/app/src/main/java/com/nsgk/ruralWeb/location/NSAMapLocation.java @@ -0,0 +1,451 @@ +package com.nsgk.ruralWeb.location; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.HandlerThread; +import android.os.Looper; +import android.util.Log; + +import com.amap.api.location.AMapLocation; +import com.amap.api.location.AMapLocationClient; +import com.amap.api.location.AMapLocationClientOption; +import com.amap.api.location.AMapLocationListener; +import com.amap.api.location.CoordinateConverter; +import com.amap.api.location.DPoint; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +// 主线程调用, 无线程安全 +public class NSAMapLocation implements AMapLocationListener +{ + // 定位场景: 默认无场景 + public static final int FLAG_PURPOSE_SIGNIN = 1; // 签到 + public static final int FLAG_PURPOSE_TRANSPORT = 1 << 1; // 出行 + public static final int FLAG_PURPOSE_SPORT = 1 << 2; // 运动 + + // 定位模式 + public static final int FLAG_MODE_HIGH = 1 << 3; // 高精度模式 + public static final int FLAG_MODE_SAVING = 1 << 4; // 低功耗模式 + public static final int FLAG_MODE_SENSORS = 1 << 5; // 仅设备模式 + + // 获取一次定位结果: 默认为false + public static final int FLAG_ONCE = 1 << 6; // true + + // 获取最近3s内精度最高的一次定位结果: 默认为false + public static final int FLAG_ONCE_LATEST = 1 << 7; // true + + // 设置是否返回地址信息: 默认返回地址信息 + public static final int FLAG_NO_NEED_ADDRESS = 1 << 8; // false + + // 设置是否允许模拟位置: 默认为true + public static final int FLAG_MOCK_DISABLE = 1 << 9; // false + + // 关闭缓存机制: 默认缓存 + public static final int FLAG_CACHE_DISABLE = 1 << 10; // false + + // 线程 + public static final int FLAG_BUILTIN_THREAD = 1 << 11; // 使用内建线程 + public static final int FLAG_CUSTOM_THREAD = 1 << 12; // 使用外部线程 + public static final int FLAG_MAIN_THREAD = 1 << 13; // 使用主线程 + + public static final int DEFAULT_FLAG = FLAG_MODE_HIGH | FLAG_NO_NEED_ADDRESS; + + + public static final int OPTION_INTERVAL = 1; // 定位间隔(毫秒) 默认1000 + public static final int OPTION_HTTP_TIMEOUT = 2; // 定位间隔(毫秒) 默认20000 + + private static final int DEFAULT_INTERVAL = 1000; + private static final int DEFAULT_HTTP_TIMEOUT = 20000; + + private static final String ID_TAG = NSSystemLocation.class.getName(); + + private final Object m_lock = new Object(); + + //声明AMapLocationClient类对象 + public AMapLocationClient mLocationClient = null; + //声明定位回调监听器 + public AMapLocationListener mLocationListener = this; + //声明AMapLocationClientOption对象 + public AMapLocationClientOption mLocationOption = null; + private final Context m_context; + private int m_flag = DEFAULT_FLAG; + private boolean m_isPreInit = false; + private NSLocationInfo m_lastLocation = null; + private HandlerThread m_thread = null; + private Looper m_customLooper = null; + private AtomicInteger m_readOnce = new AtomicInteger(-1); + private Map m_options = new HashMap<>(); + + public NSAMapLocation(Context context) + { + m_context = context; + } + + public boolean Init(int flag) + { + if(IsInitialized()) + { + Log.e(ID_TAG, "定位已初始化"); + return true; + } + + try + { + m_readOnce.set(-1); + m_flag = flag < 0 ? DEFAULT_FLAG : flag; + + // 在构造AMapLocationClient 之前必须进行合规检查,设置接口之前保证隐私政策合规 + PreInit(); + + Context applicationContext = m_context.getApplicationContext(); + //初始化定位 + if(HasFlag(FLAG_BUILTIN_THREAD)) + { + m_thread = new HandlerThread("AMap内建定位"); + m_thread.start(); + mLocationClient = new AMapLocationClient(m_thread.getLooper(), applicationContext); + Log.d(ID_TAG, "定位使用内建线程: " + m_thread.getId()); + } + else if(HasFlag(FLAG_CUSTOM_THREAD)) + { + if(null == m_customLooper) + { + throw new RuntimeException("请先传入线程Looper"); + } + mLocationClient = new AMapLocationClient(m_customLooper, applicationContext); + Log.d(ID_TAG, "定位使用用户指定线程: " + m_customLooper.getThread().getId()); + } + else if(HasFlag(FLAG_MAIN_THREAD)) + { + Looper mainLooper = Looper.getMainLooper(); + mLocationClient = new AMapLocationClient(mainLooper, applicationContext); + Log.d(ID_TAG, "定位使用主线程: " + mainLooper.getThread().getId()); + } + else + { + mLocationClient = new AMapLocationClient(applicationContext); + Log.d(ID_TAG, "定位使用当前线程: " + Thread.currentThread().getId()); + } + + //设置定位回调监听 + mLocationClient.setLocationListener(mLocationListener); + + InitOptions(); + + mLocationClient.setLocationOption(mLocationOption); + + //V6.4.9版本起 client设置逆地理回调 +/* mLocationClient.setReGeoLocationCallback(new IReGeoLocationCallback() + { + @Override + public void onReGeoLocation(AMapLocation reGeoLocation) + { + Log.i(ID_TAG, reGeoLocation.toStr()); + } + });*/ + + Log.i(ID_TAG, "定位初始化完成"); + return true; + } + catch(Exception e) + { + e.printStackTrace(); + mLocationClient = null; + return false; + } + } + + public void SetThread(Looper looper) + { + CheckInitialization(false); + m_customLooper = looper; + } + + public void SetThread(HandlerThread thread) + { + CheckInitialization(false); + m_customLooper = thread.getLooper(); + } + + public boolean IsRunning() + { + return IsInitialized() && mLocationClient.isStarted(); + } + + public void Start() + { + Run(-1); + } + + public void Stop() + { + CheckInitialization(true); + + //设置场景模式后最好调用一次stop,再调用start以保证场景模式生效 + mLocationClient.stopLocation(); + + Log.i(ID_TAG, "定位停止"); + } + + public void Shutdown() + { + if(!IsInitialized()) + { + Log.e(ID_TAG, "请先初始化"); + return; + } + + Stop(); + + mLocationClient.onDestroy(); + mLocationClient = null; + if(null != m_thread) + { + m_thread.quit(); + m_thread = null; + Log.d(ID_TAG, "内建线程结束"); + } + + Log.i(ID_TAG, "定位销毁"); + } + + public void PreInit() + { + if(m_isPreInit) + return; + Context applicationContext = m_context.getApplicationContext(); + /** 设置包含隐私政策,并展示用户授权弹窗 必须在AmapLocationClient实例化之前调用 + * + * @param context + * @param isContains: 是隐私权政策是否包含高德开平隐私权政策 true是包含 + * @param isShow: 隐私权政策是否弹窗展示告知用户 true是展示 + * @since 5.6.0 + */ + AMapLocationClient.updatePrivacyShow(applicationContext, true, true); + /** + * 设置是否同意用户授权政策 必须在AmapLocationClient实例化之前调用 + * @param context + * @param isAgree:隐私权政策是否取得用户同意 true是用户同意 + * + * @since 5.6.0 + */ + AMapLocationClient.updatePrivacyAgree(applicationContext, true); + Log.i(ID_TAG, "定位预初始化"); + m_isPreInit = true; + } + + public boolean IsInitialized() + { + return null != mLocationClient; + } + + private void CheckInitialization(boolean inited) + { + if(IsInitialized() != inited) + { + String msg = inited ? "定位未初始化" : "定位已初始化"; + Log.e(ID_TAG, msg); + throw new RuntimeException(msg); + } + } + + private boolean HasFlag(int f) + { + return (m_flag & f) != 0; + } + + public void SetOption(int type, Object value) + { + CheckInitialization(false); + m_options.put(type, value); + } + + @SuppressLint("unchecked") + private T GetOption(int type, T def) + { + Object o = m_options.get(type); + if(null == o) + return def; + return (T) o; + } + + private void InitOptions() + { + //初始化AMapLocationClientOption对象 + mLocationOption = new AMapLocationClientOption(); + + /** + * 设置定位场景,目前支持三种场景(签到、出行、运动,默认无场景) + */ + if(HasFlag(FLAG_PURPOSE_SIGNIN)) + mLocationOption.setLocationPurpose(AMapLocationClientOption.AMapLocationPurpose.SignIn); + else if(HasFlag(FLAG_PURPOSE_TRANSPORT)) + mLocationOption.setLocationPurpose(AMapLocationClientOption.AMapLocationPurpose.Transport); + else if(HasFlag(FLAG_PURPOSE_SPORT)) + mLocationOption.setLocationPurpose(AMapLocationClientOption.AMapLocationPurpose.Sport); + + //设置定位模式为AMapLocationMode.Hight_Accuracy,高精度模式。 + if(HasFlag(FLAG_MODE_HIGH)) + mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy); + //设置定位模式为AMapLocationMode.Battery_Saving,低功耗模式。 + else if(HasFlag(FLAG_MODE_SAVING)) + mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Battery_Saving); + //设置定位模式为AMapLocationMode.Device_Sensors,仅设备模式。 + else if(HasFlag(FLAG_MODE_SENSORS)) + mLocationOption.setLocationMode(AMapLocationClientOption.AMapLocationMode.Device_Sensors); + + //获取一次定位结果: + //该方法默认为false。 + if(HasFlag(FLAG_ONCE)) + mLocationOption.setOnceLocation(true); + + //获取最近3s内精度最高的一次定位结果: + //设置setOnceLocationLatest(boolean b)接口为true,启动定位时SDK会返回最近3s内精度最高的一次定位结果。如果设置其为true,setOnceLocation(boolean b)接口也会被设置为true,反之不会,默认为false。 + if(HasFlag(FLAG_ONCE_LATEST)) + mLocationOption.setOnceLocationLatest(true); + + //设置定位间隔,单位毫秒,默认为2000ms,最低1000ms。 + int interval = GetOption(OPTION_INTERVAL, DEFAULT_INTERVAL); + mLocationOption.setInterval(interval); + + //设置是否返回地址信息(默认返回地址信息) + if(HasFlag(FLAG_NO_NEED_ADDRESS)) + mLocationOption.setNeedAddress(false); + + //设置是否允许模拟位置,默认为true,允许模拟位置 + if(HasFlag(FLAG_MOCK_DISABLE)) + mLocationOption.setMockEnable(false); + + //单位是毫秒,默认30000毫秒,建议超时时间不要低于8000毫秒。 + int timeout = GetOption(OPTION_HTTP_TIMEOUT, DEFAULT_HTTP_TIMEOUT); + mLocationOption.setHttpTimeOut(timeout); + + //关闭缓存机制 + if(HasFlag(FLAG_CACHE_DISABLE)) + mLocationOption.setLocationCacheEnable(false); + } + + private void Run(int num) + { + CheckInitialization(true); + + m_readOnce.set(num); + + //设置场景模式后最好调用一次stop,再调用start以保证场景模式生效 + mLocationClient.stopLocation(); + mLocationClient.startLocation(); + + Log.i(ID_TAG, "定位开始"); + } + + /* + 在调用线程中执行 + */ + @Override + public void onLocationChanged(AMapLocation amapLocation) + { + Log.d(ID_TAG, "onLocationChanged线程: " + Thread.currentThread().getId()); + System.err.println(amapLocation); + SetLastLocation(amapLocation); + if(null != amapLocation) + { + if(m_readOnce.get() > 0) + { + m_readOnce.decrementAndGet(); + if(m_readOnce.get() == 0) + { + synchronized(m_lock) + { + m_lock.notifyAll(); + } + } + } + } + } + + private double[] Convert(double longitude, double latitude) + { + try + { + //初始化坐标转换类 + CoordinateConverter converter = new CoordinateConverter(m_context.getApplicationContext()); + converter.from(CoordinateConverter.CoordType.GPS); + //设置需要转换的坐标 + converter.coord(new DPoint(latitude, longitude)); + //转换成高德坐标 + DPoint destPoint = converter.convert(); + double dx = destPoint.getLongitude() - longitude; + double dy = destPoint.getLatitude() - latitude; + longitude = longitude - dx; + latitude = latitude - dy; + return new double[]{ + longitude, latitude + }; + } + catch(Exception e) + { + throw new RuntimeException(e); + } + } + + private void SetLastLocation(AMapLocation amapLocation) + { + if(null != amapLocation) + { + double longitude = amapLocation.getLongitude(); + double latitude = amapLocation.getLatitude(); + double[] coord = Convert(longitude, latitude); + longitude = coord[0]; + latitude = coord[1]; + m_lastLocation = new NSLocationInfo(amapLocation.getProvider(), longitude, latitude); + } + else + { + m_lastLocation = null; + } + } + + /** + * 同步调用, 会锁住线程 + */ + public NSLocationInfo Read(int count) + { + CheckInitialization(true); +/* if(IsRunning()) + { + throw new RuntimeException("请先停止定位"); + }*/ + + Log.d(ID_TAG, "定位线程: " + Thread.currentThread().getId()); + synchronized(m_lock) + { + try + { + CleanLastLocation(); + Run(count); + m_lock.wait(); + } + catch(Exception e) + { + throw new RuntimeException(e); + } + finally + { + Stop(); + } + } + return m_lastLocation; + } + + public NSLocationInfo GetLastLocation() + { + return m_lastLocation; + } + + public void CleanLastLocation() + { + m_lastLocation = null; + } +}