From a59370cf32f7794d8186c6dd4366ec7cd6cc23b6 Mon Sep 17 00:00:00 2001 From: TongTongStudio Date: Thu, 11 Jun 2026 20:35:48 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=9C=A8android=206.0?= =?UTF-8?q?=EF=BC=8CApi=2023=E9=97=AA=E9=80=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ttstd/dialer/manager/WeatherManager.java | 10 +- .../{manager => parser}/CsvDeserializer.java | 2 +- .../ttstd/dialer/parser/LegacyCsvParser.java | 305 ++++++++++++++++++ 3 files changed, 315 insertions(+), 2 deletions(-) rename app/src/main/java/com/ttstd/dialer/{manager => parser}/CsvDeserializer.java (99%) create mode 100644 app/src/main/java/com/ttstd/dialer/parser/LegacyCsvParser.java diff --git a/app/src/main/java/com/ttstd/dialer/manager/WeatherManager.java b/app/src/main/java/com/ttstd/dialer/manager/WeatherManager.java index 47319dd..0f2a940 100644 --- a/app/src/main/java/com/ttstd/dialer/manager/WeatherManager.java +++ b/app/src/main/java/com/ttstd/dialer/manager/WeatherManager.java @@ -2,6 +2,7 @@ package com.ttstd.dialer.manager; import android.annotation.SuppressLint; import android.content.Context; +import android.os.Build; import android.text.TextUtils; import com.jeremyliao.liveeventbus.LiveEventBus; @@ -20,6 +21,8 @@ import com.ttstd.dialer.BuildConfig; import com.ttstd.dialer.bean.CityInfo; import com.ttstd.dialer.config.CommonConfig; import com.ttstd.dialer.gson.GsonUtils; +import com.ttstd.dialer.parser.CsvDeserializer; +import com.ttstd.dialer.parser.LegacyCsvParser; import com.ttstd.dialer.utils.Logger; import com.ttstd.dialer.utils.NativeUtils; @@ -136,7 +139,12 @@ public class WeatherManager { @Override public void subscribe(@NonNull ObservableEmitter> emitter) throws Throwable { long time = System.currentTimeMillis(); - List cityInfos = CsvDeserializer.deserializeFromAssets(mContext, "China-City-List-latest.csv"); + List cityInfos; + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) { + cityInfos = CsvDeserializer.deserializeFromAssets(mContext, "China-City-List-latest.csv"); + } else { + cityInfos = LegacyCsvParser.parseFromAssets(mContext, "China-City-List-latest.csv"); + } Logger.e(TAG, "subscribe: deserializeFromAssets time = " + (System.currentTimeMillis() - time) + "ms"); emitter.onNext(cityInfos); } diff --git a/app/src/main/java/com/ttstd/dialer/manager/CsvDeserializer.java b/app/src/main/java/com/ttstd/dialer/parser/CsvDeserializer.java similarity index 99% rename from app/src/main/java/com/ttstd/dialer/manager/CsvDeserializer.java rename to app/src/main/java/com/ttstd/dialer/parser/CsvDeserializer.java index 0e2883a..8570a0b 100644 --- a/app/src/main/java/com/ttstd/dialer/manager/CsvDeserializer.java +++ b/app/src/main/java/com/ttstd/dialer/parser/CsvDeserializer.java @@ -1,4 +1,4 @@ -package com.ttstd.dialer.manager; +package com.ttstd.dialer.parser; import android.content.Context; diff --git a/app/src/main/java/com/ttstd/dialer/parser/LegacyCsvParser.java b/app/src/main/java/com/ttstd/dialer/parser/LegacyCsvParser.java new file mode 100644 index 0000000..882375e --- /dev/null +++ b/app/src/main/java/com/ttstd/dialer/parser/LegacyCsvParser.java @@ -0,0 +1,305 @@ +package com.ttstd.dialer.parser; + +import android.content.Context; + +import com.ttstd.dialer.bean.CityInfo; +import com.ttstd.dialer.utils.Logger; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 简单的CSV解析器,兼容Android 5.0 + * 不依赖OpenCSV库,手动解析CSV文件 + */ +public class LegacyCsvParser { + private static final String TAG = "SimpleCsvParser"; + + /** + * 从assets文件夹中的CSV文件解析为CityInfo对象列表 + * + * @param context 上下文 + * @param fileName assets中的CSV文件名 + * @return CityInfo对象列表 + */ + public static List parseFromAssets(Context context, String fileName) { + InputStream inputStream = null; + BufferedReader reader = null; + List cityList = new ArrayList<>(); + + try { + long startTime = System.currentTimeMillis(); + + // 从assets获取CSV文件输入流 + inputStream = context.getAssets().open(fileName); + reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + + // 读取第一行(标题行) + String headerLine = reader.readLine(); + if (headerLine == null) { + Logger.w(TAG, "CSV file is empty"); + return cityList; + } + + // 解析标题行,建立列名到索引的映射 + String[] headers = parseLine(headerLine); + Map columnIndexMap = buildColumnIndexMap(headers); + + // 逐行读取数据 + String line; + int lineNumber = 1; // 标题行已读,从第2行开始 + + while ((line = reader.readLine()) != null) { + lineNumber++; + + // 跳过空行 + if (line.trim().isEmpty()) { + continue; + } + + try { + // 解析当前行 + String[] values = parseLine(line); + + // 创建CityInfo对象并填充数据 + CityInfo cityInfo = createCityInfo(values, columnIndexMap); + + if (cityInfo != null) { + cityList.add(cityInfo); + } + } catch (Exception e) { + Logger.w(TAG, "Error parsing line " + lineNumber + ": " + e.getMessage()); + } + } + + long endTime = System.currentTimeMillis(); + Logger.e(TAG, "parseFromAssets: parsed " + cityList.size() + " cities in " + + (endTime - startTime) + "ms"); + + } catch (IOException e) { + Logger.e(TAG, "Error reading CSV file: " + e.getMessage()); + e.printStackTrace(); + } catch (Exception e) { + Logger.e(TAG, "Error during CSV parsing: " + e.getMessage()); + e.printStackTrace(); + } finally { + // 确保关闭流,避免资源泄漏 + try { + if (reader != null) { + reader.close(); + } + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + Logger.w(TAG, "Failed to close stream: " + e.getMessage()); + } + } + + return cityList; + } + + /** + * 解析CSV行,处理引号和逗号 + * + * @param line CSV行文本 + * @return 解析后的字段数组 + */ + private static String[] parseLine(String line) { + List fields = new ArrayList<>(); + StringBuilder field = new StringBuilder(); + boolean inQuotes = false; + int i = 0; + + while (i < line.length()) { + char c = line.charAt(i); + + if (c == '"') { + if (inQuotes) { + // 检查是否是转义的双引号 "" + if (i + 1 < line.length() && line.charAt(i + 1) == '"') { + field.append('"'); + i += 2; + continue; + } else { + // 结束引号 + inQuotes = false; + } + } else { + // 开始引号 + inQuotes = true; + } + } else if (c == ',' && !inQuotes) { + // 字段分隔符 + fields.add(field.toString().trim()); + field.setLength(0); + } else { + field.append(c); + } + + i++; + } + + // 添加最后一个字段 + fields.add(field.toString().trim()); + + return fields.toArray(new String[0]); + } + + /** + * 建立列名到索引的映射 + * + * @param headers 标题数组 + * @return 列名到索引的映射 + */ + private static Map buildColumnIndexMap(String[] headers) { + Map columnIndexMap = new HashMap<>(); + + for (int i = 0; i < headers.length; i++) { + String header = headers[i].trim(); + // 移除可能的引号 + if (header.startsWith("\"") && header.endsWith("\"")) { + header = header.substring(1, header.length() - 1); + } + columnIndexMap.put(header, i); + } + + return columnIndexMap; + } + + /** + * 根据解析的数据创建CityInfo对象 + * + * @param values 字段值数组 + * @param columnIndexMap 列名到索引的映射 + * @return CityInfo对象 + */ + private static CityInfo createCityInfo(String[] values, Map columnIndexMap) { + try { + CityInfo cityInfo = new CityInfo(); + + // 根据列名映射设置字段值 + setFieldIfExists(cityInfo, "setLocation_ID", values, columnIndexMap, "Location_ID"); + setFieldIfExists(cityInfo, "setLocation_Name_EN", values, columnIndexMap, "Location_Name_EN"); + setFieldIfExists(cityInfo, "setLocation_Name_ZH", values, columnIndexMap, "Location_Name_ZH"); + setFieldIfExists(cityInfo, "setISO_3166_1", values, columnIndexMap, "ISO_3166_1"); + setFieldIfExists(cityInfo, "setCountry_Region_EN", values, columnIndexMap, "Country_Region_EN"); + setFieldIfExists(cityInfo, "setCountry_Region_ZH", values, columnIndexMap, "Country_Region_ZH"); + setFieldIfExists(cityInfo, "setAdm1_Name_EN", values, columnIndexMap, "Adm1_Name_EN"); + setFieldIfExists(cityInfo, "setAdm1_Name_ZH", values, columnIndexMap, "Adm1_Name_ZH"); + setFieldIfExists(cityInfo, "setAdm2_Name_EN", values, columnIndexMap, "Adm2_Name_EN"); + setFieldIfExists(cityInfo, "setAdm2_Name_ZH", values, columnIndexMap, "Adm2_Name_ZH"); + setFieldIfExists(cityInfo, "setTimezone", values, columnIndexMap, "Timezone"); + setFieldIfExists(cityInfo, "setLatitude", values, columnIndexMap, "Latitude"); + setFieldIfExists(cityInfo, "setLongitude", values, columnIndexMap, "Longitude"); + setFieldIfExists(cityInfo, "setAD_code", values, columnIndexMap, "AD_code"); + + return cityInfo; + } catch (Exception e) { + Logger.w(TAG, "Error creating CityInfo: " + e.getMessage()); + return null; + } + } + + /** + * 如果字段存在则设置值 + * + * @param cityInfo CityInfo对象 + * @param methodName setter方法名 + * @param values 字段值数组 + * @param columnIndexMap 列名到索引的映射 + * @param columnName 列名 + */ + private static void setFieldIfExists(CityInfo cityInfo, String methodName, + String[] values, Map columnIndexMap, + String columnName) { + try { + Integer index = columnIndexMap.get(columnName); + if (index != null && index < values.length) { + String value = values[index]; + // 移除可能的引号 + if (value != null && value.startsWith("\"") && value.endsWith("\"")) { + value = value.substring(1, value.length() - 1); + } + + // 使用反射调用setter方法 + java.lang.reflect.Method method = cityInfo.getClass().getMethod(methodName, String.class); + method.invoke(cityInfo, value); + } + } catch (Exception e) { + Logger.w(TAG, "Error setting field " + columnName + ": " + e.getMessage()); + } + } + + /** + * 从输入流解析为CityInfo对象列表 + * + * @param inputStream CSV文件输入流 + * @return CityInfo对象列表 + */ + public static List parseFromStream(InputStream inputStream) { + if (inputStream == null) { + return new ArrayList<>(); + } + + BufferedReader reader = null; + List cityList = new ArrayList<>(); + + try { + reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + + // 读取标题行 + String headerLine = reader.readLine(); + if (headerLine == null) { + return cityList; + } + + // 解析标题行 + String[] headers = parseLine(headerLine); + Map columnIndexMap = buildColumnIndexMap(headers); + + // 逐行读取数据 + String line; + while ((line = reader.readLine()) != null) { + if (line.trim().isEmpty()) { + continue; + } + + try { + String[] values = parseLine(line); + CityInfo cityInfo = createCityInfo(values, columnIndexMap); + + if (cityInfo != null) { + cityList.add(cityInfo); + } + } catch (Exception e) { + Logger.w(TAG, "Error parsing line: " + e.getMessage()); + } + } + + } catch (Exception e) { + Logger.e(TAG, "Error during CSV parsing: " + e.getMessage()); + e.printStackTrace(); + } finally { + try { + if (reader != null) { + reader.close(); + } + if (inputStream != null) { + inputStream.close(); + } + } catch (IOException e) { + Logger.w(TAG, "Failed to close stream: " + e.getMessage()); + } + } + + return cityList; + } +}