Android面试知识整理

Android基础知识

一、组件类

1. Activity相关

(1) Activity生命周期

Activity生命周期.jpg


1) 整个的生命周期,从onCreate(Bundle)开始到onDestroy()结束。

2) 可见的生命周期,从onStart()开始到onStop()结束。在这段时间,可以看到Activity在屏幕上,尽管有可能不在前台,不能和用户交互。在这两个接口之间,需要保持显示给用户的UI数据和资源等,例如:可以在onStart中注册一个IntentReceiver来监听数据变化导致UI的变动,当不再需要显示时候,可以在onStop()中注销它。

3) 前台的生命周期,从onResume()开始到onPause()结束。在这段时间里,该Activity处于所有 Activity的最前面,和用户进行交互。Activity可以经常性地在resumed和paused状态之间切换。

4) 从界面A跳转到界面B,生命周期变化情况:
当用户点击A中按钮来到B时,假设B全部遮挡住了A,将依次执行A:onPause -> B:onCreate -> B:onStart -> B:onResume -> A:onStop。
此时如果点击Back键,将依次执行B:onPause -> A:onRestart -> A:onStart -> A:onResume -> B:onStop -> B:onDestroy。

5) 对于栈最顶上的界面A,按Back键和按Home键的区别:
如果按下Back键,系统返回到桌面,并依次执行A:onPause -> A:onStop -> A:onDestroy。
此时如果按下Home键(非长按),系统返回到桌面,并依次执行A:onPause -> A:onStop。由此可见,Back键和Home键主要区别在于是否会执行onDestroy。

(2) Activity的4种启动模式

1)standard:默认的标准启动模式,不管有没有已存在的实例,都生成新的实例。即使是A startActivity A,也会重新生成一个新的实例,再回退时,A也会出现两次;
2)singleTop:如果发现有对应的Activity实例正位于栈顶,则重复利用,不再生成新的实例,如A启动A,不会生成新的实例,会走A的onNewIntent方法,而不是onCreate方法,回退时,也只会回退一次;
3)singleTask:所在Activity栈中有对应的Activity实例,则使此Activity实例之上的其他Activity实例统统出栈,使此Activity实例成为栈顶对象,显示到幕前,这一般用在程序的主界面上;
4)singleInstance:当被启动时,系统会首先判断系统其他栈中是否已经存在此Activity实例,有则直接使用,并且其所在的Activity栈理论上只有它一个Activity元素。
singleInstance表示该Activity在系统范围内“实例唯一”。ingInstance和singleTask主要区别在与系统范围内的“实例唯一”还是当前Activity栈“实例唯一”。

(3) Activity的状态保存

Activity的状态保存onSaveInstanceState()、onRestoreInstanceState()
Activity的 onSaveInstanceState() 和onRestoreInstanceState()并不是生命周期方法,当应用遇到意外情况(如:内存不足、用户直接按Home键)由系统销毁一个Activity时,onSaveInstanceState()会被调用。但是当用户主动去销毁一个Activity时,例如在应用中按返回键,onSaveInstanceState()就不会被调用。通常onSaveInstanceState()只适合用于保存一些临时性的状态,而onPause()适合用于数据的持久化保存。当某个activity变得“容易”被系统销毁时,该activity的onSaveInstanceState就会被执行,开发者可以覆写onSaveInstanceState()方法。onSaveInstanceState()方法接受一个Bundle类型的参数, 开发者可以将状态数据存储到这个Bundle对象中, 这样即使activity被系统摧毁, 当用户重新启动这个activity而调用它的onCreate()方法时, 上述的Bundle对象会作为实参传递给onCreate()方法, 开发者可以从Bundle对象中取出保存的数据, 然后利用这些数据将activity恢复到被摧毁之前的状态。如在横竖屏切换时,应用的Activity可能要保存edittext中的值,需要调用onSaveInstanceState()来何存输入值。

(4) Activity启动方式

startActivityForResult两个注意事项:
1) 这里需要注意的是,如果被启动的与Activity在AndroidManifest.xml文件中配置的launchMode设为"singleTask"的时候,startActivityForResult执行后,onActivityResult立即会被执行到。
原因:startActivityForResult(Intent,int,Bundle)有说明:if the activity you are launching uses thesingleTask launch mode, it will not run in your task and thus you willimmediately receive a cancel result.因为Activity1用startActivityForResult启动Activity2, 如果一个Activity2的启动方式是singleTask时,若Activity2之前在任务栈中存在,再次启动它时,它会把它上面所有的Activity实例都pop出栈,这样再调setResult时,返回的是Activity2任务栈下面的界面,而不是Activity1,所以,Activity1收到的一个cancel的Result。

2) 另外startActivityForResult的requestCode值必须要大于等于0,不然,startActivityForResult就变成了 startactivity。

(5) Activity的isFinish/finish()和destroy的区别

activity的finish()方法有两个层面含义,1.将此Activity从Activity栈中移除,2.调用了此Activity的onDestroy方法。
1) finish()方法用于结束一个Activity的生命周期,但是并没有释放他的资源。
2) onDestory()方法则是Activity的一个生命周期方法,其作用是在一个Activity对象被销毁之前,Android系统会调用该方法,用于释放此Activity之前所占用的资源。
有可能程序在运行了finish()方法而没有运行onDestory()方法,因为Activity生命周期里的方法,不确定什么时候会被调用,皆是由系统确定;
3) isFinishing方法来判断Activity是否处于销毁状态。

(6) Activity和Fragment的区别

一个fragment必须总是嵌入在一个activity中,同时fragment的生命周期受activity而影响,一个Activity可以运行多个 Fragment,一个fragment也可以在多个activity中作为一个模块,fragment有自己的生命周期,接收自己的输入事件,可以从运行中的activity添加或移除。
Activity 的后退栈由系统管理,而 Fragment 的后退栈由所在的Activity 管理。

2. Broadcast广播相关

(1) Broadcast的生命周期

广播接收器仅在它执行这个方法时处于活跃状态。当 onReceive() 返回后,它即为失活状态。

(2) Broadcast的两种注册方式

1) 静态注册,在AndroidManifest.xml中用<receiver>标签声明注册,并在标签内用<intent- filter>标签设置过滤器。

2) 动态注册:动态地在代码中先定义并设置好一个 IntentFilter对象,然后在需要注册的地方调 Context.registerReceiver()方法,如果取消时就调用Context.unregisterReceiver()方法。如果用动 态方式注册的BroadcastReceiver的Context对象被销毁时,BroadcastReceiver也就自动取消注册了。
注意事项:若在使用sendBroadcast()的方法是指定了接收权限,则只有在AndroidManifest.xml中用<uses- permission>标签声明了拥有此权限的BroascastReceiver才会有可能接收到发送来的Broadcast。

3. Content Provider广播相关

4. Service服务相关

(1) 定义:一个专门在后台处理时间任务,没有UI界面;
(2) Service的生命周期


Service生命周期.jpg

① 通过bindService启动的Service生命周期:onCreate()->onBind()->onUnbind()->onDestory()
② 通过startService启动的Service生命周期:onCreate()->onStartCommand()->onDestory()
(3) Service两种启动方式和区别:bindService, startService
① startService只是启动Service,与启动的组件没有关联,当Service调用stopSelf或是相关组件调用stopService服务才会终止;
② bindService,和组件绑定,其他组件可通过回调获取Service代理来和Service交互,当启动方销毁,Service也会调用unBinder进行解绑,只有所有绑定该Service的组件都解绑了,Service才会销毁。
(4) Service和Activity怎么进行通信
(5) Service与IntentService
① Service与它所在应用位于同一个进程中; Service还是在主线程中跑,所以不应该在Service中直接处理耗时的任务;
② IntentService:IntentService是Service的子类,比普通的Service增加了额外的功能。
它会创建独立的worker线程来处理所有的Intent请求;
会创建独立的worker线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题;
所有请求处理完成后,IntentService会自动停止,无需调用stopSelf()方法停止Service;
为Service的onBind()提供默认实现,返回null,故适合用startService来启动
为Service的onStartCommand提供默认实现,将请求Intent添加到队列中;

5. Fragment相关

(1)Fragment的生命周期


fragment生命周期.png


(2)Fragment之间数据传递方式

(3)Fragment与Activity之间数据传递方式

6. SharedPreferences相关

二、进程、线程相关

1. 多进程通信方式

(1)Intent通过bundle发送数据
(2)文件共享
(3)content Provider
(4)AIDL
(5)Messager
(6)Socket

2. 线程间如何进行通信

3. 线程间同步有哪几种

(1)由synchronized关键字修饰方法、代码块,如果synchronized修饰的是静态方法,此时若调用该静态方法,将会锁住整修类。
使用方法,如饿汉式的线程安全写法(修饰方法):
//公有的同步静态方法
public static synchronized Singleton getInstance() { //锁住获取实例方法
if (instance == null) {
instance = new Singleton();
}
return instance;
}

(2)用特殊域变量(volatile)实现线程同步
volatile修饰的变量每次都是从内存中读取,而不是从缓存中读取,保证每个线程访问到的变量值都是一样的;适应用于需要读同步的线程同步中,常结合synchronized一起使用。
如单例的双重锁写法
public class Singleton {
private volatile static Singleton singleton; //使用volatile 关键字
private Singleton (){} //私有构造函数
public static Singleton getInstance() {
if (singleton == null) { //第一层校验
synchronized (Singleton.class) {
if (singleton == null) { //第二层校验
singleton = new Singleton();
}
}
}
return singleton;
}
}
这里需要使用volatile来修饰单例的实例,因为在执行singleton = new Singleton();这句里需要三步:
1) 给Singleton的实例分配内存;
2) 调用Singleton()的构造函数,需要初始化成员字段;
3) 将singleton对象指向分配的内存空间(即singleton不为空了);
由于步骤2和步骤3的运行顺序是不确定的,如果某线程创建实例步骤是:1-3-2,在执行3还没有执行2时,另外一个线程就来使用该实例,由于执行了3就直接把实例拿走了,这样在使用过程中就会因为singleton中某些成员没有初始化而报错。
具体的单例写法可参考:Android设计模式之单例模式的七种写法

(3)使用重入锁ReentrantLock类实现线程同步
用法:先声明一个 ReentrantLock实例
Lock lock = new ReentrantLock();
在需要同步的代码块前后添加上lock.lock();和lock.unlock();
(4)使用ThreadLocal管理变量
1) 用ThreadLocal修饰的变量有什么不同?
使用ThreadLocal创建的变量叫线程局部变量,只可以被当前线程访问并修改,其他线程无法访问或修改。若使用ThreadLocal来管理变量,每个线程使用的都是该变量的副本,副本之间是相互独立的,这样每个线程都可以随意修改自己变量的副本,而不会对其他线程产生影响。

2) ThreadLocal内部实现原理,怎么保证数据仅被当前线程持有?
ThreadLocal在进行放值时的代码如下:
public void set(T value) {
Thread currentThread = Thread.currentThread();//获取当前线程
Values values = values(currentThread);//利用当前线程作为句柄获取一个Values对象
if (values == null) {
values = initializeValues(currentThread);//values为空则创建一个values对象
}
values.put(this, value);//将值放入values对象中
}
ThreadLocal的值是放入了当前线程的一个Values实例中,所以只能在本线程访问,其他线程无法访问。

3) ThreadLocal修饰的变量一定不能被其他线程访问么?
答案是不是:对于子线程是可以访问父线程中的threadlocal修饰的变量的。
如果在主线程中创建一个InheritableThreadLocal实例,那么在子线程中就可以得到InheritableThreadLocal实例,并获取相应的值。
在ThreadLocal中# inheritValues(Values fromParent) 方法来获取父线程中的值。

4)ThreadLocal的对象存放在哪?
答案是堆上:在Java中,第个线程都会有一个栈内存,栈内存属于单个线程,其存储的变量只能在其所属线程中可见。但是TheadLocal的值是被线程实例所有,而线程实例是由其创建的类所持有,所以ThreadLocal实例实际上也是被其创建的类所持有,故它们都存在于堆上。

5)ThreadLocal会导致内存泄漏么?
答案是不会:虽然ThreadLocal实例被线程中的Values实例所持有,也可以被看成是线程所持有,若使用线程池,之前的线程实例处理完后出于复用的目的依然存在,但Values在选择key时,并不是直接选择ThreadLocal实例,而是ThreadLocal实例的弱引用:
Reference<ThreadLocal<?>> reference = (Reference<ThreadLocal<?>>) k;
ThreadLocal<?> key = reference.get();
在get方法中也是采用弱引用:
private final Reference<ThreadLocal<T>> reference
= new WeakReference<ThreadLocal<T>>(this);
if (this.reference == table[index]) {
return (T) table[index + 1];
}
这样能保证如查当前thread被销毁时,ThreadLocal也会随着销毁被GC回收。

6)在android中的应用
android中的looper类就是利用了ThreadLocal特性,保证每个线程只存在一个looper对象。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}

7) 应用场景:
在实现单个线程上下文信息存储,比如交易id等,

4. AIDL实现原理

5. Binder机制

6. Handler实现机制

7. Rxjava中的线程切换机制原理

8. AsyncTask原理

AsyncTask是对Thread和Handler的组合包装,一些使用规择:
类的定义、实例化、调用execute必须在主线程中调用,一个task只能执行一次;定义了一个线程池THREAD_POOL_EXECUTOR

5、,它实现的原理是什么?

6、 集合类中哪些是线程安全、哪些不是线程安全的,还可引申出,不同集合类的区别,Arraymap和HashMap的区别等

7、

三、内存相关

1. 图片类

(1) 如何加载大图,如几百M甚至1G的大图如何加载?
①在内存中压缩图片,如通过设置压缩比options.inSampleSize,再进行读图
②除低要显示的图片色彩质量
如:alpah_8:每个像素占1byte的内存
RGB_565:每个像素占2byte的内存
ARBG_444:每个像素占2byte的内存(渐变色效果图片,会使渐变色呈现色彩条状)
ARGB_888:每个像素占4byte的内存(默认显示方式)
如果对图片要求不高,可以RGB_565或是ARBG_444格式显示图片
③若只查询图片信息,可通过设置options.inJustDecodeBounds=true;设置不把图片加载到内存中,但可以获取到图片的宽、高和大小等参数
(2) 100*100像素图片,需要占用手机多少内存?


屏幕快照 2017-03-05 下午3.55.50.png


参考资料:[ 一张图片占多大内存的计算-android]http://blog.csdn.net/shareus/article/details/50947185

2. 一般一个应用手机会分配多少内存

3. 内存溢出,哪些情况下会导致内存溢出,如何避免

4. 内存泄漏,一些常见的可能导致内存泄漏的场景,如何避免

5. 内存泄漏的检测

6. 内存优化

四、性能优化

1. ListView可以采取哪些提升性能

(1)采用convertview进行布局复用,通过setTag, getTag,获取布局
(2)运用ViewHolder,避免重复调用findViewById
(3)item的布局层级越少越好
(4)滑动时不加载一些耗资源如图片等,停止时再加载,使用异步加载
(5)在adapter中的getView方法中尽量少使用逻辑
(6)将ListView的scrollingCache和animateCache设置为false
(7)在listview滑动时,判断不进行图片等的加载
参考资料:[提高ListView性能的技巧]http://www.androidchina.net/3189.html

五、控件类

1. RecyclerView和ListView的区别

2. 自定义view的步骤

(1)确定是完全自定义还是继承View的派生子类
(2) 定义自定义属性,在资源元素<declare-styleable>中为您的view定义自定义属性。
(3) 获取自定义属性,当view从XML布局中创建了之后,XML标签中所有的属性都从资源包中读取出来并作为一个AttributeSet传递给view的构造函数。
(4) 添加属性和事件
(5) 自定义绘制(实施),重写onDraw()\onMesure()方法,如果是viewGroup,还需要重写onLayout()方法

3. View和ViewGroup事件分发机制

(1)dispatchTouchEvent
(2)OnInterceptTouchEvent
(3)TouchEvent

4.RecycleView的侧滑删除实现

5.图片缩放的实现

6.include\ merge\ Stub区别,merge要求

7.ScrollView和ListView事件冲突

8.ScrollView嵌套ListView, 导致ListView显示不完整,只显示一行

第三方库及实现原理

一、网络加载

1.网络请求库(Volley、OkHttp、Retrofit ,android-async-http 的作者已经不维护不讨论)

1) OkHttp 是 Square 公司开源的针对 Java 和 Android 程序,封装的一个高性能 http 请求库,所以它的职责跟 HttpUrlConnection 是一样的,支持 spdy、http 2.0、websocket ,支持同步、异步, OkHttp 还封装了线程池、数据转换、参数使用、错误处理等,api 使用起来更加方便。可以把它理解成是一个封装之后的类似 HttpUrlConnection 的一个东西,但在使用的时候仍然需要自己再做一层封装,这样才能像使用一个框架一样更加顺手。OkHttp 基于 NIO 和 Okio ,所以性能上要比 Volley更快。

2) Volley 是 Google 官方出的一套小而巧的异步请求库,该框架封装的扩展性很强,支持 HttpClient、HttpUrlConnection,甚至支持 OkHttp,但不支持 post 大数据,所以不适合上传文件。Volley 设计的初衷本身也就是为频繁的、数据量小的网络请求而生。

3) Retrofit 是 Square 公司出品的默认基于 OkHttp 封装的一套 RESTful 网络请求框架,通过注解直接配置请求,可以使用不同的 http 客户端,默认是用 http ,可以使用不同 Json Converter 来序列化数据,同时提供对 RxJava 的支持。

4) OKHttp的优点:
HTTP/2 以及 SPDY的支持多路复用
连接池会降低并发连接数
透明GZIP加密减少下载体积
响应缓存避免大量重复请求
同时支持同步的阻塞式调用与异步回调式调用

2.Retrofit中通过注解方式定义参数、地址、请求方式等,是怎么实现的

3.Retrofit中网络请求框架实现原理

它使用动态代理技术,实现在接口中声明的方法,采用注解方式
参考资料:1)[Android开源项目推荐之「网络请求哪家强」]https://zhuanlan.zhihu.com/p/21879931
2)[[Android] Retrofit 2.0实现原理
(http://blog.qiji.tech/archives/category/android)

4.Http头部一般包括哪些内容

5.Http协议相关

总体来说,http协议是从http1.0->http1.1->https-> SPDY->http2.0发展过来的;
(1)HTTP1.1相对于HTTP1.0的改进:添加了更多的缓存头策略;宽带优化;优化了错误通知处理;添加了Host头部处理;支持长连接;
(2)https相对于http1.x的改进:
HTTPS协议需要到CA申请证书;
HTTP协议运行在TCP之上,所有传输的内容都是明文,HTTPS运行在SSL/TLS之上,SSL/TLS运行在TCP之上,所有传输的内容都经过加密的;
HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443;
HTTPS可以有效的防止运营商劫持,解决了防劫持的一个大问题。
(3)SPDY相对于https的改进:
采取了多路复用(multiplexing),降低延迟,还可设置请求的优先级;
请求头部header压缩;服务端推送(server push);
(4)http2.0相对于SPDY的改进:
HTTP2.0的协议解析采用二进制格式,更健壮;
优化http请求头部压缩。
参考资料:(HTTP,HTTP2.0,SPDY,HTTPS你应该知道的一些事

6.webview和js相互调用

二、图片加载库

1.Glide, UniversalImageLoader, Picasso, Fresco区别

(1) Glide 默认的 Bitmap 格式是 RGB_565 格式,而 Picasso 默认的是 ARGB_8888 格式,这个内存开销要小一半。
(2) 在磁盘缓存方面,Picasso 只会缓存原始尺寸的图片,而 Glide 缓存的是多种规格,也就意味着 Glide 会根据你 ImageView 的大小来缓存相应大小的图片尺寸,比如你 ImageView 大小是200 x 200,原图是 400 x 400 ,而使用 Glide 就会缓存 200 x 200 规格的图,而 Picasso 只会缓存 400 x 400 规格的。这个改进就会导致 Glide 比 Picasso 加载的速度要快,毕竟少了每次裁剪重新渲染的过程。
(3) Glide 支持加载 Gif 动态图,而 Picasso 不支持该特性。
(4) Fresco 将图片放到一个特别的内存区域叫 Ashmem 区,就是属于 Native 堆,图片将不再占用 App 的内存。
(5)各网络请求库使用线程池情况:
1) Picasso在Wifi下线程数为4,而4G下线程数为3, 3G下为2, 2G下为1,默认状况为3.
2) UniversalImageLoader的加载任务线程池和缓存处理线程池的默认大小都为3,同时默认线程优先级是Thread.NORM_PRIORITY-2,线程池的任务处理类型都是FIFO。
3) Glide加载缓存未命中的线程池会根据根据CPU的数量和Java虚拟机中可用的处理器数量来选择合适的线程数,但是最多不超过4;而加载缓存命中的图片的线程池默认大小为1.
参考资料:1)[[ Android ] Fresco 与 Picasso 、Glide 的比较]http://blog.qiji.tech/archives/6344
2) [Android的App中线程池的使用,具体使用多少个线程池?]https://www.zhihu.com/question/37804956
3)[Android开源项目推荐之「图片加载到底哪家强」]https://zhuanlan.zhihu.com/p/21397115

2.Glide是怎么实现图片加载的

一般只需要对一种图片加载库进行研究即可,如以Glide为例,图片加载流程图:


流程图.png


它主要采用的技术有:
1) 图片池:(BitmapPool,实现类是LruBitmapPool,是一个基于LRU方式的Bitmap缓存池,用于Bitmap的复用)
2) 两级内存缓存:LruResourceCache, activeResources(先从LruResourceCache中寻找资源,若找到则将其从cache中移除并放入activeResources中,否则从activeResources中寻找。比一般内存缓存额外多一级缓存的意义在于,当内存不足时清理cache中的资源时,不会对使用中的Bitmap造成影响)。
3) 磁盘缓存(若内存中没有,则从磁盘中查找,根据不同的磁盘缓存策略,源数据可首先被写入到磁盘,对获取的Bitmap将其转换然后从缓存文件中加载,而不是直接返回)
4)网络获取(利用线程池策略,采用okHttp进行请求,并将文件流写入磁盘缓存,再对文件流进行编码)
参考资料:1) [Glide源码分析]http://www.jianshu.com/p/96fc561eada1

3.MultiDex是怎么实现分包的,它的实现原理,多个dex是怎么加载的

参考资料:1)[Android MultiDex实现原理解析]http://www.jianshu.com/p/79a14d340cb0

Java基础知识、数据结构、算法、设计模式

一、 算法

1. 排序算法

快速排序、归并排序、两个有序数组合并

2. 栈和队列

(1)用两个队列实现一个栈,用两个栈实现一个队列
(2)写一个栈的实现代码(用数组实现)

3. 判断一个点在一个三角形内

4. 七种单例写法及优缺点

5. 设计模式

(1) 单例模式(饿汉式、懒汉式、如何保证线程安全、双重锁机制)
(2) 观察者模式(Rxjava中的Observer和Observable、List中的adapter#notifyDataSetChanged())
(3) Builder模式(一般的链式调用,如Glide.with)

参考资料: 从Android代码中来记忆23种设计模式

架构、性能等知识专项研究

1.apk 瘦包机制


屏幕快照 2017-03-03 上午8.26.00.png


上图是一般的apk包包含的文件,如果加入混淆等还会有proguard.cfg、project.properties等文件,从图中可以看出AndroidManifest.xml、META-INF这些本身就很小没有必要做进一步压缩的文件,而其它文件或者文件夹都可以考虑进行优化,从而减小APK的体积。下面具体说说android apk
(1) 使用lint去除一些无用的代码、资源减小resources.arsc体积
可以借助Android Studio→Inspect Code...对工程做静态代码检查,删掉无用的代码;
(2) 代码混淆减少classes.dex大小
代码混淆可以减小该文件的大小,因为混淆后的代码将较长的文件名、实例、变量、方法名等等做了简化,从而实现字节长度上的优化;
(3)压缩资源:
1) 使用一些小图片代替大图,有些适配图片可能只需要保留一套如xxhdpi,有些背景图片用代码实现,用.9.png;
2) 使用tinypng压缩图片;
3) 对图片质量要求不是很严格,可以考虑不带alpha值的jpg图片、同等质量下文件更小的webP图片格式;
4) 借助微信提供的AndResGuard资源文件混淆工具对资源文件做混淆,进一步压缩资源文件所占用的空间;
(4) 去掉一些不用的适配
1) 如果只需要支持中文,可在build.grade中添加resConfigs "zh"去除无用的语言资源;
2) 去掉一些so包支持
一种CPU架构 = 一种ABI = 一种对应的SO库;
现在手机市场总共支持以下七种不同的CPU架构:ARMv5,ARMv7,x86,MIPS,ARMv8,MIPS64和x86_64, 这7种CPU类型对应的SO库的文件夹名是:armeabi,armeabi-v7a,x86,mips,arm64-v8a,mips64,x86_64。所有的x86/x86_64/armeabi-v7a/arm64-v8a设备都支持armeabi架构的.so文件,64位设备(arm64-v8a, x86_64, mips64)能够运行32位的函数库,所以应用不需要支持很多设备,建议实际工作的配置是只保留armable、armable-x86下的so文件,当然so包是向下兼容的,如果提供了其他cpu类型的文件夹,也需要在相应的文件夹里补全所有的so包,要不手机到时适配找不到合适的so包导致crash。
(5) 减小或甚用第三方依赖库
如果只使用第三方依赖库的少部分功能,可以考虑只提取少部分代码,而不是直接把第三库全部引入;
用较少的库替换大库;
(6) 插件化和动态加载
上面提到的so包,可以只提供一套,在应用运行时,需要用到so包的地方,可以从服务器下载so包,再动态加载;

参考资料:
1) 如何做到将apk大小减少6M
2)[Android动态加载]http://www.jianshu.com/p/e9da13f647a9

2.Android配配

(1) 多语言适配
不同国家语方适配、相同语言不同国家
(2) 多系统版本适配
(3) 不同屏幕分辨率适配
除了h、xh、xxh多图,还包括一些特殊屏幕分辨率的适配
(4)不同手机版本适配

3.架构

MVC 、MVP、 MVVM框架的区别,实现

4.Android打包

Ant打包和Gradle打包流程、步骤、区别
JNI、NDK相关

5.Android测试

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值