java - 如何在 Android Widget (SDK 29+) 中运行 http 请求并获得响应
问题描述
我正在构建一个简单的信息显示小部件。所有离线的东西都可以正常工作。如何处理异步 Web 请求。(我可以在正常活动中处理一个罚款)
我正在尝试使用基于另一个 stackoverflow 答案的处理程序,但小部件挂起而没有任何明显的错误。
我已经尽可能地简化了我的代码以使其更容易发布,并且它使用一个任何人都可以使用的简单比特币价格指数作为一个简单的测试场景(我的应用程序会更复杂)
显现:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="co.uk.kicktechnic.lcdclockwidget">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<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"><!--android:theme="@style/Theme.LCDClockWidget"-->
<activity
android:name="uk.co.kicktechnic.lcdclockwidget.LCDClockWidgetGrantSettingsActivity"
android:label="@string/title_activity_initial_grant_settings">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="uk.co.kicktechnic.lcdclockwidget.LCDClockWidget">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/l_c_d_clock_widget_info" />
</receiver>
<activity android:name="uk.co.kicktechnic.lcdclockwidget.LCDClockWidgetConfigureActivity">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>
</activity>
</application>
</manifest>
代码:
public class LCDClockWidget extends AppWidgetProvider {
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.l_c_d_clock_widget);
//views.setTextViewText(R.id.appwidget_sun, String.valueOf(appWidgetId)); //original - show widget ID
Intent intentUpdate = new Intent(context, LCDClockWidget.class);
intentUpdate.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
views.setTextViewText(R.id.appwidget_moon, "set in updateAppWidget");
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.l_c_d_clock_widget);
loadBTC(context, appWidgetManager, appWidgetId);
views.setTextViewText(R.id.appwidget_moon, "set in onUpdate");
appWidgetManager.updateAppWidget(appWidgetId, views);
}
}
static void loadBTC(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {
try {
Request request = new Request.Builder()
.url("https://api.coindesk.com/v1/bpi/currentprice.json")
.build();
OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Toast.makeText(context, "Error during BPI loading : "
+ e.getMessage(), Toast.LENGTH_SHORT).show();
}
@Override
public void onResponse(Call call, Response response)
throws IOException {
final String body = response.body().string();
//original code - works in a normal activity
//runOnUiThread(new Runnable() {
// @Override
// public void run() {
// parseBpiResponse(body, context);
// }
//});
//based on a stackoverflow answer
Handler mHandler = new Handler();
mHandler.post(new Runnable(){
public void run() {
parseBpiResponse(body, context);
}
});
}
});
} catch (Exception e) {
//Toast.makeText(context, "Error: " + e.getMessage(), Toast.LENGTH_LONG).show();
}
}
static void parseBpiResponse(String body, Context context) {
try {
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.l_c_d_clock_widget);
StringBuilder builder = new StringBuilder();
JSONObject jsonObject = new JSONObject(body);
//JSONObject timeObject = jsonObject.getJSONObject("time");
//builder.append(timeObject.getString("updated")).append("\n\n");
JSONObject bpiObject = jsonObject.getJSONObject("bpi");
JSONObject usdObject = bpiObject.getJSONObject("USD");
builder.append("$ ").append(usdObject.getString("rate")).append("\n");
JSONObject gbpObject = bpiObject.getJSONObject("GBP");
builder.append("£ ").append(gbpObject.getString("rate")).append("\n");
JSONObject euroObject = bpiObject.getJSONObject("EUR");
builder.append("€ ").append(euroObject.getString("rate")).append("\n");
views.setTextViewText(R.id.appwidget_btc, builder.toString());
} catch (Exception e) {
}
}
}
编辑1:
遵循建议并使用文章:https ://medium.com/@sambhaji2134/jobintentservice-android-example-7f58bd2720bf
我已经添加了这个(并暂时删除了任何网络/BTC 内容)
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
Toast.makeText(context, "Widget has been updated! ", Toast.LENGTH_SHORT).show();
mServiceResultReceiver = new BTCServiceResultReceiver(new Handler());
mServiceResultReceiver.setReceiver((BTCServiceResultReceiver.Receiver) this);
showDataFromBackground(context, mServiceResultReceiver);
}
}
public void showData(String data, Context context) {
//mTextView.setText(String.format("%s\n%s", mTextView.getText(), data));
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.l_c_d_clock_widget);
views.setTextViewText(R.id.appwidget_btc, data);
}
public void onReceiveResult(int resultCode, Bundle resultData, Context context) {
final int SHOW_RESULT = 123;
switch (resultCode) {
case SHOW_RESULT:
if (resultData != null) {
showData(resultData.getString("data"), context);
}
break;
}
}
根据文章,我将 ServiceResultReciever 和 JobIntentService 破解在一起:
public class BTCServiceResultReceiver extends ResultReceiver {
private Receiver mReceiver;
/**
* Create a new ResultReceive to receive results. Your
* {@link #onReceiveResult} method will be called from the thread running
* <var>handler</var> if given, or from an arbitrary thread if null.
*
* @param handler the handler object
*/
public BTCServiceResultReceiver(Handler handler) {
super(handler);
}
public void setReceiver(Receiver receiver) {
mReceiver = receiver;
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (mReceiver != null) {
mReceiver.onReceiveResult(resultCode, resultData);
}
}
public interface Receiver {
void onReceiveResult(int resultCode, Bundle resultData);
}
}
public class BTCJobIntentService extends JobIntentService {
private static final String TAG = JobService.class.getSimpleName();
public static final String RECEIVER = "receiver";
public static final int SHOW_RESULT = 123;
/**
* Result receiver object to send results
*/
private ResultReceiver mResultReceiver;
/**
* Unique job ID for this service.
*/
static final int DOWNLOAD_JOB_ID = 1000;
/**
* Actions download
*/
private static final String ACTION_DOWNLOAD = "action.DOWNLOAD_DATA";
static void enqueueWork(Context context, BTCServiceResultReceiver workerResultReceiver) {
Intent intent = new Intent(context, JobService.class);
intent.putExtra(RECEIVER, workerResultReceiver);
intent.setAction(ACTION_DOWNLOAD);
enqueueWork(context, JobService.class, DOWNLOAD_JOB_ID, intent);
}
@Override
protected void onHandleWork(@NonNull Intent intent) {
// We have received work to do. The system or framework is already
// holding a wake lock for us at this point, so we can just go.
Log.i("SimpleJobIntentService", "Executing work: " + intent);
String label = intent.getStringExtra("label");
if (label == null) {
label = intent.toString();
}
toast("Executing: " + label);
for (int i = 0; i < 5; i++) {
Log.i("SimpleJobIntentService", "Running service " + (i + 1) + "/5 @ " + SystemClock.elapsedRealtime());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
Log.i("SimpleJobIntentService", "Completed service @ " + SystemClock.elapsedRealtime());
}
@Override
public void onDestroy() {
super.onDestroy();
toast("All work complete");
}
@SuppressWarnings("deprecation")
final Handler mHandler = new Handler();
// Helper for showing tests
void toast(final CharSequence text) {
mHandler.post(new Runnable() {
@Override public void run() {
Toast.makeText(BTCJobIntentService.this, text, Toast.LENGTH_SHORT).show();
}
});
}
解决方案
您正在尝试在后台线程中完成工作,但无法保证您的工作会在进程终止之前完成。
更重要的是,您views
在通话parseBpiResponse()
后会及时更新。updateAppWidget()
您启动网络 I/O 但随后立即执行updateAppWidget()
,因此您的更改views
发生得太晚了。
至少,您也需要updateAppWidget()
加入parseBpiResponse()
。
更好的做法是onUpdate()
将所有这些逻辑移到一个JobIntentService
.
推荐阅读
- c# - DevExpress 集合过滤器和BetweenOperator
- django - 使用 MongoDB 在 Django Admin 中记录主键错误
- vue.js - 如何正确设置产品的 sw-entity-multi-select 字段并将它们保存到您的扩展
- multimap - c++ std::multi_map 迭代equal_range问题
- flutter - 如何在 gridview.builder 中通过 Tabbar 导航?
- php - 如何在资源 laravel 中显示分页链接
- reactjs - reactjs - 如何在reactjs的material-UI多选框中将默认值设置为选中?
- mysql - MySQL 8.0 - 用户定义的函数在其主体 (STR_TO_DATE) 生成的警告上崩溃
- c++ - 从文件中读取数组并将它们存储到结构成员中
- flutter - 输入'未来
- >' 不是类型“列表”的子类型
' 在类型转换中