android开发过程中遇到的棘手的问题笔记(sp引起的anr,4g网络请求慢,app启动白屏)(持续更新)(代码片段)

mictoy_朱 mictoy_朱     2022-12-03     473

关键词:

前言

经历过面试的人应该都知道,一般我们在进行技术面试的时候,面试官都会问你,在项目开发中遇到过什么棘手的问题?最后是怎么解决的?本人之前就问到过好几次,可是由于准备不足,一时之间想不起来所遇到过的“棘手”问题,所以好几次都没能很好地回答这个问题。因此,在这里记录下本人在接下来工作中遇到的棘手问题以及最后解决方案。

1. 通过SharePerference初始化数据(数据量比较大)时,造成ANR(Application Not Response)
问题描述:
第一次启动应用,考虑到初始化的数据量过多,直接在UI线程里操作可能会阻塞UI线程造成ANR,所以使用了异步方式(线程)完成数据初始化操作,然后通过Handler通知主线程继续往下运行,然而实际运行时还是造成了ANR,经调试结合debug信息发现,初始化早已完成,但主线程却没有接收到Handler发出的完成信息继续往下执行,如果没有其他原因,即使他在主线程中执行完成它也不应该也不足以导致ANR,然而实际却出现了。
PS:打断点debug时又正常了(╬◣д◢)

解决方案
通查看ANR的trace日志,终于找到了发生ANR的地方
可以看到是卡在了QueueWork.waitToFinish,该函数的等待导致发生了ANR
看日志中描述的waitToFinish()方法:

/**
 * Trigger queued work to be processed immediately. The queued work is processed on a separate
 * thread asynchronous. While doing that run and process all finishers on this thread. The
 * finishers can be implemented in a way to check weather the queued work is finished.
 *
 * Is called from the Activity base class's onPause(), after BroadcastReceiver's onReceive,
 * after Service command handling, etc. (so async work is never lost)
 */
public static void waitToFinish() 
    ...

看上面方法解释:当Activity调用onPause()方法,或者BroadcastReceiver调用onReceive()方法的时候会调用该方法,这样就能保证异步任务不会丢失。

在结合调用的apply方法,找到对应代码出问题地方

        public void apply() 
            final long startTime = System.currentTimeMillis();

            final MemoryCommitResult mcr = commitToMemory();
            final Runnable awaitCommit = new Runnable() 
                    public void run() 
                        try 
                            mcr.writtenToDiskLatch.await();
                         catch (InterruptedException ignored) 
                        

                        if (DEBUG && mcr.wasWritten) 
                            Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                                    + " applied after " + (System.currentTimeMillis() - startTime)
                                    + " ms");
                        
                    
                ;

            QueuedWork.addFinisher(awaitCommit);

            Runnable postWriteRunnable = new Runnable() 
                    public void run() 
                        awaitCommit.run();
                        QueuedWork.removeFinisher(awaitCommit);
                    
                ;

            SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable);

            // Okay to notify the listeners before it's hit disk
            // because the listeners should always get the same
            // SharedPreferences instance back, which has the
            // changes reflected in memory.
            notifyListeners(mcr);
        

可以看到apply方法会将等待写入到文件系统的任务放在QueuedWork的等待完成队列里。所以如果我们使用SharedPreference的apply方法, 虽然该方法可以很快返回, 并在其它线程里将键值对写入到文件系统, 但是当Activity的onPause等方法被调用时,会等待写入到文件系统的任务完成,所以如果写入比较慢,主线程就会出现ANR问题。
再看commit()方法

public boolean commit() 
    long startTime = 0;

    if (DEBUG) 
        startTime = System.currentTimeMillis();
    

    MemoryCommitResult mcr = commitToMemory();

    SharedPreferencesImpl.this.enqueueDiskWrite(
        mcr, null /* sync write on this thread okay */);
    try 
        mcr.writtenToDiskLatch.await();
     catch (InterruptedException e) 
        return false;
     finally 
        if (DEBUG) 
            Log.d(TAG, mFile.getName() + ":" + mcr.memoryStateGeneration
                    + " committed after " + (System.currentTimeMillis() - startTime)
                    + " ms");
        
    
    notifyListeners(mcr);
    return mcr.writeToDiskResult;

它会在调用线程就等待写入任务完成,所以不会将等待的时间转嫁到主线程

小结

apply方法和commit方法对比:

  1. apply没有返回值而commit返回boolean表明修改是否提交成功
  2. apply方法不会提示任何失败的提示
  3. apply是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘, 而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率
  4. 虽然apply方法不会阻塞调用线程, 但是会将等待时间转嫁到主线程(UI线程),容易造成ANR问题

为了避免出现ANR问题,最好还是别使用apply操作,用commit方法最保险。如果担心在主线程调用commit方法会出现ANR,可以将所有的commit任务放到单线程池的线程里去执行。

2.Android app被杀后台进程后再启动使用电信网络时启动非常慢,而使用WiFi、移动、联通网络则正常启动

问题背景描述

测试在测试app时反馈一个问题,说App被杀进程后再次启动时变得很慢了,于是在我自己手机上也测试了一下,发现并不存在这样的问题,启动速度非常正常,因此首先怀疑是网络的问题。试着把他的手机切换到WiFi网络状态,果然启动变正常了,然后切换到他的4G网络,又变慢了。但是同样的4G网络,他启动其他app速度是很快的,正常的,而且看高清视频也完全没有问题,那就说明不是网络信号不良的问题,这就非常奇怪了。
经过网上查阅资料,发现都指向了电信卡的问题,问过测试,果然,他用的也是电信的4G网络,然后又试着切换到了移动的4G网络,果然启动速度正常了( 晕,这是什么鬼问题???
继续百度寻找答案,发现都指向了DNS解析设置的问题,即ipv4、ipv6解析的问题,android默认不支持ipv6解析。

android为何不支持ipv6解析?
ipv4地址即将枯竭,但是ipv6依然迟迟无法普及,原因之一就是有些巨头不提供支持。
不过在Google开发者、IPv6权威专家Lorenzo Colliti看来,Android不支持IPv6也是有苦衷的,比如会影响那些依赖IPv4的应用,无法强制开发者采用IPv6网络地址,地址转换后性能会有损失等等。

问题排查

使用以下代码 DNS 解析的 IP 地址

try 
   InetAddress[] mInetAddresses= InetAddress.getAllByName("xxxx.cn");
   for(InetAddress address: mInetAddresses)
     System.out.println(address.getHostAddress());
    catch (UnknownHostException e) 
   e.printStackTrace();

发现
1、连接到公司wifi,只解析到 ipv4 地址
2、连接到4G网,解析到了ipv4、ipv6俩个地址但是ipv6默认为集合中的第一个,是否我们可以尝试修改集合第一个为ipv4呢?

解决方案
自定义okhttp中dns解析

查看了下okhttp开放了自定义dns方法,于是追踪到源码可以看到

package okhttp3;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;

public interface Dns 
    Dns SYSTEM = new Dns() 
        public List<InetAddress> lookup(String hostname) throws UnknownHostException 
            if (hostname == null) 
                throw new UnknownHostException("hostname == null");
             else 
                return Arrays.asList(InetAddress.getAllByName(hostname));
            
        
    ;

    List<InetAddress> lookup(String var1) throws UnknownHostException;

于是我们去调换集合中ipv4 ipv6位置,将ipv4当到集合首位

package cn.finalteam.okhttpfinal.https;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import okhttp3.Dns;

public class ApiDns implements Dns 
    @Override
    public List<InetAddress> lookup(String hostname) throws UnknownHostException 
        if (hostname == null) 
            throw new UnknownHostException("hostname == null");
         else 
            try 
                List<InetAddress> mInetAddressesList = new ArrayList<>();
                InetAddress[] mInetAddresses = InetAddress.getAllByName(hostname);
                for (InetAddress address : mInetAddresses) 
                    if (address instanceof Inet4Address) 
                        mInetAddressesList.add(0, address);
                     else 
                        mInetAddressesList.add(address);
                    
                
                return mInetAddressesList;
             catch (NullPointerException var4) 
                UnknownHostException unknownHostException = new UnknownHostException("Broken system behaviour");
                unknownHostException.initCause(var4);
                throw unknownHostException;
            
        
    

将自定义方法插入到okhttp中

OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .connectTimeout(timeout, TimeUnit.MILLISECONDS)
                .writeTimeout(timeout, TimeUnit.MILLISECONDS)
                .readTimeout(timeout, TimeUnit.MILLISECONDS)
                .dns(new ApiDns());

最后在打包,运行,切换电信4G网络,果然应用启动正常了

后记

这个问题困扰了我好长一段时间,因为之前查找原因时在网上看到有网友反馈华为手机在升级到安卓9.0系统后,好多应用启动速度都变慢了,而同事的手机刚好也是华为手机,而且也是差不多在升级了9.0系统后出现了这个问题,因此想着不是我应用代码的问题,同时期间还有其他开发任务,就没有过多花时间在这上面,今天着重花了点时间来解决,终于在网上找到了相关问题解决方案的大神的文章Android部分手机4G网第一次请求很慢(wifi正常)解决方案至此,问题终于解决。

3.app启动白屏

描述

APP在被杀进程后再次启动(或第一次启动)时,由于Application里面做了较多的一些列初始化工作,导致启动APP后没有第一时间展示启动页面,取而代之的是2秒左右的白屏,之后才展示启动页面,这就导致不太好的用户体验。

为什么出现白屏

Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量布局绘制显示在界面上。

从你点击桌面的图标开始安卓系统会从Zygote进程中fork创建出一个新的进程分配给该应用,之后会依次创建和初始化Application类。当这些操作走完之后,你才能看到SplashActivity的第一眼。那么这期间你Application里onCreate做了N多的SDK的初始化,这段时间您的屏幕都是你手机默认主题的颜色(白色/黑色)。这就是为什么我们做的APP会出现白屏。

解决方案

1. 新增一个Activity并将其作为启动页(这里命名为FastStartAppActivity)

<activity
    android:name=".activity.FastStartAppActivity"
    android:theme="@style/FastStartTheme">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

2. 重写FastStartAPPActivity的onCreate()方法如下

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    startActivity(new Intent(this, SplashActivity.class));//SplashActivity为原启动页Activity
    overridePendingTransition(0, 0);
    finish();

3. 指定并设置FastStartAppActivity的Theme,设置Activity背景

<style name="FastStartTheme" parent="AppTheme">
    <item name="android:background">@drawable/bg_splash</item>
    <item name="android:windowFullscreen">false</item>
</style>

bg_splash为原启动页SplashActivity的背景图片,根据需求设置全屏与否

完成以上步骤再次运行,就可以发现白屏没有了。

参考资料:
SharedPreferences的apply和Commit方法的那些坑
Android部分手机4G网第一次请求很慢(wifi正常)解决方案
Android - 启动白屏分析与优化

面试必问_你在开发过程中有没有遇到什么棘手的问题,是怎么解决的

文章目录文章目录文章目录开发过程中有没有遇到什么棘手的问题,是怎么解决的1、表单提交的时候,刷新一下,会重新提交一次的问题,这样造成了性能的浪费,因为重新提交没啥用,还会重新查一次... 查看详情

android开发细节——上班实战项目中遇到的棘手问题与解决方案汇总(代码片段)

Paint的细节用法1、设置笔帽mPaint.setStrokeCap(Paint.Cap.BUTT);//没有mPaint.setStrokeCap(Paint.Cap.ROUND);//圆形mPaint.setStrokeCap(Paint.Cap.SQUARE);//方形2、设置滤镜1、模糊遮罩滤镜(BlurMaskFilter)2、浮雕遮罩滤镜(E 查看详情

android开发细节——上班实战项目中遇到的棘手问题与解决方案汇总(代码片段)

Paint的细节用法1、设置笔帽mPaint.setStrokeCap(Paint.Cap.BUTT);//没有mPaint.setStrokeCap(Paint.Cap.ROUND);//圆形mPaint.setStrokeCap(Paint.Cap.SQUARE);//方形2、设置滤镜1、模糊遮罩滤镜(BlurMaskFilter)2、浮雕遮罩滤镜(EmbossMaskFilter)3、设... 查看详情

android开发细节——上班实战项目中遇到的棘手问题与解决方案汇总(代码片段)

Paint的细节用法1、设置笔帽mPaint.setStrokeCap(Paint.Cap.BUTT);//没有mPaint.setStrokeCap(Paint.Cap.ROUND);//圆形mPaint.setStrokeCap(Paint.Cap.SQUARE);//方形2、设置滤镜1、模糊遮罩滤镜(BlurMaskFilter)2、浮雕遮罩滤镜(EmbossMaskFilter)3、设... 查看详情

vue开发过程中遇到的问题

1.gitlab团队协作开发2.elementui问题集锦3. 使用vue和ElementUI快速开发后台管理系统 查看详情

开发过程中遇到的各种问题汇总

TLS/SSL/HTTPS1.我的个人网站https改造过程中,遇到问题MixedContent:Thepageat‘https://www.liberalman.cn/login’wasloadedoverHTTPS,butrequestedaninsecurestylesheet‘http://o9gqjr7iy.bkt.clouddn.com/libertyblog//css/ 查看详情

二.jsp开发过程中遇到的问题及解决

一.开发环境问题问题一:FailedtoloadtheJNIsharedlibrary启动Eclipse时弹出“FailedtoloadtheJNIsharedlibrary……”这样的一段提示,如下图:大概意思就是说在某个位置找不到jvm.dll这个文件原因1: 指定目录下jvm.dll不存在,可能... 查看详情

开发过程中遇到问题

遇到问题1、接口传递批次数据及解析的问题?通过HttpClient调用接口接收方使用fastjson解析解决方案:传递的对象包含集合封装成json字符串,如果传递的单个对象,对象里包含有集合可以使用ATSATSInfo=JSON.parseObject(result,newTypeReferen... 查看详情

前端开发过程中遇到过啥困难?

面试前端开发工程师,对方问:“开发中遇到过什么困难,如何解决的?”这个问题应该怎么回答?希望大神给个范文。不要给什么问题分析、思路之类的,就是要一个可以背下来的范文。开发中主要会用到Vue、vue-cli、webpack、n... 查看详情

开发过程中遇到的小问题

com.ibatis.sqlmap.client.SqlMapException:ThereisnostatementnamedinthisSqlMap.意思是没有对应的名字的sql语句。检查了在xxx.xml文件中是否有两个标签的id命名相同(问题不在这)DAO实现类方法中有没有写对应xxx.xml的id名称(问题也不在这)那就只... 查看详情

源始web开发过程中遇到的一些兼容问题

...持下去,使自己能有所提高...   先来总结下在前端开发过程中容易遇到的一些兼容问题。 1、块属性标签float之后,如果有横向的margin属性,在IE6显示margin比设置的值大。  解决方案:给float的元素添加display:inline属... 查看详情

visualstudio2015community安装过程中遇到问题的终极解决

去年就有给自己电脑升级VS的想法,可是安装过程并不顺利,一直拖到现在,昨天下定决心,把遇到的问题一个个解决,终于安装成功了,将安装过程中遇到的问题和解决方法记录一下。需要说明一下的是,不同的电脑环境可能... 查看详情

loadrunner11安装过程中遇到的问题解答

...《loadrunner11安装、汉化、破解完整安装》在loadrunner11安装过程中,确认软件包没问题,但在安装过程之初就遇到了好几个问题,下边就做一整理:问题1:当安装提示"MicosoftVisualC++2005SP1可再发行组件包(X86):‘命令行选项语法... 查看详情

exchange2003sp2迁移到exchange2010sp2

文章内容只是记录了公司的邮件升级的过程,写的不是很规范,就当做是一个参考,望见谅。。。在这个安装过程中我们遇到了一些问题,事后感觉问题很好解决,但是在没有找到解决方法之前却花费了好多的时间,导致整个进... 查看详情

springmvc之旅-开发到部署过程中遇到的问题整理(不断更新)

开发中: 第一个问题:我在做一个APP的后台服务接口的时候遇到一个问题。在SpringMVC中有两个注解,@RequestBody和@ResponseBody。@RequestBody的作用是将输入参数为json时将json数据转换为java对象,@ResponseBody的作用是将输出结果的java... 查看详情

reactnative在开发过程中遇到的一些问题(俗称:坑)

4900服务器地址错误运行时产生以下错误:Couldnotconnecttodevelopmentserver.1、URL地址设置问题:[objc] viewplain copyCould not connect to development server.    Ensure  查看详情

阅读笔记六

...求变更分析  正如前面所说,需求频繁变更是许多项目开发过程中都会遇到的重大问题 查看详情

android中textview中的字体大小能设置吗

可以布局文件中使用android:textSize属性设置,例如android:textSize="15sp"代码中使用setTextSize(int unit,int size)方法设置,第一个参数是字体大小单位,第二个参数是字号值,例如setTextSize(TypedValue.COMPLEX_UNIT_SP, 15); //... 查看详情