欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android开发中遇到的问题记录

程序员文章站 2022-06-15 09:41:40
Android开发中遇到的问题记录1. WebView的WebViewClient请求网络图标失败导致404,引起加载空白页在使用WebView加载网页的时候,在网页加载失败的时候我可能需要做一些处理,比如加载自己指定的错误页面等。所以我们需要重写 android.webkit.WebViewClient#onReceivedHttpError但是我发现加载我写的一个简单页面也会出现白屏,会退一下返回栈才能正常显示。这就很奇怪了,页面非常简单不太可能出错,而且也没有跳转和重定向,最后找了很久才知道...

Android开发中遇到的问题记录

1. WebView的WebViewClient请求网络图标失败导致404,引起加载空白页

在使用WebView加载网页的时候,在网页加载失败的时候我可能需要做一些处理,比如加载自己指定的错误页面等。
所以我们需要重写 android.webkit.WebViewClient#onReceivedHttpError
但是我发现加载我写的一个简单页面也会出现白屏,会退一下返回栈才能正常显示。这就很奇怪了,页面非常简单不太可能出错,
而且也没有跳转和重定向,最后找了很久才知道定位到问题。

@Override
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
    super.onReceivedHttpError(view, request, errorResponse);
    log.info("onReceivedHttpError " + request.toString() + " " + errorResponse.getStatusCode());
    int statusCode = errorResponse.getStatusCode();
    if (404 == statusCode || 500 == statusCode) {
        //view.loadUrl("about:blank");// 避免出现默认的错误界面
        // view.loadUrl("");
    }
    log.info("errorResponse:" + errorResponse.getData());
}

在加载网页的时候,安卓会加载 favicon.ico 图标,而一些小的网页和网站可能就没有配置,所以这就解释了为什么我写的简单页面不能这正常加载。

我现在不对错误进行处理。 可以参考

2. BottomNavigationView 长按出现Toast影响美观

在使用 com.google.android.material.bottomnavigation.BottomNavigationView 显示底部导航栏时,长按item会出现标题,产品觉得影响美观所以需要去掉

    BottomNavigationView bottomNavigationView = getBinding().bottomNavigationView;
    List<Integer> ids = Arrays.asList(...);
    ViewGroup bottomNavigationMenuView = (ViewGroup) bottomNavigationView.getChildAt(0);
    for (int i = 0; i < ids.size(); i++) {
        bottomNavigationMenuView.getChildAt(i).findViewById(ids.get(i)).setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                return true;
            }
        });
    }

大家可以通过阅读源码为什么长按事件之后就能不显示Toast

可以从 com.google.android.material.bottomnavigation.BottomNavigationItemView#initialize 

然后一步步 就会找到 android.view.View#showLongClickTooltip 方法


最后揭露迷底

/**
    * Calls this view's OnLongClickListener, if it is defined. Invokes the
    * context menu if the OnLongClickListener did not consume the event,
    * optionally anchoring it to an (x,y) coordinate. 
    
    调用此视图的OnLongClickListener(如果已定义)。 
    如果OnLongClickListener没有使用事件,则调用上下文菜单,可以选择将其锚定到(x,y)坐标。
    *
    * @param x x coordinate of the anchoring touch event, or {@link Float#NaN}
    *          to disable anchoring
    * @param y y coordinate of the anchoring touch event, or {@link Float#NaN}
    *          to disable anchoring
    * @return {@code true} if one of the above receivers consumed the event,
    *         {@code false} otherwise
    */
private boolean performLongClickInternal(float x, float y) {
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);

    boolean handled = false;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnLongClickListener != null) {
        // 关键就在于这里,长按事件返回的ture
        handled = li.mOnLongClickListener.onLongClick(View.this);
    }
    if (!handled) {
        final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
        handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
    }
    if ((mViewFlags & TOOLTIP) == TOOLTIP) {
        if (!handled) {
            // 显示toast
            handled = showLongClickTooltip((int) x, (int) y);
        }
    }
    if (handled) {
        performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
    }
    return handled;
}


3. Android O JobIntentService的SecurityException问题解决办法

场景:当日志文件大于设定的文件大小则启动后台服务上传应用日志到后台,或者产生闪退立即上传日志

如何产生错误:在Android 8.0以上的设备,同一时间段启动相同JOB_ID的服务就会出现安全异常(一个服务只能有一个JOB_ID

错误详情如下:(这个错误信息是我拷贝的网上其他的,并不是我机器上的。)

[2020-12-31 13:45:30 041][GlobalExceptionHelper]: android.os.RemoteException: Remote stack trace:
	at com.android.server.job.JobServiceContext.assertCallerLocked(libmapleservices.so:4784420)
	at com.android.server.job.JobServiceContext.doDequeueWork(libmapleservices.so:4677844)
	at com.android.server.job.JobServiceContext$JobCallback.dequeueWork(libmapleservices.so:4680440)
	at android.app.job.IJobCallback$Stub.onTransact(libmapleframework.so:3676756)
	at android.os.Binder.execTransactInternal(libmapleframework.so:5532620)

	at com.android.server.job.JobServiceContext.assertCallerLocked(libmapleservices.so:4784420)
	at com.android.server.job.JobServiceContext.doDequeueWork(libmapleservices.so:4677844)
	at com.android.server.job.JobServiceContext$JobCallback.dequeueWork(libmapleservices.so:4680440)
	at android.app.job.IJobCallback$Stub.onTransact(libmapleframework.so:3676756)
	at android.os.Binder.execTransactInternal(libmapleframework.so:5532620)
 Caused by: java.lang.SecurityException: Caller no longer running, last stopped +525ms because: timed out while starting
	at android.os.Parcel.createException(Parcel.java:2071)
	at android.os.Parcel.readException(Parcel.java:2039)
	at android.os.Parcel.readException(Parcel.java:1987)
	at android.app.job.IJobCallback$Stub$Proxy.dequeueWork(IJobCallback.java:292)
	at android.app.job.JobParameters.dequeueWork(JobParameters.java:248)
	at androidx.core.app.JobIntentService$JobServiceEngineImpl.dequeueWork(JobIntentService.java:315)
	at androidx.core.app.JobIntentService.dequeueWork(JobIntentService.java:640)
	at androidx.core.app.JobIntentService$CommandProcessor.doInBackground(JobIntentService.java:390)
	at androidx.core.app.JobIntentService$CommandProcessor.doInBackground(JobIntentService.java:383)
	at android.os.AsyncTask$3.call(AsyncTask.java:389)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
 Caused by: java.lang.RuntimeException: An error occurred while executing doInBackground()
	at android.os.AsyncTask$AsyncFutureTask.done(AsyncTask.java:429)
	at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383)
	at java.util.concurrent.FutureTask.setException(FutureTask.java:252)
	at java.util.concurrent.FutureTask.run(FutureTask.java:271)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
	at java.lang.Thread.run(Thread.java:929)

这个问题在google IssueTracker 上也有追踪 isccies 63622293

因为没有直接的方法可以解决这个问题。并且我们已经知道了问题产生的原因,所以我们可以避免发生这样的情况,或者寻找可以满足当前情景的方案。

我目前想到的方案有如下两种

第一个可行的方案是用IntentService 再做兼容,老方法,我不准备采用

第二个可以的方案是用Jetpack WorkManager, 虽然它不能保证服务立即被执行,但是可以不丢失服务,满足我的需要。新的方法,而且比较灵活,后面有更多去求也可以满足,所以我选择这个方案。我并非喜新厌旧之人。


如果应用没有使用AndroidX,可以通过重写v4下的JobIntentService#dequeueWork

package android.support.v4.app;

import timber.log.Timber;

public abstract class MyJobIntentService extends JobIntentService {   

    @Override

    GenericWorkItem dequeueWork() { 
        try {
            return super.dequeueWork();
        } catch (SecurityException ignored) {
            Timber.e(ignored);
        }    
        return null;
    }
}

PS: 上面方法我并没有在自己应用验证过。

如果应用中使用AndroidX则不适用上述方法,我这里用Jetpack WorkManager

引入WorkManager api 'androidx.work:work-runtime:2.4.0',然后初始化 WorkManager.getInstance(this); 注意Application需要实现 androidx.work.Configuration.Provider#getWorkManagerConfiguration 接口

@Log
public class LogUploadService extends Worker{// 继承 androidx.work.Worker
    
    public LogUploadService(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }

    public static void startWork(){
        // 启动一次性任务
        OneTimeWorkRequest.Builder builder = new OneTimeWorkRequest.Builder(LogUploadService.class);
        // 传递参数
        Data data = new Data.Builder().putString("action", intent.getAction())
                .build();
        builder.setInputData(data);
        // 提交任务
        WorkManager.getInstance(context)
                .enqueue(builder.build());
    }

    private void uploadLogFile(Intent intent){
        // 上传日志相关业务
    }

    @NonNull
    @Override
    public Result doWork() {// 重写 androidx.work.Worker#doWork
        try {
            // 因为我在应用通过startWork()启动任务,所以这里不会为空
            String action = getInputData().getString("action");
            uploadLogFile(new Intent(action));
        } catch (Exception e) {
            log.severe(ThrowableUtils.getFullStackTrace(e));
            return Result.failure();
        }
        // 这里的返回值很重要
        // 可以在 androidx.work.ListenableWorker.Result 中看到还有个
        // androidx.work.ListenableWorker.Result#retry 方法 
        // 上面异常处理中也有个 androidx.work.ListenableWorker.Result#failure()

        // 这里拷贝下Result类的注释(我英文水平不太好,没办法准确翻译,为避免误导,请自行理解)

        /**
        * The result of a {@link ListenableWorker}'s computation. Call {@link #success()},
        * {@link #failure()}, or {@link #retry()} or one of their variants to generate an object
        * indicating what happened in your background work.
        */

        return Result.success();
    }
}

Jetpack WorkManager不能保证服务立即被执行,但是可以保证服务一定不丢失。它本身有数据库记录服务

经测试,该替代方案,暂时没问题

4. WorkManager doWork()会执行两次

在第三个问题解决后,在项目中更多的使用WorkManager才出现了这个问题。

WorkManager.getInstance(context).enqueueUniqueWork("StatisticsService", ExistingWorkPolicy.KEEP, builder.build());

当我启动一个唯一任务之后,doWork()方法执行失败之后,该方法会重复执行一次。

即这个方法执行了两次,非常的奇怪。目前还没有找到问题的原因。问题日志及详情 *

望指教.

虽然不知道原因,但是还有办法不让我的代码块不重复执行。添加一个静态变量即可。但是这不是长久之计,而是缓兵之计。

本文地址:https://blog.csdn.net/jayz3368/article/details/112802232