Android开发Android开发Android开发经验谈

Android面试指南:第二章

2017-08-11  本文已影响120人  LogosLi
面试指南

前言

此处我们还是就一部分的Android基础知识进行一下复习,不过我们了解了Android的各种知识,只是有时我们需要做一下深入,毕竟是专业的Android工程师,不能只懂一点皮毛的,我们需要深入一下整个加载机制和原理的,特别是对于渲染机制和view的视图树机制,此处我也会帮助大家综合和扩展一下我们的知识面,不要局限于条目性和刻板的回答,另外就是我这边刚好辞掉了工作,可能会尽心去写完这样一个系列,也算是我对Android知识点的一个总结,我这个人本身也是喜欢写作的一个人,一方面也是为了让自己复习一下知识点,在工作期间总是抽不出时间去总结以及去get一点新的知识点,太过匆忙了,还有一方面就是在写作方面的一个进阶,倒不是想要以此谋生,而是想要在这个方向上探求自我的一个突破口,为自己的人生重新定位,或许以后还会去读研,从逻辑学方面的研究工作,人世间的事情还是事在人为,不要去幻想,Just do IT

两个Activity之间跳转必然会执行到的方法

Activity:A
Activity:B
从A跳转到B:A调用onPause()之后B会调用onCreate()-onStart()-onResume()
假设我们B覆盖了A,A会调用onStop()方法,如果我们的B是透明的或者说是对话框的样式,我们说A不会调用我们的onStop()方法。
注意:此处需要注意的就是透明状态下我们的A界面还是被显示出来了,所以不会调用A的onStop()方法的,假设B界面是对话框,就无法完全覆盖我们的A界面了,所以也是无法让A界面调用onStop()方法的。

横竖屏切换时Activity的生命周期

假设不设置android:configChanges时,我们说重新Activity会被销毁重建。
假设我们设置了这个属性,切换时不会销毁我们当前的Activity,而是会执行到我们的onConfigurationChanged方法,然而在游戏开发中我们说朝向通常是被写死的。

如何将我们的Activity设置为窗口样式

这个就是一个简单的主题样式设置:android:theme="@android:style/Theme.Dialog"

如何退出Activity,如何安全退出已经调用多个Activity的Application?

1.通常代码当中我们直调用finish()方法就能够结束当前的Activity了,当然用户也可以使用我们的back键进行退出,其实也是调用finish()方法,时一样的。
2.也可以在Application中创建一个List集合,将所有的Activity全部存储在这个集合之中,退出时遍历我们的集合,将所有的Activity-finish掉就可以了。
3.还有就是给每一个Activity注册一个广播接收者,此处最好还是使用我们的动态注册方式,给每一个接收者发送一个广播,在接收时finish掉当前的Activity就可以了。
4.递归关闭,这个也是极为简单的,当然我还是不太推荐的,这样我们不能finish掉多余的Activity,就是全部使用startActivityForResult,之后使用setResult()方法,finish掉当前的Acitivity,让前面的代码执行onActivityResult()方法里面也进行这样的操作,递归进行关闭。
5.这个就是给Activity栈底的Activity配置一个SingleTask的启动方式,我们下面马上会开始讲我们的启动模式,在栈顶马上开一个同样的Activity,这样不会在栈顶重新去开这个Activity,而是会重新使用栈底的这Activity,而且会clear掉在其之上的所有Activity实例,之后我们finish掉当前的Activity就可以达到清除所有Activity的目的了。

谈一下Activity的启动方式(早期的App全部是会配置的)

1.standard:这个单词我们经常会碰到,特别是在安装英文软件时,就是一种标准模式或者说是经典模式,对于我们的Activity来讲就是一种标准的启动模式,这个模式也是我们栈结构模式,支持的是先进后出,后进先出的模式,相信大家对于Android里面的栈结构不会太陌生,此处我也就不展开去说了,补充一下,这个属性为:android:launchMode="standard",默认模式就是我们的standard模式。
2.singleTop:这个也是极为简单的,就是假设你现在选择了这个模式,假设你去开启这个Activity,假设栈顶已经存在这个Activity的实例,就不会重新去创建这个Activity,而是会去复用来的Activity实例,而且会调用原来实例的onNewIntent()方法,通常在以前的App中我们全部会给开启这个模式,最为主要的原因是当我们触发事件打开一个界面时,我们怕用户手快触发了两次或者两次以上,打开了好多个重复的界面,造成了不必要的空间损失。
3.singleTask:这个模式就是我们在上面如何在退出Application时退出所有的Activity这个面试题中已经有所涉及,通常这个模式的启动,当我们重新创建一个在栈中已经存在的Activity实例,当然也是不会让你创建成功的,而是让你复用栈中原来已经存在的实例,另外就是clear掉全部在其之后的Activity,这种做法显然是为了释放空间,另一方面说明这个Activity本身特别占用我们系统的空间或者说性能,所以应用场景除了上面的关闭所有的Activity之外,就是一部分特别占用系统资源的Activity需要配置我们的这个启动模式,比方说我们的浏览器。
补充:Activity中的一个属性: parentActivityName,我们设置父级的Activity,最好给父级的Activity设置一个singleTop,否则可能会重新创建实例,具体子Activity如何返回父级的Activity大家可以在网上搜索,该功能需要API14以上才能使用,当然也可以添加所谓的支持包,这个启动方式最为主要的目的是让我们的子界面返回时总能回到我们的父界面,而不是在栈结构中总是返回启动自己的界面,当你有这样的需求时可以使用和配置一下这个属性进行我们子界面返回操作。
注意:其实我们对于栈的理解还是不够充分的,我们将栈称之为Task,其实官方对于上述的解释是会新建一个Task然后clear掉所有在其之上的Activity实例,这和我们上述的描述可能存在一定的冲突,这时我们需要引入一个属性:taskAffinity,也就是栈的亲属性,我们默认全部是我们当前应用的报名,由于这个属性的存在所以这个实例会被创建到我们当前的Task中,大家有空要自己去了解一下,我们当然可以通过这个属性的修改将其创建在一个其它的任务栈中,特别是当我们的两个应用之间进行切换时,你可能就会利用这个属性了。
4.singleInstance:这个设计称之为共享模式,比方说我们前面谈到Zygote时也说到了一部分共享的内存区域存放我们的系统库资源给我们的dvm实例进行使用,这个设计也是如此,我们只会创建一个Activity的实例,这个实例有着自己单独的任务栈,当你在任何的Task使用到这个对象,假设不存在,就创建单独的任务栈,创建实例对象,假设存在就复用这个原来的实例对象,当然也是调用我们的onNewIntent(),参数方法我们就不写了,这个的应用场景就是一个界面需要被不同的任务栈调用,打开或者说操作,我们只需要保存一个实例对象就可以了,大家在实际的应用场景中可以根据自己的需求进行设置。
补充:对于这个启动方式,假设你给taskAffnity设置不同的值,还是会创建一个独立的任务栈,此处大家了解一下就可以了。
大补充:对于任务栈的概念可能我这样跟读者去描述,大家可能会理解不了,其实我以前去网上百度时读文章也是特备纠结,不同的人对于这个栈的描述有着不同的理解,官方的解释又是太片面,当然有的社区会有好一点的总结和分析到位的文档,此处我就不指出来了,这样会让我有打广告的嫌疑,其实我认为是需要我们自己去调试的,可是这样又太浪费时间了,于是,我们大家有了一个共识,我们会收藏一部分好的资料和文档,在用到时使用一下或者是根据教程去做一下调试,baidu不行我们就google,任务栈是一个显性的概念,就是当我们的打开我们的Android手机切换到后台,就能见到有多少个任务栈正在运行之中,通常任务栈是按照我们的应用程序去划分的,不同的应用程序有着不同的任务栈,当然你如果读懂了我们的前面所说的启动模式,这种描述当然是不够严谨的,可是google本身是尽量让相同应用程序的Activity处于同一个任务栈之中,界面的切换除了栈本身的回退切换之外,当然还有我们所说的任务栈之间的切换,不过当然是切换到栈顶的Activity实例对象,这一点我相信是不难理解的,这样我们会对界面之间的交互(切换也是交互的一种)有着更加深刻的理解,最后说一下:我们的启动方式可以通过Intent实例调用setFlags进行设置。
关于onNewIntent:假设直接使用getIntent()获取到的是老的Intent对象,假设想要获取到最新的,需要使用setIntent()把最近的Intent对象传进去。

Service

Service是否在main thread进行执行,Service中能否进行耗时操作

第一问假设回答时,肯定是不能进行进行耗时操作的,假设回答不是,当然是可以的,可是最好不要,假设我们未能显性地对我们的Service线程进行设定,当然默认就是在main thread里面的,在Acitivity中启动Service的方法:bindService()和startService(),前者能够获取到服务对象,准确来讲是一个代理对象,操作里面的方法,后者就是直接开启了我们的服务。
补充:Service可以被配置为一个单独的进程

Code:

  <service
android:name="com.baidu.location.f"
android:enabled="true"
android:process=":remote" >
</service>

描述一下Service的生命周期

这个知识点我们还是结合一张图给大家说明一下,这样会更加直观,我相信大部分读者还是不喜欢读一大段文字

Service生命周期图

说明:服务的开启主要还是分为绑定模式,非绑定模式以及混合模式
非绑定模式:直接startService就可以了,重复执行这个方法开启同一个服务,只会开启一个服务实例,大家需要注意一下,可是却会反复地去调用我们的onStartCommand(),以前还有一个onStart方法,现在我们已经不用了,当我们在Activity调用一下stopService指令时,服务就会被销毁了。
绑定模式:就是调用我们说的bindService开启我们的服务,这是会比上述例子中多执行一个onBind()方法返回一个代理对象,我们说多个Client可以绑定多次的Service,全部会执行bind()方法,假设我们想要销毁这个服务就必须要让所有的客户端进行解绑操作,实际上是不同的服务连接对象,全部会执行到Service的onUnbind()方法,之后我们的服务就会被销毁。
混合模式:通常我们不会使用太过混乱的混合模式,我们通常是start之后进行bind操作,关闭的话只要全部先解绑,之后使用stop方法就能够将我们的服务实例销毁了,切记在实际开发中不要使用太过复杂的混合模式去开启我们的后台服务。

描述一下IntentService,以及这个服务类的优点?

在当前的AS版本中我们可以直接创建这个类的子类对象,话说这个类本身也是Service的子类,主要是为了处理耗时任务出现的一个子类,我们需要注意到两个点:
i:会有一个方法启动子线程去单独地处理我们的耗时操作,而且会自动去运行我们的这个方法,处理完毕之后会自动结束我们当前的服务,不需要我们调用stopSlef()方法。
ii:假设我们发起多次的请求,不会开启多个线程,会进入到消息队列中,依次被执行,另外我们需要注意的是假设我们手动调用结束服务的方法,消息队列中的全部任务会被清空,不会被处理,其实这个方式和我们的Handler机制比较相似,后续我们也会详细地去描述这个机制,最好使用我们的start方法开启服务,最好不要用我们的bind去开启我们的IntentService,不然你会踩坑的。

Code:

public class MyIntentService extends IntentService {
private String ex = "";
private Handler mHandler = new Handler() {
public void handleMessage(android.os.Message msg) {
Toast.makeText(MyIntentService.this, "-e " + ex,
Toast.LENGTH_LONG).show();
}
};
public MyIntentService(){
super("MyIntentService");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
ex = intent.getStringExtra("start");
return super.onStartCommand(intent, flags, startId);
}
@Override
protected void onHandleIntent(Intent intent) {
/**
* 模拟执行耗时任务
* 该方法是在子线程中执行的,因此需要用到handler 跟主线程进行通信
*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mHandler.sendEmptyMessage(0);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

谈一下Service,Activity以及Intent之间的关系

服务我们可以认为是一个不存在界面的Activity,两者全部是Contex子类,全部是四大组件之一,可以说兄弟关系,我们可以在Service中打开我们的Activity,也可以在我们的Activity中打开我们的Service,两者之间通过我们的Intent进行通信。
注意:在服务中打开我们的Activity需要设置一下Intent设置一个new task的flag,我们说服务本身不在任务栈中,可是我们说打开的Activity通常还是会回到原来的任务栈中,这是由其默认的任务栈亲属性所决定的。

Service和Activity是否处于同一线程?

通常情况,只要你不在子线程中开启我们的服务,如果想让我们的服务线程处理耗时操作已经有了我们前面IntentService,就不要纠结了,两者通常全部开在我们的主线程中,通常我们也将这个线程称之为UI线程,这是一个小问题,如果面试官让再谈下IntentService,上面的知识点就被连接起来了,读者们千万不要讲知识点分开去理解,一定要注意知识点内在的连续性或者我们称之为连贯性。
补充:在Service是否使用Toast的,在UI线程当然可以,会被排进消息队列中,这个面试题我们就不要纠结了。

如何让我们的服务成为前台进程

之后我会上代码,原来的方法现在已经被google给删除了(应该是在API23被删除的),其实这个问题的关键是,当你的服务要成为前台进程时,你必须通知一下你的用户,让用户具有一定的知情权,这就是google的设计,下面我们直接上代码。

Code:

 @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
 @Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Notification.Builder builder = new Notification.Builder(this);//新建Notification.Builder对象
    Intent notificationIntent = new Intent(this, MainActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
    builder.setContentTitle("This is title");//设置标题
    builder.setContentText("This is content");//设置内容
    builder.setSmallIcon(R.mipmap.ic_launcher);//设置图片
    builder.setContentIntent(pendingIntent);//执行intent
    Notification notification = builder.getNotification();//将builder对象转换为普通的notification
    startForeground(1,notification);//让 MyService 变成一个前台服务,并在系统状态栏中显示出来。
    NotificationManager manager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
    manager.notify(1,notification);//运行notification
//        |=代表的是或运算符的赋值
    notification.flags |= Notification.FLAG_AUTO_CANCEL;//点击通知后通知消失
    return super.onStartCommand(intent, flags, startId);
}

Service中的onStartCommand()中的返回值代表的含义

还是简单地说明一下

START_STICKY:这个表示当我们的服务被系统kill之后,系统会重启这个服务,可是不会讲原来的Intent进行保存传递。
START_NOT_STICKY:这个被kill掉之后不会重启我们的Service。
START_FLAG_RETRY:这个也会重启,而且被重传我们的Intent。
补充一下:sticky表示的是粘性,之后我们可能还会接触这个单词。

Service中的Rebind()方法在什么情况下会执行?

这个是在onUnbind()方法返回true时才会被执行,否则是不会被执行的。
官方解释如下:
Called when new clients have connected to the service, after it had previously been notified that all
had disconnected in its onUnbind. This will only be called if the implementation of onUnbind was overridden
to return true.

Activity调用Service中方法的方式

1.最为常用的方式当然是通过获取Binder对象来调用Service中的方法,这种方式bindService(),需要获取一个ServiceConnection对象来返回一个对象。
2.第二种方式是官方提供的,使用一个Messenger类,当然大家可以去查阅一下官方的代码,我简单讲一下思路,首先你在服务类中创建一个Handler,创建一个Messenger对象,将这个handler对象作为实参传入,另外就是返回binder对象时返回messenger对象使用getBinder()进行返回,在Activity中使用ServiceConnection传进来的对象创建我们的Messenger对象,用这个对象发送消息,我们的Handler就能在Service中接受并且处理了,当然我们的服务使用消息对象还能给我们的客户端回传消息,客户端也要创建Messenger传入一个Handler对象进行处理,这个方式我们有个印象就可以了,这也是Android提供给我们的一个IPC通信方式,底层也是封装了我们的AIDL,大家可以了解一下。
3.当然还可以使用我们的远程服务调用,其实还是Binder的原理,使用AIDL,需要注意的是远程方法在子线程被运行,原因是Binder对象在子线程返回,不要进程所谓的UI更新操作。
补充:Linux下的IPC
1.管道(Pipe)及有名管道(named pipe):管道可用于具有亲缘关系进程间的通信,有名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;
2.信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数);
4.报文(Message)队列(消息队列):消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
5.共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
6.信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
7.套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。
注意:可能有的小伙伴就质疑了,Android采用的不是Linux中的IPC方式,一方面是处于效率的考虑,另一方面是处于安全的考虑,所以采用了Binder这种C/S结构的进程通讯模式,而Android本身是有采用Linux中的IPC机制的,比方说kill进程就是用了我们的信号。

请你描述一下BroadcastReceiver

又是我们的四大组件之一,用来接收系统和我们定义的广播事件。
无序广播:一个人广播能够被任何接收者接收到,优点是效率高,缺点当然是我们中途中断我们的广播。
有序广播:显然是为了弥补无序广播的缺点,有序广播是显然是有序的,这个顺序是按照广播接受者接收的优先级来决定的,优先级越高我们说接收到广播就越早,而且先接收到广播的人,有权决定广播是否继续传播下去,当然如果我们想了解广播是否发下去了,我们可以设定一下广播最终接收者,这个无论是否中断了广播,最终全部是可以接收到广播的,这样我们就能了解广播是否已经被接收了。

如何在我们的Manifest文件中注册和使用我们的广播

广播的注册主要是分为两种:静态注册以及动态注册,前者是app在运行就能接收到广播,后者是随着Activity或者Service被销毁,也就无法接收到广播了。

静态注册:

<receiver android:name=".BroadcastReceiver1" >
<intent-filter>
<action android:name="android.intent.action.CALL" >
</action>
</intent-filter>
</receiver>

动态注册:

receiver = new BroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(CALL_ACTION);
context.registerReceiver(receiver, intentFilter);
//记着在销毁时,注销我们的广播接受者

BroadcastReceiver的生命周期

我们说广播接收者的生命极为短暂,在onReceive()执行完毕之后就被销毁了,最后不要在这个方法中进行耗时操作,我们说这样可能会造成ANR,阻碍UI线程进行UI的更新工作,耗时的操作还是可以交给我们前面的IntentService。

如何让自己的广播被指定的app所接收

想一想肯定是加权限,自定义一个权限,使用<permission></permission>就可以定义了,定义权限的名字和等级就可以了,然后要在自己的应用该权限,别人只有使用该权限,或者在广播接收者中声明该权限,才能够接收到广播。
权限等级说明:
每个权限通过 protectionLevel 来标识保护级别:
normal : 低 风 险 权 限 , 只 要 申 请 了 就 可 以 使 用 ( 在 AndroidManifest.xml 中 添 加<uses-permission>标签),安装时不需要用户确认;
dangerous:高风险权限,安装时需要用户的确认才可使用;
signature: 只有当申请权限的应用程序的数字签名与声明此权限的应用程序的数字签名相同时 (如果是申请系统权限,则需要与系统签名相同),才能将权限授给它;
signatureOrSystem:签名相同,或者申请权限的应用为系统应用(在 system image 中)。上述四类权限级别同样可用于自定义权限中。
如果开发者需要对自己的应用程序(或部分应用)进行访问控制,则可以通过在 AndroidManifest.xml 中添加<permission>标签,
将其属性中的protectionLevel 设置为上述四类级别中的某一种来实现。
补充:我们的优先级同样对我们的无序广播生效,动态广播接收者,谁先注册我们说谁的优先级就高的,另外就是我们可以通过isOrderBroadcast()判断一下我们接收到是否有序广播,假设是有序广播,我们就可以对广播的消息和传递进行操作了。

描述一下ContentProvider

这个当然也是我们的四大组件之一,而且负责我们的数据共享,通常是共享我们的数据库,我们主要自定义一个类继承我们的ContentProvider就可以了,重写里面增删改查的方法,之后也是要在我们的Manifest文件中进行声明,最为重要的属性是:authorities。

<provider
android:exported="true"
android:name="com.itheima.contenProvider.provider.PersonContentPro
vider"android:authorities="com.itheima.person" />

ContentProvider和sql之间的区别

我们说前者隐藏了数据存储的细节,用户只需要关心数据的uri就行了,而且可以实现在不同app中的数据共享,最后还能进行本地文件和xml文件的增删改查。

谈一下Contentprovider,ContentResilver,ContentObserver之间的关系

Contentprovider:只是对外提供数据
ContentResilver:内容解析者,负责解析和获取对外提供的数据,还可以注册数据观察者
ContentResilve.notifyChange(uri):负责发出信息
ContentObserver:内容监听者,可以监听数据的变化

如何读取asserts目录下的数据库?

其实就是和我们获取目录下的资源文件是一样的操作

AssetManager assetManager = getAssets();
InputStream is = assetManager.open("myuser.db");
//将文件拷贝到
/data/data/com.itheima.android.asserts.sqlite/databases/myuser.db
//如果databases 目录不存在则创建
File file = new
File("/data/data/com.itheima.android.asserts.sqlite/databases");
if (!file.exists()) {
file.mkdirs();
}FileOutputStream fos = new FileOutputStream(new File(file, "myuser.db"));
byte[] buff = new byte[1024*8];
int len=-1;
while((len=is.read(buff))!=-1){
fos.write(buff, 0, len);
}
fos.close();
is.close();
//访问数据库
SQLiteDatabase database = openOrCreateDatabase("myuser.db",  MODE_PRIVATE,
null);
String sql = "select c_name from t_user";
Cursor cursor = database.rawQuery(sql , null);
while(cursor.moveToNext()){
String string = cursor.getString(0);
Log.d("tag", string);
}
cursor.close();
database.close();
}

补充:尽量减少多表联合查询,创建索引(这个是必须的),将查询到的数据进行特定的缓存策略(大家可以自行去查询一下数据库的优化)。

Android中常见的布局

此处我们唯一可能需要介绍一下的是我们的CoordinatorLayout布局,这个布局现在已经作为AS默认的布局出现,必须说这是一个好的布局,对于协调View的联动的确有极大的工作,比方说滚动的联动性,可是目前不支持ListView和ScrollView,滚动的话我们可以使用NestedScrollView来进行协调,当然我们说这个布局还有一个重要的静态内部类为:Behavior,这个类特别的关键,这个类能够协调两个view之间的动作,不过我们需要设置一下child和dependency,前者就是一个子孩子,另一个是这个子孩子依赖的view对象,泛型类型为子孩子的类型,我们需要将这个Behavior对象设置给我们的子孩子,通过我们的xml文件配置,也就是说我们可以定义这个子孩子让其跟随着我们的依赖对象作出相应的动画或者是其它属性上的改变,还有就是我们在AS中默认的这个布局不是协调者,官方的解释是一个超级的FramLayout布局,可是官方也添加了好多其它的布局属性来完善这个布局的能力,大家现在可以在实际开发中掌握一下,最新的design包可能最小的API版本为14(也就是Android4.0)。
LinearLayout:线性布局,所谓线性就是你可以制定是水平布局的方式,也可以执行垂直布局的方式,假设你们UI给你的效果图是否偏向于这种布局,就使用该布局,这种布局相对而言比较简单一点的。
RelativeLayout:这是一个相对布局,我们可以使用各种相对父布局以及其它view的布局属性,这种比较适合非线性的,每个View之间相互制约进行布局的UI效果图。
AbsoluteLayout:绝对坐标布局,当然是根据内容的左上角为起始坐标进行设置的,在TV开发中我们曾经会用到,原因是TV的型号是确定的,我们完全可以将位置定死,我们可以给布局中的空间设置x和y的值来确定控件的位置。
TableLayout:表格布局,可以使用一个行标签,对每一行进行设置,这个布局我们现在也通常不用的,当然也可以对Column的各种属性进行设置。
GrideLayout:网格布局,在API14时被提出,网格我相信大家应该有所了解,如同我们Excel中尺寸相同的表格一样,也就是说我们根据一个设置好的网格去布局,在实际开发中我真的不建议去使用这布局,大体上我们还是会使用线性布局和相对布局。
FramLayout:我们称之为帧布局,这个是一个原生的布局,也就是说特容易产生叠加现象,这个布局对性能的损耗是最少的,当我们需要这种叠加性质时可以采用这种布局,包括我们的根布局DecorView也是继承了这个布局。

如何优化我们的布局(这是一个典型的面试题)

1.使用include标签加载相同的布局
2.尽可能的减少布局的嵌套
3.避免过渡绘制
4.使用merge作为根标签,比方说在include加载xml布局文件中,不需要用到布局的根布局,我们说View只能有一个父布局。
5.不需要马上加载的布局,可以使用ViewStub标签,可以进行我们的延迟加载。

XML:

<ViewStub
android:id="@+id/vs"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:inflatedId="@+id/my_view"
android:layout="@layout/my_layout" />

Code:

public void loadVS(View view){
ViewStub vs = (ViewStub) findViewById(R.id.vs);
View inflate = vs.inflate();
int inflatedId = vs.getInflatedId();
int id = inflate.getId();
Toast.makeText(this, "inflatedId="+inflatedId+"***"+"id="+id,
Toast.LENGTH_SHORT).show();
}

关于ListView(重点)

我相信对于ListView的使用大家一定不会陌生,此处我的总结可能相对而言只会就重点部分的知识点讲一下,分页加载的实现是依靠监听我们ListView的滑动状态实现的,我们真正关心的是最后一个条目,还有就是ListView能否加载多种类型的条目,其实我们完全可以在getView方法里面判断一下返回不同的条目,当然我们也可以使用ListView内置的方法getViewTypeCount()和getItemType()来实现。
如何在ScrollView中嵌套ListView,其实这个google官方已经说明不允许我们在ScrollView中添加任何的滚动条目,我们讲一下StackOverFlow上提出真正有效的方法:

Code:

lv = (ListView) findViewById(R.id.lv);
adapter = new MyAdapter();
lv.setAdapter(adapter);
setListViewHeightBasedOnChildren(lv);
----------------------------------------------------
public void setListViewHeightBasedOnChildren(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
if (listAdapter == null) {
return;
}
int totalHeight = 0;
for (int i = 0; i < listAdapter.getCount(); i++) {
View listItem = listAdapter.getView(i, null, listView);
listItem.measure(0, 0);
totalHeight += listItem.getMeasuredHeight();
}
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalHeight + (listView.getDividerHeight() *
(listAdapter.getCount() - 1));
params.height += 5;// if without this statement,the listview will be a
// little short
listView.setLayoutParams(params);
}

XML:

1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
2. android:layout_width="match_parent"
3. android:layout_height="match_parent"
4. android:orientation="vertical" >
5. <ScrollView
6. android:id="@+id/sv"
7. android:layout_width="match_parent"
8. android:layout_height="0dp"
9. android:layout_weight="1" >
10.
11. <LinearLayout
12. android:layout_width="match_parent"
13. android:layout_height="match_parent"
14. android:orientation="vertical" >
15.
16. <ListView
17. android:id="@+id/lv"
18. android:layout_width="match_parent"
19. android:layout_height="wrap_content" >
20. </ListView>
21. </LinearLayout>
22. </ScrollView>
23.
24. </LinearLayout>

注意:不能讲我们的ListView直接放在我们的ScrollView中,当然也是可以重写我们的ListView的onMeasure()返回一个最大的高度,当然我们说ListView本身就可以加载头布局好脚布局,我们完全可以使用这个功能来替换掉我们的ScrollView,如果条目不是特别多,就直接使用我们的LinearLayout布局来加载我们的条目。
补充:我们在加载图片时,不要直接加载到我们的内存之中,前要使用一下Bitmap中的Option功能从sd卡或者是其它存储设备加载时进行图片的裁剪或者是压缩,然后图片不显示时可以释放掉我们的图片资源,另外注意的是处于滑动状态下不要去网络获取图片,或者是做加载图片的操作,另外就是使用图片加载的框架,比方说Android内置的ImgeLoader,里面就维系了一个线程池,在获取网络图片时也是现将其下载到我们的SD卡中,可以使用我们的读写流,之后对图片进行处理之后将其加载到我们的内存之中通过显示器显示出来的,通常我们现在图片加载的话可能会用到Universal-Image-Loader,Glide(google官方推荐,能够加载gif图,还能加载渐变图,图片质量比较高),还有一个还是Fresco,当然我们现在也是可以使用RecycleView进行嵌套的,相对而言会简单许多。

JNI and NDK

JNI实际上就是Java和我们的C语言进行互调的一个接口,在我们书写的C语言中我们的通常会去include一个jni.h的头文件,我们通过Java的native修饰符去生成一个.cpp的文件,之后在这个文件的方法中书写我们的C代码,当然反过来我们说C语言调用我们的Java代码也是通过这样一个jni接口来实现的,不过我们需要启动我们的jvm虚拟机,之后我们在生成的.cpp文件中就可以调用我们的Java方法了,我们获取到一个Class之后,通过反射去调用我们想要的方法,说到底JNI是为了打通我们C和JAVA之间的互调而出现的,其中肯定是要进行一个兼容化的处理,我们只需要按照特定的方式进行代码的关联,之后按照JNI提供的方式进行调用就可以了。
NDK我们说是Android提供给我们的一个软件工具,这个工具主要是帮助我们生成.so文件,当前最新版本的AS已经帮助我们配置好了一个native-libs文件,只要我们直接导入就可以了,在里面生成的方法中调用我们C语言的代码,之后在我们的build文件夹里面就会生成对应的.so文件可以为我们所使用,这个是AS为了方便直接就将我们配置好的,方便我们的开发工作,大家也不必去配置相关的参数,当然在构建项目你需要导入我们的CMAKE,还有下载和配置我们的NDK,在我们的AS这种配置相对而言已经极为的简单,大部分时间我们所做的事情可能不太需要修改我们的C或者C++部分的代码,我们通常也是直接引入别人打包好的.so文件,我们直接将其放在我们的jniLibs文件夹里面,通过静态块中:System.loadLibarary(".so库的名称,不带后缀名"),直接引用就可以了,注意方法名需要和我们的.so库中的方法名要一模一样,这样我们就可以直接调用这个方法了,如果大家果真有需要,特别是在音频视频处理或者图像处理上,我们需要可能会用到较多的C库,另外就是在调用底层命令时,原因是我们的底层就是Linux系统,本身就是C语言所写的代码。

在Android中如何访问我们的网络

以前在我们的Android中访问网络使用的是Android-SDK封装的apache的HttpClient,当然还有我们的java中的HttpURLConnection,其实两个我们全部是可以使用的,可是HttpClien相对于我们在移动端的网络访问来讲特别的笨重,关键是之后又出现了OKHttp这个好的网络访问框架,google在API23时就把HttpClient的代码全部给删除了,现在如果我们想要使用这个网络访问的框架就必须在libs中导入一个jar包才能够使用,等于是google将其删除,你又将其增加到你的代码中,其实通常来讲我们是不会这么去做的,关键是我们在工作和项目的导入中可能会碰到以前有的人使用这个网络框架进行编程,相别XUtils框架,这个框架真心是被国人用烂了,这个框架其实就是一个框架的大合集,其中的HttpUtils本质上底层就是使用了我们的HttpClient,鉴于会用这个框架的人实在会有太多,所以有时在接手别人的项目时我们可能会去导入这个jar包,有需要的人也可以私信我,我的资料全部是在我的网盘中存储着,相信在网上找一个也是不费劲的事情,另外我们通常会接收服务端的json数据,然后将其封装为bean类,这一切可以通过Gson来完成,而且我们的bean类可以通过这个工具直接被生成,另外就是Android中内置了JSONArray,JSONObject,JSONStringer,JSONTokener,来满足我们对于JSON数据的封装。,

上一篇 下一篇

猜你喜欢

热点阅读