一、引言
今天连发两篇关于聚合数据API的文章,前一篇记录了我通过使用聚合数据API获取天气预报的应用开发心得(文章链接)。这一篇则记录,我使用聚合数据API实现货币汇率换算的应用开发。
这个应用开发,我实现了根据聚合数据API获取到的货币列表,动态更新Spinner组件下拉列表的内容,相比前一个应用更具有挑战性。最终效果如下:
二、聚合数据平台选择API
如何使用聚合数据平台的API,在官网上有相关的说明。在前一篇文章中,我也做了简单介绍,这里就不再赘述。
在聚合数据官网API接口页面搜索“汇率”,会找到两个API。我这次实现货币汇率换算,使用了第一个——“汇率”。
这个API提供了货币列表和实时汇率查询换算两个接口。第一个接口方便我们自动更新软件应用中的原始货币和目标货币下列选项框,而不用将选项写死。第二个接口能够获得实时汇率,方便我们计算指定数额的原始货币能够兑换多少目标货币。
完成申请后就可以在“个人中心 - 数据中心 - 我的API”中看到申请到的API了。聚合数据对免费接口有限制,普通会员只能申请3个。大多数免费接口每天的请求次数为50次,进行开发学习还是够用了。调用API需要的Key也在这个页面里。
完成上述操作,就可以进行软件开发了。
三、软件设计
本应用的UI设计是参考的常见的网页版汇率换算应用,这些网页端的应用都提供了原始货币和目标货币两个选择框供用户选择,这两个下列选框中提供的货币选项也非常多,方便用户在任意两种货币之间进行计算(如下图)。
我也希望在自己的应用中实现这一点。而聚合数据的“汇率”API正好提供了货币列表的查询接口,这样,就可以在每次打开我的汇率换算应用时,通过“汇率”API获取到所有的货币列表,然后再更新到应用的两个下列选择框中,而不用将这两个下列选框的选项写死。通过聚合数据官网的接口测试页面,我们可以进行相关的查询测试,其中货币列表查询,可以返回125种货币。
货币列表的curl如下:
http://op.juhe.cn/onebox/exchange/list?key=key&version=2
其中,key在“个人中心 - 数据中心 - 我的API”中获取,version的值为2。
返回的结果如下:
{
"reason":"查询成功",
"result":{
"list":[
{
"name":"美元",
"code":"USD"
},
{
"name":"人民币",
"code":"CNY"
},
{
"name":"日元",
"code":"JPY"
},
{
"name":"欧元",
"code":"EUR"
},
{
"name":"英镑",
"code":"GBP"
},
{
"name":"韩元",
"code":"KRW"
},
{
"name":"港元",
"code":"HKD"
},
{
"name":"澳大利亚元",
"code":"AUD"
},
{
"name":"加拿大元",
"code":"CAD"
},
{
"name":"瑞士法郎",
"code":"CHF"
},
{
"name":"阿联酋迪拉姆",
"code":"AED"
},
{
"name":"阿尔巴尼亚列克",
"code":"ALL"
},
{
"name":"亚美尼亚德拉姆",
"code":"AMD"
},
{
"name":"荷属安的列斯盾",
"code":"ANG"
},
{
"name":"安哥拉宽扎",
"code":"AOA"
},
{
"name":"阿根廷比索",
"code":"ARS"
},
{
"name":"阿塞拜疆马纳特",
"code":"AZN"
},
{
"name":"巴巴多斯元",
"code":"BBD"
},
{
"name":"孟加拉塔卡",
"code":"BDT"
},
{
"name":"保加利亚列弗",
"code":"BGN"
},
{
"name":"巴林第纳尔",
"code":"BHD"
},
{
"name":"布隆迪法郎",
"code":"BIF"
},
{
"name":"百慕大元",
"code":"BMD"
},
{
"name":"文莱元",
"code":"BND"
},
{
"name":"玻利维亚币",
"code":"BOB"
},
{
"name":"巴西雷阿尔",
"code":"BRL"
},
{
"name":"巴哈马元",
"code":"BSD"
},
{
"name":"博茨瓦纳普拉",
"code":"BWP"
},
{
"name":"白俄罗斯卢布",
"code":"BYN"
},
{
"name":"伯利兹元",
"code":"BZD"
},
{
"name":"智利比索",
"code":"CLP"
},
{
"name":"哥伦比亚比索",
"code":"COP"
},
{
"name":"哥斯达黎加科郎",
"code":"CRC"
},
{
"name":"古巴比索",
"code":"CUP"
},
{
"name":"佛得角埃斯库多",
"code":"CVE"
},
{
"name":"捷克克朗",
"code":"CZK"
},
{
"name":"吉布提法郎",
"code":"DJF"
},
{
"name":"丹麦克朗",
"code":"DKK"
},
{
"name":"多米尼加比索",
"code":"DOP"
},
{
"name":"阿尔及利亚第纳尔",
"code":"DZD"
},
{
"name":"埃及镑",
"code":"EGP"
},
{
"name":"埃塞俄比亚比尔",
"code":"ETB"
},
{
"name":"斐济元",
"code":"FJD"
},
{
"name":"格鲁吉亚拉里",
"code":"GEL"
},
{
"name":"加纳塞地",
"code":"GHS"
},
{
"name":"冈比亚达拉西",
"code":"GMD"
},
{
"name":"几内亚法郎",
"code":"GNF"
},
{
"name":"危地马拉格查尔",
"code":"GTQ"
},
{
"name":"洪都拉斯伦皮拉",
"code":"HNL"
},
{
"name":"克罗地亚库纳",
"code":"HRK"
},
{
"name":"海地古德",
"code":"HTG"
},
{
"name":"匈牙利福林",
"code":"HUF"
},
{
"name":"印尼盾",
"code":"IDR"
},
{
"name":"以色列新谢克尔",
"code":"ILS"
},
{
"name":"印度卢比",
"code":"INR"
},
{
"name":"伊拉克第纳尔",
"code":"IQD"
},
{
"name":"伊朗里亚尔",
"code":"IRR"
},
{
"name":"冰岛克朗",
"code":"ISK"
},
{
"name":"牙买加元",
"code":"JMD"
},
{
"name":"约旦第纳尔",
"code":"JOD"
},
{
"name":"肯尼亚先令",
"code":"KES"
},
{
"name":"吉尔吉斯斯坦索姆",
"code":"KGS"
},
{
"name":"柬埔寨瑞尔",
"code":"KHR"
},
{
"name":"科威特第纳尔",
"code":"KWD"
},
{
"name":"开曼群岛元",
"code":"KYD"
},
{
"name":"哈萨克斯坦坚戈",
"code":"KZT"
},
{
"name":"老挝基普",
"code":"LAK"
},
{
"name":"黎巴嫩镑",
"code":"LBP"
},
{
"name":"斯里兰卡卢比",
"code":"LKR"
},
{
"name":"莱索托洛蒂",
"code":"LSL"
},
{
"name":"利比亚第纳尔",
"code":"LYD"
},
{
"name":"摩洛哥迪拉姆",
"code":"MAD"
},
{
"name":"摩尔多瓦列伊",
"code":"MDL"
},
{
"name":"马其顿第纳尔",
"code":"MKD"
},
{
"name":"缅甸缅元",
"code":"MMK"
},
{
"name":"澳门帕塔卡",
"code":"MOP"
},
{
"name":"毛里求斯卢比",
"code":"MUR"
},
{
"name":"马拉维客瓦查",
"code":"MWK"
},
{
"name":"墨西哥比索",
"code":"MXN"
},
{
"name":"马来西亚令吉",
"code":"MYR"
},
{
"name":"纳米比亚元",
"code":"NAD"
},
{
"name":"尼日利亚奈拉",
"code":"NGN"
},
{
"name":"尼加拉瓜新科多巴",
"code":"NIO"
},
{
"name":"挪威克朗",
"code":"NOK"
},
{
"name":"尼泊尔卢比",
"code":"NPR"
},
{
"name":"新西兰元",
"code":"NZD"
},
{
"name":"阿曼里亚尔",
"code":"OMR"
},
{
"name":"巴拿马巴波亚",
"code":"PAB"
},
{
"name":"秘鲁索尔",
"code":"PEN"
},
{
"name":"菲律宾比索",
"code":"PHP"
},
{
"name":"巴基斯坦卢比",
"code":"PKR"
},
{
"name":"波兰兹罗提",
"code":"PLN"
},
{
"name":"巴拉圭瓜拉尼",
"code":"PYG"
},
{
"name":"卡塔尔里亚尔",
"code":"QAR"
},
{
"name":"罗马尼亚新列伊",
"code":"RON"
},
{
"name":"塞尔维亚第纳尔",
"code":"RSD"
},
{
"name":"俄罗斯卢布",
"code":"RUB"
},
{
"name":"卢旺达法郎",
"code":"RWF"
},
{
"name":"沙特里亚尔",
"code":"SAR"
},
{
"name":"塞舌尔卢比",
"code":"SCR"
},
{
"name":"苏丹镑",
"code":"SDG"
},
{
"name":"瑞典克朗",
"code":"SEK"
},
{
"name":"新加坡元",
"code":"SGD"
},
{
"name":"索马里先令",
"code":"SOS"
},
{
"name":"斯威士兰埃马兰吉尼",
"code":"SZL"
},
{
"name":"泰国泰铢",
"code":"THB"
},
{
"name":"土库曼斯坦马纳特",
"code":"TMT"
},
{
"name":"突尼斯第纳尔",
"code":"TND"
},
{
"name":"土耳其新里拉",
"code":"TRY"
},
{
"name":"特立尼达和多巴哥元",
"code":"TTD"
},
{
"name":"新台币",
"code":"TWD"
},
{
"name":"坦桑尼亚先令",
"code":"TZS"
},
{
"name":"乌克兰格里夫纳",
"code":"UAH"
},
{
"name":"乌干达先令",
"code":"UGX"
},
{
"name":"乌拉圭比索",
"code":"UYU"
},
{
"name":"乌兹别克斯坦索姆",
"code":"UZS"
},
{
"name":"委内瑞拉玻利瓦尔",
"code":"VES"
},
{
"name":"越南盾",
"code":"VND"
},
{
"name":"中非金融合作法郎",
"code":"XAF"
},
{
"name":"东加勒比元",
"code":"XCD"
},
{
"name":"非洲金融共同体法郎",
"code":"XOF"
},
{
"name":"太平洋金融共同体法郎",
"code":"XPF"
},
{
"name":"也门里亚尔",
"code":"YER"
},
{
"name":"南非兰特",
"code":"ZAR"
},
{
"name":"赞比亚克瓦查",
"code":"ZMW"
}
]
},
"error_code":0
}
通过API获得货币列表,就可以更新Sipnner组件的选项内容了。但由于货币列表的GET请求是放在分线程中的,虽然Sipnner组件的初始化代码是放在了GET请求之后,但因其是在主线程中的,实际上Sipnner组件的初始化会在货币列表的GET请求结果返回前就完成了,这样会导致Sipnner没有可选项。另外,即使Sipnner组件的初始化晚于GET请求的结果返回,但也可能会遇到网络问题导致请求不成功的情况,这样也会使得Sipnner组件无可选项。因此,在代码设计中,我采取了一些技术手段来避免上述因素造成的软件不可用情况发生。如增加一些判断语句,预设一组默认的选项,在GET请中通过runOnUiThread方法来刷新界面中的Sipnner组件内容等待。经过测试,确实有效的避免异常的发生。
但这一过程又造成了另外一个小插曲。根源就在于,我预设的选项中人民币和美元的位置与货币列表返回的结果是不一致的。预设列表中人民币在首位,美元在第二位,而货币列表中美元在首位,人民币在第二位。在软件设计时,我没有注意到这个差异的影响,直到进行测试时才发现出了问题。当第一次进入界面时,货币列表的GET请求正常,获取结果后,在分线程中使用runOnUiThread方法刷新了Sipnner组件的选项。原始货币默认选中第一项,目标货币默认是选中的第二项。这看上去都没问题。但点击换算按钮后就出问题了(如下图)。换算的结果是35美元兑换4.89元人民币?!!!!这是什么情况?
(软件bug)
通过Android Studio对软件进行调试,查看Logcat信息,终于找到了原因。在Sipnner组件初始化时,货币列表的GET请求还没有返回。软件采用的预设下列选项(此时人民币在首位,美元在第二位),预设的原始货币默认选中首位,目标货币默认选中第二位。初始化后,两个Sipnner组件的监听器都对选中事件进行了响应。我在监听器的onItemSelected方法中,将预设的货币代码赋值给了全局变量mFrom(转换汇率前的货币代码)和mTo(转换汇率成的货币代码)。当货币列表的GET请求完成后,我在分线程中通过runOnUiThread方法刷新了Sipnner组件的选项,使得美元到了首位,人民币到了第二位。但这个时候没有更新mFrom变量和mTo变量。就导致了看到的选中项和应用中实际记录的货币代码是不一致的。此时不对两个Sipnner组件进行选项修改操作,直接点“换算”,就得到了上面的错误结果。
发现错误后,我对代码做了一点小修改。就解决了这个问题。
至于实时汇率的获取,有了前面的经验,也就没啥问题了。其curl如下:
http://op.juhe.cn/onebox/exchange/currency?key=key&from=xxx&to=xxx&version=2
key与上面的货币列表一样。from通过原始货币下列选项组件设置是货币,to通过目标货币下列组件设置,这两个参数都是货币的代码如人民币CNY,美元USD等。version赋值为2。GET请求返回的结果格式如下:
{
"reason":"查询成功!",
"result":[
{
"currencyF":"USD",
"currencyF_Name":"美元",
"currencyT":"CNY",
"currencyT_Name":"人民币",
"currencyFD":"1",
"exchange":"7.1795",
"result":"7.1795",
"updateTime":"2024-08-07 14:20:00"
},
{
"currencyF":"CNY",
"currencyF_Name":"人民币",
"currencyT":"USD",
"currencyT_Name":"美元",
"currencyFD":"1",
"exchange":"0.1393",
"result":"0.1393",
"updateTime":"2024-08-07 14:20:00"
}
],
"error_code":0
}
返回结果提供了两个货币直接相互兑换的汇率,我的软件应用值需要用到result列表中的第一项中的exchange的数值。
这个请求同样是在分线程中操作,获取到exchange值后,用兑换金额乘汇率得到结果,然后通过runOnUiThread方法更新到结果显示文本组件中。
四、代码展示
最终的代码如下:
1. 界面设计文件 activity_exchange.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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ExchangeActivity">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="汇率换算"
android:textSize="28sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/ll_amount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="30dp"
android:layout_marginEnd="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tv_title">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="兑换金额:" />
<EditText
android:id="@+id/et_amount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/edit_bg"
android:autofillHints="金额"
android:hint="0"
android:ems="10"
android:inputType="numberDecimal" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_original"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ll_amount">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="原始货币:" />
<Spinner
android:id="@+id/sp_original"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:id="@+id/ll_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="20dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ll_original">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="目标货币:" />
<Spinner
android:id="@+id/sp_target"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" />
</LinearLayout>
<Button
android:id="@+id/btn_exchange"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="换算"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ll_target" />
<TextView
android:id="@+id/tv_targetAmount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:textStyle="bold"
android:textSize="28sp"
android:text="0"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/btn_exchange" />
</androidx.constraintlayout.widget.ConstraintLayout>
2.逻辑代码 ExchangeActivity.java
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
public class ExchangeActivity extends AppCompatActivity implements View.OnClickListener {
private final static String TAG = "ExchangeActivity";
// 货币列表查询接口地址
private static final String LIST_API_URL = "http://op.juhe.cn/onebox/exchange/list";
// 实时汇率查询接口地址
private static final String CURRENCY_API_URL = "http://op.juhe.cn/onebox/exchange/currency";
// 接口请求Key(在聚合数据网站申请天气预报API后生成的AppKey)
private static final String API_KEY = "************************";
private int mListErrorCode = 1; // 货币列表查询错误码
private String mFrom, mTo; // 转换汇率前的货币代码,转换汇率成的货币代码
private ArrayAdapter<String> moneyAdapter; // 下拉选择框适配器
private EditText et_amount; // 兑换货币文本输入框
private TextView tv_targetAmount; // 显示换算结果的文本框
private int mOriginaIndex, mTargetIndex; // 两个下列选项的索引
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_exchange);
et_amount = findViewById(R.id.et_amount);
tv_targetAmount = findViewById(R.id.tv_targetAmount);
findViewById(R.id.btn_exchange).setOnClickListener(this); // 汇率换算
initialization(); // 对下拉选项菜单进行初始化
}
// 对两个下拉选框进行初始化
private void initialization() {
// Android 4.0 之后不能在主线程中请求HTTP
new Thread(() -> getCurrencyList()).start(); // 分线程中获取货币列表
// 分线程中的获取货币列表此时还没有得到返回结果,下拉列表先采用默认数据进行初始化
if ( mListErrorCode == 1) {
moneyList.add("人民币CNY");
moneyCodeList.add("CNY");
moneyList.add("美元USD");
moneyCodeList.add("USD");
moneyList.add("欧元EUR");
moneyCodeList.add("EUR");
moneyList.add("英镑GBP");
moneyCodeList.add("GBP");
moneyList.add("日元JPY");
moneyCodeList.add("JPY");
moneyList.add("卢布RUB");
moneyCodeList.add("RUB");
}
// 初始化Spinner组件
initSpinnerForDropdown();
}
// 初始化下拉模式的列表框
private void initSpinnerForDropdown() {
Log.d(TAG, "当前的错误码:" + mListErrorCode);
// 声明一个下拉列表的数组适配器
moneyAdapter = new ArrayAdapter<>(this,
R.layout.item_select, moneyList);
// 从布局文件中获取下拉框
Spinner sp_original = findViewById(R.id.sp_original);
Spinner sp_target = findViewById(R.id.sp_target);
// 设置下拉框的标题。对话框模式才显示标题,下拉模式不显示标题
sp_original.setPrompt("请选择货币");
sp_target.setPrompt("请选择货币");
// 设置下拉框的数组适配器
sp_original.setAdapter(moneyAdapter);
sp_target.setAdapter(moneyAdapter);
// 设置下拉框默认显示第一项
sp_original.setSelection(0);
sp_target.setSelection(1);
// 给下拉框设置选择监听器,一旦用户选中某一项,就触发监听器的onItemSelected方法
sp_original.setOnItemSelectedListener(new OriginalSelectedListener());
sp_target.setOnItemSelectedListener(new TargetSelectedListener());
}
// 定义下拉列表需要显示的文本数组
private ArrayList<String> moneyList = new ArrayList<>();
private ArrayList<String> moneyCodeList = new ArrayList<>();
// 定义一个选择监听器,它实现了接口OnItemSelectedListener
class OriginalSelectedListener implements AdapterView.OnItemSelectedListener {
// 选择事件的处理方法,其中arg2代表选择项的序号
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
mOriginaIndex = arg2; // 记录选中项的索引号
Log.d(TAG, "原始货币代码:" + mFrom);
}
// 未选择时的处理方法,通常无需关注
public void onNothingSelected(AdapterView<?> arg0) {}
}
// 定义一个选择监听器,它实现了接口OnItemSelectedListener
class TargetSelectedListener implements AdapterView.OnItemSelectedListener {
// 选择事件的处理方法,其中arg2代表选择项的序号
public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
mTargetIndex = arg2; // 记录选中项的索引号
Log.d(TAG, "目标货币代码:" + mTo);
}
// 未选择时的处理方法,通常无需关注
public void onNothingSelected(AdapterView<?> arg0) {}
}
// 通过聚合数据API获取货币列表
private void getCurrencyList() {
HashMap<String, String> map = new HashMap<>();
map.put("key", API_KEY);
map.put("version", "2");
String response = get(LIST_API_URL, map);
// 对查询结果进行处理
try {
JSONObject jsonObject = new JSONObject(response);
int error_code = jsonObject.getInt("error_code");
if (error_code == 0) {
mListErrorCode = 0; // 保存错误码状态,方便在程序其他地方进行判定
JSONObject result = jsonObject.getJSONObject("result"); // 获取结果
JSONArray listArray = result.getJSONArray("list"); // 获取货币列表
int length = listArray.length();
Log.d(TAG, "获取到的货币列表有" + length + "条");
// 将获取到的货币列表转换成ArrayList
ArrayList<String> itemList = new ArrayList<>(); // 用于更新moneyList
ArrayList<String> codeList = new ArrayList<>(); // 用于更新moneyCodeList
// 遍历listArray
for (int i = 0; i < length; i ++) {
JSONObject listObject = listArray.getJSONObject(i);
String name = listObject.getString("name");
String code = listObject.getString("code");
String spinnerItem = name + code;
itemList.add(spinnerItem);
codeList.add(code);
}
Log.d(TAG, "itemList的size为:" + itemList.size());
// 更新moneyArray 和 moneyCodeArray
moneyList = itemList;
moneyCodeList = codeList;
// 在线程中更新界面Ui
runOnUiThread(() -> {
// 更新Spinner
moneyAdapter.clear();
moneyAdapter.addAll(moneyList);
moneyAdapter.notifyDataSetChanged();
});
} else {
Log.d(TAG, "调用接口失败:" + jsonObject.getString("reason"));
mListErrorCode = 1;
}
} catch (JSONException e) {
e.printStackTrace();
}
}
// 通过聚合数据API获取汇率
private void getExchange(String amountStr) {
HashMap<String, String> map = new HashMap<>();
map.put("key", API_KEY);
map.put("from", mFrom);
map.put("to", mTo);
map.put("version", "2");
String response = get(CURRENCY_API_URL, map);
// 对查询结果进行处理
try {
JSONObject jsonObject = new JSONObject(response);
int error_code = jsonObject.getInt("error_code");
if (error_code == 0) {
JSONArray resultArray = jsonObject.getJSONArray("result"); // 获取结果
JSONObject exchangeObject = resultArray.getJSONObject(0);
String exchange = exchangeObject.getString("exchange"); // 获取汇率数据
Double exchangeRate = Double.parseDouble(exchange);
Double amount = Double.parseDouble(amountStr);
Double targetAmount = amount * exchangeRate;
Log.d(TAG, "目标金额:" + targetAmount);
// 在线程中更新界面Ui
runOnUiThread(() -> {
// 更新计算结果
tv_targetAmount.setText(String.format(Locale.CHINESE,"%f", targetAmount));
});
} else {
Log.d(TAG, "调用接口失败:" + jsonObject.getString("reason"));
}
} catch (JSONException e) {
e.printStackTrace();
}
}
// get方式请求数据
private static String get(String apiUrl, HashMap<String, String> map) {
String resp = ""; // 应答内容
try {
URL url = new URL(apiUrl + "?" + params(map));
Log.d(TAG, "URL=" + url);
BufferedReader in = new BufferedReader(new InputStreamReader((url.openConnection()).getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
Log.d(TAG, response.toString());
resp = response.toString();
} catch (IOException e) {
e.printStackTrace();
}
return resp;
}
// 拼接请求参数
private static String params(Map<String, String> map) {
return map.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
}
@Override
public void onClick(View v) {
if(v.getId() == R.id.btn_exchange ) { // 换算汇率R.id.btn_update
Log.d(TAG, "执行换算操作");
String amountStr = et_amount.getText().toString();
// 兑换金额不为空才能执行查询操作
if (!amountStr.equals("")) {
// 兑换金额不为零才能执行查询操作
if (Double.parseDouble(amountStr) != 0) {
// 原始货币代码与目标货币代码不相同才执行查询操作
if (mOriginaIndex != mTargetIndex) {
mFrom = moneyCodeList.get(mOriginaIndex); // 获取原始货币代码
mTo = moneyCodeList.get(mTargetIndex); // 获取目标货币代码
// Android 4.0 之后不能在主线程中请求HTTP
new Thread(() -> getExchange(amountStr)).start(); // 分线程中获取汇率
} else {
Toast.makeText(ExchangeActivity.this, "原始货币与目标货币一样!",
Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(ExchangeActivity.this, "兑换金额为0!",
Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(ExchangeActivity.this, "兑换金额为空!",
Toast.LENGTH_LONG).show();
}
}
}
}