如何执行重复的 JSoup 任务,即使应用程序在 android 中处于后台也能正常工作
How can you execute a repeating JSoup task which will work even if the app is in background in android
对于我的应用程序,我需要由 jSoup 从网站解析的值,然后使用通知 returned 给用户,这些值每分钟都会更改,因此要与值保持同步我使用处理程序设置了一个任务,这在应用程序处于前台时效果很好,但是一旦用户进入主屏幕,应用程序就会 return 多个异常,例如java.net.UnknownHostException 或 java.net.SocketTimeoutException,在代码中,当 jSoup 连接到指定站点时,我已经尝试使用 Services 和 AsyncTasks 而不是线程,但它总是完全相同的问题,我还搜索了有类似经历的人,但我想我的问题很具体。
这是处理程序的代码:
private final static int INTERVAL = 1000 * 60;
Handler mHandler = new Handler();
Runnable mHandlerTask = new Runnable()
{
@Override
public void run() {
try {
wakeLock.release();
} catch (RuntimeException e) {
e.printStackTrace();
}
if (!isUpdating) {
isUpdating = true;
App.shouldUpdate = true;
System.out.println("update");
final TinyDB tinydb = new TinyDB(getApplicationContext());
TextView priceEditText = loadedLayouts.get(1).findViewById(R.id.priceTextView);
TextView increaseEditText = loadedLayouts.get(1).findViewById(R.id.increaseTextView);
updatePricesStock(tinydb.getString("current_isin"), priceEditText, increaseEditText);
}
wakeLock.acquire(2*60*1000L /*10 minutes*/);
}
mHandler.postDelayed(mHandlerTask, INTERVAL);
}
}
};
这是 updateStockPrices 方法的代码(我不会包括 updatePricesWarrant 和 updatePricesKnockout,因为它们本质上做同样的事情并且抛出同样的异常)
public void updatePricesStock(final String ISIN, TextView priceText, TextView increaseText) {
final TinyDB tinydb = new TinyDB(getApplicationContext());
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
TinyDB tinydb = new TinyDB(getApplicationContext());
try {
Document doc = Jsoup.connect("https://www.ls-tc.de/de/aktie/" + ISIN).get();
System.out.println(ISIN);
increase = doc.selectFirst("#page_content > div > div:nth-child(1) > div > div.mpe_bootstrapgrid.col-md-8 > div > div:nth-child(3) > div > span:nth-child(3)").text().replace(" ", "");
tinydb.putString(ISIN + "notification_price", doc.selectFirst("#page_content > div > div:nth-child(1) > div > div.mpe_bootstrapgrid.col-md-8 > div > div:nth-child(3) > div > span:nth-child(1)").text() + "€");
} catch (IOException e) {
e.printStackTrace();
tinydb.putString(ISIN + "notification_price", "Error Scraping Price");
}
}
});
if(!thread.isAlive()) {
thread.start();
}
try{
thread.join();
}catch (Exception ex){
ex.printStackTrace();
}
if(!thread.isAlive()) {
notificationCompat = NotificationManagerCompat.from(getApplicationContext());
System.out.println("done");
System.out.println(tinydb.getString(ISIN + "notification_price"));
priceText.setText(tinydb.getString(ISIN + "notification_price"));
increaseText.setText(increase);
System.out.println("Text Updated " + priceText.getText().toString());
if(tinydb.getBoolean(ISIN + "_notification_status")) {
Notification notification = new NotificationCompat.Builder(getApplicationContext(), App.notificationChannel)
.setSmallIcon(R.drawable.ic_baseline_attach_money_24).setContentTitle(tinydb.getString(ISIN + "notification_name")).setContentText(tinydb.getString(ISIN + "notification_price")).setPriority(NotificationCompat.PRIORITY_MAX).setCategory(NotificationCompat.CATEGORY_STATUS).setOnlyAlertOnce(true).build();
notificationCompat.notify(tinydb.getInt(ISIN + "_notification_id"), notification);
}
isUpdating = false;
}
}
最后这些是我得到的堆栈跟踪:
W/System.err: java.net.SocketTimeoutException: timeout
W/System.err: at com.android.okhttp.okio.Okio.newTimeoutException(Okio.java:225)
at com.android.okhttp.okio.AsyncTimeout.exit(AsyncTimeout.java:263)
W/System.err: at com.android.okhttp.okio.AsyncTimeout.read(AsyncTimeout.java:217)
at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:317)
at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:311)
W/System.err: at com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:207)
W/System.err: at com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:388)
at com.android.okhttp.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:146)
W/System.err: at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:900)
at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:772)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:493)
W/System.err: at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:429)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:560)
W/System.err: at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:106)
at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:30)
at org.jsoup.helper.HttpConnection$Response.execute(HttpConnection.java:734)
W/System.err: at org.jsoup.helper.HttpConnection$Response.execute(HttpConnection.java:706)
W/System.err: at org.jsoup.helper.HttpConnection.execute(HttpConnection.java:299)
W/System.err: at org.jsoup.helper.HttpConnection.get(HttpConnection.java:288)
at de.xliquid.stockwatchultimate.MainActivity.run(MainActivity.java:266)
W/System.err: at java.lang.Thread.run(Thread.java:919)
W/System.err: Caused by: java.net.SocketException: socket is closed
at com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read(ConscryptFileDescriptorSocket.java:588)
at com.android.okhttp.okio.Okio.read(Okio.java:145)
at com.android.okhttp.okio.AsyncTimeout.read(AsyncTimeout.java:213)
W/System.err: ... 18 more
java.net.UnknownHostException: Unable to resolve host "www.onvista.de": No address associated with hostname
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:156)
W/System.err: at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103)
at java.net.InetAddress.getAllByName(InetAddress.java:1152)
at com.android.okhttp.Dns.lookup(Dns.java:41)
W/System.err: at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:178)
at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:144)
at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:86)
at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:192)
at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:144)
at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:106)
at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:400)
W/System.err: at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:333)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:483)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:429)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:560)
at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:106)
at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:30)
at org.jsoup.helper.HttpConnection$Response.execute(HttpConnection.java:734)
at org.jsoup.helper.HttpConnection$Response.execute(HttpConnection.java:706)
at org.jsoup.helper.HttpConnection.execute(HttpConnection.java:299)
at org.jsoup.helper.HttpConnection.get(HttpConnection.java:288)
W/System.err: at de.xliquid.stockwatchultimate.MainActivity.run(MainActivity.java:371)
at java.lang.Thread.run(Thread.java:919)
W/System.err: Caused by: android.system.GaiException: android_getaddrinfo failed: EAI_NODATA (No address associated with hostname)
W/System.err: at libcore.io.Linux.android_getaddrinfo(Native Method)
W/System.err: at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:74)
at libcore.io.BlockGuardOs.android_getaddrinfo(BlockGuardOs.java:200)
at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:74)
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:135)
W/System.err: ... 22 more
此外,此应用仅供我使用,因此如果解决方案不是最干净的或耗电更快,我真的不会在意。
问题是节能模式,如果开启了phone在后台/空闲模式下不会做请求,不管唤醒锁,我通过添加权限解决了我的问题,所以即使 phone 处于节能待机状态,应用程序也可以请求数据。
对于我的应用程序,我需要由 jSoup 从网站解析的值,然后使用通知 returned 给用户,这些值每分钟都会更改,因此要与值保持同步我使用处理程序设置了一个任务,这在应用程序处于前台时效果很好,但是一旦用户进入主屏幕,应用程序就会 return 多个异常,例如java.net.UnknownHostException 或 java.net.SocketTimeoutException,在代码中,当 jSoup 连接到指定站点时,我已经尝试使用 Services 和 AsyncTasks 而不是线程,但它总是完全相同的问题,我还搜索了有类似经历的人,但我想我的问题很具体。
这是处理程序的代码:
private final static int INTERVAL = 1000 * 60;
Handler mHandler = new Handler();
Runnable mHandlerTask = new Runnable()
{
@Override
public void run() {
try {
wakeLock.release();
} catch (RuntimeException e) {
e.printStackTrace();
}
if (!isUpdating) {
isUpdating = true;
App.shouldUpdate = true;
System.out.println("update");
final TinyDB tinydb = new TinyDB(getApplicationContext());
TextView priceEditText = loadedLayouts.get(1).findViewById(R.id.priceTextView);
TextView increaseEditText = loadedLayouts.get(1).findViewById(R.id.increaseTextView);
updatePricesStock(tinydb.getString("current_isin"), priceEditText, increaseEditText);
}
wakeLock.acquire(2*60*1000L /*10 minutes*/);
}
mHandler.postDelayed(mHandlerTask, INTERVAL);
}
}
};
这是 updateStockPrices 方法的代码(我不会包括 updatePricesWarrant 和 updatePricesKnockout,因为它们本质上做同样的事情并且抛出同样的异常)
public void updatePricesStock(final String ISIN, TextView priceText, TextView increaseText) {
final TinyDB tinydb = new TinyDB(getApplicationContext());
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
TinyDB tinydb = new TinyDB(getApplicationContext());
try {
Document doc = Jsoup.connect("https://www.ls-tc.de/de/aktie/" + ISIN).get();
System.out.println(ISIN);
increase = doc.selectFirst("#page_content > div > div:nth-child(1) > div > div.mpe_bootstrapgrid.col-md-8 > div > div:nth-child(3) > div > span:nth-child(3)").text().replace(" ", "");
tinydb.putString(ISIN + "notification_price", doc.selectFirst("#page_content > div > div:nth-child(1) > div > div.mpe_bootstrapgrid.col-md-8 > div > div:nth-child(3) > div > span:nth-child(1)").text() + "€");
} catch (IOException e) {
e.printStackTrace();
tinydb.putString(ISIN + "notification_price", "Error Scraping Price");
}
}
});
if(!thread.isAlive()) {
thread.start();
}
try{
thread.join();
}catch (Exception ex){
ex.printStackTrace();
}
if(!thread.isAlive()) {
notificationCompat = NotificationManagerCompat.from(getApplicationContext());
System.out.println("done");
System.out.println(tinydb.getString(ISIN + "notification_price"));
priceText.setText(tinydb.getString(ISIN + "notification_price"));
increaseText.setText(increase);
System.out.println("Text Updated " + priceText.getText().toString());
if(tinydb.getBoolean(ISIN + "_notification_status")) {
Notification notification = new NotificationCompat.Builder(getApplicationContext(), App.notificationChannel)
.setSmallIcon(R.drawable.ic_baseline_attach_money_24).setContentTitle(tinydb.getString(ISIN + "notification_name")).setContentText(tinydb.getString(ISIN + "notification_price")).setPriority(NotificationCompat.PRIORITY_MAX).setCategory(NotificationCompat.CATEGORY_STATUS).setOnlyAlertOnce(true).build();
notificationCompat.notify(tinydb.getInt(ISIN + "_notification_id"), notification);
}
isUpdating = false;
}
}
最后这些是我得到的堆栈跟踪:
W/System.err: java.net.SocketTimeoutException: timeout
W/System.err: at com.android.okhttp.okio.Okio.newTimeoutException(Okio.java:225)
at com.android.okhttp.okio.AsyncTimeout.exit(AsyncTimeout.java:263)
W/System.err: at com.android.okhttp.okio.AsyncTimeout.read(AsyncTimeout.java:217)
at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:317)
at com.android.okhttp.okio.RealBufferedSource.indexOf(RealBufferedSource.java:311)
W/System.err: at com.android.okhttp.okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.java:207)
W/System.err: at com.android.okhttp.internal.http.Http1xStream.readResponse(Http1xStream.java:388)
at com.android.okhttp.internal.http.Http1xStream.readResponseHeaders(Http1xStream.java:146)
W/System.err: at com.android.okhttp.internal.http.HttpEngine.readNetworkResponse(HttpEngine.java:900)
at com.android.okhttp.internal.http.HttpEngine.readResponse(HttpEngine.java:772)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:493)
W/System.err: at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:429)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:560)
W/System.err: at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:106)
at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:30)
at org.jsoup.helper.HttpConnection$Response.execute(HttpConnection.java:734)
W/System.err: at org.jsoup.helper.HttpConnection$Response.execute(HttpConnection.java:706)
W/System.err: at org.jsoup.helper.HttpConnection.execute(HttpConnection.java:299)
W/System.err: at org.jsoup.helper.HttpConnection.get(HttpConnection.java:288)
at de.xliquid.stockwatchultimate.MainActivity.run(MainActivity.java:266)
W/System.err: at java.lang.Thread.run(Thread.java:919)
W/System.err: Caused by: java.net.SocketException: socket is closed
at com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream.read(ConscryptFileDescriptorSocket.java:588)
at com.android.okhttp.okio.Okio.read(Okio.java:145)
at com.android.okhttp.okio.AsyncTimeout.read(AsyncTimeout.java:213)
W/System.err: ... 18 more
java.net.UnknownHostException: Unable to resolve host "www.onvista.de": No address associated with hostname
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:156)
W/System.err: at java.net.Inet6AddressImpl.lookupAllHostAddr(Inet6AddressImpl.java:103)
at java.net.InetAddress.getAllByName(InetAddress.java:1152)
at com.android.okhttp.Dns.lookup(Dns.java:41)
W/System.err: at com.android.okhttp.internal.http.RouteSelector.resetNextInetSocketAddress(RouteSelector.java:178)
at com.android.okhttp.internal.http.RouteSelector.nextProxy(RouteSelector.java:144)
at com.android.okhttp.internal.http.RouteSelector.next(RouteSelector.java:86)
at com.android.okhttp.internal.http.StreamAllocation.findConnection(StreamAllocation.java:192)
at com.android.okhttp.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:144)
at com.android.okhttp.internal.http.StreamAllocation.newStream(StreamAllocation.java:106)
at com.android.okhttp.internal.http.HttpEngine.connect(HttpEngine.java:400)
W/System.err: at com.android.okhttp.internal.http.HttpEngine.sendRequest(HttpEngine.java:333)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.execute(HttpURLConnectionImpl.java:483)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:429)
at com.android.okhttp.internal.huc.HttpURLConnectionImpl.getResponseCode(HttpURLConnectionImpl.java:560)
at com.android.okhttp.internal.huc.DelegatingHttpsURLConnection.getResponseCode(DelegatingHttpsURLConnection.java:106)
at com.android.okhttp.internal.huc.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:30)
at org.jsoup.helper.HttpConnection$Response.execute(HttpConnection.java:734)
at org.jsoup.helper.HttpConnection$Response.execute(HttpConnection.java:706)
at org.jsoup.helper.HttpConnection.execute(HttpConnection.java:299)
at org.jsoup.helper.HttpConnection.get(HttpConnection.java:288)
W/System.err: at de.xliquid.stockwatchultimate.MainActivity.run(MainActivity.java:371)
at java.lang.Thread.run(Thread.java:919)
W/System.err: Caused by: android.system.GaiException: android_getaddrinfo failed: EAI_NODATA (No address associated with hostname)
W/System.err: at libcore.io.Linux.android_getaddrinfo(Native Method)
W/System.err: at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:74)
at libcore.io.BlockGuardOs.android_getaddrinfo(BlockGuardOs.java:200)
at libcore.io.ForwardingOs.android_getaddrinfo(ForwardingOs.java:74)
at java.net.Inet6AddressImpl.lookupHostByName(Inet6AddressImpl.java:135)
W/System.err: ... 22 more
此外,此应用仅供我使用,因此如果解决方案不是最干净的或耗电更快,我真的不会在意。
问题是节能模式,如果开启了phone在后台/空闲模式下不会做请求,不管唤醒锁,我通过添加权限解决了我的问题,所以即使 phone 处于节能待机状态,应用程序也可以请求数据。