使用 Android Profiler 找出 ANR 的原因
Find out the reason of ANR using Android Profiler
我的应用程序弹出 ANR(应用程序未响应)对话框。 很长一段时间以来,我一直在努力解决这个问题。
我做了很多优化,最后我的主线程需要 2~3 百万微秒,但这也不够。
而且几乎所有的服务调用都是用 Rxjava 完成的。不知道回调是不是他们的。
当我对显示弹出窗口的特定 activity 使用 Android Profiler 时,有 2 个进程耗时过长.
第一个是onCreate方法。
第二个是handleCallback。我无法减少这些过程的时间。我不知道该怎么做以及它是什么。
我尝试使用异步任务,但即使我取得了一些成果,仍然遇到同样的问题。
您可能会看到 traces.txt 文件 link => gofile.io/?c=BKXCJ2
更新:
我杀死了应用程序 当它弹出ANR对话框时,这些是最后的日志行:
2019-11-27 16:36:53.856 13843-14117/br.com.gomus.androidapp D/DownloadInteractor: addSongDownloadedToCurrentPlaylist
2019-11-27 16:36:53.858 13843-14117/br.com.gomus.androidapp D/DownloadRepositoryImpl: File download and update status finished, should verify all files download status
2019-11-27 16:36:53.860 13843-14117/br.com.gomus.androidapp D/DownloadRepositoryImpl: File download and update status concluded with success emitting progress
2019-11-27 16:36:53.863 13843-14117/br.com.gomus.androidapp D/DownloadRepositoryImpl: mItemsDownloaded: 4 mTotalItems14
2019-11-27 16:36:53.866 13843-14117/br.com.gomus.androidapp D/DownloadRepositoryImpl: Current progress : 28 %
2019-11-27 16:36:53.868 13843-14117/br.com.gomus.androidapp D/DownloadRepositoryImpl: mCallback != null
2019-11-27 16:36:53.881 13843-14113/br.com.gomus.androidapp D/DownloadRepositoryImpl: Starting download of file at url http://s3.amazonaws.com/br.com.gomus.songs/2124950971
2019-11-27 16:36:53.925 13843-14143/br.com.gomus.androidapp D/OkHttp: --> GET http://s3.amazonaws.com/br.com.gomus.songs/2124950971
2019-11-27 16:36:53.926 13843-14143/br.com.gomus.androidapp D/OkHttp: --> END GET
2019-11-27 16:36:53.929 13843-14143/br.com.gomus.androidapp D/WebService: --> GET http://s3.amazonaws.com/br.com.gomus.songs/2124950971
2019-11-27 16:36:53.931 13843-14143/br.com.gomus.androidapp D/WebService: --> END GET
2019-11-27 16:36:54.266 13843-14143/br.com.gomus.androidapp D/WebService: <-- 200 OK http://s3.amazonaws.com/br.com.gomus.songs/2124950971 (330ms)
2019-11-27 16:36:54.269 13843-14143/br.com.gomus.androidapp D/WebService: x-amz-id-2: njC3Sfnqwkg+tJsRdGem8sNtQrJBIKSK1JgrL4SZvLCp7Cosw1mKLfdgARtOjE+ubVDa8KDE1Ig=
2019-11-27 16:36:54.271 13843-14143/br.com.gomus.androidapp D/WebService: x-amz-request-id: CD185B026FA3B166
2019-11-27 16:36:54.274 13843-14143/br.com.gomus.androidapp D/WebService: Date: Wed, 27 Nov 2019 18:36:55 GMT
2019-11-27 16:36:54.276 13843-14143/br.com.gomus.androidapp D/WebService: Last-Modified: Wed, 06 Jul 2011 17:23:22 GMT
2019-11-27 16:36:54.279 13843-14143/br.com.gomus.androidapp D/WebService: ETag: "e9c453f55a3f41bc04ec84de14f0861f"
2019-11-27 16:36:54.281 13843-14143/br.com.gomus.androidapp D/WebService: Accept-Ranges: bytes
2019-11-27 16:36:54.283 13843-14143/br.com.gomus.androidapp D/WebService: Content-Type: audio/mpeg
2019-11-27 16:36:54.286 13843-14143/br.com.gomus.androidapp D/WebService: Content-Length: 3390468
2019-11-27 16:36:54.288 13843-14143/br.com.gomus.androidapp D/WebService: Server: AmazonS3
2019-11-27 16:36:56.069 13843-13843/? D/ViewRootImpl@810767d[DownloadContentActivity]: MSG_WINDOW_FOCUS_CHANGED 0 1
2019-11-27 16:36:56.074 13843-13843/? D/InputMethodManager: prepareNavigationBarInfo() DecorView@2321ffe[DownloadContentActivity]
2019-11-27 16:36:56.075 13843-13843/? D/InputMethodManager: getNavigationBarColor() -855310
我在这里添加了相关代码。有人可以告诉我此代码是否存在会导致 ANR 弹出窗口的不便之处吗?
DownloadContentActivity
handlerThread = new HandlerThread("MyHandlerThread");
handlerThread.start();
Looper looper = handlerThread.getLooper();
Handler handler = new Handler(looper);
handler.post(new Runnable(){
@Override
public void run() {
mViewModel.startDownload();
}
});
DownloadContentViewModel
public void startDownload() {
mInteractor.startDownload(DownloadContentViewModel.this::downloadResult);
log(Log.DEBUG, TAG, "Thread.currentThread().getName() " + Thread.currentThread().getName());
}
DownloadContentInteractorImpl
@Override
public void startDownload(Action1<Result<Void>> resultAction) {
log(Log.DEBUG, TAG, "Thread.currentThread().getName() " + Thread.currentThread().getName());
mDownloadContentRepository.startDownload(resultAction);
}
DownloadRepositoryImpl
在这个方法中,有3个级联调用; downloadPendentSongs、downloadPendentSpots 和 dowloadPendentVideos。我将只分享其中一个,因为它们都具有相同的实现。只有 downloadPendentVideos 会显示操作结果,因为它是最后一次调用,所以我也添加了它的实现。
@Override
public void startDownload(Action1<Result<Void>> resultAction) {
log(Log.DEBUG, TAG, "Thread.currentThread().getName() startDownload" + Thread.currentThread().getName());
checkPreviousError();
mNetworkStatus.isOnline(isOnline -> {
log(Log.DEBUG, TAG, "isOnline Thread.currentThread().getName() " + Thread.currentThread().getName());
if (isOnline) {
downloadPendentSongs(resultAction,
f -> {
log(Log.DEBUG, TAG, "downloadPendentSongs Thread.currentThread().getName() " + Thread.currentThread().getName());
log(Log.DEBUG, TAG, "All songs download processed with success.");
// log(Log.DEBUG, TAG, "JUST TO TEST = Downloaded file location is "+ f.get(0).getAbsolutePath());
downloadPendentSpots(resultAction,
f1 -> {
log(Log.DEBUG, TAG, "downloadPendentSpots Thread.currentThread().getName() " + Thread.currentThread().getName());
log(Log.DEBUG, TAG, "All spots download processed with success");
downloadPendentVideos(resultAction);
}
);
});
} else {
mHasErrors = true;
resultAction.call(Result.getNetworkError());
}
});
}
NetworkStatusImpl
@Override
public void isOnline(Action1<Boolean> resultAction) {
log(Log.DEBUG, "NetworkStatusImpl", "Thread.currentThread().getName() " + Thread.currentThread().getName());
CheckConnectionTask task = new CheckConnectionTask(resultAction);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);
}
CheckConnectionTask
@Override
protected Void doInBackground(Void... voids) {
log(Log.DEBUG,TAG,"success HyperLog");
log(Log.DEBUG, TAG, "doInBackground");
log(Log.DEBUG, "doInBackground", "Thread.currentThread().getName() " + Thread.currentThread().getName());
try {
Socket sock = new Socket();
InetSocketAddress sockaddr = new InetSocketAddress("8.8.8.8", 53);
sock.connect(sockaddr, NETWORK_TIMEOUT_MS);
sock.close();
log(Log.DEBUG,TAG, "doInBackground try");
mConnectionResult = true;
} catch (Exception e) {
Log.e( TAG, "doInBackground error caught"+e.getMessage());
mConnectionResult = false;
}
mResultConnection.call(mConnectionResult);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
// log(Log.DEBUG,TAG, "onPostExecute");
super.onPostExecute(aVoid);
}
DownloadRepositoryImpl
private void downloadPendentSongs(Action1<Result<Void>> resultAction,
Action1<List<File>> onDownloadFinished) {
log(Log.DEBUG, TAG, "Fetching pendent download songs");
log(Log.DEBUG, TAG, "downloadPendentSongs Thread.currentThread().getName() " + Thread.currentThread().getName());
getPendentDownloadEntity(mSongDao,
result -> {
if (result.isSuccess) {
Gson gson = new Gson();
String pendentSong = gson.toJson(result.data);
log(Log.DEBUG, TAG, result.data.size() + " ,pendent song(s) found"+pendentSong);
log(Log.DEBUG, TAG, "getPendentDownloadEntity Thread.currentThread().getName() " + Thread.currentThread().getName());
if (result.data.size() > 0) {
downloadSongs(result.data, resultAction, onDownloadFinished);
} else {
onDownloadFinished.call(new ArrayList<>());
log(Log.DEBUG, TAG, "downloadPendentSongs result.data.size() > 0");
}
} else {
log(Log.DEBUG, TAG, result.data.size() + " downloadPendentSongs error");
mHasErrors = true;
resultAction.call(Result.getError(result.error));
}
}
);
}
private <T extends RealmObject> void getPendentDownloadEntity(BaseDao<T> dao,
Action1<Result<List<T>>> resultsAction) {
log(Log.DEBUG, TAG, "getPendentDownloadEntity Thread.currentThread().getName() " + Thread.currentThread().getName());
dao.find(this::getPendentEntityQuery, resultsAction);
}
DownloadRepositoryImpl
private void downloadSongs(List<Song> data, Action1<Result<Void>> resultAction, Action1<List<File>> onDownloadFinished) {
log(Log.DEBUG, TAG, "Starting download of pendent songs");
log(Log.DEBUG, TAG, "Thread.currentThread().getName() " + Thread.currentThread().getName());
//if(data.size()>0 && data.get(0).getId()!=0){
downloadData(
mSongDao,
data,
Song::getId,
Song::getUrl,
resultAction,
(result, file) -> updateSongStatus(result, file == null ? "" : file.getAbsolutePath(), resultAction),
onDownloadFinished
);
//}
}
private void downloadVideos(List<Video> data, Action1<Result<Void>> resultAction) {
log(Log.DEBUG, TAG, "downloadVideos Thread.currentThread().getName() " + Thread.currentThread().getName());
log(Log.DEBUG, TAG, "Starting download of pendent videos");
downloadData(
mVideoDao,
data,
Video::getId,
Video::getUrl,
resultAction,
(result, file) -> updateVideoStatus(result, file.getAbsolutePath(), resultAction),
f -> {
checkMustNotifyDownloadFinished(resultAction);
log(Log.DEBUG, TAG, "checkMustNotifyDownloadFinished downloadVideos Thread.currentThread().getName() " + Thread.currentThread().getName());
log(Log.DEBUG, TAG, "All pendent downloads were processed without success guarantee.");
}
);
}
checkMustNotifyDownloadFinished
private void checkMustNotifyDownloadFinished(Action1<Result<Void>> resultAction) {
log(Log.DEBUG, TAG, "checkMustNotifyDownloadFinished Thread.currentThread().getName() " + Thread.currentThread().getName());
if (mItemsDownloaded == mTotalItems) {
if (!mHasErrors) {
onAllFilesDownloadFinished(resultAction);
}
} else {
log(Log.DEBUG, TAG, "File download and update status concluded with success emitting progress");
if (mCallback != null){
mCallback.onReceiveProgress(getProgress());
log(Log.DEBUG, TAG, "mCallback != null");
}
}
}
onAllFilesDownloadFinished
private void onAllFilesDownloadFinished(Action1<Result<Void>> resultAction) {
log(Log.DEBUG, TAG, "All files download with success emitting success result");
log(Log.DEBUG, TAG, "onAllFilesDownloadFinished Thread.currentThread().getName() " + Thread.currentThread().getName());
if (mCallback != null)
mCallback.onReceiveProgress(getProgress());
resultAction.call(Result.getData());
mHasErrors = false;
mItemsDownloaded = 0;
//when pendent downloads are done why do not we set mtotalitems as 0.
mTotalItems = 0;
GomusApplication.sPreferenceManager.setValue(PENDENT_DOWNLOAD_ITEMS, 0);
}
DownloadRepositoryImpl
private <T extends RealmObject> void downloadData(
BaseDao<T> dao,
List<T> data,
Func1<T, Long> id,
Func1<T, String> url,
Action1<Result<Void>> resultAction,
Action2<Result<T>, File> updateEntity,
Action1<List<File>> onDownloadFinished) {
//long d = Long.parseLong(null);
log(Log.DEBUG, TAG, "downloadData Thread.currentThread().getName() " + Thread.currentThread().getName());
mCurrentDownloadSubscription = Observable.from(data)
//.observeOn(Schedulers.io())
.subscribeOn(Schedulers.io())
.flatMap(song -> downloadAndSaveData(url.call(song), id.call(song)), 2)
.map(file -> {
findEntity(dao, Long.parseLong(file == null ? "-1" : file.getName()),
resultAction, tResult -> updateEntity.call(tResult, file));
log(Log.DEBUG, TAG, "map downloadData Thread.currentThread().getName() " + Thread.currentThread().getName());
return file;
})
.toList()
//.subscribe(onDownloadFinished, err -> onDownloadError(err, resultAction));
.subscribe(onDownloadFinished, err -> onDownloadError(err, resultAction));
}
DownloadRepositoryImpl
private <T extends RealmObject> void findEntity(BaseDao<T> dao, long id,
Action1<Result<Void>> resultAction,
Action1<Result<T>> result) {
log(Log.DEBUG, TAG, "Searching for database entity with id " + id + " to update download status ");
dao.find(query -> query.equalTo("id", id), listResult -> {
if (listResult.isSuccess) {
Gson gson = new Gson();
String resultItem = gson.toJson(listResult.data);
log(Log.DEBUG, TAG, "Entity with id " + id + " found: " + resultItem);
if(listResult.data.size()>0)
result.call(Result.getData(listResult.data.get(0)));
else {
log(Log.DEBUG, TAG, "Result is null " + id + " found: ");
result.call(Result.getData(null));
}
} else {
mHasErrors = true;
resultAction.call(Result.getError(listResult.error));
}
});
}
DownloadRepositoryImpl
private Observable<File> downloadAndSaveData(String url, long id) {
log(Log.DEBUG,TAG, "Starting download of file at url " + url);
log(Log.DEBUG, TAG, "downloadAndSaveData Thread.currentThread().getName() " + Thread.currentThread().getName());
if(url==null)
url = "";
return mWebService.downloadData(url)
.subscribeOn(Schedulers.io())
//.observeOn(AndroidSchedulers.mainThread())
//.observeOn(Schedulers.io())
.flatMap(responseBodyResponse -> onSaveFileToDisk(responseBodyResponse, id));
//.compose(RxJavaUtils.applySchedulers(false));
}
下载网络服务
public interface DownloadWebService {
@Streaming
@GET
Observable<Response<ResponseBody>> downloadData(@Url String url);
}
远程数据模块
@Provides
@Singleton
DownloadWebService provideDownloadWebService(ServiceFactory factory) {
return factory.createService(DownloadWebService.class);
}
DownloadRepositoryImpl
private Observable<File> onSaveFileToDisk(Response<ResponseBody> responseBodyResponse, long id) {
log(Log.DEBUG, TAG, "onSaveFileToDisk Thread.currentThread().getName() " + Thread.currentThread().getName());
return mLocalStorageProvider
.saveFromResponse(responseBodyResponse, String.valueOf(id))
.subscribeOn(Schedulers.io());
//.observeOn(AndroidSchedulers.mainThread());
}
LocalStorageProviderImpl
@Override
public Observable<File> saveFromResponse(Response<ResponseBody> responseBodyResponse, String fileName) {
return Observable.defer( new Func0<Observable<File>>() {
@Override
public Observable<File> call() {
log(Log.DEBUG, TAG, "call Thread.currentThread().getName() " + Thread.currentThread().getName());
try {
log(Log.DEBUG, TAG, "File download concluded, saving file from url " +
responseBodyResponse.raw().request().url().toString() + " to disk");
File file = null;
if(responseBodyResponse.body() != null)
{
file = new File(GomusApplication.getInstance().getFilesDir(), fileName);
BufferedSink sink = Okio.buffer(Okio.sink(file));
sink.writeAll(responseBodyResponse.body().source());
sink.close();
}
return Observable.just(file);
//subscriber.onNext(file);
//subscriber.onCompleted();
//return file;
} catch (IOException e) {
e.printStackTrace();
return Observable.error(e);
//subscriber.onError(e);
}
}
});
}
你还记得第一个电话吗:
public void startDownload() {
mInteractor.startDownload(DownloadContentViewModel.this::downloadResult);
log(Log.DEBUG, TAG, "Thread.currentThread().getName() " + Thread.currentThread().getName());
}
因此它调用了 downloadResult 方法:
DownloadContentViewModel
private void downloadResult(Result<Void> voidResult) {
if (voidResult.isSuccess) {
log(Log.DEBUG, TAG, "downloadResult Thread.currentThread().getName() " + Thread.currentThread().getName());
log(Log.DEBUG, TAG, "downloadResult isSuccess");
downloadSuccess.set(true);
//mActivity.setContentView(R.layout.download_success);
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
log(Log.DEBUG, TAG, "runOnUiThread Thread.currentThread().getName() " + Thread.currentThread().getName());
((DownloadContentActivity) mActivity).inflateDownloadSuccessLayout();
}
});
getViewState().onNext(ViewState.success());
GomusApplication.getInstance().setInitialDownloadComplete();
initTimerDownloadFinished();
} else {
log(Log.DEBUG, TAG, "downloadResult errorr");
log(Log.DEBUG, TAG, "downloadResulterr Thread.currentThread().getName() " + Thread.currentThread().getName());
downloadFail.set(true);
downloadSuccess.set(false);
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
log(Log.DEBUG, TAG, "downloadResulterr runOnUiThread Thread.currentThread().getName() " + Thread.currentThread().getName());
((DownloadContentActivity)mActivity).inflateDownloadFailedLayout();
}
});
getViewState().onNext(ViewState.error(voidResult.error));
}
}
DownloadContentViewModel
private void initTimerDownloadFinished() {
log(Log.DEBUG, TAG, "initTimerDownloadFinished()");
mDownloadTimerHandler.postDelayed(this::okButton, 10000);
}
我需要一些帮助来找出此主线程中出现 ANR 对话框的原因。
提前致谢。
终于找到了与我分享的代码无关的问题
在项目中启动了一个服务,但没有像startForeground(ID, notification)
那样设置通知。它没有显示在日志中。当我选择信息级别日志时,我看到的原因是
E/ActivityManager: ANR in PID: 6516 Reason: Context.startForegroundService() did not then call Service.startForeground()
这是一个不必要的服务呼叫,所以我评论说,ANR 对话框消失了。
我的应用程序弹出 ANR(应用程序未响应)对话框。 很长一段时间以来,我一直在努力解决这个问题。 我做了很多优化,最后我的主线程需要 2~3 百万微秒,但这也不够。
而且几乎所有的服务调用都是用 Rxjava 完成的。不知道回调是不是他们的。
当我对显示弹出窗口的特定 activity 使用 Android Profiler 时,有 2 个进程耗时过长.
第一个是onCreate方法。
第二个是handleCallback。我无法减少这些过程的时间。我不知道该怎么做以及它是什么。
我尝试使用异步任务,但即使我取得了一些成果,仍然遇到同样的问题。
您可能会看到 traces.txt 文件 link => gofile.io/?c=BKXCJ2
更新:
我杀死了应用程序 当它弹出ANR对话框时,这些是最后的日志行:
2019-11-27 16:36:53.856 13843-14117/br.com.gomus.androidapp D/DownloadInteractor: addSongDownloadedToCurrentPlaylist
2019-11-27 16:36:53.858 13843-14117/br.com.gomus.androidapp D/DownloadRepositoryImpl: File download and update status finished, should verify all files download status
2019-11-27 16:36:53.860 13843-14117/br.com.gomus.androidapp D/DownloadRepositoryImpl: File download and update status concluded with success emitting progress
2019-11-27 16:36:53.863 13843-14117/br.com.gomus.androidapp D/DownloadRepositoryImpl: mItemsDownloaded: 4 mTotalItems14
2019-11-27 16:36:53.866 13843-14117/br.com.gomus.androidapp D/DownloadRepositoryImpl: Current progress : 28 %
2019-11-27 16:36:53.868 13843-14117/br.com.gomus.androidapp D/DownloadRepositoryImpl: mCallback != null
2019-11-27 16:36:53.881 13843-14113/br.com.gomus.androidapp D/DownloadRepositoryImpl: Starting download of file at url http://s3.amazonaws.com/br.com.gomus.songs/2124950971
2019-11-27 16:36:53.925 13843-14143/br.com.gomus.androidapp D/OkHttp: --> GET http://s3.amazonaws.com/br.com.gomus.songs/2124950971
2019-11-27 16:36:53.926 13843-14143/br.com.gomus.androidapp D/OkHttp: --> END GET
2019-11-27 16:36:53.929 13843-14143/br.com.gomus.androidapp D/WebService: --> GET http://s3.amazonaws.com/br.com.gomus.songs/2124950971
2019-11-27 16:36:53.931 13843-14143/br.com.gomus.androidapp D/WebService: --> END GET
2019-11-27 16:36:54.266 13843-14143/br.com.gomus.androidapp D/WebService: <-- 200 OK http://s3.amazonaws.com/br.com.gomus.songs/2124950971 (330ms)
2019-11-27 16:36:54.269 13843-14143/br.com.gomus.androidapp D/WebService: x-amz-id-2: njC3Sfnqwkg+tJsRdGem8sNtQrJBIKSK1JgrL4SZvLCp7Cosw1mKLfdgARtOjE+ubVDa8KDE1Ig=
2019-11-27 16:36:54.271 13843-14143/br.com.gomus.androidapp D/WebService: x-amz-request-id: CD185B026FA3B166
2019-11-27 16:36:54.274 13843-14143/br.com.gomus.androidapp D/WebService: Date: Wed, 27 Nov 2019 18:36:55 GMT
2019-11-27 16:36:54.276 13843-14143/br.com.gomus.androidapp D/WebService: Last-Modified: Wed, 06 Jul 2011 17:23:22 GMT
2019-11-27 16:36:54.279 13843-14143/br.com.gomus.androidapp D/WebService: ETag: "e9c453f55a3f41bc04ec84de14f0861f"
2019-11-27 16:36:54.281 13843-14143/br.com.gomus.androidapp D/WebService: Accept-Ranges: bytes
2019-11-27 16:36:54.283 13843-14143/br.com.gomus.androidapp D/WebService: Content-Type: audio/mpeg
2019-11-27 16:36:54.286 13843-14143/br.com.gomus.androidapp D/WebService: Content-Length: 3390468
2019-11-27 16:36:54.288 13843-14143/br.com.gomus.androidapp D/WebService: Server: AmazonS3
2019-11-27 16:36:56.069 13843-13843/? D/ViewRootImpl@810767d[DownloadContentActivity]: MSG_WINDOW_FOCUS_CHANGED 0 1
2019-11-27 16:36:56.074 13843-13843/? D/InputMethodManager: prepareNavigationBarInfo() DecorView@2321ffe[DownloadContentActivity]
2019-11-27 16:36:56.075 13843-13843/? D/InputMethodManager: getNavigationBarColor() -855310
我在这里添加了相关代码。有人可以告诉我此代码是否存在会导致 ANR 弹出窗口的不便之处吗?
DownloadContentActivity
handlerThread = new HandlerThread("MyHandlerThread");
handlerThread.start();
Looper looper = handlerThread.getLooper();
Handler handler = new Handler(looper);
handler.post(new Runnable(){
@Override
public void run() {
mViewModel.startDownload();
}
});
DownloadContentViewModel
public void startDownload() {
mInteractor.startDownload(DownloadContentViewModel.this::downloadResult);
log(Log.DEBUG, TAG, "Thread.currentThread().getName() " + Thread.currentThread().getName());
}
DownloadContentInteractorImpl
@Override
public void startDownload(Action1<Result<Void>> resultAction) {
log(Log.DEBUG, TAG, "Thread.currentThread().getName() " + Thread.currentThread().getName());
mDownloadContentRepository.startDownload(resultAction);
}
DownloadRepositoryImpl
在这个方法中,有3个级联调用; downloadPendentSongs、downloadPendentSpots 和 dowloadPendentVideos。我将只分享其中一个,因为它们都具有相同的实现。只有 downloadPendentVideos 会显示操作结果,因为它是最后一次调用,所以我也添加了它的实现。
@Override
public void startDownload(Action1<Result<Void>> resultAction) {
log(Log.DEBUG, TAG, "Thread.currentThread().getName() startDownload" + Thread.currentThread().getName());
checkPreviousError();
mNetworkStatus.isOnline(isOnline -> {
log(Log.DEBUG, TAG, "isOnline Thread.currentThread().getName() " + Thread.currentThread().getName());
if (isOnline) {
downloadPendentSongs(resultAction,
f -> {
log(Log.DEBUG, TAG, "downloadPendentSongs Thread.currentThread().getName() " + Thread.currentThread().getName());
log(Log.DEBUG, TAG, "All songs download processed with success.");
// log(Log.DEBUG, TAG, "JUST TO TEST = Downloaded file location is "+ f.get(0).getAbsolutePath());
downloadPendentSpots(resultAction,
f1 -> {
log(Log.DEBUG, TAG, "downloadPendentSpots Thread.currentThread().getName() " + Thread.currentThread().getName());
log(Log.DEBUG, TAG, "All spots download processed with success");
downloadPendentVideos(resultAction);
}
);
});
} else {
mHasErrors = true;
resultAction.call(Result.getNetworkError());
}
});
}
NetworkStatusImpl
@Override
public void isOnline(Action1<Boolean> resultAction) {
log(Log.DEBUG, "NetworkStatusImpl", "Thread.currentThread().getName() " + Thread.currentThread().getName());
CheckConnectionTask task = new CheckConnectionTask(resultAction);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, null);
}
CheckConnectionTask
@Override
protected Void doInBackground(Void... voids) {
log(Log.DEBUG,TAG,"success HyperLog");
log(Log.DEBUG, TAG, "doInBackground");
log(Log.DEBUG, "doInBackground", "Thread.currentThread().getName() " + Thread.currentThread().getName());
try {
Socket sock = new Socket();
InetSocketAddress sockaddr = new InetSocketAddress("8.8.8.8", 53);
sock.connect(sockaddr, NETWORK_TIMEOUT_MS);
sock.close();
log(Log.DEBUG,TAG, "doInBackground try");
mConnectionResult = true;
} catch (Exception e) {
Log.e( TAG, "doInBackground error caught"+e.getMessage());
mConnectionResult = false;
}
mResultConnection.call(mConnectionResult);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
// log(Log.DEBUG,TAG, "onPostExecute");
super.onPostExecute(aVoid);
}
DownloadRepositoryImpl
private void downloadPendentSongs(Action1<Result<Void>> resultAction,
Action1<List<File>> onDownloadFinished) {
log(Log.DEBUG, TAG, "Fetching pendent download songs");
log(Log.DEBUG, TAG, "downloadPendentSongs Thread.currentThread().getName() " + Thread.currentThread().getName());
getPendentDownloadEntity(mSongDao,
result -> {
if (result.isSuccess) {
Gson gson = new Gson();
String pendentSong = gson.toJson(result.data);
log(Log.DEBUG, TAG, result.data.size() + " ,pendent song(s) found"+pendentSong);
log(Log.DEBUG, TAG, "getPendentDownloadEntity Thread.currentThread().getName() " + Thread.currentThread().getName());
if (result.data.size() > 0) {
downloadSongs(result.data, resultAction, onDownloadFinished);
} else {
onDownloadFinished.call(new ArrayList<>());
log(Log.DEBUG, TAG, "downloadPendentSongs result.data.size() > 0");
}
} else {
log(Log.DEBUG, TAG, result.data.size() + " downloadPendentSongs error");
mHasErrors = true;
resultAction.call(Result.getError(result.error));
}
}
);
}
private <T extends RealmObject> void getPendentDownloadEntity(BaseDao<T> dao,
Action1<Result<List<T>>> resultsAction) {
log(Log.DEBUG, TAG, "getPendentDownloadEntity Thread.currentThread().getName() " + Thread.currentThread().getName());
dao.find(this::getPendentEntityQuery, resultsAction);
}
DownloadRepositoryImpl
private void downloadSongs(List<Song> data, Action1<Result<Void>> resultAction, Action1<List<File>> onDownloadFinished) {
log(Log.DEBUG, TAG, "Starting download of pendent songs");
log(Log.DEBUG, TAG, "Thread.currentThread().getName() " + Thread.currentThread().getName());
//if(data.size()>0 && data.get(0).getId()!=0){
downloadData(
mSongDao,
data,
Song::getId,
Song::getUrl,
resultAction,
(result, file) -> updateSongStatus(result, file == null ? "" : file.getAbsolutePath(), resultAction),
onDownloadFinished
);
//}
}
private void downloadVideos(List<Video> data, Action1<Result<Void>> resultAction) {
log(Log.DEBUG, TAG, "downloadVideos Thread.currentThread().getName() " + Thread.currentThread().getName());
log(Log.DEBUG, TAG, "Starting download of pendent videos");
downloadData(
mVideoDao,
data,
Video::getId,
Video::getUrl,
resultAction,
(result, file) -> updateVideoStatus(result, file.getAbsolutePath(), resultAction),
f -> {
checkMustNotifyDownloadFinished(resultAction);
log(Log.DEBUG, TAG, "checkMustNotifyDownloadFinished downloadVideos Thread.currentThread().getName() " + Thread.currentThread().getName());
log(Log.DEBUG, TAG, "All pendent downloads were processed without success guarantee.");
}
);
}
checkMustNotifyDownloadFinished
private void checkMustNotifyDownloadFinished(Action1<Result<Void>> resultAction) {
log(Log.DEBUG, TAG, "checkMustNotifyDownloadFinished Thread.currentThread().getName() " + Thread.currentThread().getName());
if (mItemsDownloaded == mTotalItems) {
if (!mHasErrors) {
onAllFilesDownloadFinished(resultAction);
}
} else {
log(Log.DEBUG, TAG, "File download and update status concluded with success emitting progress");
if (mCallback != null){
mCallback.onReceiveProgress(getProgress());
log(Log.DEBUG, TAG, "mCallback != null");
}
}
}
onAllFilesDownloadFinished
private void onAllFilesDownloadFinished(Action1<Result<Void>> resultAction) {
log(Log.DEBUG, TAG, "All files download with success emitting success result");
log(Log.DEBUG, TAG, "onAllFilesDownloadFinished Thread.currentThread().getName() " + Thread.currentThread().getName());
if (mCallback != null)
mCallback.onReceiveProgress(getProgress());
resultAction.call(Result.getData());
mHasErrors = false;
mItemsDownloaded = 0;
//when pendent downloads are done why do not we set mtotalitems as 0.
mTotalItems = 0;
GomusApplication.sPreferenceManager.setValue(PENDENT_DOWNLOAD_ITEMS, 0);
}
DownloadRepositoryImpl
private <T extends RealmObject> void downloadData(
BaseDao<T> dao,
List<T> data,
Func1<T, Long> id,
Func1<T, String> url,
Action1<Result<Void>> resultAction,
Action2<Result<T>, File> updateEntity,
Action1<List<File>> onDownloadFinished) {
//long d = Long.parseLong(null);
log(Log.DEBUG, TAG, "downloadData Thread.currentThread().getName() " + Thread.currentThread().getName());
mCurrentDownloadSubscription = Observable.from(data)
//.observeOn(Schedulers.io())
.subscribeOn(Schedulers.io())
.flatMap(song -> downloadAndSaveData(url.call(song), id.call(song)), 2)
.map(file -> {
findEntity(dao, Long.parseLong(file == null ? "-1" : file.getName()),
resultAction, tResult -> updateEntity.call(tResult, file));
log(Log.DEBUG, TAG, "map downloadData Thread.currentThread().getName() " + Thread.currentThread().getName());
return file;
})
.toList()
//.subscribe(onDownloadFinished, err -> onDownloadError(err, resultAction));
.subscribe(onDownloadFinished, err -> onDownloadError(err, resultAction));
}
DownloadRepositoryImpl
private <T extends RealmObject> void findEntity(BaseDao<T> dao, long id,
Action1<Result<Void>> resultAction,
Action1<Result<T>> result) {
log(Log.DEBUG, TAG, "Searching for database entity with id " + id + " to update download status ");
dao.find(query -> query.equalTo("id", id), listResult -> {
if (listResult.isSuccess) {
Gson gson = new Gson();
String resultItem = gson.toJson(listResult.data);
log(Log.DEBUG, TAG, "Entity with id " + id + " found: " + resultItem);
if(listResult.data.size()>0)
result.call(Result.getData(listResult.data.get(0)));
else {
log(Log.DEBUG, TAG, "Result is null " + id + " found: ");
result.call(Result.getData(null));
}
} else {
mHasErrors = true;
resultAction.call(Result.getError(listResult.error));
}
});
}
DownloadRepositoryImpl
private Observable<File> downloadAndSaveData(String url, long id) {
log(Log.DEBUG,TAG, "Starting download of file at url " + url);
log(Log.DEBUG, TAG, "downloadAndSaveData Thread.currentThread().getName() " + Thread.currentThread().getName());
if(url==null)
url = "";
return mWebService.downloadData(url)
.subscribeOn(Schedulers.io())
//.observeOn(AndroidSchedulers.mainThread())
//.observeOn(Schedulers.io())
.flatMap(responseBodyResponse -> onSaveFileToDisk(responseBodyResponse, id));
//.compose(RxJavaUtils.applySchedulers(false));
}
下载网络服务
public interface DownloadWebService {
@Streaming
@GET
Observable<Response<ResponseBody>> downloadData(@Url String url);
}
远程数据模块
@Provides
@Singleton
DownloadWebService provideDownloadWebService(ServiceFactory factory) {
return factory.createService(DownloadWebService.class);
}
DownloadRepositoryImpl
private Observable<File> onSaveFileToDisk(Response<ResponseBody> responseBodyResponse, long id) {
log(Log.DEBUG, TAG, "onSaveFileToDisk Thread.currentThread().getName() " + Thread.currentThread().getName());
return mLocalStorageProvider
.saveFromResponse(responseBodyResponse, String.valueOf(id))
.subscribeOn(Schedulers.io());
//.observeOn(AndroidSchedulers.mainThread());
}
LocalStorageProviderImpl
@Override
public Observable<File> saveFromResponse(Response<ResponseBody> responseBodyResponse, String fileName) {
return Observable.defer( new Func0<Observable<File>>() {
@Override
public Observable<File> call() {
log(Log.DEBUG, TAG, "call Thread.currentThread().getName() " + Thread.currentThread().getName());
try {
log(Log.DEBUG, TAG, "File download concluded, saving file from url " +
responseBodyResponse.raw().request().url().toString() + " to disk");
File file = null;
if(responseBodyResponse.body() != null)
{
file = new File(GomusApplication.getInstance().getFilesDir(), fileName);
BufferedSink sink = Okio.buffer(Okio.sink(file));
sink.writeAll(responseBodyResponse.body().source());
sink.close();
}
return Observable.just(file);
//subscriber.onNext(file);
//subscriber.onCompleted();
//return file;
} catch (IOException e) {
e.printStackTrace();
return Observable.error(e);
//subscriber.onError(e);
}
}
});
}
你还记得第一个电话吗:
public void startDownload() {
mInteractor.startDownload(DownloadContentViewModel.this::downloadResult);
log(Log.DEBUG, TAG, "Thread.currentThread().getName() " + Thread.currentThread().getName());
}
因此它调用了 downloadResult 方法:
DownloadContentViewModel
private void downloadResult(Result<Void> voidResult) {
if (voidResult.isSuccess) {
log(Log.DEBUG, TAG, "downloadResult Thread.currentThread().getName() " + Thread.currentThread().getName());
log(Log.DEBUG, TAG, "downloadResult isSuccess");
downloadSuccess.set(true);
//mActivity.setContentView(R.layout.download_success);
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
log(Log.DEBUG, TAG, "runOnUiThread Thread.currentThread().getName() " + Thread.currentThread().getName());
((DownloadContentActivity) mActivity).inflateDownloadSuccessLayout();
}
});
getViewState().onNext(ViewState.success());
GomusApplication.getInstance().setInitialDownloadComplete();
initTimerDownloadFinished();
} else {
log(Log.DEBUG, TAG, "downloadResult errorr");
log(Log.DEBUG, TAG, "downloadResulterr Thread.currentThread().getName() " + Thread.currentThread().getName());
downloadFail.set(true);
downloadSuccess.set(false);
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
log(Log.DEBUG, TAG, "downloadResulterr runOnUiThread Thread.currentThread().getName() " + Thread.currentThread().getName());
((DownloadContentActivity)mActivity).inflateDownloadFailedLayout();
}
});
getViewState().onNext(ViewState.error(voidResult.error));
}
}
DownloadContentViewModel
private void initTimerDownloadFinished() {
log(Log.DEBUG, TAG, "initTimerDownloadFinished()");
mDownloadTimerHandler.postDelayed(this::okButton, 10000);
}
我需要一些帮助来找出此主线程中出现 ANR 对话框的原因。
提前致谢。
终于找到了与我分享的代码无关的问题
在项目中启动了一个服务,但没有像startForeground(ID, notification)
那样设置通知。它没有显示在日志中。当我选择信息级别日志时,我看到的原因是
E/ActivityManager: ANR in PID: 6516 Reason: Context.startForegroundService() did not then call Service.startForeground()
这是一个不必要的服务呼叫,所以我评论说,ANR 对话框消失了。