19.随机应变

1. Android为每个应用程序分配的内存大小是多少

android程序内存一般限制在16M,也有的是24M。近几年手机发展较快,一般都会分配两百兆左右,和具体机型有关。

2. 更新UI方式

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable),View.postDelay(Runnable, long)(可以理解为在当前操作视图UI线程添加队列)
  • Handler
  • AsyncTask
  • Rxjava
  • LiveData

3. 数据加载更多涉及到分页,你是怎么实现的?

分页加载就是一页一页加载数据,当滑动到底部、没有更多数据加载的时候,我们可以手动调用接口,重新刷新RecyclerView。

4. 内存泄露,怎样查找,怎么产生的内存泄露?

1.资源对象没关闭造成的内存泄漏

描述: 资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。因为有些资源性对象,比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。

程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会复现内存问题,这样就会给以后的测试和问题排查带来困难和风险。

2.构造Adapter时,没有使用缓存的convertView

描述: 以构造ListView的BaseAdapter为例,在BaseAdapter中提供了方法: public View getView(int position, ViewconvertView, ViewGroup parent) 来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的 view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。由此可以看出,如果我们不去使用 convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。 ListView回收list item的view对象的过程可以查看: android.widget.AbsListView.java --> voidaddScrapView(View scrap) 方法。 示例代码:

public View getView(int position, ViewconvertView, ViewGroup parent) {
View view = new Xxx(...);
... ...

return view;
}
<span class="copy-code-btn">&#x590D;&#x5236;&#x4EE3;&#x7801;</span>

修正示例代码:

public View getView(int position, ViewconvertView, ViewGroup parent) {
View view = null;
if (convertView != null) {
view = convertView;
populate(view, getItem(position));
...

} else {
view = new Xxx(...);
...

}
return view;
}
<span class="copy-code-btn">&#x590D;&#x5236;&#x4EE3;&#x7801;</span>

3.Bitmap对象不在使用时调用recycle()释放内存

描述: 有时我们会手工的操作Bitmap对象,如果一个Bitmap对象比较占内存,当它不在被使用的时候,可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存,但这不是必须的,视情况而定。可以看一下代码中的注释:

/* •Free up the memory associated with thisbitmap's pixels, and mark the •bitmap as "dead", meaning itwill throw an exception if getPixels() or •setPixels() is called, and will drawnothing. This operation cannot be •reversed, so it should only be called ifyou are sure there are no •further uses for the bitmap. This is anadvanced call, and normally need •not be called, since the normal GCprocess will free up this memory when •there are no more references to thisbitmap. /

4.试着使用关于application的context来替代和activity相关的context

这是一个很隐晦的内存泄漏的情况。有一种简单的方法来避免context相关的内存泄漏。最显著地一个是避免context逃出他自己的范围之外。使用Application context。这个context的生存周期和你的应用的生存周期一样长,而不是取决于activity的生存周期。如果你想保持一个长期生存的对象,并且这个对象需要一个context,记得使用application对象。你可以通过调用 Context.getApplicationContext() or Activity.getApplication()来获得。更多的请看这篇文章如何避免 Android内存泄漏。

5.注册没取消造成的内存泄漏

一些Android程序可能引用我们的Anroid程序的对象(比如注册机制)。即使我们的Android程序已经结束了,但是别的引用程序仍然还有对我们的Android程序的某个对象的引用,泄漏的内存依然不能被垃圾回收。调用registerReceiver后未调用unregisterReceiver。 比如:假设我们希望在锁屏界面(LockScreen)中,监听系统中的电话服务以获取一些信息(如信号强度等),则可以在LockScreen中定义一个 PhoneStateListener的对象,同时将它注册到TelephonyManager服务中。对于LockScreen对象,当需要显示锁屏界面的时候就会创建一个LockScreen对象,而当锁屏界面消失的时候LockScreen对象就会被释放掉。 但是如果在释放 LockScreen对象的时候忘记取消我们之前注册的PhoneStateListener对象,则会导致LockScreen无法被垃圾回收。如果不断的使锁屏界面显示和消失,则最终会由于大量的LockScreen对象没有办法被回收而引起OutOfMemory,使得system_process 进程挂掉。 虽然有些系统程序,它本身好像是可以自动取消注册的(当然不及时),但是我们还是应该在我们的程序中明确的取消注册,程序结束时应该把所有的注册都取消掉。

6.集合中对象没清理造成的内存泄漏

我们通常把一些对象的引用加入到了集合中,当我们不需要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就更严重了。

查找内存泄漏可以使用Android Studio 自带的AndroidProfiler工具或MAT,也可以使用Square产品的LeakCanary.

1、使用AndroidProfiler的MEMORY工具:

运行程序,对每一个页面进行内存分析检查。首先,反复打开关闭页面5次,然后收到GC(点击Profile MEMORY左上角的垃圾桶图标),如果此时total内存还没有恢复到之前的数值,则可能发生了内存泄露。此时,再点击Profile MEMORY左上角的垃圾桶图标旁的heap dump按钮查看当前的内存堆栈情况,选择按包名查找,找到当前测试的Activity,如果引用了多个实例,则表明发生了内存泄露。

2、使用MAT:

1、运行程序,所有功能跑一遍,确保没有改出问题,完全退出程序,手动触发GC,然后使用adb shell dumpsys meminfo packagename -d命令查看退出界面后Objects下的Views和Activities数目是否为0,如果不是则通过Leakcanary检查可能存在内存泄露的地方,最后通过MAT分析,如此反复,改善满意为止。

1、在使用MAT之前,先使用as的Profile中的Memory去获取要分析的堆内存快照文件.hprof,如果要测试某个页面是否产生内存泄漏,可以先dump出没进入该页面的内存快照文件.hprof,然后,通常执行5次进入/退出该页面,然后再dump出此刻的内存快照文件.hprof,最后,将两者比较,如果内存相除明显,则可能发生内存泄露。(注意:MAT需要标准的.hprof文件,因此在as的Profiler中GC后dump出的内存快照文件.hprof必须手动使用android sdk platform-tools下的hprof-conv程序进行转换才能被MAT打开)

2、然后,使用MAT打开前面保存的2份.hprof文件,打开Overview界面,在Overview界面下面有4中action,其中最常用的就是Histogram和Dominator Tree。

Dominator Tree:支配树,按对象大小降序列出对象和其所引用的对象,注重引用关系分析。选择Group by package,找到当前要检测的类(或者使用顶部的Regex直接搜索),查看它的Object数目是否正确,如果多了,则判断发生了内存泄露。然后,右击该类,选择Merge Shortest Paths to GC Root中的exclude all phantom/weak/soft etc.references选项来查看该类的GC强引用链。最后,通过引用链即可看到最终强引用该类的对象。

Histogram:直方图注重量的分析。使用方式与Dominator Tree类似。

3、对比hprof文件,检测出复杂情况下的内存泄露:

通用对比方式:在Navigation History下面选择想要对比的dominator_tree/histogram,右击选择Add to Compare Basket,然后在Compare Basket一栏中点击红色感叹号(Compare the results)生成对比表格(Compared Tables),在顶部Regex输入要检测的类,查看引用关系或对象数量去进行分析即可。

针对于Historam的快速对比方式:直接选择Histogram上方的Compare to another Heap Dump选择要比较的hprof文件的Historam即可。

5. ViewPager使用细节,如何设置成每次只初始化当前的Fragment,其他的不初始化(提示:Fragment懒加载)?

自定义一个 LazyLoadFragment 基类,利用 setUserVisibleHint 和 生命周期方法,通过对 Fragment 状态判断,进行数据加载,并将数据加载的接口提供开放出去,供子类使用。然后在子类 Fragment 中实现 requestData 方法即可。这里添加了一个 isDataLoaded 变量,目的是避免重复加载数据。考虑到有时候需要刷新数据的问题,便提供了一个用于强制刷新的参数判断。

public abstract class LazyLoadFragment extends BaseFragment {
    protected boolean isViewInitiated;
    protected boolean isDataLoaded;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        isViewInitiated = true;
        prepareRequestData();
    }
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        prepareRequestData();
    }
    public abstract void requestData();
    public boolean prepareRequestData() {
        return prepareRequestData(false);
    }
    public boolean prepareRequestData(boolean forceUpdate) {
        if (getUserVisibleHint() && isViewInitiated && (!isDataLoaded || forceUpdate)) {
            requestData();
            isDataLoaded = true;
            return true;
        }
        return false;
    }
}

6. 是否了解硬件加速?

硬件加速就是运用GPU优秀的运算能力来加快渲染的速度,而通常的基于软件的绘制渲染模式是完全利用CPU来完成渲染。

1.硬件加速是从API 11引入,API 14之后才默认开启。对于标准的绘制操作和控件都是支持的,但是对于自定义View的时候或者一些特殊的绘制函数就需要考虑是否需要关闭硬件加速。

2.我们面对不支持硬件加速的情况,就需要限制硬件加速,这个兼容性的问题是因为硬件加速是把View的绘制函数转化为使用OpenGL的函数来进完成实际的绘制的,那么必然会存在OpenGL中不支持原始回执函数的情况,对于这些绘制函数,就会失效。

3.硬件加速的消耗问题,因为是使用OpenGL,需要把系统中OpenGL加载到内存中,OpenGL API调用就会占用8MB,而实际上会占用更多内存,并且使用了硬件必然增加耗电量了。

4.硬件加速的优势还有display list的设计,使用这个我们不需要每次重绘都执行大量的代码,基于软件的绘制模式会重绘脏区域内的所有控件,而display只会更新列表,然后绘制列表内的控件。

  1. CPU更擅长复杂逻辑控制,而GPU得益于大量ALU和并行结构设计,更擅长数学运算。

7. 对于应用更新这块是如何做的?(灰度,强制更新,分区域更新)

1、通过接口获取线上版本号,versionCode 2、比较线上的versionCode 和本地的versionCode,弹出更新窗口 3、下载APK文件(文件下载) 4、安装APK

灰度: (1)找单一渠道投放特别版本。 (2)做升级平台的改造,允许针对部分用户推送升级通知甚至版本强制升级。 (3)开放单独的下载入口。 (4)是两个版本的代码都打到app包里,然后在app端植入测试框架,用来控制显示哪个版本。测试框架负责与服务器端api通信,由服务器端控制app上A/B版本的分布,可以实现指定的一组用户看到A版本,其它用户看到B版本。服务端会有相应的报表来显示A/B版本的数量和效果对比。最后可以由服务端的后台来控制,全部用户在线切换到A或者B版本~

无论哪种方法都需要做好版本管理工作,分配特别的版本号以示区别。 当然,既然是做灰度,数据监控(常规数据、新特性数据、主要业务数据)还是要做到位,该打的数据桩要打。 还有,灰度版最好有收回的能力,一般就是强制升级下一个正式版。

强制更新:一般的处理就是进入应用就弹窗通知用户有版本更新,弹窗可以没有取消按钮并不能取消。这样用户就只能选择更新或者关闭应用了,当然也可以添加取消按钮,但是如果用户选择取消则直接退出应用。

增量更新:bsdiff:二进制差分工具bsdiff是相应的补丁合成工具,根据两个不同版本的二进制文件,生成补丁文件.patch文件。通过bspatch使旧的apk文件与不定文件合成新的apk。 注意通过apk文件的md5值进行区分版本。

8. 请解释安卓为啥要加签名机制。

1、发送者的身份认证 由于开发商可能通过使用相同的 Package Name 来混淆替换已经安装的程序,以此保证签名不同的包不被替换。

2、保证信息传输的完整性 签名对于包中的每个文件进行处理,以此确保包中内容不被替换。

3、防止交易中的抵赖发生, Market 对软件的要求。

9. MVP,MVVM,MVC解释和实践

参考https://www.jianshu.com/p/4898e59fe13c

MVC:

  • 视图层(View) 对应于xml布局文件和java代码动态view部分
  • 控制层(Controller) MVC中Android的控制层是由Activity来承担的,Activity本来主要是作为初始化页面,展示数据的操作,但是因为XML视图功能太弱,所以Activity既要负责视图的显示又要加入控制逻辑,承担的功能过多。
  • 模型层(Model) 针对业务模型,建立数据结构和相关的类,它主要负责网络请求,数据库处理,I/O的操作。

总结

具有一定的分层,model彻底解耦,controller和view并没有解耦 层与层之间的交互尽量使用回调或者去使用消息机制去完成,尽量避免直接持有 controller和view在android中无法做到彻底分离,但在代码逻辑层面一定要分清 业务逻辑被放置在model层,能够更好的复用和修改增加业务。

MVP

通过引入接口BaseView,让相应的视图组件如Activity,Fragment去实现BaseView,实现了视图层的独立,通过中间层Preseter实现了Model和View的完全解耦。MVP彻底解决了MVC中View和Controller傻傻分不清楚的问题,但是随着业务逻辑的增加,一个页面可能会非常复杂,UI的改变是非常多,会有非常多的case,这样就会造成View的接口会很庞大。

MVVM

MVP中我们说过随着业务逻辑的增加,UI的改变多的情况下,会有非常多的跟UI相关的case,这样就会造成View的接口会很庞大。而MVVM就解决了这个问题,通过双向绑定的机制,实现数据和UI内容,只要想改其中一方,另一方都能够及时更新的一种设计理念,这样就省去了很多在View层中写很多case的情况,只需要改变数据就行。

MVVM与DataBinding的关系?

MVVM是一种思想,DataBinding是谷歌推出的方便实现MVVM的工具。

看起来MVVM很好的解决了MVC和MVP的不足,但是由于数据和视图的双向绑定,导致出现问题时不太好定位来源,有可能数据问题导致,也有可能业务逻辑中对视图属性的修改导致。如果项目中打算用MVVM的话可以考虑使用官方的架构组件ViewModel、LiveData、DataBinding去实现MVVM。

三者如何选择?

  • 如果项目简单,没什么复杂性,未来改动也不大的话,那就不要用设计模式或者架构方法,只需要将每个模块封装好,方便调用即可,不要为了使用设计模式或架构方法而使用。
  • 对于偏向展示型的app,绝大多数业务逻辑都在后端,app主要功能就是展示数据,交互等,建议使用mvvm。
  • 对于工具类或者需要写很多业务逻辑app,使用mvp或者mvvm都可。

10. Base64、MD5是加密方法么?

Base64是什么?

Base64是用文本表示二进制的编码方式,它使用4个字节的文本来表示3个字节的原始二进制数据。 它将二进制数据转换成一个由64个可打印的字符组成的序列:A-Za-z0-9+/

MD5是什么?

MD5是哈希算法的一种,可以将任意数据产生出一个128位(16字节)的散列值,用于确保信息传输完整一致。我们常在注册登录模块使用MD5,用户密码可以使用MD5加密的方式进行存储。如:md5(hello world,32) = 5eb63bbbe01eeed093cb22bb8f5acdc3

加密,指的是对数据进行转换以后,数据变成了另一种格式,并且除了拿到解密方法的人,没人能把数据转换回来。 MD5是一种信息摘要算法,它是不可逆的,不可以解密。所以它只能算的上是一种单向加密算法。 Base64也不是加密算法,它是一种数据编码方式,虽然是可逆的,但是它的编码方式是公开的,无所谓加密。

11. 如何选择第三方,从那些方面考虑?

大方向:从软件环境做判断

性能是开源软件第一解决的问题。

一个好的生态,是一个优秀的开源库必备的,取决标准就是观察它是否一直在持续更新迭代,是否能及时处理github上用户提出来的问题。大家在社区针对这个开源库是否有比较活跃的探讨。

背景,该开源库由谁推出,由哪个公司推出来的。

用户数和有哪些知名的企业落地使用

小方向:从软件开发者的角度做判断

是否解决了我们现有问题或长期来看带来的维护成本。

公司有多少人会。

学习成本。

12. 简单说下接入支付的流程,是否自己接入过支付功能?

Alipay支付功能:

1.首先登录支付宝开放平台创建应用,并给应用添加App支付功能, 由于App支付功能需要签约,因此需要上传公司信息和证件等资料进行签约。

2.签约成功后,需要配置秘钥。使用支付宝提供的工具生成RSA公钥和私钥,公钥需要设置到管理后台。

3.android studio集成

13. 为什么Google给开发者默认新建了个RelativeLayout,而自己却在DecorView中用了个LinearLayout?

因为DecorView的层级深度是已知而且固定的,上面一个标题栏,下面一个内容栏。采用RelativeLayout并不会降低层级深度,所以此时在根节点上用LinearLayout是效率最高的。而之所以给开发者默认新建了个RelativeLayout是希望开发者能采用尽量少的View层级来表达布局以实现性能最优,因为复杂的View嵌套对性能的影响会更大一些。

14. Android Holo主题与MD主题的理念,以及你的看法

Holo Theme

Holo Theme 是 Android Design 的最基础的呈现方式。因为是最为基础的 Android Design 呈现形式,每一台 Android 4.X 的手机系统内部都有集成 Holo Theme 需要的控件,即开发者不需要自己设计控件,而是直接从系统里调用相应的控件。在 UI 方面没有任何的亮点,和 Android4.X 的设置/电话的视觉效果极度统一。由此带来的好处显而易见,这个应用作为 Android 应用的辨识度极高,且完全不可能与系统风格产生冲突。

Material Design

Material design其实是单纯的一种设计语言,它包含了系统界面风格、交互、UI,更加专注拟真,更加大胆丰富的用色,更加丰富的交互形式,更加灵活的布局形式

1.鲜明、形象的界面风格,

2.色彩搭配使得应用看起来非常的大胆、充满色彩感,凸显内容

3.Material design对于界面的排版非常的重视

4.Material design的交互设计上采用的是响应式交互,这样的交互设计能把一个应用从简单展现用户所请求的信息,提升至能与用户产生更强烈、更具体化交互的工具。

15. 从网络加载一个 10M 的图片,说下注意事项

16. 如何从 0 设计一款 App 整体架构

想要设计App的总体框架,首先要清楚咱们作的是什么后端

通常咱们与网络交互数据的方式有两种:主动请求(http),长链接推送缓存

结合网络交互数据的方式来讲一下咱们开发的App的类型和特色:网络

  • 数据展现类型的App:特色是页面多,须要频繁调用后端接口进行数据交互,以http请求为主;推送模块,IM类型App的IM核心功能以长链接为主,比较看重电量、流量消耗。
  • 手机助手类App:主要着眼于系统API的调用,达到辅助管理系统的目的,网络调用的方式以http为主。
  • 游戏:通常分为游戏引擎和业务逻辑,业务脚本化编写,网络以长链接为主,http为辅。

通常咱们作的App都是类型1,简要来讲这类app的主要工做就是app

  • 把服务端的数据拉下来给用户展现
  • 把用户在客户端修改的数据上传给服务端处理

因此这类App的网络调用至关频繁,并且须要考虑到网络差,没网络等状况下,App的运行,成熟的商业应用的网络调用通常是以下流程:框架

UI发起请求 - 检查缓存 - 调用网络模块 - 解析返回JSON / 统一处理异常 - JSON对象映射为Java对象 - 缓存 - UI获取数据并展现spa

这之中能够看到很明显职责划分,即:数据获取;数据管理;数据展现设计

posted @ 2022-02-12 13:53  契阔  阅读(41)  评论(0编辑  收藏  举报