Android 面试札记第 1 篇

目录

1.自定义Handler时如何避免内存泄漏

2.onNewIntent()的调用时机

3.RecyclerView相比ListView有哪些优势

4.谈一谈Proguard混淆技术

5.ANR出现的场景及解决方案

6.HTTPS中SSL证书认证的过程

7.简述Android的Activity的内部机制

8.对Android Framework层的某一个模块(或者System App)做简要介绍

9.Android Handler的机制和原理

10.线程间通信和进程间通信有什么不同,Android开发过程中是怎么实现的

11.简述项目中对于内存优化的几个细节点

12.简述Android的视图层级优化,简述自定义View或者自定义ViewGroup的步骤

13.选取一个常用的第三方开源库,简述其接入步骤

14.TCP和UPD的区别以及使用场景

15.简述一个设计模式的概念,并简要谈谈framework层哪些地方用到了什么设计模式

16.字节流和字符流的区别

17.View的绘制流程,是先测量父View还是先测量子View

18.OOM异常是否可以被try...catch捕获(或者可否用try-catch捕获Out Of Memory Error以避免其发生?)

19.WeakReference和SoftReference的区别

20.请计算一张100像素*100像素的图片所占用内存

21.okHttp实现的原理

22.okHttp有哪些拦截器

23.计算1+2!+3!+4!+5!+...+20!的结果,用代码实现

24.写出单例模式,哪些是线程安全的,为什么是线程安全的

25.Retrofit实现原理

26.android图片有哪些格式

27.sqlite可以执行多线程操作吗?如何保证多线程操作数据库的安全性

28.有两个长度已知的链表,怎样确定两个链表的交集


备注:以下问题的答案均是个人整理的,如有不同意见,欢迎斧正。

1.自定义 Handler 时如何避免内存泄漏

一般非静态内部类会默认持有外部类的引用,导致外部类在使用完成后不能被系统回收内存,从而造成内存泄漏。

为了避免这个问题,我们可以将 Handler 定义为为静态内部类形式,然后通过弱引用的方式让 Handler 持有外部类的引用,这样就可以可避免内存泄漏问题。

以下是代码实现

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private TextView mTextView;

    private WeakReference<MainActivity> activityWeakReference;
    private MyHandler myHandler;

    static class MyHandler extends Handler {
        private MainActivity activity;

        MyHandler(WeakReference<MainActivity> ref) {
            this.activity = ref.get();
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    //需要做判空操作
                    if (activity != null) {
                        activity.mTextView.setText("new Value");
                    }
                    break;
                default:
                    Log.i(TAG, "handleMessage: default ");
                    break;
            }


        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //在onCreate中初始化
        activityWeakReference = new WeakReference<MainActivity>(this);
        myHandler = new MyHandler(activityWeakReference);

        myHandler.sendEmptyMessage(1);
        mTextView = (TextView) findViewById(R.id.tv_test);
    }
}

参考博文http://blog.csdn.net/ucxiii/article/details/50972747

2.onNewIntent() 的调用时机

如果 ActivityA 已经启动过,且处于当前应用的 Activity 堆栈中;那么以下情况会走 onNewIntent 方法:


1. 当 ActivityA 的 LaunchMode 为 singleTop (栈顶复用)时,如果 ActivityA 在栈顶,且现在要再启动 ActivityA,这时会调用 onNewIntent() 方法 ;
2. 当 ActivityA 的 LaunchMode 为 singleInstance (单任务模式),singleTask (栈内复用)时,如果 ActivityA 已经在堆栈中,那么此时会调用 onNewIntent() 方法 。

当 ActivityA 的 LaunchMode 为 Standard 时,由于每次启动 ActivityA 都是启动新的实例,和原来启动的 Activity 没关系,所以不会调用原来 ActivityA 的 onNewIntent方法,仍然走的是正常的生命周期方法。

以下是代码实例

1.设置 MainActivity 的启动模式为 singleTask (栈内复用)

 <activity
            android:name=".MainActivity"
            android:launchMode="singleTask">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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

2.MainActivity 中重写 onNewIntent 方法

package code.xzy.com.handlerdemo;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mButton = (Button) findViewById(R.id.forward_btn);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivity(new Intent(MainActivity.this, Main2Activity.class));
            }
        });

    }

    @Override
    protected void onNewIntent(Intent intent) {
        Toast.makeText(this, "onnewIntent", Toast.LENGTH_SHORT).show();
        Log.i(TAG, "onNewIntent: i done....");
    }
}

3.Main2Actvity 执行点击跳转,MainActivity 被复用,执行 onNewIntent 方法

package code.xzy.com.handlerdemo;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

public class Main2Activity extends AppCompatActivity {
    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main2);

        mButton = (Button)findViewById(R.id.btn);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivity(new Intent(Main2Activity.this,MainActivity.class));
                finish();
            }
        });
    }
}

打印截图:

3.RecyclerView 相比 ListView 有哪些优势

RecyclerView 和 ListView 的不同:

1. Recycleview 布局效果更多,增加了纵向,表格,瀑布流等效果;
2. Recycleview 去掉了一些 api,比如 setEmptyview,onItemClickListener 等等,给到用户更多的自定义可能;
3. Recycleview 去掉了设置头部底部 item 的功能,转向通过 viewholder 的不同 type 实现;
4. Recycleview 实现了一些局部刷新,比如 notifyitemchanged;
5. Recycleview 自带了一些布局变化的动画效果,也可以通过自定义 ItemAnimator 类实现自定义动画效果;
6. Recycleview缓存机制更全面,增加两级缓存,还支持自定义缓存逻辑。

参考

http://jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1118/2004.html

http://blog.csdn.net/lmj623565791/article/details/45059587

http://www.360doc.com/content/16/0808/14/9200790_581676933.shtml

4.谈一谈 Proguard 混淆技术

Proguard 技术有如下功能:

压缩 -- 检查并移除代码中无用的类
优化 -- 对字节码的优化,移除无用的字节码
混淆 -- 混淆定义的名称,避免反编译

预监测 -- 在java平台对处理后的代码再次进行检测

代码混淆只在上线时才会用到,debug 模式下会关闭,是一种可选的技术。

那么为什么要使用代码混淆呢?

因为Java是一种跨平台的解释性开发语言,而java的源代码会被编译成字节码文件,存储在.class文件中,由于跨平台的需要,java的字节码中包含了很多源代码信息,诸如变量名、方法名等等。并且通过这些名称来访问变量和方法,这些变量很多是无意义的,但是又很容易反编译成java源代码。

为了防止这种现象,我们就需要通过 proguard 技术来对 java 的字节码进行混淆,混淆就是对发布的程序进行重新组织和处理,使得处理后的代码与处理前的代码有相同的功能,和不同的代码展示,即使被反编译也很难读懂代码的含义,那些混淆过的代码仍能按照之前的逻辑执行得到一样的结果。

但是,某些 java 类是不能被混淆的,下面这类代码混淆的时候要注意保留,不能混淆。

1. Android 系统组件,系统组件有固定的方法被系统调用。
2. 被 Android Resource 文件引用到的。名字已经固定,也不能混淆,比如自定义的 View 。
3. Android Parcelable ,需要使用 android 序列化的。
4. 枚举 ,系统需要处理枚举的固定方法。
5. 本地方法,不能修改本地方法名。
6. annotations 注解。
7. 用到反射的地方。

5.ANR 出现的场景及解决方案

造成ANR的原因有三类:
1.输入事件在5s内没有得到及时响应而造成响应超时;
2.广播接收处理事件(BroadcastReveiver)在10s内没有完成返回;(前台广播10s,后台广播60s);
3.Service服务超时(前台服务20s,后台服务200s)。

如何避免ANR:
1.避免在主线程执行耗时操作
2.广播接收事件和服务类中不要做耗时操作,如果需要做耗时操作,单独开启一个线程。

6.HTTPS中SSL证书认证的过程


1. 客户端访问 https 连接。这时会在浏览器地址栏输入一个 https 网址;

2. 服务端发送证书(公钥)给客户端。证书包含证书时间、证书日期、证书颁发机构;

3. 客户端验证服务端的证书。比如颁发机构是否合法,证书是否过期,证书中包含的网址是否和正在访问的网址一致。验证通过后,客户端会生成一个随机字符串,然后用服务端的公钥进行加密。

这里就保证了只有服务端才能看到这串随机字符串(因为服务端拥有公钥对应的私钥,RSA 解密,可以知道客户端的随机字符串)。生成握手信息,用约定好的HASH算法,对握手信息进行取HASH,然后用随机字符串加密握手信息和握手信息的签名HASH值,并把结果发给服务端。这里之所以要带上握手信息的HASH是因为,防止信息被篡改。如果信息被篡改,那么服务端接收到信息进行HASH时,就会发现HASH值和客户端传回来的不一样。这里就保证了信息不会被篡改。

4. 服务端收到加密信息,解密得到客户端提供的随机字符串。

服务端接收到加密信息后,首先用私钥解密得到随机字符串。然后用随机字符串解密握手信息,获得握手信息和握手信息的HASH值,服务端对握手信息进行HASH,比对客户端传回来的HASH。如果相同,则说明信息没有被篡改。

服务端验证完客户端的信息以后,同样使用随机字符串加密握手信息和握手信息的HASH值发给客户端。

5. 客户端验证服务端返回的握手信息,完成握手。

客户端接收到服务端发回来的握手信息后,用一开始生成的随机字符串对密文进行解密,得到握手信息和握手信息的HASH值,像第4步中服务端验证一样对握手信息进行校验,校验通过后,握手完毕。从这里开始,客户端和服务端的通信就使用那串随机字符串进行AES对称加密通信。

https://blog.csdn.net/ahou2468/article/details/108368246

7.简述Android的Activity的内部机制

注意,以下答案仅供参考。

ActivityThread本质上不是一个线程,是一个java类,ActivityThread是所在主线程中的一个类,它的启动是调用main方法,其中内部有几个重点方法:

Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
Looper.loop()

ActivityThread的main()中,首先调用Looper.prepareMainLooper();然后调用ActivityThread()方法,但事实上这个方法并没有继承什么,是一个普通的方法,最重要的是attach()方法,这个方法主要与ActivityManagerService进行交互,首先调用ActivityManagerNative对象,这是一个远程代理接口,以获取远程代理对象。之后调用attachApplication(),通知AMS。

ApplicationThread
这是一个很关键的类,它是继承自ApplicationThreadNative类,ApplicationThreadNative类是一个Binder的子类,说明ApplicationThread需要与远程端进行通信,即与ActivityManagerService进行通信,通信的过程封装成一个Binder对象。

ActivityClientRecord
是客户端Activity的一个描述的类,是对客户端的描述。

Context
是一个接口,Activity其实是Context的一个子类。

Instrumentation

用于辅助创建Application, 对生命周期的管理,启动Activity。

ActivityManagerNative

用于与远程Service进行数据交互。

https://blog.csdn.net/davidddl/article/details/62043670

8.对Android Framework层的某一个模块(或者System App)做简要介绍

涉及Android framework层系统源码分析,我们要熟读某一个模块的源码,然后梳理大致的流程。

9.Android Handler 的机制和原理

主线程使用Handler的过程:

首先在主线程创建一个Handler对象 ,并重写handleMessage 方法。

然后当在子线程中需要进行更新UI的操作,我们就创建一个Message对象,并通过handler发送这条消息出去。

之后这条消息被加入到MessageQueue队列中等待被处理,通过Looper对象的loop方法会一直尝试从MessageQueue中取出待处理的消息。

最后通过调用msg.dispatchMessage 方法将消息分发回 Handler 对象的 handleMessage 方法中。在这个回调方法中执行一些 UI 更新操作。

这里写图片描述

参考 http://blog.csdn.net/u012827296/article/details/51236614

10.线程间通信和进程间通信有什么不同,Android开发过程中是怎么实现的

https://www.cnblogs.com/yangtao1995/p/6079067.html

11.简述项目中对于内存优化的几个细节点

1.当查询完数据库之后,及时关闭Cursor对象。

2.记得在Activity的onPause方法中调用unregisterReceiver()方法,反注册广播

3.避免Content内存泄漏,比如在4.0.1之前的版本上不要讲Drawer对象置为static。当一个Drawable绑定到了View上,实际上这个View对象就会成为这个Drawable的一个callback成员变量,上面的例子中静态的sBackground持有TextView对象lable的引用,而lable只有Activity的引用,而Activity会持有其他更多对象的引用。sBackground生命周期要长于Activity。当屏幕旋转时,Activity无法被销毁,这样就产生了内存泄露问题。

4.尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用,当非静态内部类的引用的声明周期长于Activity的声明周期时,会导致Activity无法被GC正常回收掉。

5.谨慎使用线程Thread!!这条是很多人会犯的错误: Java中的Thread有一个特点就是她们都是直接被GC Root所引用,也就是说Dalvik虚拟机对所有被激活状态的线程都是持有强引用,导致GC永远都无法回收掉这些线程对象,除非线程被手动停止并置为null或者用户直接kill进程操作。所以当使用线程时,一定要考虑在Activity退出时,及时将线程也停止并释放掉。

6.使用Handler时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。

12.简述Android的视图层级优化,简述自定义View或者自定义ViewGroup的步骤

个人的理解是,Android视图渲染必须经过measure、layout、draw三个步骤,measure过程是在一个树形结构中不断遍历的,如果UI层级嵌套很深,必将花费大量的时间,所以应该尽量减少层级嵌套,保证树的结构扁平,并移除不需要渲染的views。

自定义view步骤:

Android自定义View的一般步骤

13.选取一个常用的第三方开源库,简述其接入步骤

Volley教程 http://blog.csdn.net/jdfkldjlkjdl/article/details/79074259

14.TCP和UPD的区别以及使用场景

TCP与UDP基本区别
  1.基于连接与无连接
  2.TCP要求系统资源较多,UDP较少; 
  3.UDP程序结构较简单 
  4.流模式(TCP)与数据报模式(UDP); 
  5.TCP保证数据正确性,UDP可能丢包 
  6.TCP保证数据顺序,UDP不保证 
  
UDP应用场景:
  1.面向数据报方式
  2.网络数据大多为短消息 
  3.拥有大量Client
  4.对数据安全性无特殊要求
  5.网络负担非常重,但对响应速度要求高

15.简述一个设计模式的概念,并简要谈谈framework层哪些地方用到了什么设计模式

单例模式:

单例模式是一种对象创建模式,它用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例。

framework层的AMS等各种服务,使用的是用集合实现的单例,通过统一的key去获取唯一的实例。

16.字节流和字符流的区别

字节流操作的基本单元为字节;字符流操作的基本单元为Unicode码元(2个字节)。
字节流默认不使用缓冲区;字符流使用缓冲区。

字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元。

参考 理解Java中字符流与字节流的区别

17.View的绘制流程,是先测量父View还是先测量子View

测量过程是先测量子View,再测量父View,因为父View的宽高会用到子View的测量结果。

View的测量、布局和绘制过程中父View(当前View)和子View的先后顺序

View和ViewGroup的基本绘制流程

18.OOM异常是否可以被try...catch捕获(或者可否用try-catch捕获Out Of Memory Error以避免其发生?)

只有在一种情况下,这样做是可行的:在try语句中声明了很大的对象,导致OOM,并且可以确认OOM是由try语句中的对象声明导致的,那么在catch语句中,可以释放掉这些对象,解决OOM的问题,继续执行剩余语句。

但是这通常不是合适的做法。Java中管理内存除了显式地catch OOM之外还有更多有效的方法:比如SoftReference, WeakReference, 硬盘缓存等。在JVM用光内存之前,会多次触发GC,这些GC会降低程序运行的效率。如果OOM的原因不是try语句中的对象(比如内存泄漏)导致的,那么在catch语句中会继续抛出 OOM。

19.WeakReference和SoftReference的区别

Java的StrongReference, SoftReference, WeakReference, PhantomReference的区别

20.请计算一张100像素*100像素的图片所占用内存

http://blog.csdn.net/u010652002/article/details/72676723

21.okHttp实现的原理

22.okHttp有哪些拦截器

23.计算1+2!+3!+4!+5!+...+20!的结果,用代码实现

24.写出单例模式,哪些是线程安全的,为什么是线程安全的

25.Retrofit实现原理

26.android图片有哪些格式

答案:http://blog.csdn.net/xmc281141947/article/details/72768175

27.sqlite可以执行多线程操作吗?如何保证多线程操作数据库的安全性

答:

每当你需要使用数据库时,你需要使用DatabaseManager的openDatabase()方法来取得数据库,这个方法里面使用了单例模式,保证了数据库对象的唯一性,也就是每次操作数据库时所使用的sqlite对象都是一致得到。其次,我们会使用一个引用计数来判断是否要创建数据库对象。如果引用计数为1,则需要创建一个数据库,如果不为1,说明我们已经创建过了。
在closeDatabase()方法中我们同样通过判断引用计数的值,如果引用计数降为0,则说明我们需要close数据库。

 大致的做法就是在多线程访问的情况下需要自己来封装一个DatabaseManager来管理Sqlite数据库的读写,需要同步的同步,需要异步的异步,不要直接操作数据库,这样很容易出现因为锁的问题导致加锁后的操作失败。

该答案参考了这篇文章http://blog.csdn.net/rockcode_li/article/details/39024497

28.有两个长度已知的链表,怎样确定两个链表的交集

解析:leetcode 两个链表的交集点 http://www.360doc.com/content/16/0926/20/10408243_593854682.shtml

有以下几种思路:

(1) 暴力破解 ,遍历链表A的所有节点,并且对于每个节点,都与链表B中的所有节点比较,退出条件是在B中找到第一个相等的节点。时间复杂度O(lengthA*lengthB),空间复杂度O(1)。

(2) 哈希表 。遍历链表A,并且将节点存储到哈希表中。接着遍历链表B,对于B中的每个节点,查找哈希表,如果在哈希表中找到了,说明是交集开始的那个节点。时间复杂度O(lengthA+lengthB),空间复杂度O(lengthA)或O(lengthB)。

(3) 双指针法 ,指针pa、pb分别指向链表A和B的首节点。

遍历链表A,记录其长度lengthA,遍历链表B,记录其长度lengthB。

因为两个链表的长度可能不相同,比如题目所给的case,lengthA=5,lengthB=6,则作差得到 lengthB- lengthA=1,将指针pb从链表B的首节点开始走1步,即指向了第二个节点,pa指向链表A首节点,然后它们同时走,每次都走一步,当它们相等时,就是交集的节点。

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
生物信息学札记是一本关于生物信息学的电子书籍,以PDF格式呈现。生物信息学是一门融合生物学和计算机科学的学科,主要研究如何通过计算和统计方法来分析和解释生物学数据。 生物信息学的发展与信息技术的飞速进步密切相关。随着高通量测序技术(如基因测序和蛋白质测序)的发展,我们面临着海量并且复杂的生物学数据,如基因组序列、转录组数据和蛋白质结构等。这些数据的分析与解释对于我们深入理解生物学问题,寻找新的医药和农业应用具有重要意义。 生物信息学札记的PDF版本提供了便捷的阅读体验,读者可以通过电脑、平板电脑或手机等设备进行阅读。该电子书籍系统地介绍了生物信息学的基本原理、常用方法和技术工具,涵盖了从数据预处理、基因组组装和注释、蛋白质结构预测到生物信息学数据库的使用等内容。 生物信息学札记的PDF版本具有以下几个优点。首先,电子书籍的PDF格式方便读者进行检索和标注,使得阅读和复习更加高效。其次,通过PDF格式,读者可以随时随地进行阅读,无需携带大量纸质书籍。再次,PDF版本具有良好的可视化效果,可以展示复杂的图表和数据分析结果,更直观地呈现生物信息学的研究成果。 总而言之,生物信息学札记的PDF版本为生物信息学学习者提供了便利的阅读工具,帮助他们深入理解生物信息学的原理和应用。通过阅读和学习生物信息学札记,读者可以获得必要的知识和技能,为进一步的研究和应用奠定基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值