Я сделал приложение погоды, которое содержит одно действие.
activity_main. xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/ic_clear_day"
tools:context=".MainActivity">
<TextView
android:id="@+id/city"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="@string/city_name"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/temperature" />
<TextView
android:id="@+id/temperature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="200dp"
android:text="@string/weather_temp"
android:textSize="48sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/getWeatherInfo"
style="@style/Theme.AppCompat.Light"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="28dp"
android:layout_marginBottom="44dp"
android:drawableStart="@drawable/ic_renew_info"
android:onClick="renewWeatherInfo"
android:text="@string/request_info"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/humidity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/humidity"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/city" />
<EditText
android:id="@+id/searchBox"
android:layout_width="246dp"
android:layout_height="37dp"
android:layout_marginStart="28dp"
android:layout_marginTop="32dp"
android:drawableLeft="@drawable/ic_city"
android:ems="10"
android:hint=" 输入要查询的城市..."
android:inputType="text"
android:textSize="12sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/searchCity"
android:layout_width="58dp"
android:layout_height="42dp"
android:layout_marginStart="322dp"
android:layout_marginTop="28dp"
android:layout_marginEnd="31dp"
android:drawableTop="@drawable/ic_search_city"
android:onClick="searchForCity"
android:text="@string/placeholder"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@+id/searchBox"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/airQuality"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="@string/air_quality"
app:layout_constraintBottom_toTopOf="@+id/temperature"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.986" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity. java
package xyz.geshkii.weather;
import xyz.geshkii.weather.R;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.icu.text.Edits;
import android.location.Location;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.alibaba.fastjson.JSON;
import com.google.android.material.snackbar.Snackbar;
import com.jaeger.library.StatusBarUtil;
import org.jetbrains.annotations.NotNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Text;
import java.io.IOException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
/**
* @author sudo2u
*/
public class MainActivity extends AppCompatActivity {
static final String weatherInfoBaseUrl = "https://api.caiyunapp.com/v2.5/";
public static String weatherInfoToken = "ABqYRMOVFp2oTB7l",longitude, latitude;
static final String geocodeBaseUrl = "https://restapi.amap.com/v3/geocode/geo?";
public static String geocodeKey = "45ef5896ebf2469215dd0e4ab9964457";
public static String geocodeCity, geocodeAddress;
static final String regeocodeBaseUrl = "https://restapi.amap.com/v3/geocode/regeo?", regeocodeKey = "45ef5896ebf2469215dd0e4ab9964457";
static String[] permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION};
private String locationProvider;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
StatusBarUtil.setTransparent(this);
if(checkNetWork()){
getGPSLocation(this);
requestWeatherInfo();
getReGeoInformation();
}
}
public boolean checkNetWork() {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
if (networkInfo == null || !networkInfo.isAvailable()) {
Toast toast = Toast.makeText(getApplicationContext(), "网络异常", Toast.LENGTH_SHORT);
toast.show();
return false;
}
return true;
}
public void checkPermissions(Context context) {
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_FINE_LOCATION) || ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.ACCESS_COARSE_LOCATION)) {
showPermissionRationale();
} else {
requestPermissions(permissions, 1);
}
}
}
public void showPermissionRationale() {
Snackbar.make(findViewById(R.id.layout), "天气需要精确位置来提供服务,是否授权?", Snackbar.LENGTH_LONG)
.setAction("授权", new View.OnClickListener() {
@Override
public void onClick(View view) {
requestPermissions(permissions, 1);
}
}).show();
}
public void getGPSLocation(Context context) {
LocationManager loc = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
List<String> providers = loc.getProviders(true);
/*
for(String prov : providers){
Log.i("GPS Providers", prov);
}
*/
if (providers.contains(LocationManager.GPS_PROVIDER)) {
//如果是GPS定位
locationProvider = LocationManager.GPS_PROVIDER;
}
else if(providers.contains(LocationManager.PASSIVE_PROVIDER)){
locationProvider = LocationManager.PASSIVE_PROVIDER;
}
else if(providers.contains(LocationManager.NETWORK_PROVIDER)){
locationProvider = LocationManager.NETWORK_PROVIDER ;
}
else {
Toast.makeText(this, "没有可用的位置提供器", Toast.LENGTH_SHORT).show();
}
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
showPermissionRationale();
getGPSLocation(this);
}
else{
Location location = loc.getLastKnownLocation(locationProvider);
if (location!=null) {
longitude = String.valueOf(location.getLongitude());
latitude = String.valueOf(location.getLatitude());
Log.i("GPS Location, longitude", longitude);
Log.i("GPS Location, latitude", latitude);
}
}
}
private String getGeocodeInfoUrl() { return geocodeBaseUrl + "key=" + geocodeKey + "&address=" + geocodeAddress + "&city=" + geocodeCity; }
private String getWeatherInfoUrl() { return weatherInfoBaseUrl + weatherInfoToken + "/" + longitude + "," + latitude + "/realtime.json"; }
private String getRegeocodeInfoUrl(){ return regeocodeBaseUrl + "key=" + regeocodeKey + "&location=" + longitude + "," + latitude + "&poitype=&radius=&extensions=base&batch=false&roadlevel=0"; }
public void renewWeatherInfo(View view) {
if(checkNetWork()){
requestWeatherInfo();
}
}
public void requestWeatherInfo() {
String url = getWeatherInfoUrl();
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
final String result = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
deserializeWeatherInfo(result);
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
});
}
public void deserializeWeatherInfo(String result) throws JSONException {
JSONObject totalRes = new JSONObject(result);
String totalResResult = totalRes.optString("result");
Log.i("Weather info", totalResResult);
JSONObject realtimeRes = new JSONObject(totalResResult);
String realtimeResResult = realtimeRes.optString("realtime");
JSONObject weatherInfoRes = new JSONObject(realtimeResResult);
double temp = weatherInfoRes.optDouble("temperature");
double humidity = weatherInfoRes.optDouble("humidity");
String skyCondition = weatherInfoRes.optString("skycon");
String description = weatherInfoRes.optString("air_quality");
JSONObject aqi_descriptionJson = new JSONObject(description);
String aqi_description = aqi_descriptionJson.optString("description");
JSONObject air_qualJson = new JSONObject(aqi_description);
String air_quality = air_qualJson.optString("chn");
Log.i("Air quality", air_quality);
//Set temperature
TextView tempView = (TextView) findViewById(R.id.temperature);
tempView.setText(String.valueOf(temp) + "°");
//Set humidity
TextView humidView = (TextView) findViewById(R.id.humidity);
humidView.setText(String.valueOf(humidity * 100) + "%");
//Set air quality
TextView airQualityView = (TextView) findViewById(R.id.airQuality);
airQualityView.setText("空气" + air_quality);
//Call and check sky condition
changeSkyconStatus(skyCondition);
}
//TODO: 需要做完所有的Skycon背景图
public void changeSkyconStatus(String skycon) {
ConstraintLayout layout = (ConstraintLayout) findViewById(R.id.layout);
if (skycon.equals("MODERATE_RAIN")) {
layout.setBackgroundResource(R.drawable.ic_moderate_rain);
} else if (skycon.equals("CLEAR_DAY")) {
layout.setBackgroundResource(R.drawable.ic_clear_day);
} else if (skycon.equals("CLEAR_NIGHT")) {
layout.setBackgroundResource(R.drawable.ic_clear_night);
} else if (skycon.equals("CLOUDY")) {
layout.setBackgroundResource(R.drawable.ic_cloudy);
} else if (skycon.equals("HEAVY_RAIN")) {
layout.setBackgroundResource(R.drawable.ic_heavy_rain);
} else if (skycon.equals("LIGHT_RAIN")) {
layout.setBackgroundResource(R.drawable.ic_light_rain);
} else if (skycon.equals("PARTLY_CLOUDY_DAY")) {
layout.setBackgroundResource(R.drawable.ic_partly_cloudy_day);
} else if (skycon.equals("PARTLY_CLOUDY_NIGHT")) {
layout.setBackgroundResource(R.drawable.ic_partly_cloudy_night);
} else if (skycon.equals("STORM_RAIN")) {
layout.setBackgroundResource(R.drawable.ic_storm_rain);
} else {
String curDate = java.text.DateFormat.getDateTimeInstance().format(Calendar.getInstance().getTime());
String timeOfDay = curDate.substring(curDate.length() - 2);
Log.i("Time", timeOfDay);
if (timeOfDay.equals("AM")) {
layout.setBackgroundResource(R.drawable.ic_clear_day);
} else {
layout.setBackgroundResource(R.drawable.ic_clear_night);
}
}
}
public void searchForCity(View view) {
EditText search = (EditText) findViewById(R.id.searchBox);
String location = search.getText().toString();
if(location.indexOf(" ") != -1){
geocodeCity = location.substring(0, location.indexOf(" "));
geocodeAddress = location.substring(location.indexOf(" ") + 1);
String url = getGeocodeInfoUrl();
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
final String result = response.body().string();
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
deserializeGeocodeInfo(result);
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
});
}
else{
Toast.makeText(this, "查询区域格式不正确;参照格式: 北京 朝阳", Toast.LENGTH_LONG).show();
}
}
public void deserializeGeocodeInfo(String result) throws JSONException {
String location = null;
String geocodeInfo = result;
Log.i("Geocode info", geocodeInfo);
JSONObject geoInfo = new JSONObject(result);
String count = geoInfo.optString("count");
if(Integer.valueOf(count) != 0){
String gcode = geoInfo.optString("geocodes");
Log.i("Geocode info", gcode);
List<HashMap> geocodeList = JSON.parseArray(gcode, HashMap.class);
for (int i = 0; i < geocodeList.size(); i++) {
location = (String) geocodeList.get(i).get("location");
}
longitude = location.substring(0, location.indexOf(","));
latitude = location.substring(location.indexOf(",") + 1);
TextView cityView = (TextView) findViewById(R.id.city);
cityView.setText(geocodeCity);
requestWeatherInfo();
}
else{
Toast.makeText(this, "找不到该区域", Toast.LENGTH_LONG).show();
}
}
public void getReGeoInformation(){
String url = getRegeocodeInfoUrl();
Log.i("Regeo info", url);
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(url).build();
Call call = client.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
final String result = response.body().string();
Log.i("Regeo info", result);
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
deserializeRegeocodeInfo(result);
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
});
}
public void deserializeRegeocodeInfo(String result) throws JSONException{
String info = result;
JSONObject infoJson = new JSONObject(info);
String regeocodeInfo = infoJson.optString("regeocode");
Log.i("Regeo info", regeocodeInfo);
JSONObject addressComponentJson = new JSONObject(regeocodeInfo);
String addressComponentInfo = addressComponentJson.optString("addressComponent");
Log.i("Regeo info", addressComponentInfo);
JSONObject specAddressJson = new JSONObject(addressComponentInfo);
String city = specAddressJson.optString("city");
String province = specAddressJson.optString("province");
Log.i("Regeo info", city);
Log.i("Regeo info", province);
TextView cityView = (TextView) findViewById(R.id.city);
cityView.setText(province + " " + city);
}
}
AndroidManifest. xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="xyz.geshkii.weather">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
>
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Казалось, что приложение работает без проблем на моем Xperia X Compact с Android Oreo. Но, похоже, трещал sh при запуске с моим Xperia 1, работающим Android 10.
Вот лог-код:
Connected to process 16273 on device 'sony-j9110-QV713V4B1T'.
Capturing and displaying logcat messages from application. This behavior can be disabled in the "Logcat output" section of the "Debugger" settings page.
I/Perf: Connecting to perf service.
E/Perf: Fail to get file list xyz.geshkii.weather
getFolderSize() : Exception_1 = java.lang.NullPointerException: Attempt to get length of null array
Fail to get file list xyz.geshkii.weather
getFolderSize() : Exception_1 = java.lang.NullPointerException: Attempt to get length of null array
W/geshkii.weathe: Accessing hidden method Landroid/view/View;->computeFitSystemWindows(Landroid/graphics/Rect;Landroid/graphics/Rect;)Z (greylist, reflection, allowed)
Accessing hidden method Landroid/view/ViewGroup;->makeOptionalFitsSystemWindows()V (greylist, reflection, allowed)
Есть идеи, как это могло произойти? Спасибо.