android interview

为什么不建议使用Intent传递大的数据

Intent 传递大数据,会出现 TransactionTooLargeException 的场景。
简单来说,Intent 传输数据的机制中,用到了 BinderIntent 中的数据,会作为 Parcel 被存储在 Binder 的事务缓冲区(Binder transaction buffer)中的对象进行传输。

而这个 Binder 事务缓冲区具有一个有限的固定大小,当前为 1MB。你可别以为传递 1MB 以下的数据就安全了,这里的 1MB 空间并不是当前操作独享的,而是由当前进程所共享。也就是说 Intent 在 Activity 间传输数据,本身也不适合传递太大的数据。

参考资料为什么阿里巴巴不建议使用Intent传递大的数据

activity启动模式,与taskAffinity

每一个 Activity 都有一个 Affinity 属性,如果不在清单文件中指定,默认为当前应用的包名。
taskAffinity是与任务栈有关属性。
单纯使用 taskAffinity 不能导致 Activity 被创建在新的任务栈中,需要配合 singleTask 或者 singleInstance.

可以使用如下命令,查看activity

adb shell dumpsys activity activities

allowTaskReparenting 赋予 Activity 在各个 Task 中间转移的特性。一个在后台任务栈中的 Activity A,当有其他任务进入前台,并且 taskAffinity 与 A 相同,且栈的affinity属性相同,则会自动将 A 添加到当前启动的任务栈中。

参考资料Android 启动模式和 taskAffinity 属性详解

progress造成多个Application

Activity 可以在不同的进程中启动,而每一个不同的进程都会创建出一个 Application,因此有可能造成 Application 的 onCreate 方法被执行多次。
可以在applycation onCreate 方法中判断进程的名称,只有在符合要求的进程里,才执行初始化操作。通过如下方法,获取进程名称。

private String getProcessName(Context cxt) {
        int pid = android.os.Process.myPid();
        ActivityManager am = (ActivityManager) cxt.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningAppProcessInfo> runningApps = am.getRunningAppProcesses();
        if (runningApps == null) {
            return null;
        }
        for (ActivityManager.RunningAppProcessInfo procInfo : runningApps) {
            if (procInfo.pid == pid) {
                return procInfo.processName;
            }
        }
        return null;
    }

RecyclerView的缓存机制

RecyclerViewonMeasureonLayout 工作委托给LayoutManager来执行,不同的LayoutManager会有不同风格的布局显示(策略模式)。
RecyclerView绘制原理
缓存复用原理 Recycler
onLayout 阶段时,有介绍在 layoutChunk 方法中通过调用 layoutState.next 方法拿到某个子 ItemView,然后添加到 RV
最终调用是在RecyclergetViewForPosition tryGetViewHolderForPositionByDeadline方法来查找相应位置上的ViewHolder,在这个方法中会从下面介绍的 4 级缓存中依次查找。
如果在各级缓存中都没有找到相应的 ViewHolder,则会使用 Adapter 中的 createViewHolder 方法创建一个新的 ViewHolder

第一级缓存 mAttachedScrap&mChangedScrap
这两者主要用来缓存屏幕内的 ViewHolder

第二级缓存 mCachedViews
用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存个数是 2,不过可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则将旧 ViewHolder 抛弃,然后添加新的 ViewHolder

第三级缓存 ViewCacheExtension
这是 RV 预留给开发人员的一个抽象类,在这个类中只有一个抽象方法geViewForPositionAndType

第四级缓存 RecycledViewPool
RecycledViewPool 同样是用来缓存屏幕外的 ViewHolder,当 mCachedViews 中的个数已满(默认为 2),则从 mCachedViews 中淘汰出来的 ViewHolder 会先缓存到 RecycledViewPool 中。ViewHolder 在被缓存到 RecycledViewPool 时,会将内部的数据清理,因此从 RecycledViewPool 中取出来的 ViewHolder 需要重新调用 onBindViewHolder 绑定数据。

多个 RV 之间可以共享一个 RecycledViewPool,这对于多 tab 界面的优化效果会很显著。需要注意的是,RecycledViewPool 是根据 type 来获取 ViewHolder,每个 type 默认最大缓存 5 个。因此多个 RecyclerView 共享 RecycledViewPool 时,必须确保共享的 RecyclerView 使用的 Adapter 是同一个,或 view type 是不会冲突的。

对OkHttp的理解

优势
更好的WebSocket支持
更多的Interceptor责任链,可以更好的扩展
核心 HttpCodec
调度器
DispatcherOkHttpClient 的调度器,是一种门户模式。主要用来实现执行、取消异步请求操作。本质上是内部维护了一个线程池去执行异步操作,并且在 Dispatcher 内部根据一定的策略,保证最大并发个数、同一 host 主机允许执行请求的线程个数等。
任务执行,会根据依次执行拦截器。常用拦截器说明。
BridgeInterceptor:主要对 Request 中的 Head 设置默认值,比如 Content-Type、Keep-Alive、Cookie 等。
CacheInterceptor:负责 HTTP 请求的缓存处理。
ConnectInterceptor:负责建立与服务器地址之间的连接,也就是 TCP 链接。
CallServerInterceptor:负责向服务器发送请求,并从服务器拿到远端数据结果。

首先 OkHttp 内部是一个门户模式,所有的下发工作都是通过一个门户 Dispatcher 来进行分发。
然后在网络请求阶段通过责任链模式,链式的调用各个拦截器的 intercept 方法。 2 个比较重要的拦截器:CacheInterceptor 和 CallServerInterceptor。它们分别用来做请求缓存和执行网络请求操作。

网络优化
OkHttp 3.11.0版本提供了EventListener接口,可以让调用者接收一系列网络请求过程中的事件,例如DNS解析、TSL/SSL连接、Response接收等。通过继承此接口,调用者可以监视整个应用中网络请求次数、流量大小、耗时情况。
我们要实现 EventListener 接口,及其工厂类。
在创建OkHttpClient时将EventListener工厂作为构建参数添加进去。

OkHttpClient.Builder builder = new OkHttpClient.Builder()
		  //HttpEventListener为自己实现EventListener类,FACTORY对应工厂
          .eventListenerFactory(HttpEventListener.FACTORY)
          .build();

使用OkHttp一次完整http请求,包括以下几个步骤

0,000 callStart
0,027 dnsStart
5,189 dnsEnd
5,359 secureConnectStart
5,907 secureConnectEnd
5,910 connectEnd
5,921 connectionAcquired
5,925 requestHeadersStart
5,930 requestHeadersEnd
5,938 responseHeadersStart
6,181 responseHeadersEnd
6,189 responseBodyEnd
6,233 connectionReleased 

DNS解析优化
首先是防劫持,我们可以考虑使用 HttpDns.
目前业内主要由第三方厂商提供实现了 HttpDns 的 SDK,比较普及的是阿里云和腾讯云的 HttpDns Service.可以考虑使用七牛云提供的免费的 happy-dns.
在接口 Dns 中只有一个方法需要实现——lookup,这个方法返回查找到的服务器地址集合。并且 OkHttp 已经实现了一个默认的 DNS 解析器,定义中的SYSTEM,它使用 java net 包中的 InetAddress 获取某域名的 IP 地址集合。

参考资料
对于网络编程,你做过哪些优化?
OkHttp 之 网络请求耗时统计

bitmap的优化

内存中,一个像素点占几个byte,见Bitmap.Config的设置,默认是ARGB_8888.
BitmapFactory 加载 resource中图片,会根据设备屏幕密度进行缩放。
BitmapFactory加载Assert中图片,不会进行缩放。

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rodman);
//getAllocationByteCount 获取 bitmap占用的字节大小
Log.i(TAG, "bitmap size is "+bitmap.getAllocationByteCount());

imageView.setImageBitmap(bitmap)

加载优化

BitmapFactory.Options options = new BitmapFactory.Options();
//优化1,修改像素的表示
options.inPreferredConfig = Bitmap.Config.RGB_565;
//优化2,宽和高没隔2个像素进行一次采样
options.inSampleSize = 2;
//优化3,使用bitmap的复用,(1.android4.4要求相等 2. >4.4要求要大于等于 2)
option.inBimap = reuseBitmap;
option.inMutable = true;

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rodman);
//注意getByteCount() 和 bitmap.getAllocationByteCount() (后者可能会更大) 的区别
Log.i(TAG, "bitmap size is " + bitmap.getByteCount());

imageView.setImageBitmap(bitmap);

大图加载
在不压缩图片的前提下,不建议一次性将整张图加载到内存,而是采用分片加载的方式来显示图片部分内容,然后根据手势操作,放大缩小或者移动图片显示区域。

/**
 * 显示图片的左上角 200*200 区域
**/
private void showRegionImage()  {
    try {
        InputStream inputStream = getAssets().open("rodman3.png");
        //设置显示图片的中心区域
        BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance( inputStream, false );
        BitmapFactory.Options options = new BitmapFactory.Options();
        Bitmap bitmap = decoder.decoder.decodeRegion( new Rect(0, 0, 200, 200), options );
        regionImage.setImageBitmap( bitmap );
    } catch (IOException e) {}
}

Bitmap 缓存,结合LruCache进行优化

activity的启动流程

这一过程主要涉及 3 个进程间的通信过程:

  • 首先进程 A 通过 Binder 调用 AMSstartActivity 方法。(startActivity->AMS)
  • 然后 AMS 通过一系列的计算构造目标 Intent,然后在 ActivityStackActivityStackSupervisor 中处理 TaskActivity 的入栈操作。(AMS->ApplicationThread)
  • 最后 AMS 通过 Binder 机制,调用目标进程中 ApplicationThread 的方法来创建并执行 Activity 生命周期方法,实际上 ApplicationThreadActivityThread 的一个内部类,它的执行最终都调用到了 ActivityThread 中的相应方法。(ApplicationThread->Activity)

Window、Activity、View之间关系

PhoneWindow
一个 Activity 中有一个 Window(也就是 PhoneWindow 对象,PhoneWindow 是Window的唯一实现类)。
PhoneWindow中有一个DecorView (继承自FrameLayout,mContentParent也是取自DecorView中)。

activitysetContentView最终是通过PhoneWindow中方法,setContentView
mLayoutInflater.inflate(layoutResID, mContentParent); 完成添加的。

WindowManager
PhoneWindow 只是负责处理一些应用窗口通用的逻辑(设置标题栏,导航栏等)。但是真正完成把一个 View 作为窗口添加到 WMS 的过程是由 WindowManager (其实现类 WindowManagerImpl )来完成的。
ActivityThread - handleResumeActivity为入口,
WindowMangerGlobal (单例类) -> addView,
ViewRootImpl -> setView
在此处完成绘制。并通过 mWindowSessionaddToDisplay 方法将 View 添加到 WMS 中。
事件的分发,也是通过WMS,发送到上层。最终调用了 PhoneWindow 中的dispatchTouchEvent 方法。

内存泄漏问题

常见的内存泄漏

  • 静态变量。 特别是ViewContext置为static时。

  • 没有反注册各种Listener 例如广播,没有对应的反注册方法

  • 非静态Handler导致Activity泄漏 可以将其置为static,然后内部持有一个Activity的弱引用。

  • 第三方库使用的Context 传参,尽量使用ApplicationContext.

内存泄漏检测
可以使用LeakCanary.
原理
Java 中的 WeakReference 是弱引用类型,每当发生 GC 时,它所持有的对象如果没有被其他强引用所持有,那么它所引用的对象就会被回收。
WeakReference 的构造函数可以传入 ReferenceQueue,当 WeakReference 指向的对象被垃圾回收器回收时,会把 WeakReference 放入 ReferenceQueue 中。

public class WeakRefDemo {
	public static void main(String[] args) {
		ReferenceQueue<BigObject> queue = new ReferenceQueue<>();
		
		WeakReference<BigObject> reference = new WeakReference<>(new BigObject(), queque);
		
		System.out.println("before gc, reference.get is "+ reference.get());
		System.out.println("before gc, queue is"+ queue.pull());
		//打印值为 BigObject@7852e922, null

		System.gc();
		Thread.sleep(1000);
		
		System.out.println("after gc, reference.get is "+ reference.get());
		System.out.println("after gc, queue is"+ queue.pull());
		//null, WeakReference@4e25154f
	}
	static class BigObject {
	}
}

LeakCanary 中对内存泄漏检测的核心原理就是基于 WeakReferenceReferenceQueue 实现的。

  1. 当一个 Activity 需要被回收时,就将其包装到一个 WeakReference 中,并且在 WeakReference
    的构造器中传入自定义的 ReferenceQueue。

  2. 然后给包装后的 WeakReference 做一个标记 Key,并且在一个强引用 Set 中添加相应的 Key 记录

  3. 最后主动触发 GC,遍历自定义 ReferenceQueue 中所有的记录,并根据获取的 Reference 对象将 Set
    中的记录也删除

经过上面 3 步之后,还保留在 Set 中的就是:应当被 GC 回收,但是实际还保留在内存中的对象,也就是发生泄漏了的对象。
参考资料
面对内存泄漏,如何进行优化?
Android Studio和MAT结合使用来分析内存问题

APK包体积优化

包体积分析

  • Android Studio的APK Analyzer,直接使用AS打开即可。
  • 使用腾讯开源框架Matrix中的ApkChecker. 支持命令行。

优化的方法

  • 删除无用文件。使用Lint查看未引用资源。在 Android Studio 中点击 Analyze -> Inspect Code.
  • 资源优化。设置shrinkResources.需要minifyEnabled 也为true.
  • 代码混淆及优化。
  • 文件优化,例如多使用VectorDrawable 图片, 使用webp格式图片(as可以直接将png转成webp)。
  • so优化,例如只需要支持arm平台,就没有必要打包进其他平台的so
  • google play store 使用 App Bundle
android {
	defaultConfig {
        applicationId "xxx"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.compileSdkVersion
        
        ndk {
            // 设置支持的SO库架构
            abiFilters 'armeabi', "armeabi-v7a", "arm64-v8a"
        }
    }
    buildTypes {
        release {
            minifyEnabled true //启用代码压缩
            shrinkResources true //启用资源压缩
            proguardFiles getDefaultProguardFile(‘proguard-android.txt'),
                    'proguard-rules.pro'//混淆规则文件配置
        }
    }
}

参考资料
APK如何做到包体积优化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值