浏览代码

Merge branch 'master' of http://218.59.175.43:3000/xwj/nsgk_android

 Conflicts:
	app/src/main/AndroidManifest.xml
	app/src/main/java/com/nsgk/ruralWeb/FullscreenActivity.java
master
张泽亮 2 个月前
父节点
当前提交
988a99d9bd
共有 37 个文件被更改,包括 3583 次插入19 次删除
  1. +54
    -0
      README.md
  2. 二进制
      app-keystore.jks
  3. +58
    -2
      app/build.gradle
  4. 二进制
      app/libs/AMap_Location_V6.4.9_20241226.jar
  5. +51
    -6
      app/src/main/AndroidManifest.xml
  6. +290
    -8
      app/src/main/java/com/nsgk/ruralWeb/FullscreenActivity.java
  7. +27
    -0
      app/src/main/java/com/nsgk/ruralWeb/SettingsActivity.java
  8. +29
    -0
      app/src/main/java/com/nsgk/ruralWeb/WelcomeActivity.java
  9. +23
    -0
      app/src/main/java/com/nsgk/ruralWeb/enums/NSEnums.java
  10. +462
    -0
      app/src/main/java/com/nsgk/ruralWeb/location/NSAMapLocation.java
  11. +84
    -0
      app/src/main/java/com/nsgk/ruralWeb/location/NSLastKnownLocation.java
  12. +25
    -0
      app/src/main/java/com/nsgk/ruralWeb/location/NSLocationInfo.java
  13. +380
    -0
      app/src/main/java/com/nsgk/ruralWeb/location/NSRealtimeLocation.java
  14. +161
    -0
      app/src/main/java/com/nsgk/ruralWeb/location/NSSystemLocation.java
  15. +92
    -0
      app/src/main/java/com/nsgk/ruralWeb/sys/NSConstants.java
  16. +81
    -0
      app/src/main/java/com/nsgk/ruralWeb/sys/NSPreference.java
  17. +506
    -0
      app/src/main/java/com/nsgk/ruralWeb/ui/SettingsFragment.java
  18. +122
    -0
      app/src/main/java/com/nsgk/ruralWeb/utils/NSContextUtils.java
  19. +19
    -0
      app/src/main/java/com/nsgk/ruralWeb/utils/NSMisc.java
  20. +38
    -0
      app/src/main/java/com/nsgk/ruralWeb/utils/NSStr.java
  21. +468
    -0
      app/src/main/java/com/nsgk/ruralWeb/web/NSEnvWindowObject.java
  22. +12
    -0
      app/src/main/res/drawable/indicator_background.xml
  23. +4
    -2
      app/src/main/res/layout/activity_welcome.xml
  24. +24
    -0
      app/src/main/res/layout/location_indicator.xml
  25. +28
    -0
      app/src/main/res/values/arrays.xml
  26. +5
    -0
      app/src/main/res/values/strings.xml
  27. +7
    -0
      app/src/main/res/xml/file_paths.xml
  28. +149
    -0
      app/src/main/res/xml/settings_preference.xml
  29. +17
    -0
      app/src/main/res/xml/shortcuts.xml
  30. +24
    -1
      gradle.properties
  31. +172
    -0
      gradlew
  32. +84
    -0
      gradlew.bat
  33. +7
    -0
      安装-发布包.bat
  34. +7
    -0
      安装-调试包.bat
  35. +10
    -0
      打包-debug.bat
  36. +48
    -0
      打包-正式.bat
  37. +15
    -0
      生成keystore.bat

+ 54
- 0
README.md 查看文件

@@ -0,0 +1,54 @@
## NSGK App
> 首页地址列表

| 名称 | 地址(实际需自行添加协议,地址,端口) | 图标 | 圆角图标 |
|:-----|:-----|:-----|:-----|
| 事项审批 | /yinnongLogin | ic_launcher_sxsp 或者 ic_launcher_yhzl | ic_launcher_sxsp_round 或者 ic_launcher_yhzl_round |
| 阳光村务(村级事项) | /sunVillage_info/login | ic_launcher_ygcw | ic_launcher_ygcw_round |
| 大托管 | /agriculturalTrusteeship/index | ic_launcher_dtg | ic_launcher_dtg_round |
| 产权交易 | /index | ic_launcher_cqjy | ic_launcher_cqjy_round |
| 农业执法 | /lawEnforcement/login | ic_launcher_nyzf | ic_launcher_nyzf_round |
| 宅基地审批(乌市版) | /zjdLogin | ic_launcher_zjd | ic_launcher_zjd_round |
| 宅基地调查 | /homesteadLogin | ic_launcher_zjddc | ic_launcher_zjddc_round |
| 两清三化 | /homestead/login | ic_launcher_2q3h | ic_launcher_2q3h_round |
| 网上家园(废) | /onlineHomeLogin | ic_launcher_white | ic_launcher_white_round |
| 确权调查 | /contracted/login | ic_launcher_qqdc | ic_launcher_qqdc_round |
| 一体机 | / | ic_launcher_njytj | ic_launcher_njytj_round |

> 使用`Android Studio`修改步骤

* 编辑`gradle.properties`文件
* 配置```appHomeUrl=```首页地址, 不要加双引号
* 配置```appName=```App桌面快捷方式名称, 可以直接使用字符串(需要加双引号, 如 "NSGK APP"), 也可以使用国际化字符串配置(不要加双引号, 如 @string/app_name)
* 配置```appIconKey=```图标名称, 不要加双引号, 去掉前后缀(如 yhzl, 图标为ic_launcher_yhzl; 圆角图标为ic_launcher_yhzl_round)
* 使用Android Studio生成签名包
* 配置```appCopyright=```启动页版权文本, 不要加双引号
* 配置```appVendor=```启动页厂商名称, 不要加双引号
* 配置```appUpdateUrl=```apk下载更新地址, 不要加双引号
* 生成的apk路径为 `/app/release/app-release.apk`

> 使用`gradle`脚本打签名的正式包

* 执行 ```gradlew assembleRelease``` 将使用`gradle.properties`文件里的配置进行打包
* 如果需要自定义配置(无需修改`gradle.properties`文件), 执行 ```gradlew assembleRelease -PappHomeUrl="首页地址" -PappName="App桌面快捷方式名称(只能使用字符串)" -PappIconKey="图标名称" -PappIconKey="启动页版权文本" -PappCopyright="启动页厂商名称" -PappUpdateUrl="apk下载更新地址"``` 将使用命令行里的配置进行打包
* 生成的apk路径为 `/app/build/outputs/apk/release/app-release.apk`

> 帮助脚本

* `打包-正式.bat`: 构建正式包(直接执行将使用`gradle.properties`文件里的配置进行打包, 完整命令行用法为 ```.\打包-正式.bat App主页链接地址 App名称 App图标简称 App启动页版权文本 App启动页厂商名称```, 此时会使用命令行里的配置进行打包. 生成的路径为`/app/build/outputs/apk/release/app-release.apk`)
* `打包-debug.bat`: 构建Debug包(生成的路径为`/app/build/outputs/apk/debug/app-debug.apk`)
* `安装-发布包.bat`: 将已打好的Release包安装至手机(手机需连接到电脑, 并且启用开发者模式)
* `安装-调试包.bat`: 将已打好的Debug包安装至手机(手机需连接到电脑, 并且启用开发者模式)

> 签名证书(安装签名的发布包需要先卸载已安装的debug包)

* 路径: `/app-keystore.jks`
* 密码: `ns61GK32x%`
* Key Alias: `nsgk_rural_web`
* Key Password: `ns61GK32x%`

> 示例
```shell
.\打包-正式.bat http://mxixiaxian.nongshen.net/sunVillage_info/login_code_new 阳光三资 ygcw
```

二进制
app-keystore.jks 查看文件


+ 58
- 2
app/build.gradle 查看文件

@@ -10,15 +10,66 @@ android {
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
versionName "1.0.0.1"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

// App首页链接地址
buildConfigField "String", "APP_HOME_URL", "\"${project.properties.appHomeUrl}\""

// App启动页版权文本
buildConfigField "String", "APP_COMPYRIGHT", "\"${project.properties.appCopyright}\""
// App启动页厂商名称
buildConfigField "String", "APP_VENDOR", "\"${project.properties.appVendor}\""

// App图标
buildConfigField "String", "APP_ICON", "\"${project.properties.appIconKey}\""

// App更新下载地址
buildConfigField "String", "APP_UPDATE_URL", "\"${project.properties.appUpdateUrl}\""

// AndroidManifest.xml占位符
manifestPlaceholders = [
// App名称
APP_NAME: "${project.properties.appName}",
// App图标
APP_ICON: "@mipmap/ic_launcher_${project.properties.appIconKey}",
// App圆角图标
APP_ROUND_ICON: "@mipmap/ic_launcher_${project.properties.appIconKey}_round",
// 高德key
AMAP_KEY: "${project.properties.amapKey}",
]
}

signingConfigs {
release {
v1SigningEnabled true
v2SigningEnabled true
storeFile file(project.properties.storeFile)
storePassword project.properties.storePassword
keyAlias project.properties.keyAlias
keyPassword project.properties.keyPassword
}
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
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}\"")
resValue("string", "app_update_url", "\"${project.properties.appUpdateUrl}\"")
}
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}\"")
resValue("string", "app_update_url", "\"${project.properties.appUpdateUrl}\"")
}
}
compileOptions {
@@ -39,4 +90,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'
}
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'
}

二进制
app/libs/AMap_Location_V6.4.9_20241226.jar 查看文件


+ 51
- 6
app/src/main/AndroidManifest.xml 查看文件

@@ -13,33 +13,63 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!--AgentWeb 是默认允许定位的 ,如果你需要该功能 , 请在你的 AndroidManifest 文件里面加入如下权限 。-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />


<!--用于获取wifi的获取权限,wifi信息会用来进行网络定位-->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>
<!--用于申请调用A-GPS模块-->
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"></uses-permission>
<!--如果设置了target >= 28 如果需要启动后台定位则必须声明这个权限-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<!--如果您的应用需要后台定位权限,且有可能运行在Android Q设备上,并且设置了target>28,必须增加这个权限声明-->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>

<!-- android:icon 、 android:roundIcon 是APP显示图标-->
<!--
APP_NAME占位符:
可以在 /gradle.properties 里配置 appName="自定义名称", 必须携带双引号; 或appName=@string/app_name, 使用国际化文件字符串资源时不要携带双引号
也可以在命令行添加 -PappName="自定义名称", 必须携带双引号; 不支持国际化文件字符串资源
APP_ICON占位符:
APP_ROUND_ICON占位符:
可以在 /gradle.properties 里配置 appIconKey=图标类型, 不要携带双引号
也可以在命令行添加 -PappIconKey=图标类型, 不要携带双引号
APP_ICON拼接为 @mipmap/ic_launcher_${APP_ICON}
APP_ROUND_ICON拼接为 @mipmap/ic_launcher_${APP_ICON}_round
-->
<application
android:requestLegacyExternalStorage="true"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher_zjddc"
android:roundIcon="@mipmap/ic_launcher_zjddc_round"
android:label="@string/app_name"
android:icon="${APP_ICON}"
android:roundIcon="${APP_ROUND_ICON}"
android:label="${APP_NAME}"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:theme="@style/Theme.Nsgk_rural_web">
<meta-data
android:name="com.amap.api.v2.apikey"
android:value="${AMAP_KEY}"/>

<activity
android:name=".WelcomeActivity"
android:configChanges="orientation|keyboardHidden|screenSize|layoutDirection|uiMode"
android:label="@string/app_name"
android:label="${APP_NAME}"
android:theme="@style/Theme.Nsgk_rural_web.Fullscreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
<activity
android:name=".FullscreenActivity"
android:label="${APP_NAME}"
android:configChanges="orientation|keyboardHidden|screenSize|layoutDirection|uiMode" />
<activity
android:name=".SettingsActivity"
android:label="设置"
android:configChanges="orientation|keyboardHidden|screenSize|layoutDirection|uiMode" />

<!--开机广播接受者
@@ -49,6 +79,21 @@
</intent-filter>
</receiver>
-->
<service android:name="com.amap.api.location.APSService"></service>

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS">
<!-- <intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>-->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>

</manifest>

+ 290
- 8
app/src/main/java/com/nsgk/ruralWeb/FullscreenActivity.java 查看文件

@@ -1,28 +1,62 @@
package com.nsgk.ruralWeb;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.ConsoleMessage;
import android.webkit.GeolocationPermissions;
import android.webkit.WebBackForwardList;
import android.webkit.WebHistoryItem;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.Toast;

import com.just.agentweb.AgentWeb;
import com.just.agentweb.AgentWebConfig;
import com.just.agentweb.DefaultWebClient;
import com.just.agentweb.WebChromeClient;
import com.just.agentweb.WebViewClient;
import com.nsgk.ruralWeb.sys.NSConstants;
import com.nsgk.ruralWeb.sys.NSPreference;
import com.nsgk.ruralWeb.utils.NSContextUtils;
import com.nsgk.ruralWeb.utils.NSStr;
import com.nsgk.ruralWeb.web.NSEnvWindowObject;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import cn.hutool.core.util.StrUtil;


/**
* An example full-screen activity that shows and hides the system UI (i.e.
* status bar and navigation/system bar) with user interaction.
*/
public class FullscreenActivity extends AppCompatActivity {
private static final String ID_TAG = FullscreenActivity.class.getName();
public static final int PERMISSION_LOCATION_REQUEST_CODE = 0x1001;


private AgentWeb mAgentWeb;
private NSPreference preference;
private NSEnvWindowObject envWindowObject;

private String m_lastUrl;
private String m_mainUrl;
private View m_progressIndicator;

@SuppressLint("SetJavaScriptEnabled")
@Override
@@ -30,12 +64,71 @@ public class FullscreenActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_fullscreen);
String appHomeUrl = GetHomeUrl();
boolean logcatConsole = GetPreference().GetBool(NSPreference.LOGCAT_CONSOLE_OUTPUT, NSContextUtils.BuildIsDebug(this));
if(logcatConsole)
Log.i(ID_TAG, logcatConsole ? "输出控制台到logcat" : "不输出控制台");
//appHomeUrl = "http://192.168.0.250:85/sunVillage_info/login";
Log.i(ID_TAG, "App home url: " + appHomeUrl);
// init();
mAgentWeb = AgentWeb.with(this)//
.setAgentWebParent((RelativeLayout) findViewById(R.id.ll), -1, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))//传入AgentWeb的父控件。
AgentWeb.PreAgentWeb builder = AgentWeb.with(this)//
.setAgentWebParent(findViewById(R.id.ll), -1, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))//传入AgentWeb的父控件。
.useDefaultIndicator(-1, 3)//设置进度条颜色与高度,-1为默认值,高度为2,单位为dp。
.setWebViewClient(new com.just.agentweb.WebViewClient() {
.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress)
{
super.onProgressChanged(view, newProgress);
if(newProgress != 100)
return;
String url = view.getUrl();
if(IsInDefaultPage())
{
m_mainUrl = url;
DumpMainUrl(url);
Log.i(ID_TAG, "设定主页地址: " + m_mainUrl);
}
else if(IsDefaultPage(url))
{
m_mainUrl = null;
Log.i(ID_TAG, "清空主页地址");
}
m_lastUrl = url;
}

@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage)
{
if(logcatConsole)
{
String text = "[" + consoleMessage.sourceId() + ":" + consoleMessage.lineNumber() + "] " + consoleMessage.message();
switch(consoleMessage.messageLevel())
{
case WARNING:
Log.w("Console", text);
break;
case ERROR:
Log.e("Console", text);
break;
case DEBUG:
Log.d("Console", text);
break;
case LOG:
default:
Log.i("Console", text);
break;
}
}
return super.onConsoleMessage(consoleMessage);
}

@Override
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
callback.invoke(origin, true, false);
super.onGeolocationPermissionsShowPrompt(origin, callback);
}
})
.setWebViewClient(new WebViewClient() {
})//WebViewClient , 与 WebView 使用一致 ,但是请勿获取WebView调用setWebViewClient(xx)方法了,会覆盖AgentWeb DefaultWebClient,同时相应的中间件也会失效。
.setSecurityType(AgentWeb.SecurityType.STRICT_CHECK) //严格模式 Android 4.2.2 以下会放弃注入对象 ,使用AgentWebView没影响。
.setMainFrameErrorView(R.layout.agentweb_error_page, -1) //参数1是错误显示的布局,参数2点击刷新控件ID -1表示点击整个布局都刷新, AgentWeb 3.0.0 加入。
@@ -43,7 +136,51 @@ public class FullscreenActivity extends AppCompatActivity {
.interceptUnkownUrl() //拦截找不到相关页面的Url AgentWeb 3.0.0 加入。
.createAgentWeb()//创建AgentWeb。
.ready()//设置 WebSettings。
.go("http://218.59.175.43:71/homesteadLogin"); //WebView载入该url地址的页面并显示。
;

boolean openDef = IsDefaultPage(appHomeUrl);

if(!openDef)
{
Log.i(ID_TAG, "载入主页: " + appHomeUrl);
RestoreCookie(); // 重载cookie
m_mainUrl = appHomeUrl;
}
else
{
Log.i(ID_TAG, "载入登录页");
GetPreference().Remove(NSPreference.COOKIES); // Remove cookies
}

mAgentWeb = builder.go(appHomeUrl); //WebView载入该url地址的页面并显示。

WebSettings settings = GetWebView().getSettings();

int fontScale = GetPreference().GetInt(NSPreference.FONT_SCALE);
Log.i(ID_TAG, "全局字体缩放: " + fontScale + "%");
if(fontScale > 0 && fontScale != 100)
settings.setTextZoom(fontScale);

settings.setDatabaseEnabled(true);
settings.setDomStorageEnabled(true);
settings.setGeolocationEnabled(true);

// 注入宿主对象
envWindowObject = new NSEnvWindowObject(this, new Handler(), GetWebView());
mAgentWeb.getJsInterfaceHolder().addJavaObject("_Native_object", envWindowObject);
mAgentWeb.getJsInterfaceHolder().addJavaObject("Android", envWindowObject);

// 等待条指示器
m_progressIndicator = getLayoutInflater().inflate(R.layout.location_indicator, null);
m_progressIndicator.findViewById(R.id.terminate_button).setOnClickListener((View view) -> {
envWindowObject.TerminateLocation();
});
m_progressIndicator.setVisibility(View.GONE);
m_progressIndicator.setZ(99);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.CENTER_IN_PARENT);
((RelativeLayout)findViewById(R.id.ll)).addView(m_progressIndicator, params);


/* 上边url是各个APP项目的入口地址
事项审批 http://116.255.223.226:82/yinnongLogin 图标 ic_launcher_sxsp 或者 ic_launcher_yhzl
@@ -59,10 +196,15 @@ public class FullscreenActivity extends AppCompatActivity {
一体机 http://47.98.113.57:81 图标 ic_launcher_njytj
*/

Log.d(ID_TAG, "UI线程: " + Thread.currentThread().getId());
}

@Override
protected void onPause() {
if(IsRecordUrl())
{
DumpCookie();
}
mAgentWeb.getWebLifeCycle().onPause();
super.onPause();
}
@@ -75,25 +217,165 @@ public class FullscreenActivity extends AppCompatActivity {

@Override
protected void onDestroy() {
if(!IsRecordUrl())
{
Log.d(ID_TAG, "Remove url and cookie record");
GetPreference().Remove(NSPreference.COOKIES);
GetPreference().Remove(NSPreference.LAST_ACCESS_URL);
}
mAgentWeb.getWebLifeCycle().onDestroy();
envWindowObject.OnDestroy();
super.onDestroy();
}

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// if (keyCode == KeyEvent.KEYCODE_BACK && mWebView.canGoBack()) {
// mWebView.goBack();
// return true;
// }
if (keyCode == KeyEvent.KEYCODE_BACK && HandleBackKeyEvent(1)) {
return true;
}
if (mAgentWeb.handleKeyEvent(keyCode, event)) {
return true;
}
return super.onKeyDown(keyCode, event);
}

@Override
public void onBackPressed()
{
if(!HandleBackKeyEvent(2))
super.onBackPressed();
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_LOCATION_REQUEST_CODE) {
int granted = 0;
for(int i = 0; i < permissions.length; i++)
{
boolean b = grantResults[i] == PackageManager.PERMISSION_GRANTED;
Log.i(ID_TAG, String.format("请求权限: %s -> %s", permissions[i], b ? "通过" : "拒绝"));
if(b)
granted++;
}
if (granted < permissions.length) {
Toast.makeText(this, "需要定位权限", Toast.LENGTH_LONG).show();
NSContextUtils.RequestLocationPermission(this, FullscreenActivity.PERMISSION_LOCATION_REQUEST_CODE);
}
}
}

private NSPreference GetPreference()
{
if(null == preference)
preference = new NSPreference(this);
return preference;
}

private boolean IsRecordUrl()
{
return GetPreference().GetBool(NSPreference.OPEN_LAST_URL);
}

private String GetHomeUrl()
{
String url = null;
if(IsRecordUrl())
url = GetPreference().GetString(NSPreference.LAST_ACCESS_URL, NSConstants.AppHomeUrl());
if(StrUtil.isBlank(url))
url = NSConstants.AppHomeUrl();
return url;
}

private WebView GetWebView()
{
return mAgentWeb.getWebCreator().getWebView();
}

private void DumpMainUrl(String url)
{
if(StrUtil.isNotEmpty(url))
GetPreference().SetString(NSPreference.LAST_ACCESS_URL, url);
else
GetPreference().Remove(NSPreference.LAST_ACCESS_URL);
Log.d(ID_TAG, "Dump URL: " + url);
}

private void DumpCookie()
{
String url = NSStr.GetUrlPart(NSConstants.AppHomeUrl());
String cookies = AgentWebConfig.getCookiesByUrl(url);
GetPreference().SetString(NSPreference.COOKIES, cookies);
Log.d(ID_TAG, "Dump cookie: " + "url=" + url + ", cookie=" + cookies);
}

private void RestoreCookie()
{
String cookies = GetPreference().GetString(NSPreference.COOKIES);
if(StrUtil.isNotBlank(cookies))
{
String url = NSStr.GetUrlPart(NSConstants.AppHomeUrl());
AgentWebConfig.syncCookie(url, cookies);
Log.d(ID_TAG, "Restore cookie: " + "url=" + url + ", cookie=" + cookies);
}
}

private boolean IsInDefaultPage()
{
return IsDefaultPage(m_lastUrl);
}

private boolean IsDefaultPage(String url)
{
return NSConstants.AppHomeUrl().equals(url);
}

private void History()
{
WebBackForwardList webBackForwardList = GetWebView().copyBackForwardList();
for(int i = 0; i < webBackForwardList.getSize(); i++)
{
WebHistoryItem item = webBackForwardList.getItemAtIndex(i);
Log.d(ID_TAG, StrUtil.format("{}: {} {}", i, item.getTitle(), item.getUrl()));
}
}

public void SetIndicatorVisible(boolean on)
{
m_progressIndicator.setVisibility(on ? View.VISIBLE : View.GONE);
}

private boolean HandleBackKeyEvent(int from)
{
if(!IsRecordUrl())
return false;
WebView webView = GetWebView();
if(null == webView)
return false;

boolean inDefaultPage = IsInDefaultPage();
//System.err.println(m_lastUrl +" -> " + inDefaultPage + " = " + webView.canGoBack());
if(inDefaultPage)
{
if(from == 1 && !webView.canGoBack())
{
finish();
return true;
}
else
return false;
}
if (!webView.canGoBack() && !inDefaultPage) {
webView.loadUrl(NSConstants.AppHomeUrl());
webView.clearHistory();
//History();
return true;
}
return false;
}
}

+ 27
- 0
app/src/main/java/com/nsgk/ruralWeb/SettingsActivity.java 查看文件

@@ -0,0 +1,27 @@
package com.nsgk.ruralWeb;

import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;

import com.nsgk.ruralWeb.ui.SettingsFragment;

public class SettingsActivity extends AppCompatActivity
{
private static final String ID_TAG = SettingsActivity.class.getName();

@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);

setTitle(R.string.settings_name);

getSupportFragmentManager().beginTransaction().replace(android.R.id.content, new SettingsFragment()).commit();
}

@Override
protected void onDestroy() {
super.onDestroy();
}
}

+ 29
- 0
app/src/main/java/com/nsgk/ruralWeb/WelcomeActivity.java 查看文件

@@ -9,10 +9,13 @@ import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.nsgk.ruralWeb.sys.NSConstants;

public class WelcomeActivity extends AppCompatActivity {
private ImageView imageView;
private LinearLayout llcenter;
@@ -36,12 +39,38 @@ public class WelcomeActivity extends AppCompatActivity {
}
};

private String GetTrimString(String str)
{
if(null == str || str.isEmpty())
return null;
str = str.trim();
if(str.isEmpty())
return null;
return str;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_welcome);
imageView = findViewById(R.id.imageView);
llcenter = findViewById(R.id.llcenter);

// 动态设置启动页版权信息
String text = GetTrimString(NSConstants.AppCopyright());
if(null != text)
{
TextView copyrightText = findViewById(R.id.welcome_text_copyright);
copyrightText.setText(text);
}
// 动态设置启动页厂商名称
text = GetTrimString(NSConstants.AppVendor());
if(null != text)
{
TextView vendorText = findViewById(R.id.welcome_text_vendor);
vendorText.setText(text);
}

Animation animation = AnimationUtils.loadAnimation(this, R.anim.img_anim);
animation.start();
handler.sendEmptyMessageDelayed(1, 1000);


+ 23
- 0
app/src/main/java/com/nsgk/ruralWeb/enums/NSEnums.java 查看文件

@@ -0,0 +1,23 @@
package com.nsgk.ruralWeb.enums;

public final class NSEnums
{
public final class LocationMode
{
public static final String SYSTEM = "system";
public static final String GAODE = "gaode";
public static final String REALTIME = "realtime";
public static final String H5 = "h5";

private LocationMode() {}
}

public final class LocationProvider
{
public static final String AUTO = "auto";

private LocationProvider() {}
}

private NSEnums() {}
}

+ 462
- 0
app/src/main/java/com/nsgk/ruralWeb/location/NSAMapLocation.java 查看文件

@@ -0,0 +1,462 @@
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<Integer, Object> 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;
}

TerminateRead();
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();
/** 设置包含隐私政策,并展示用户授权弹窗 <b>必须在AmapLocationClient实例化之前调用</b>
*
* @param context
* @param isContains: 是隐私权政策是否包含高德开平隐私权政策 true是包含
* @param isShow: 隐私权政策是否弹窗展示告知用户 true是展示
* @since 5.6.0
*/
AMapLocationClient.updatePrivacyShow(applicationContext, true, true);
/**
* 设置是否同意用户授权政策 <b>必须在AmapLocationClient实例化之前调用</b>
* @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> 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, "AMap定位回调线程: " + Thread.currentThread().getId() + " -> " + 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, int timeout)
{
CheckInitialization(true);
/* if(IsRunning())
{
throw new RuntimeException("请先停止定位");
}*/

if(timeout < 0)
timeout = 0;
Log.d(ID_TAG, "AMap定位线程: " + Thread.currentThread().getId() + ", 超时设置: " + timeout);
synchronized(m_lock)
{
try
{
CleanLastLocation();
Run(count);
m_lock.wait(timeout);
}
catch(Exception e)
{
throw new RuntimeException(e);
}
finally
{
Stop();
}
}
return m_lastLocation;
}

public NSLocationInfo GetLastLocation()
{
return m_lastLocation;
}

public void TerminateRead()
{
synchronized(m_lock)
{
Log.i(ID_TAG, "AMap中止定位");
m_lock.notifyAll();
}
}

public void CleanLastLocation()
{
m_lastLocation = null;
}
}

+ 84
- 0
app/src/main/java/com/nsgk/ruralWeb/location/NSLastKnownLocation.java 查看文件

@@ -0,0 +1,84 @@
package com.nsgk.ruralWeb.location;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationManager;
import android.os.Build;
import android.os.CancellationSignal;
import android.util.Log;
import android.widget.Toast;

import com.nsgk.ruralWeb.sys.NSConstants;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class NSLastKnownLocation
{
private static final String ID_TAG = NSLastKnownLocation.class.getName();

private final Context m_context;
private NSLocationInfo m_lastLocation = null;

public NSLastKnownLocation(Context context)
{
m_context = context;
}

@SuppressLint("MissingPermission")
public NSLocationInfo Read(String type)
{
NSLocationInfo loc = null;
try
{
LocationManager lm = (LocationManager) m_context.getSystemService(Context.LOCATION_SERVICE);

List<String> allProviders = lm.getAllProviders();
Log.i(ID_TAG, "使用最近所有定位提供器: " + allProviders);
Map<String, NSLocationInfo> map = new HashMap<>();
for(String provider : allProviders)
{
Location lastKnownLocation = lm.getLastKnownLocation(provider);
if(null == lastKnownLocation)
continue;
NSLocationInfo l = new NSLocationInfo(provider, lastKnownLocation.getLongitude(), lastKnownLocation.getLatitude());
map.put(provider, l);
}

Log.i(ID_TAG, "最近定位提供器结果: " + map);
if(null != type && !type.isEmpty() && map.containsKey(type))
loc = map.get(type);
else if(map.containsKey(LocationManager.GPS_PROVIDER))
loc = map.get(LocationManager.GPS_PROVIDER);
else if(map.containsKey(LocationManager.NETWORK_PROVIDER))
loc = map.get(LocationManager.NETWORK_PROVIDER);
else if(map.containsKey(NSConstants.FUSED_PROVIDER))
loc = map.get(NSConstants.FUSED_PROVIDER);
else if(map.containsKey(LocationManager.PASSIVE_PROVIDER))
loc = map.get(LocationManager.PASSIVE_PROVIDER);
if(null == loc)
Log.i(ID_TAG, "所有提供器无最近定位");
else
{
Log.i(ID_TAG, "使用提供器最近定位: " + loc.provider);
m_lastLocation = loc;
}
}
catch(Throwable e)
{
e.printStackTrace();
}
return loc;
}

public NSLocationInfo GetLastLocation()
{
return m_lastLocation;
}
}

+ 25
- 0
app/src/main/java/com/nsgk/ruralWeb/location/NSLocationInfo.java 查看文件

@@ -0,0 +1,25 @@
package com.nsgk.ruralWeb.location;

public final class NSLocationInfo
{
public final String provider;
public final double longitude;
public final double latitude;

public NSLocationInfo(String provider, double longitude, double latitude)
{
this.provider = provider;
this.longitude = longitude;
this.latitude = latitude;
}

@Override
public String toString()
{
return "定位信息: " +
"提供器=" + provider +
"; 经度=" + longitude +
"; 纬度=" + latitude
;
}
}

+ 380
- 0
app/src/main/java/com/nsgk/ruralWeb/location/NSRealtimeLocation.java 查看文件

@@ -0,0 +1,380 @@
package com.nsgk.ruralWeb.location;

import android.annotation.SuppressLint;
import android.content.Context;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.HandlerThread;
import android.os.Looper;
import android.util.Log;

import androidx.annotation.NonNull;

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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

// 主线程调用, 无线程安全
public class NSRealtimeLocation implements LocationListener
{
// 定位提供器
public static final int FLAG_PROVIDER_NETWORK = 1; // 网络定位
public static final int FLAG_PROVIDER_GPS = 1 << 1; // GPS定位

// 定位提供器优先级
public static final int FLAG_PRIORITY_NETWORK = 1 << 2; // 网络定位优先
public static final int FLAG_PRIORITY_GPS = 1 << 3; // GPS定位优先

// 线程
public static final int FLAG_BUILTIN_THREAD = 1 << 4; // 使用内建线程
public static final int FLAG_CUSTOM_THREAD = 1 << 5; // 使用外部线程
public static final int FLAG_MAIN_THREAD = 1 << 6; // 使用主线程

public static final int DEFAULT_FLAG = FLAG_PROVIDER_NETWORK | FLAG_PROVIDER_GPS | FLAG_PRIORITY_NETWORK;


public static final int OPTION_MIN_TIME = 1; // 最小定位间隔(毫秒) 默认60000
public static final int OPTION_MIN_DISTANCE = 2; // 最小定位距离变化(米) 默认10

private static final int DEFAULT_MIN_TIME = 1000;
private static final int DEFAULT_MIN_DISTANCE = 1;

private static final String ID_TAG = NSRealtimeLocation.class.getName();

private final Object m_lock = new Object();

private LocationManager m_locationManager;
private final Context m_context;
private int m_flag = DEFAULT_FLAG;
private NSLocationInfo m_lastLocation = null;
private HandlerThread m_thread = null;
private Looper m_customLooper = null;
private AtomicInteger m_readOnce = new AtomicInteger(-1);
private Map<Integer, Object> m_options = new HashMap<>();
private String m_provider = null;
private Looper m_looper = null;

public NSRealtimeLocation(Context context)
{
m_context = context;
}

public boolean Init(int flag)
{
if(IsInitialized())
{
Log.e(ID_TAG, "定位已初始化");
return true;
}

if(!HasFlag(FLAG_PROVIDER_NETWORK | FLAG_PROVIDER_GPS))
{
Log.e(ID_TAG, "请设置定位提供器");
return false;
}

try
{
m_readOnce.set(-1);
m_flag = flag < 0 ? DEFAULT_FLAG : flag;

m_locationManager = (LocationManager) m_context.getSystemService(Context.LOCATION_SERVICE);

//初始化定位
if(HasFlag(FLAG_BUILTIN_THREAD))
{
m_thread = new HandlerThread("系统实时内建定位");
m_thread.start();
m_looper = m_thread.getLooper();
Log.d(ID_TAG, "定位使用内建线程: " + m_thread.getId());
}
else if(HasFlag(FLAG_CUSTOM_THREAD))
{
if(null == m_customLooper)
{
throw new RuntimeException("请先传入线程Looper");
}
m_looper = m_customLooper;
Log.d(ID_TAG, "定位使用用户指定线程: " + m_customLooper.getThread().getId());
}
else if(HasFlag(FLAG_MAIN_THREAD))
{
Looper mainLooper = Looper.getMainLooper();
m_looper = mainLooper;
Log.d(ID_TAG, "定位使用主线程: " + mainLooper.getThread().getId());
}
else
{
m_looper = Looper.myLooper();
Log.d(ID_TAG, "定位使用当前线程: " + Thread.currentThread().getId());
}

Log.i(ID_TAG, "定位初始化完成");
return true;
}
catch(Exception e)
{
e.printStackTrace();
m_locationManager = 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() && null != m_provider;
}

public void Start()
{
Run(-1);
}

public void Stop()
{
CheckInitialization(true);

m_locationManager.removeUpdates(this);
m_provider = null;

Log.i(ID_TAG, "实时定位停止");
}

public void Shutdown()
{
if(!IsInitialized())
{
Log.e(ID_TAG, "请先初始化");
return;
}

TerminateRead();
Stop();

m_locationManager = null;
if(null != m_thread)
{
m_thread.quit();
m_thread = null;
Log.d(ID_TAG, "实时定位内建线程结束");
}

Log.i(ID_TAG, "实时定位销毁");
}

public boolean IsInitialized()
{
return null != m_locationManager;
}

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> T GetOption(int type, T def)
{
Object o = m_options.get(type);
if(null == o)
return def;
return (T) o;
}

private void InitOptions()
{
List<String> providers = new ArrayList<>();
m_provider = null;
if(HasFlag(FLAG_PROVIDER_NETWORK))
{
if(m_locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER))
providers.add(LocationManager.NETWORK_PROVIDER);
else
Log.w(ID_TAG, "网络定位提供器未启用");
}
if(HasFlag(FLAG_PROVIDER_GPS))
{
if(m_locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER))
providers.add(LocationManager.GPS_PROVIDER);
else
Log.w(ID_TAG, "GPS定位提供器未启用");
}
if(providers.isEmpty())
throw new RuntimeException("无定位提供器");

if(HasFlag(FLAG_PRIORITY_NETWORK))
{
if(providers.contains(LocationManager.NETWORK_PROVIDER))
m_provider = LocationManager.NETWORK_PROVIDER;
else
m_provider = LocationManager.GPS_PROVIDER;
}
else if(HasFlag(FLAG_PRIORITY_GPS))
{
if(providers.contains(LocationManager.GPS_PROVIDER))
m_provider = LocationManager.GPS_PROVIDER;
else
m_provider = LocationManager.NETWORK_PROVIDER;
}
/* else
{
if(providers.contains(LocationManager.NETWORK_PROVIDER))
m_provider = LocationManager.NETWORK_PROVIDER;
else
m_provider = LocationManager.GPS_PROVIDER;
}*/

if(null == m_provider)
throw new RuntimeException("未确定定位提供器");

Log.i(ID_TAG, "使用定位器: " + m_provider);
}

@SuppressLint("MissingPermission")
private void Run(int num)
{
CheckInitialization(true);

Stop();

m_readOnce.set(num);

InitOptions();

int minTime = GetOption(OPTION_MIN_TIME, DEFAULT_MIN_TIME);
int minDistance = GetOption(OPTION_MIN_DISTANCE, DEFAULT_MIN_DISTANCE);
m_locationManager.requestLocationUpdates(m_provider, minTime, minDistance, this, m_looper);

Location lastKnownLocation = m_locationManager.getLastKnownLocation(m_provider);
SetLastLocation(lastKnownLocation);

Log.i(ID_TAG, "定位开始");
}

private void SetLastLocation(Location location)
{
if(null != location)
{
m_lastLocation = new NSLocationInfo(location.getProvider(), location.getLongitude(), location.getLatitude());
}
else
{
m_lastLocation = null;
}
}

/**
* 同步调用, 会锁住线程
*/
public NSLocationInfo Read(int count, int timeout)
{
CheckInitialization(true);
/* if(IsRunning())
{
throw new RuntimeException("请先停止定位");
}*/

if(timeout < 0)
timeout = 0;
Log.d(ID_TAG, "实时定位线程: " + Thread.currentThread().getId() + ", 超时设置: " + timeout);
synchronized(m_lock)
{
try
{
CleanLastLocation();
Run(count);
m_lock.wait(timeout);
}
catch(Exception e)
{
throw new RuntimeException(e);
}
finally
{
Stop();
}
}
return m_lastLocation;
}

public NSLocationInfo GetLastLocation()
{
return m_lastLocation;
}

public void CleanLastLocation()
{
m_lastLocation = null;
}

/*
在调用线程中执行
*/
@Override
public void onLocationChanged(@NonNull Location location)
{
Log.d(ID_TAG, "系统实时定位线程: " + Thread.currentThread().getId() + " -> " + location);
SetLastLocation(location);
//if(null != location)
{
if(m_readOnce.get() > 0)
{
m_readOnce.decrementAndGet();
if(m_readOnce.get() == 0)
{
synchronized(m_lock)
{
m_lock.notifyAll();
}
}
}
}
}

public void TerminateRead()
{
synchronized(m_lock)
{
Log.i(ID_TAG, "系统实时中止定位");
m_lock.notifyAll();
}
}
}

+ 161
- 0
app/src/main/java/com/nsgk/ruralWeb/location/NSSystemLocation.java 查看文件

@@ -0,0 +1,161 @@
package com.nsgk.ruralWeb.location;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationManager;
import android.os.Build;
import android.os.CancellationSignal;
import android.util.Log;
import android.widget.Toast;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class NSSystemLocation
{
private static final String ID_TAG = NSSystemLocation.class.getName();

private final Context m_context;
private NSLocationInfo m_lastLocation = null;
private volatile CancellationSignal m_cancellationSignal;
private volatile CompletableFuture<Location> m_future;

public NSSystemLocation(Context context)
{
m_context = context;
}

@SuppressLint("MissingPermission")
public NSLocationInfo Read(String provider, int timeout)
{
if(Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
{
Log.w(ID_TAG, "获取当前定位请求Android 11+");
return null;
}

NSLocationInfo loc = null;
try
{
LocationManager lm = (LocationManager) m_context.getSystemService(Context.LOCATION_SERVICE);
if(null != provider)
{
if(LocationManager.GPS_PROVIDER.equals(provider))
{
if(!lm.isProviderEnabled(provider))
{
Log.w(ID_TAG, "GPS定位提供器未启用, 尝试网络定位器");
provider = LocationManager.NETWORK_PROVIDER;
if(!lm.isProviderEnabled(provider))
{
Log.w(ID_TAG, "网络定位提供器也未启用");
provider = null;
}
}
}
else if(LocationManager.NETWORK_PROVIDER.equals(provider))
{
Log.w(ID_TAG, "网络定位提供器未启用, 尝试GPS定位器");
if(!lm.isProviderEnabled(provider))
{
provider = LocationManager.GPS_PROVIDER;
if(!lm.isProviderEnabled(provider))
{
Log.w(ID_TAG, "GPS定位提供器也未启用");
provider = null;
}
}
}
}

if(null == provider)
{
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE); // 高精度
// criteria.setPowerRequirement(Criteria.POWER_HIGH);
// criteria.setAltitudeRequired(false);
provider = lm.getBestProvider(criteria, true);
}
Log.i(ID_TAG, "使用高精度定位提供器: " + provider);

if(null == provider)
{
Log.i(ID_TAG, "无法获取高精度定位提供器");
return null;
}

m_cancellationSignal = new CancellationSignal();
m_future = new CompletableFuture<>();
lm.getCurrentLocation(provider, m_cancellationSignal, m_context.getMainExecutor(), m_future::complete);
Location location;
if(timeout > 0)
location = m_future.get(timeout, TimeUnit.MILLISECONDS);
else
location = m_future.get();
m_cancellationSignal = null;
m_future = null;
if(null == location)
{
Log.i(ID_TAG, "无法使用高精度提供器定位");
return null;
}
loc = new NSLocationInfo(provider, location.getLongitude(), location.getLatitude());
Log.i(ID_TAG, "使用高精度提供器获取定位: " + loc);
m_lastLocation = loc;
}
catch(Throwable e)
{
e.printStackTrace();
}
finally
{
m_cancellationSignal = null;
m_future = null;
}
return loc;
}

private void ShowToast(final String message, int duration)
{
((Activity)m_context).runOnUiThread((new Runnable() {
@Override
public void run()
{
Toast.makeText(m_context, message, duration).show();
}
}));
}

public void TerminateRead()
{
CancellationSignal signal = m_cancellationSignal;
if(null != signal && !signal.isCanceled())
{
Log.i(ID_TAG, "系统中止定位: 1. 取消定位");
signal.cancel();
}
CompletableFuture<Location> future = m_future;
if(null != future && !future.isCancelled())
{
Log.i(ID_TAG, "系统中止定位: 2. 取消等待结果");
future.cancel(true);
}
}

public void Shutdown()
{
Log.i(ID_TAG, "销毁系统定位");
TerminateRead();
}

public NSLocationInfo GetLastLocation()
{
return m_lastLocation;
}
}

+ 92
- 0
app/src/main/java/com/nsgk/ruralWeb/sys/NSConstants.java 查看文件

@@ -0,0 +1,92 @@
package com.nsgk.ruralWeb.sys;

import android.location.LocationManager;

import com.nsgk.ruralWeb.BuildConfig;
import com.nsgk.ruralWeb.enums.NSEnums;

public final class NSConstants
{
/**
* App首页链接地址
* 可以在 /gradle.properties 里配置 appHomeUrl=链接地址, 不要携带双引号
* 也可以在命令行添加 -PappHomeUrl="链接地址", 双引号可携带也可不携带
*/
public static String AppHomeUrl()
{
return BuildConfig.APP_HOME_URL;
}

/**
* App启动页版权文本
* 可以在 /gradle.properties 里配置 appCopyright=文本, 不要携带双引号
* 也可以在命令行添加 -PappCopyright="文本", 双引号可携带也可不携带
*/
public static String AppCopyright()
{
return BuildConfig.APP_COMPYRIGHT;
}

/**
* App启动页厂商文本
* 可以在 /gradle.properties 里配置 appVendor=文本, 不要携带双引号
* 也可以在命令行添加 -PappVendor="文本", 双引号可携带也可不携带
*/
public static String AppVendor()
{
return BuildConfig.APP_VENDOR;
}

/**
* App图标
*/
public static String AppIcon()
{
return BuildConfig.APP_ICON;
}

/**
* App更新下载地址
* 可以在 /gradle.properties 里配置 appUpdateUrl=链接地址, 不要携带双引号
* 也可以在命令行添加 -PappUpdateUrl="链接地址", 双引号可携带也可不携带
*/
public static String AppUpdateUrl()
{
return BuildConfig.APP_UPDATE_URL;
}

public static boolean IsHttps()
{
return AppHomeUrl().startsWith("https://");
}

public static boolean IsDebug()
{
return BuildConfig.DEBUG;
}

public static boolean IsAMapLocationEnabled()
{
return true;
}

// 偏好默认值
public static final String DEFAULT_LOCATION_MODE = NSEnums.LocationMode.REALTIME;
public static final int DEFAULT_LOCATION_GAODE_INTERVAL = 1000;
public static final int DEFAULT_LOCATION_GAODE_READ_COUNT = 1;
public static final String DEFAULT_LOCATION_PROVIDER = LocationManager.GPS_PROVIDER;
public static final int DEFAULT_LOCATION_REALTIME_INTERVAL = 1000;
public static final int DEFAULT_LOCATION_REALTIME_DISTANCE = 0; // 1
public static final int DEFAULT_LOCATION_REALTIME_READ_COUNT = 1;
public static final boolean DEFAULT_OPEN_LAST_URL = false;
public static final int DEFAULT_FONT_SCALE = 100;
public static final boolean DEFAULT_LOGCAT_CONSOLE_OUTPUT = false;
public static final int DEFAULT_LOCATION_TIMEOUT = 0;


public static final String FUSED_PROVIDER = "fused";
public static final String APK_UPDATE_DOWNLOAD_DIR = "update";
public static final String APK_UPDATE_DOWNLOAD_FILE = "latest.apk";

private NSConstants() {}
}

+ 81
- 0
app/src/main/java/com/nsgk/ruralWeb/sys/NSPreference.java 查看文件

@@ -0,0 +1,81 @@
package com.nsgk.ruralWeb.sys;

import android.content.Context;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;

import com.nsgk.ruralWeb.utils.NSStr;

public final class NSPreference
{
public static final String LOCATION_MODE = "location_mode";
public static final String LOCATION_GAODE_INTERVAL = "location_gaode_interval";
public static final String LOCATION_GAODE_READ_COUNT = "location_gaode_read_count";
public static final String LOCATION_PROVIDER = "location_provider";
public static final String LOCATION_REALTIME_INTERVAL = "location_realtime_interval";
public static final String LOCATION_REALTIME_DISTANCE = "location_realtime_distance";
public static final String LOCATION_REALTIME_READ_COUNT = "location_realtime_read_count";
public static final String OPEN_LAST_URL = "open_last_url";
public static final String LAST_ACCESS_URL = "last_access_url";
public static final String COOKIES = "cookies";
public static final String FONT_SCALE = "font_scale";
public static final String LOGCAT_CONSOLE_OUTPUT = "logcat_console_output";
public static final String LOCATION_TIMEOUT = "location_timeout";
public static final String RESET_SETTINGS = "RESET_SETTINGS";
public static final String VERSION = "VERSION";
public static final String UPDATE_DOWNLOAD = "UPDATE_DOWNLOAD";

private final Context context;

public NSPreference(Context context)
{
this.context = context;
}

public SharedPreferences Read()
{
return PreferenceManager.getDefaultSharedPreferences(context);
}

public SharedPreferences.Editor Write()
{
return PreferenceManager.getDefaultSharedPreferences(context).edit();
}

public String GetString(String name, String... def)
{
return Read().getString(name, null != def && def.length > 0 ? def[0] : null);
}

public boolean GetBool(String name, boolean... def)
{
return Read().getBoolean(name, null != def && def.length > 0 ? def[0] : false);
}

public void Remove(String name)
{
Write().remove(name).commit();
}

public void SetString(String name, String val)
{
Write().putString(name, val).commit();
}

public void SetInt(String name, int val)
{
Write().putInt(name, val).commit();
}

public int GetInt(String name, int... def)
{
return Read().getInt(name, null != def && def.length > 0 ? def[0] : 0);
}


public int GetIntFromString(String name, int defVal)
{
String str = Read().getString(name, "");
return NSStr.parseInt_s(str, defVal);
}
}

+ 506
- 0
app/src/main/java/com/nsgk/ruralWeb/ui/SettingsFragment.java 查看文件

@@ -0,0 +1,506 @@
package com.nsgk.ruralWeb.ui;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.provider.Settings;
import android.text.Html;
import android.util.Log;
import android.widget.Toast;

import androidx.appcompat.app.AlertDialog;
import androidx.core.app.NotificationCompat;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import androidx.preference.SeekBarPreference;

import com.nsgk.ruralWeb.BuildConfig;
import com.nsgk.ruralWeb.R;
import com.nsgk.ruralWeb.enums.NSEnums;
import com.nsgk.ruralWeb.sys.NSConstants;
import com.nsgk.ruralWeb.sys.NSPreference;
import com.nsgk.ruralWeb.utils.NSContextUtils;
import com.nsgk.ruralWeb.utils.NSMisc;
import com.nsgk.ruralWeb.utils.NSStr;

import java.io.File;
import java.util.Objects;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;

public class SettingsFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceChangeListener, Preference.OnPreferenceClickListener
{
private static final String ID_TAG = SettingsFragment.class.getName();

@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.settings_preference);
Preference preference;
final Context context = getContext();

preference = findPreference(NSPreference.LOCATION_MODE);
preference.setDefaultValue(NSConstants.DEFAULT_LOCATION_MODE);
preference.setOnPreferenceChangeListener(this);

preference = findPreference(NSPreference.LOCATION_GAODE_INTERVAL);
preference.setDefaultValue("" + NSConstants.DEFAULT_LOCATION_GAODE_INTERVAL);
preference.setOnPreferenceChangeListener(this);

preference = findPreference(NSPreference.LOCATION_GAODE_READ_COUNT);
preference.setDefaultValue("" + NSConstants.DEFAULT_LOCATION_GAODE_READ_COUNT);
preference.setOnPreferenceChangeListener(this);

preference = findPreference(NSPreference.OPEN_LAST_URL);
preference.setDefaultValue(NSConstants.DEFAULT_OPEN_LAST_URL);
preference.setOnPreferenceChangeListener(this);

preference = findPreference(NSPreference.LOCATION_PROVIDER);
preference.setDefaultValue(NSConstants.DEFAULT_LOCATION_PROVIDER);
preference.setOnPreferenceChangeListener(this);

preference = findPreference(NSPreference.LOCATION_REALTIME_INTERVAL);
preference.setDefaultValue("" + NSConstants.DEFAULT_LOCATION_REALTIME_INTERVAL);
preference.setOnPreferenceChangeListener(this);

preference = findPreference(NSPreference.LOCATION_REALTIME_DISTANCE);
preference.setDefaultValue("" + NSConstants.DEFAULT_LOCATION_REALTIME_DISTANCE);
preference.setOnPreferenceChangeListener(this);

preference = findPreference(NSPreference.LOCATION_REALTIME_READ_COUNT);
preference.setDefaultValue("" + NSConstants.DEFAULT_LOCATION_REALTIME_READ_COUNT);
preference.setOnPreferenceChangeListener(this);

preference = findPreference(NSPreference.FONT_SCALE);
preference.setDefaultValue(NSConstants.DEFAULT_FONT_SCALE);
preference.setOnPreferenceChangeListener(this);

preference = findPreference(NSPreference.LOGCAT_CONSOLE_OUTPUT);
preference.setDefaultValue("" + NSConstants.DEFAULT_LOGCAT_CONSOLE_OUTPUT);
preference.setOnPreferenceChangeListener(this);

preference = findPreference(NSPreference.LOCATION_TIMEOUT);
preference.setDefaultValue("" + NSConstants.DEFAULT_LOCATION_TIMEOUT);
preference.setOnPreferenceChangeListener(this);

preference = findPreference(NSPreference.RESET_SETTINGS);
preference.setOnPreferenceClickListener(this);

preference = findPreference(NSPreference.VERSION);
preference.setOnPreferenceClickListener(this);

preference = findPreference(NSPreference.UPDATE_DOWNLOAD);
preference.setOnPreferenceClickListener(this);
if(StrUtil.isBlank(NSConstants.AppUpdateUrl()))
preference.setVisible(false);

// 总是隐藏高德的设置
if(!NSConstants.IsAMapLocationEnabled() || !NSContextUtils.BuildIsDebug(context))
{
preference = findPreference("location_gaode");
preference.setVisible(false);
}
}

private boolean CheckValueIsNumber(Object newValue, Integer min)
{
if(null != newValue)
{
String str = newValue.toString();
if(StrUtil.isNotBlank(str))
{
if(!NumberUtil.isInteger(str))
{
Toast.makeText(getContext(), "请输入数字", Toast.LENGTH_SHORT).show();
return false;
}
int i = Integer.parseInt(str);
if(null != min)
{
if(i < min)
{
Toast.makeText(getContext(), "必须大于等于" + min, Toast.LENGTH_SHORT).show();
return false;
}
}
}
}
return true;
}

@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
String key = preference.getKey();

switch(key)
{
case NSPreference.LOCATION_GAODE_INTERVAL:
if(!CheckValueIsNumber(newValue, 1000))
return false;
break;
case NSPreference.LOCATION_GAODE_READ_COUNT:
if(!CheckValueIsNumber(newValue, 1))
return false;
break;
case NSPreference.LOCATION_REALTIME_INTERVAL:
if(!CheckValueIsNumber(newValue, 1000))
return false;
break;
case NSPreference.LOCATION_REALTIME_DISTANCE:
if(!CheckValueIsNumber(newValue, 0))
return false;
break;
case NSPreference.LOCATION_REALTIME_READ_COUNT:
if(!CheckValueIsNumber(newValue, 1))
return false;
break;
case NSPreference.LOCATION_TIMEOUT:
if(!CheckValueIsNumber(newValue, 0))
return false;
break;
case NSPreference.FONT_SCALE: {
int i = (int)newValue;
int newi = Math.round((float)i / 10.0f) * 10;
if(i < 50)
i = 50;
if(i != newi)
{
getView().post(() -> {
((SeekBarPreference)preference).setValue(newi);
preference.callChangeListener(newi);
});
return false;
}
break;
}
case NSPreference.LOCATION_MODE:
if(!NSConstants.IsAMapLocationEnabled() && Objects.equals(NSEnums.LocationMode.GAODE, newValue))
{
Log.w(ID_TAG, "高德定位被禁用");
return false;
}
if(Objects.equals(NSEnums.LocationMode.H5, newValue))
{
if(!NSConstants.IsHttps())
{
Log.w(ID_TAG, "非https不支持H5定位");
Toast.makeText(getContext(), "当前不支持H5定位", Toast.LENGTH_SHORT).show();
return false;
}
}
break;
}

SetSummary(key, newValue);
return true;
}

@Override
public boolean onPreferenceClick(final Preference preference) {
String key = preference.getKey();
final Context context = getContext();

switch(key)
{
case NSPreference.RESET_SETTINGS:
OpenQueryDialog("警告", "确定要重置到默认设置?", (dialog, which) -> {
ResetSettings();
SetSummary(null, null);
Toast.makeText(context, "设置已重置", Toast.LENGTH_LONG).show();
});
break;
case NSPreference.VERSION:
OpenAbout();
break;
case NSPreference.UPDATE_DOWNLOAD:
DownloadUpdate();
//DownloadUpdateExternally();
break;
}
return false;
}

private void DownloadUpdateExternally()
{
String updateUrl = NSConstants.AppUpdateUrl();
if(StrUtil.isBlank(updateUrl))
return;

Context context = getContext();
Toast.makeText(context, "正在打开下载地址......", Toast.LENGTH_SHORT).show();
NSContextUtils.OpenUrlExternally(context, updateUrl);
}

private void DownloadUpdate()
{
String updateUrl = NSConstants.AppUpdateUrl();
if(StrUtil.isBlank(updateUrl))
return;

Handler handler = new Handler(Looper.myLooper());
Context context = getContext();
Log.d(ID_TAG, "下载apk地址: " + updateUrl);
Toast.makeText(context, "开始下载......", Toast.LENGTH_SHORT).show();

int icon = context.getResources().getIdentifier("ic_launcher_" + NSConstants.AppIcon()/* + "_round"*/, "mipmap", context.getApplicationContext().getPackageName());
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

String channelId = "nsgk";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = notificationManager.getNotificationChannel(channelId);
if(null == notificationChannel)
{
NotificationChannel channel = new NotificationChannel(channelId, "下载更新", NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription("下载更新");
notificationManager.createNotificationChannel(channel);
}
}

NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId);
builder.setProgress(0, 0, true)
.setSmallIcon(icon)
.setTicker("下载更新")
.setOngoing(true)
.setWhen(System.currentTimeMillis())
.setContentTitle("下载更新")
.setContentText("开始下载......")
;
int notifyId = 0x1003;
Notification notification = builder.build();
notificationManager.notify(notifyId, notification);

// 创建文件夹并删除缓存文件
File externalFilesDir = context.getExternalFilesDir(null);
String dir = externalFilesDir.getAbsolutePath() + File.separator + NSConstants.APK_UPDATE_DOWNLOAD_DIR;
Log.d(ID_TAG, "创建下载缓存目录: " + dir);
notificationManager.notify(notifyId, builder.setContentText("创建下载缓存目录").build());
FileUtil.mkdir(dir);

String file = dir + File.separator + NSConstants.APK_UPDATE_DOWNLOAD_FILE;
Log.d(ID_TAG, "删除历史缓存文件: " + file);
notificationManager.notify(notifyId, builder.setContentText("删除历史缓存文件").build());
FileUtil.del(file);

new Thread(() -> {
long bytes = HttpUtil.downloadFile(updateUrl, new File(file), new StreamProgress() {
@Override
public void start()
{
handler.post(() -> {
notificationManager.notify(notifyId, builder.setContentText("开始下载......").setProgress(100, 0, false).build());
});
}

@Override
public void progress(long progressSize)
{
handler.post(() -> {
notificationManager.notify(notifyId, builder.setContentText("下载: " + FileUtil.readableFileSize(progressSize)).setProgress(0, 0, true).build());
});
}

@Override
public void finish()
{
handler.post(() -> {
notificationManager.notify(notifyId, builder.setContentText("下载完成").setProgress(100, 100, false).setOngoing(false).build());
});
}
});
Log.d(ID_TAG, "下载apk文件: " + FileUtil.readableFileSize(bytes));
if(bytes > 0)
{
handler.postDelayed(() -> {
notificationManager.notify(notifyId, builder.setContentText("准备安装更新").setProgress(0, 0, false).setOngoing(false).build());
Toast.makeText(context, "准备安装......", Toast.LENGTH_SHORT).show();
NSContextUtils.InstallApk(context, file);

// handler.postDelayed(() -> {
// notificationManager.cancel(notifyId);
// }, 2000);
}, 1000);
}
else
{
handler.postDelayed(() -> {
notificationManager.notify(notifyId, builder.setContentText("下载更新失败").setProgress(0, 0, false).setOngoing(false).build());
Toast.makeText(context, "下载更新失败", Toast.LENGTH_LONG).show();

// handler.postDelayed(() -> {
// notificationManager.cancel(notifyId);
// }, 2000);
}, 1000);
}
}).start();
}

private void ResetSettings()
{
NSPreference preference = new NSPreference(getContext());
preference.Write()
.putString(NSPreference.LOCATION_MODE, NSConstants.DEFAULT_LOCATION_MODE)
.putString(NSPreference.LOCATION_GAODE_INTERVAL, "" + NSConstants.DEFAULT_LOCATION_GAODE_INTERVAL)
.putString(NSPreference.LOCATION_GAODE_READ_COUNT, "" + NSConstants.DEFAULT_LOCATION_GAODE_READ_COUNT)
.putBoolean(NSPreference.OPEN_LAST_URL, NSConstants.DEFAULT_OPEN_LAST_URL)
.putString(NSPreference.LOCATION_PROVIDER, NSConstants.DEFAULT_LOCATION_PROVIDER)
.putString(NSPreference.LOCATION_REALTIME_INTERVAL, "" + NSConstants.DEFAULT_LOCATION_REALTIME_INTERVAL)
.putString(NSPreference.LOCATION_REALTIME_DISTANCE, "" + NSConstants.DEFAULT_LOCATION_REALTIME_DISTANCE)
.putString(NSPreference.LOCATION_REALTIME_READ_COUNT, "" + NSConstants.DEFAULT_LOCATION_REALTIME_READ_COUNT)
.putInt(NSPreference.FONT_SCALE, NSConstants.DEFAULT_FONT_SCALE)
.putString(NSPreference.LOCATION_TIMEOUT, "" + NSConstants.DEFAULT_LOCATION_TIMEOUT)
.putBoolean(NSPreference.LOGCAT_CONSOLE_OUTPUT, NSConstants.DEFAULT_LOGCAT_CONSOLE_OUTPUT)
.commit()
;
}

private void OpenQueryDialog(String title, String message, DialogInterface.OnClickListener listener)
{
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(title);
builder.setMessage(message);
builder.setIcon(R.drawable.no_words);
builder.setPositiveButton("确定", listener);
builder.setNegativeButton("取消", null);
AlertDialog dialog = builder.create();
dialog.show();
}

private void OpenAbout()
{
Context context = getContext();
AlertDialog.Builder builder = new AlertDialog.Builder(context);
String msg = "<br/>" + NSContextUtils.tr(context, R.string.company) + "<br/><br/>" + NSContextUtils.tr(context, R.string.copyright);
msg = "<div style='text-align:center;'>" + msg + "</div>";
builder.setTitle(R.string.app_name2);
builder.setMessage(Html.fromHtml(msg, Html.FROM_HTML_MODE_LEGACY, null, null));
int icon = context.getResources().getIdentifier("ic_launcher_" + NSConstants.AppIcon()/* + "_round"*/, "mipmap", context.getApplicationContext().getPackageName());
builder.setIcon(icon);
AlertDialog dialog = builder.create();
dialog.show();
}

@Override
public void onResume() {
super.onResume();
SetSummary(null, null);
}

public void SetSummary(String key, Object newValue)
{
Context context = getContext();
Preference preference;
String summary;
String value;
boolean b;
int i;
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);

if(key == null || NSPreference.LOCATION_MODE.equals(key))
{
preference = findPreference(NSPreference.LOCATION_MODE);
value = newValue != null ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_MODE, NSConstants.DEFAULT_LOCATION_MODE);
summary = NSContextUtils.GetListName(context, value, R.array.location_mode_values, R.array.location_mode_labels);
preference.setSummary(summary);
}

if(key == null || NSPreference.LOCATION_GAODE_INTERVAL.equals(key))
{
preference = findPreference(NSPreference.LOCATION_GAODE_INTERVAL);
value = newValue != null && StrUtil.isNotBlank(newValue.toString()) ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_GAODE_INTERVAL, "" + NSConstants.DEFAULT_LOCATION_GAODE_INTERVAL);
summary = value + "毫秒";
preference.setSummary(summary);
}

if(key == null || NSPreference.LOCATION_GAODE_READ_COUNT.equals(key))
{
preference = findPreference(NSPreference.LOCATION_GAODE_READ_COUNT);
value = newValue != null && StrUtil.isNotBlank(newValue.toString()) ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_GAODE_READ_COUNT, "" + NSConstants.DEFAULT_LOCATION_GAODE_READ_COUNT);
summary = value + "次";
preference.setSummary(summary);
}

if(key == null || NSPreference.OPEN_LAST_URL.equals(key))
{
preference = findPreference(NSPreference.OPEN_LAST_URL);
b = newValue != null ? (Boolean)newValue : sharedPreferences.getBoolean(NSPreference.OPEN_LAST_URL, NSConstants.DEFAULT_OPEN_LAST_URL);
summary = b ? "打开最近页面" : "打开默认页面";
preference.setSummary(summary);
}

if(key == null || NSPreference.LOCATION_PROVIDER.equals(key))
{
preference = findPreference(NSPreference.LOCATION_PROVIDER);
value = newValue != null ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_PROVIDER, NSConstants.DEFAULT_LOCATION_PROVIDER);
summary = NSContextUtils.GetListName(context, value, R.array.location_provider_values, R.array.location_provider_labels);
preference.setSummary(summary);
}

if(key == null || NSPreference.LOCATION_REALTIME_INTERVAL.equals(key))
{
preference = findPreference(NSPreference.LOCATION_REALTIME_INTERVAL);
value = newValue != null && StrUtil.isNotBlank(newValue.toString()) ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_REALTIME_INTERVAL, "" + NSConstants.DEFAULT_LOCATION_REALTIME_INTERVAL);
summary = value + "毫秒";
preference.setSummary(summary);
}

if(key == null || NSPreference.LOCATION_REALTIME_DISTANCE.equals(key))
{
preference = findPreference(NSPreference.LOCATION_REALTIME_DISTANCE);
value = newValue != null && StrUtil.isNotBlank(newValue.toString()) ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_REALTIME_DISTANCE, "" + NSConstants.DEFAULT_LOCATION_REALTIME_DISTANCE);
summary = value + "米";
preference.setSummary(summary);
}

if(key == null || NSPreference.LOCATION_REALTIME_READ_COUNT.equals(key))
{
preference = findPreference(NSPreference.LOCATION_REALTIME_READ_COUNT);
value = newValue != null && StrUtil.isNotBlank(newValue.toString()) ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_REALTIME_READ_COUNT, "" + NSConstants.DEFAULT_LOCATION_REALTIME_READ_COUNT);
summary = value + "次";
preference.setSummary(summary);
}

if(key == null || NSPreference.FONT_SCALE.equals(key))
{
preference = findPreference(NSPreference.FONT_SCALE);
i = newValue != null ? (int)newValue : sharedPreferences.getInt(NSPreference.FONT_SCALE, NSConstants.DEFAULT_FONT_SCALE);
if(i == 0)
i = 100;
summary = i + "%";
preference.setSummary(summary);
}

if(key == null || NSPreference.LOCATION_TIMEOUT.equals(key))
{
preference = findPreference(NSPreference.LOCATION_TIMEOUT);
value = newValue != null && StrUtil.isNotBlank(newValue.toString()) ? newValue.toString() : sharedPreferences.getString(NSPreference.LOCATION_TIMEOUT, "" + NSConstants.DEFAULT_LOCATION_TIMEOUT);
i = NSStr.parseInt_s(value, 0);
summary = i > 0 ? i + "毫秒" : "不超时";
preference.setSummary(summary);
}

if(key == null || NSPreference.LOGCAT_CONSOLE_OUTPUT.equals(key))
{
preference = findPreference(NSPreference.LOGCAT_CONSOLE_OUTPUT);
b = newValue != null ? (Boolean)newValue : sharedPreferences.getBoolean(NSPreference.LOGCAT_CONSOLE_OUTPUT, NSConstants.DEFAULT_LOGCAT_CONSOLE_OUTPUT);
summary = b ? "控制台输出到logcat" : "不输出控制台";
preference.setSummary(summary);
}

findPreference(NSPreference.VERSION).setSummary(NSContextUtils.GetAppVersion(getContext()));
}
}


+ 122
- 0
app/src/main/java/com/nsgk/ruralWeb/utils/NSContextUtils.java 查看文件

@@ -0,0 +1,122 @@
package com.nsgk.ruralWeb.utils;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.widget.Toast;

import androidx.core.content.FileProvider;

import com.nsgk.ruralWeb.sys.NSConstants;

import java.io.File;

public final class NSContextUtils
{
public static void RequestLocationPermission(Activity context, int requestCode)
{
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
context.startActivityForResult(intent, requestCode);
}

public static void OpenAppSetting(Context context)
{
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", context.getApplicationContext().getPackageName(), null);
intent.setData(uri);
context.startActivity(intent);
}

public static String GetAppVersion(Context context)
{
String version = "UNKNOWN";
try
{
PackageManager manager = context.getPackageManager();
PackageInfo info = manager.getPackageInfo(context.getApplicationContext().getPackageName(), 0);
version = info.versionName;
}
catch (Exception e)
{
e.printStackTrace();
}
return version;
}

public static String GetListName(Context context, String value, int keyResource, int nameResource, String... def)
{
Resources resources = context.getResources();
String[] keys = resources.getStringArray(keyResource);
String[] names = resources.getStringArray(nameResource);

for(int i = 0; i < keys.length; i++)
{
if(keys[i].equals(value))
return names[i];
}
return null != def && def.length > 0 ? def[0] : null;
}

public static String tr(Context context, int id, Object...args)
{
return context.getResources().getString(id, args);
}

public static void OpenUrlExternally(Context context, String url)
{
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
context.startActivity(intent);
}

public static void InstallApk(Context context, String apkFile)
{
File file = new File(apkFile);
if(!file.isFile())
return;

String packageName = context.getPackageName();
Uri apkUri = FileProvider.getUriForFile(context, packageName + ".fileprovider", file);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (!context.getPackageManager().canRequestPackageInstalls()) {
Toast.makeText(context, "Android 8以上安装apk请求允许未知来源App", Toast.LENGTH_LONG).show();
Intent settingsIntent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Uri.parse("package:" + packageName));
context.startActivity(settingsIntent);
} else {
context.startActivity(intent);
}
} else {
context.startActivity(intent);
}
}

public static boolean BuildIsDebug(Context context)
{
try
{
ApplicationInfo info = context.getApplicationInfo();
System.err.println(((info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) + " -> " + NSConstants.IsDebug());
return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 || NSConstants.IsDebug();
}
catch (Exception e)
{
e.printStackTrace();
return NSConstants.IsDebug(); // default is release
}
}

private NSContextUtils() {}
}

+ 19
- 0
app/src/main/java/com/nsgk/ruralWeb/utils/NSMisc.java 查看文件

@@ -0,0 +1,19 @@
package com.nsgk.ruralWeb.utils;

public final class NSMisc
{
public static void noexcept(Runnable runnable)
{
if(null != runnable)
{
try
{
runnable.run();
}
catch(Throwable e)
{
e.printStackTrace();
}
}
}
}

+ 38
- 0
app/src/main/java/com/nsgk/ruralWeb/utils/NSStr.java 查看文件

@@ -0,0 +1,38 @@
package com.nsgk.ruralWeb.utils;

import java.net.URL;

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;

public final class NSStr
{
public static String GetUrlPart(String str)
{
URL url = URLUtil.url(str);
String protocol = url.getProtocol();
String host = url.getHost();
int port = url.getPort();
StringBuilder buf = new StringBuilder();
buf.append(protocol).append("://").append(host);
if(port >= 0)
buf.append(':').append(port);
return buf.toString();
}

public static int parseInt_s(String str, int...def)
{
int res = null != def && def.length > 0 ? def[0] : 0;
if(StrUtil.isBlank(str))
return res;
try
{
return Integer.parseInt(str);
}
catch(Exception e)
{
e.printStackTrace();
return res;
}
}
}

+ 468
- 0
app/src/main/java/com/nsgk/ruralWeb/web/NSEnvWindowObject.java 查看文件

@@ -0,0 +1,468 @@
package com.nsgk.ruralWeb.web;

import android.app.Activity;

import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.os.Handler;
import android.util.Log;
import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import android.webkit.WebView;
import android.widget.Toast;

import androidx.core.app.ActivityCompat;

import com.nsgk.ruralWeb.FullscreenActivity;
import com.nsgk.ruralWeb.R;
import com.nsgk.ruralWeb.enums.NSEnums;
import com.nsgk.ruralWeb.location.NSLastKnownLocation;
import com.nsgk.ruralWeb.location.NSLocationInfo;
import com.nsgk.ruralWeb.location.NSAMapLocation;
import com.nsgk.ruralWeb.location.NSRealtimeLocation;
import com.nsgk.ruralWeb.location.NSSystemLocation;
import com.nsgk.ruralWeb.sys.NSConstants;
import com.nsgk.ruralWeb.sys.NSPreference;
import com.nsgk.ruralWeb.utils.NSContextUtils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class NSEnvWindowObject
{
private static final String ID_TAG = NSEnvWindowObject.class.getName();

private final Handler m_handler;
private final Context m_context;
private final WebView m_webView;

private final NSSystemLocation m_systemLocation;
private final NSLastKnownLocation m_lastKnownLocation;
private final NSAMapLocation m_amapLocation;
private final NSRealtimeLocation m_realtimeLocation;

private final NSPreference m_preference;
// 最近一次获取到的定位坐标, 如果获取定位失败则返回此值. 可能不需要, 因为PASSIVE_PROVIDER就为最近的定位
private String m_lastLocation = null;

private static final int LOCATION_STATE_READY = 0; // 准备
private static final int LOCATION_STATE_PROCESSING = 1; // 进行中
private static final int LOCATION_STATE_FINISH = 2; // 完成
private static final int LOCATION_STATE_TERMINATED = 3; // 中止

private AtomicInteger m_locationState = new AtomicInteger(LOCATION_STATE_READY);

public NSEnvWindowObject(Context context, Handler handler, WebView webView)
{
m_context = context;
m_handler = handler;
m_webView = webView;
m_systemLocation = new NSSystemLocation(m_context);
m_lastKnownLocation = new NSLastKnownLocation(m_context);
m_amapLocation = new NSAMapLocation(m_context);
m_realtimeLocation = new NSRealtimeLocation(m_context);
m_preference = new NSPreference(m_context);
}

public void OnDestroy()
{
if(m_amapLocation.IsInitialized())
m_amapLocation.Shutdown();
if(m_realtimeLocation.IsInitialized())
m_realtimeLocation.Shutdown();
m_systemLocation.Shutdown();
}

@JavascriptInterface
public void Toast(final String message)
{
ShowToast(message, Toast.LENGTH_LONG);
}

private void ShowToast(final String message, int duration)
{
RunOnUIThread(() -> {
Toast.makeText(m_context, message, duration).show();
});
}

@JavascriptInterface
public void Log(final String message)
{
Log.i(ID_TAG, message);
}

@JavascriptInterface
public String GetLocation(final String type)
{
Log.d(ID_TAG, "Web线程: " + Thread.currentThread().getId());

if(!CheckLocationPermission())
{
return m_lastLocation;
}

SetLocationState(LOCATION_STATE_READY);
ShowIndicator();
NSLocationInfo loc;
String location = CurrentLocationMode();
String provider = m_preference.GetString(NSPreference.LOCATION_PROVIDER, NSConstants.DEFAULT_LOCATION_PROVIDER);
Log.d(ID_TAG, "定位提供器: " + provider);
SetLocationState(LOCATION_STATE_PROCESSING);
switch(location)
{
case NSEnums.LocationMode.GAODE:
loc = GetAMapLocation();
break;
case NSEnums.LocationMode.REALTIME:
loc = GetRealtimeLocation();
break;
case NSEnums.LocationMode.SYSTEM:
default:
loc = GetCurrentLocation(provider);
break;
}
SetLocationState(LOCATION_STATE_FINISH, LOCATION_STATE_PROCESSING);
if(null == loc)
{
if(IsLocationState(LOCATION_STATE_FINISH))
ShowToast("定位失败, 请先开启位置服务", Toast.LENGTH_LONG);
loc = GetLastKnownLocation(provider);
}

HideIndicator();
return SetLastLocation(loc);
}

public String CurrentLocationMode()
{
return m_preference.GetString(NSPreference.LOCATION_MODE, NSConstants.DEFAULT_LOCATION_MODE);
}

@JavascriptInterface
public String GetLocationMode()
{
return CurrentLocationMode();
}

@JavascriptInterface
public int GetLocationTimeout()
{
return m_preference.GetIntFromString(NSPreference.LOCATION_TIMEOUT, NSConstants.DEFAULT_LOCATION_TIMEOUT);
}

@JavascriptInterface
public String SelectLocationMode()
{
final Object lock = new Object();
String current = CurrentLocationMode();
String[] res = { current };
synchronized(lock) {
try
{
RunOnUIThread(() -> {
AlertDialog.Builder builder = new AlertDialog.Builder(m_context);
String[] locationModeLabels = m_context.getResources().getStringArray(R.array.location_mode_labels);
String[] locationModeValues = m_context.getResources().getStringArray(R.array.location_mode_values);

List<String> locationModeList = new ArrayList<>(Arrays.asList(locationModeLabels));
List<String> locationModeValueList = new ArrayList<>(Arrays.asList(locationModeValues));

int[] selected = { locationModeValueList.indexOf(current) };

builder.setTitle("选择定位方式")
.setSingleChoiceItems(locationModeList.toArray(new String[0]), selected[0], (DialogInterface dialog, int which) -> {
selected[0] = which;
})
//.setCancelable(false)
;

builder.setPositiveButton("确定", (DialogInterface dialog, int which) -> {
if(selected[0] >= 0)
{
String mode = locationModeValueList.get(selected[0]);
if(NSEnums.LocationMode.H5.equalsIgnoreCase(mode))
{
if(!NSConstants.IsHttps())
{
Toast.makeText(m_context, "当前不支持H5定位", Toast.LENGTH_SHORT).show();
return;
}
}

m_preference.SetString(NSPreference.LOCATION_MODE, mode);
res[0] = mode;
String name = locationModeList.get(selected[0]);
Toast.makeText(m_context, "使用" + name + "定位", Toast.LENGTH_SHORT).show();
}
else
Toast.makeText(m_context, "请选择定位方式", Toast.LENGTH_SHORT).show();
});
builder.setNeutralButton("默认", (DialogInterface dialog, int which) -> {
res[0] = NSConstants.DEFAULT_LOCATION_MODE;
m_preference.SetString(NSPreference.LOCATION_MODE, res[0]);
int choose = locationModeValueList.indexOf(res[0]);
String name = locationModeList.get(choose);
Toast.makeText(m_context, "使用" + name + "定位", Toast.LENGTH_SHORT).show();
});
builder.setNegativeButton("取消", null);

AlertDialog dialog = builder.create();
dialog.setOnDismissListener((DialogInterface d) -> {
synchronized(lock) {
lock.notifyAll();
}
});
dialog.show();
});
lock.wait();
}
catch(Exception e)
{
e.printStackTrace();
}
}
return res[0];
}

private String ReturnLocation(NSLocationInfo loc)
{
return loc.longitude + "," + loc.latitude;
}

private String SetLastLocation(NSLocationInfo loc)
{
if(null != loc)
m_lastLocation = ReturnLocation(loc);
return m_lastLocation;
}

private NSLocationInfo GetCurrentLocation(String type)
{
Log.i(ID_TAG, "使用系统当前定位");
NSLocationInfo loc = null;
try
{
int timeout = m_preference.GetIntFromString(NSPreference.LOCATION_TIMEOUT, NSConstants.DEFAULT_LOCATION_TIMEOUT);
long start = System.currentTimeMillis();
loc = m_systemLocation.Read(type, timeout);
long end = System.currentTimeMillis();
Log.i(ID_TAG, "系统当前定位结果: " + loc + ", 耗时=" + (end - start) + "毫秒");
}
catch(Throwable e)
{
e.printStackTrace();
}
return loc;
}

private NSLocationInfo GetLastKnownLocation(String type)
{
Log.i(ID_TAG, "使用系统最近定位");
NSLocationInfo loc;
try
{
long start = System.currentTimeMillis();
loc = m_lastKnownLocation.Read(type);
long end = System.currentTimeMillis();
Log.i(ID_TAG, "系统定位最近结果: " + loc + ", 耗时=" + (end - start) + "毫秒");
}
catch(Throwable e)
{
e.printStackTrace();
}
return m_lastKnownLocation.GetLastLocation();
}

private NSLocationInfo GetRealtimeLocation()
{
Log.i(ID_TAG, "使用系统实时定位");
NSLocationInfo loc = null;
try
{
NSPreference preference = new NSPreference(m_context);
if(!m_realtimeLocation.IsInitialized())
{
m_realtimeLocation.SetOption(NSRealtimeLocation.OPTION_MIN_TIME, preference.GetIntFromString(NSPreference.LOCATION_REALTIME_INTERVAL, NSConstants.DEFAULT_LOCATION_REALTIME_INTERVAL));
m_realtimeLocation.SetOption(NSRealtimeLocation.OPTION_MIN_DISTANCE, preference.GetIntFromString(NSPreference.LOCATION_REALTIME_DISTANCE, NSConstants.DEFAULT_LOCATION_REALTIME_DISTANCE));
int flag = NSRealtimeLocation.DEFAULT_FLAG | NSRealtimeLocation.FLAG_BUILTIN_THREAD;
String provider = preference.GetString(NSPreference.LOCATION_PROVIDER, NSConstants.DEFAULT_LOCATION_PROVIDER);
if(LocationManager.GPS_PROVIDER.equals(provider))
{
flag &= ~NSRealtimeLocation.FLAG_PRIORITY_NETWORK;
flag |= NSRealtimeLocation.FLAG_PRIORITY_GPS;
}
m_realtimeLocation.Init(flag);
}
long start = System.currentTimeMillis();
int count = preference.GetIntFromString(NSPreference.LOCATION_REALTIME_READ_COUNT, NSConstants.DEFAULT_LOCATION_REALTIME_READ_COUNT);
int timeout = preference.GetIntFromString(NSPreference.LOCATION_TIMEOUT, NSConstants.DEFAULT_LOCATION_TIMEOUT);
loc = m_realtimeLocation.Read(count, timeout);
long end = System.currentTimeMillis();
Log.i(ID_TAG, "系统实时定位结果: " + loc + ", 耗时=" + (end - start) + "毫秒");
}
catch(Throwable e)
{
e.printStackTrace();
}
return loc;
}

public NSLocationInfo GetAMapLocation()
{
Log.i(ID_TAG, "使用高德定位");
NSLocationInfo loc = null;
try
{
NSPreference preference = new NSPreference(m_context);
if(!m_amapLocation.IsInitialized())
{
m_amapLocation.SetOption(NSAMapLocation.OPTION_INTERVAL, preference.GetIntFromString(NSPreference.LOCATION_GAODE_INTERVAL, NSConstants.DEFAULT_LOCATION_GAODE_INTERVAL));
m_amapLocation.Init(NSAMapLocation.DEFAULT_FLAG | NSAMapLocation.FLAG_BUILTIN_THREAD);
}
long start = System.currentTimeMillis();
int count = preference.GetIntFromString(NSPreference.LOCATION_GAODE_READ_COUNT, NSConstants.DEFAULT_LOCATION_GAODE_READ_COUNT);
int timeout = preference.GetIntFromString(NSPreference.LOCATION_TIMEOUT, NSConstants.DEFAULT_LOCATION_TIMEOUT);
loc = m_amapLocation.Read(count, timeout);
long end = System.currentTimeMillis();
Log.i(ID_TAG, "高德定位结果: " + loc + ", 耗时=" + (end - start) + "毫秒");
}
catch(Throwable e)
{
e.printStackTrace();
}
return loc;
}

private boolean CheckLocationPermission()
{
if(ActivityCompat.checkSelfPermission(m_context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(m_context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
{
Activity activity = (Activity) m_context;

if (activity.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION) && activity.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_COARSE_LOCATION)) // do not ask
{
Toast.makeText(m_context, "请先允许定位服务", Toast.LENGTH_LONG).show();
NSContextUtils.RequestLocationPermission(activity, FullscreenActivity.PERMISSION_LOCATION_REQUEST_CODE);
}
else
{
activity.requestPermissions(new String[] { Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION }, FullscreenActivity.PERMISSION_LOCATION_REQUEST_CODE);
}

return false;
}
else
return true;
}

protected void RunOnUIThread(Runnable runnable)
{
if(null != m_handler)
m_handler.post(runnable);
else if(m_context instanceof Activity)
((Activity)m_context).runOnUiThread(runnable);
else
{
Log.e(ID_TAG, "无法在UI线程中执行");
}
}

protected void CallJSFunc(final String func, Object...args)
{
StringBuilder sb = new StringBuilder();
for(int i = 0; i < args.length; i++)
{
sb.append("'").append(args[i].toString()).append("'");
if(i < args.length - 1)
sb.append(", ");
}
final String arg = sb.toString();
RunOnUIThread(() -> {
String script = "javascript:typeof(" + func + ") == 'function' && " + func + "(" + arg + ");";
Log.e(ID_TAG, String.format("调用js函数: 函数(%s), 参数(%s)", func, arg));
m_webView.evaluateJavascript(script, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
Log.e(ID_TAG, String.format("js函数 %s 返回: %s", func, value));
}
});
});
}

protected void CallJSFunc_beforeAndroid4_4(final String func, Object...args)
{
StringBuilder sb = new StringBuilder();
for(int i = 0; i < args.length; i++)
{
sb.append("'").append(args[i].toString()).append("'");
if(i < args.length - 1)
sb.append(", ");
}
final String arg = sb.toString();
RunOnUIThread(() -> {
Log.e(ID_TAG, String.format("调用js函数: 函数(%s), 参数(%s)", func, arg));
String script = "javascript:typeof(" + func + ") == 'function' && " + func + "(" + arg + ");";
Log.e(ID_TAG, script);
m_webView.loadUrl(script, null);
});
}

protected void ShowIndicator()
{
RunOnUIThread(() -> {
((FullscreenActivity)(m_context)).SetIndicatorVisible(true);
});
}

protected void HideIndicator()
{
RunOnUIThread(() -> {
((FullscreenActivity)(m_context)).SetIndicatorVisible(false);
});
}

public void TerminateLocation()
{
if(!IsLocationState(LOCATION_STATE_PROCESSING))
return;
SetLocationState(LOCATION_STATE_TERMINATED, LOCATION_STATE_PROCESSING);
String location = CurrentLocationMode();
Log.i(ID_TAG, "中止定位: " + location);
switch(location)
{
case NSEnums.LocationMode.GAODE:
m_amapLocation.TerminateRead();
break;
case NSEnums.LocationMode.REALTIME:
m_realtimeLocation.TerminateRead();
break;
case NSEnums.LocationMode.SYSTEM:
m_systemLocation.TerminateRead();
break;
}

HideIndicator();
}

private void SetLocationState(int st)
{
m_locationState.set(st);
}

private void SetLocationState(int st, int cur)
{
m_locationState.compareAndSet(cur, st);
}

private boolean IsLocationState(int st)
{
return m_locationState.get() == st;
}
}

+ 12
- 0
app/src/main/res/drawable/indicator_background.xml 查看文件

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="@color/black_overlay" />

<padding
android:bottom="10dp"
android:left="20dp"
android:right="20dp"
android:top="10dp" />
<corners android:radius="10dp" />

</shape>

+ 4
- 2
app/src/main/res/layout/activity_welcome.xml 查看文件

@@ -32,19 +32,21 @@
android:layout_marginBottom="8dp">

<TextView
android:id="@+id/welcome_text_copyright"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="Copyright © 2024 ZNRX All Rights Reserved." />
android:text="@string/copyright" />

<TextView
android:id="@+id/welcome_text_vendor"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:textStyle="bold"
android:text="中农融信(北京)科技股份有限公司" />
android:text="@string/company" />

</LinearLayout>


+ 24
- 0
app/src/main/res/layout/location_indicator.xml 查看文件

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/indicator_background">

<ProgressBar
android:id="@+id/progress_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:indeterminate="true"
/>

<Button
android:id="@+id/terminate_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="2dp"
android:text="停止" />

</LinearLayout>

+ 28
- 0
app/src/main/res/values/arrays.xml 查看文件

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>

<resources>

<string-array name="location_mode_labels">
<item>系统(实时)</item>
<item>系统</item>
<item>H5</item>
<item>高德</item>
</string-array>

<string-array name="location_mode_values">
<item>realtime</item>
<item>system</item>
<item>h5</item>
<item>gaode</item>
</string-array>

<string-array name="location_provider_labels">
<item>卫星</item>
<item>网络</item>
</string-array>

<string-array name="location_provider_values">
<item>gps</item>
<item>network</item>
</string-array>
</resources>

+ 5
- 0
app/src/main/res/values/strings.xml 查看文件

@@ -3,4 +3,9 @@
<string name="app_name">宅基地</string>
<string name="dummy_button">Dummy Button</string>
<string name="dummy_content">DUMMY\nCONTENT</string>
<string name="copyright">Copyright © 2024 ZNRX All Rights Reserved.</string>
<string name="company">中农融信(北京)科技股份有限公司</string>


<string name="settings_name">设置</string>
</resources>

+ 7
- 0
app/src/main/res/xml/file_paths.xml 查看文件

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path path="Android/data/com.nsgk.ruralWeb" name="external_root"/>
<external-path path="nsgk/ruralWeb" name="ruralweb_root"/>
<external-path path="nsgk" name="nsgk_root"/>
<external-path path="." name="external_storage_root"/>
</paths>

+ 149
- 0
app/src/main/res/xml/settings_preference.xml 查看文件

@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:title="设置">
<PreferenceCategory
android:key="base"
android:title="基础"
>
<SwitchPreference
android:title="进入App打开最近的页面"
android:key="open_last_url"
android:persistent="true"
android:defaultValue="false"
android:summary="打开最近的页面"
/>
<SeekBarPreference
android:title="字体缩放倍数(%)"
android:key="font_scale"
android:persistent="true"
android:defaultValue="100"
app:min="50"
android:max="300"
app:showSeekBarValue="true"
android:summary="字体缩放倍数"
/>
<ListPreference
android:dialogTitle="定位方式"
android:key="location_mode"
android:entries="@array/location_mode_labels"
android:entryValues="@array/location_mode_values"
android:summary="定位方式"
android:persistent="true"
android:title="选择定位方式"
android:defaultValue="realtime"
/>
<EditTextPreference
android:dialogTitle="定位超时(毫秒) 0为不超时"
android:key="location_timeout"
android:summary="定位超时"
android:persistent="true"
android:title="设置定位超时"
android:inputType="number"
android:defaultValue="0"
/>
</PreferenceCategory>

<PreferenceCategory
android:key="location_gaode"
android:title="高德定位"
>
<EditTextPreference
android:dialogTitle="定位间隔(毫秒)"
android:key="location_gaode_interval"
android:summary="定位间隔"
android:persistent="true"
android:title="设置定位间隔"
android:inputType="number"
android:defaultValue="1000"
/>
<EditTextPreference
android:dialogTitle="每次定位次数"
android:key="location_gaode_read_count"
android:summary="定位次数"
android:persistent="true"
android:title="设置定位次数"
android:inputType="number"
android:defaultValue="1"
/>
</PreferenceCategory>

<PreferenceCategory
android:key="location_system"
android:title="系统定位"
>
<ListPreference
android:dialogTitle="定位提供器"
android:key="location_provider"
android:entries="@array/location_provider_labels"
android:entryValues="@array/location_provider_values"
android:summary="定位提供器"
android:persistent="true"
android:title="选择定位提供器"
android:defaultValue="gps"
/>
<EditTextPreference
android:dialogTitle="定位间隔(毫秒)"
android:key="location_realtime_interval"
android:summary="定位间隔"
android:persistent="true"
android:title="设置定位间隔"
android:inputType="number"
android:defaultValue="1000"
/>
<EditTextPreference
android:dialogTitle="定位距离(米)"
android:key="location_realtime_distance"
android:summary="定位距离"
android:persistent="true"
android:title="设置定位距离"
android:inputType="number"
android:defaultValue="0"
/>
<EditTextPreference
android:dialogTitle="每次定位次数"
android:key="location_realtime_read_count"
android:summary="定位次数"
android:persistent="true"
android:title="设置定位次数"
android:inputType="number"
android:defaultValue="1"
/>
</PreferenceCategory>

<PreferenceCategory
android:key="other"
android:title="其他"
>
<SwitchPreference
android:title="控制台输出到logcat"
android:key="logcat_console_output"
android:persistent="true"
android:defaultValue="false"
android:summary="控制台输出到logcat"
/>

<Preference
android:key="RESET_SETTINGS"
android:summary="重置所有设置为默认值"
android:title="重置设置"
android:persistent="false">
</Preference>

<Preference
android:key="UPDATE_DOWNLOAD"
android:summary="下载更新"
android:title="下载更新"
android:persistent="false">
</Preference>

<Preference
android:key="VERSION"
android:title="版本"
android:summary=""
android:persistent="false">
</Preference>
</PreferenceCategory>

</PreferenceScreen>

+ 17
- 0
app/src/main/res/xml/shortcuts.xml 查看文件

@@ -0,0 +1,17 @@
<shortcuts xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">

<shortcut
android:shortcutId="shortcut_settings"
android:enabled="true"
android:icon="@drawable/no_words"
android:shortcutShortLabel="@string/settings_name"
tools:targetApi="n_mr1">
<intent
android:action="android.intent.action.VIEW"
android:targetPackage="com.nsgk.ruralWeb"
android:targetClass="com.nsgk.ruralWeb.SettingsActivity"
android:name="android.shortcut.conversation"/>
</shortcut>

</shortcuts>

+ 24
- 1
gradle.properties 查看文件

@@ -14,4 +14,27 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
android.useAndroidX=true

# Signing
storeFile=../app-keystore.jks
storePassword=ns61GK32x%
keyAlias=nsgk_rural_web
keyPassword=ns61GK32x%

# Command line arguments
# [String] App home url, it will save to @string/app_home_url
appHomeUrl=http://218.59.175.43:71/yinnongLogin
# [String] App custom name, it will be set on res/values/strings.xml
appName=@string/app_name
# [String] App icon mipmap key name in res/mipmaps, icon = ic_launcher_${appIconKey}, round icon = ic_launcher_${appIconKey}_round
appIconKey=yhzl
# [String] App copyright name, it will save to @string/app_copyright. default using @string/copyright
appCopyright=
# [String] App vendor name, it will save to @string/app_vendor. default using @string/company
appVendor=
# [String] App update download url, it will save to @string/app_update_url
# http://218.59.175.43:8090/nsgk/qixingguan.apk
appUpdateUrl=
# gaode amap key
amapKey=490bef43ef0182379aa1a8bacc45d054

+ 172
- 0
gradlew 查看文件

@@ -0,0 +1,172 @@
#!/usr/bin/env sh

##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn () {
echo "$*"
}

die () {
echo
echo "$*"
echo
exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`

# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option

if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi

# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")

# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"

# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi

exec "$JAVACMD" "$@"

+ 84
- 0
gradlew.bat 查看文件

@@ -0,0 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto init

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:init
@rem Get command-line arguments, handling Windows variants

if not "%OS%" == "Windows_NT" goto win9xME_args

:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2

:win9xME_args_slurp
if "x%~1" == "x" goto execute

set CMD_LINE_ARGS=%*

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega

+ 7
- 0
安装-发布包.bat 查看文件

@@ -0,0 +1,7 @@
@echo off

echo 安装apk(发布版)

call adb install app\build\outputs\apk\release\app-release.apk

pause;

+ 7
- 0
安装-调试包.bat 查看文件

@@ -0,0 +1,7 @@
@echo off

echo °²×°apk(Debug)

call adb install app\build\outputs\apk\debug\app-debug.apk

pause;

+ 10
- 0
打包-debug.bat 查看文件

@@ -0,0 +1,10 @@
@echo off

echo 构建apk(调试包)

call gradlew assembleDebug

echo 打开app-debug.apk所在目录......
start "" app\build\outputs\apk\debug

pause;

+ 48
- 0
打包-正式.bat 查看文件

@@ -0,0 +1,48 @@
@echo off

echo 构建apk(正式)

echo 用法: %0 App主页链接地址 App名称 App图标 "App版权信息" App厂商信息
echo 任意参数不传则使用App项目里gradle.properties的默认配置

set NUM_ARG=0
for %%x in (%*) do (
set /a NUM_ARG+=1
)
echo 参数数量: %NUM_ARG%

if %NUM_ARG% GEQ 5 (
echo App主页链接地址: %1
echo App名称: %2
echo App图标: ic_launcher_%3; 圆角图标: ic_launcher_%3_round
echo App版权信息: %4
echo App厂商信息: %5
call gradlew assembleRelease -PappHomeUrl=%1 -PappName=%2 -PappIconKey=%3 -PappCopyright=%4 -PappVendor=%5
) else if %NUM_ARG% GEQ 4 (
echo App主页链接地址: %1
echo App名称: %2
echo App图标: ic_launcher_%3 圆角图标: ic_launcher_%3_round
echo App版权信息: %4
call gradlew assembleRelease -PappHomeUrl=%1 -PappName=%2 -PappIconKey=%3 -PappCopyright=%4
) else if %NUM_ARG% GEQ 3 (
echo App主页链接地址: %1
echo App名称: %2
echo App图标: ic_launcher_%3; 圆角图标: ic_launcher_%3_round
call gradlew assembleRelease -PappHomeUrl=%1 -PappName=%2 -PappIconKey=%3
) else if %NUM_ARG% GEQ 2 (
echo App主页链接地址: %1
echo App名称: %2
call gradlew assembleRelease -PappHomeUrl=%1 -PappName=%2
) else if %NUM_ARG% GEQ 1 (
echo App主页链接地址: %1
call gradlew assembleRelease -PappHomeUrl=%1
) else (
echo 使用gradle.properties默认配置
call gradlew assembleRelease
)

echo 打开app-release.apk所在目录......
start "" app\build\outputs\apk\release

pause;

+ 15
- 0
生成keystore.bat 查看文件

@@ -0,0 +1,15 @@
@echo off

echo 生成keystore

call keytool -genkeypair -v -keystore app-keystore.jks -alias nsgk_rural_web -keyalg RSA -keysize 2048 -validity 365000

rem 密钥库口令: ns61GK32x%
rem 您的名字与姓氏是什么? ZZL
rem 您的组织单位名称是什么? NSGK
rem 您的组织名称是什么? NSGK
rem 您所在的城市或区域名称是什么?
rem 您所在的省/市/自治区名称是什么?
rem 该单位的双字母国家/地区代码是什么? CN

pause;

正在加载...
取消
保存