来源:以前网上买的资料+面试遇到的+百度到的
____本篇为提高篇
一款App流畅与否安装在自己的真机里,玩几天就能有个大概的感性认识。不过通过专业的分析工具可以使我们更好的分析我们的应用。而在实际开发中,我们解决完当前应用所有bug后,就会开始考虑到新能的优化。
如果不考虑使用其他第三方性能分析工具的话,我们可以直接使用ddms中的工具,其实ddms工具已经非常的强大了。ddms中有traceview、heap、allocationtracker等工具都可以帮助我们分析应用的方法执行时间效率和内存使用情况。
traceview
(一)TraceView简介
Traceview是Android平台特有的数据采集和分析工具,它主要用于分析Android中应用程序的hotspot(瓶颈)。Traceview本身只是一个数据分析工具,而数据的采集则需要使用Android SDK中的Debug类或者利用DDMS工具。
二者的用法如下:
开发者在一些关键代码段开始前调用Android SDK中Debug类的startMethodTracing函数,并在关键代码段结束前调用stopMethodTracing函数。这两个函数运行过程中将采集运行时间内该应用所有线程(注意,只能是Java线程)的函数执行情况,并将采集数据保存到/mnt/sdcard/下的一个文件中。开发者然后需要利用SDK中的Traceview工具来分析这些数据。
借助Android SDK中的DDMS工具。DDMS可采集系统中某个正在运行的进程的函数调用信息。对开发者而言,此方法适用于没有目标应用源代码的情况。DDMS工具中Traceview的使用如下图所示。
点击上图中所示按钮即可以采集目标进程的数据。当停止采集时,DDMS会自动触发Traceview工具来浏览采集数据。
下面,我们通过一个示例程序介绍Traceview的使用。
实例程序如下图所示:界面有4个按钮,对应四个方法。
点击不同的方法会进行不同的耗时操作。
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void method1(View view) {
int result = jisuan();
System.out.println(result);
}
private int jisuan() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
return 1;
}
public void method2(View view) {
SystemClock.sleep(2000);
}
public void method3(View view) {
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i;
}
System.out.println("sum=" + sum);
}
public void method4(View view) {
Toast.makeText(this, "" + new Date(), 0).show();
}
}
private int jisuan() {
for (int i = 0; i < 10000; i++) {
System.out.println(i);
}
return 1;
}
public void method2(View view) {
SystemClock.sleep(2000);
}
public void method3(View view) {
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += i;
}
System.out.println("sum=" + sum);
}
public void method4(View view) {
Toast.makeText(this, "" + new Date(), 0).show();
}
}
我们分别点击按钮一次,要求找出最耗时的方法。点击前通过DDMS 启动 Start Method Profiling按钮。
然后依次点击4个按钮,都执行后再次点击上图中红框中按钮,停止收集数据。
接下来我们开始对数据进行分析。
当我们停止收集数据的时候会出现如下分析图表。该图表分为2大部分,上面分不同的行,每一行代表一个线程的执行耗时情况。main线程对应行的的内容非常丰富,而其他线程在这段时间内干得工作则要少得多。图表的下半部分是具体的每个方法执行的时间情况。显示方法执行情况的前提是先选中某个线程。
我们主要是分析main线程。
上面方法指标参数所代表的意思如下:
列名 |
描述 |
Name |
该线程运行过程中所调用的函数名 |
Incl Cpu Time |
某函数占用的CPU时间,包含内部调用其它函数的CPU时间 |
Excl Cpu Time |
某函数占用的CPU时间,但不含内部调用其它函数所占用的CPU时间 |
Incl Real Time |
某函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间 |
Excl Real Time |
某函数运行的真实时间(以毫秒为单位),不含调用其它函数所占用的真实时间 |
Call+Recur Calls/Total |
某函数被调用次数以及递归调用占总调用次数的百分比 |
Cpu Time/Call |
某函数调用CPU时间与调用次数的比。相当于该函数平均执行时间 |
Real Time/Call |
同CPU Time/Call类似,只不过统计单位换成了真实时间 |
我们为了找到最耗时的操作,那么可以通过点击Incl Cpu Time,让其按照时间的倒序排列。我点击后效果如下图:
通过分析发现:method1最耗时,耗时2338毫秒。
那么有了上面的信息我们可以进入我们的method1方法查看分析我们的代码了。
heap
(二)heap简介
heap工具可以帮助我们检查代码中是否存在会造成内存泄漏的地方。
用heap监测应用进程使用内存情况的步骤如下:
1.启动eclipse后,切换到DDMS透视图,并确认Devices视图、Heap视图都是打开的;
2.点击选中想要监测的进程,比如system_process进程;
3.点击选中Devices视图界面中最上方一排图标中的“UpdateHeap”图标;
4.点击Heap视图中的“Cause GC”按钮;
5.此时在Heap视图中就会看到当前选中的进程的内存使用量的详细情况。
说明:
a. 点击“Cause GC”按钮相当于向虚拟机请求了一次gc操作;
b. 当内存使用信息第一次显示以后,无须再不断的点击“Cause GC”,Heap视图界面会定时刷新,在对应用的不断的操作过程中就可以看到内存使用的变化;
c. 内存使用信息的各项参数根据名称即可知道其意思,在此不再赘述。
如何才能知道我们的程序是否有内存泄漏的可能性呢?
Ø 这里需要注意一个值:Heap视图中部有一个Type叫做data object,即数据对象,也就是我们的程序中大量存在的类类型的对象。在data object一行中有一列是“Total Size”,其值就是当前进程中所有Java数据对象的内存总量,一般情况下,这个值的大小决定了是否会有内存泄漏。可以这样判断:
Ø 不断的操作当前应用,同时注意观察data object的Total Size值;
Ø 正常情况下Total Size值都会稳定在一个有限的范围内,也就是说由于程序中的的代码良好,没有造成对象不被垃圾回收的情况,所以说虽然我们不断的操作会不断的生成很多对象,而在虚拟机不断的进行GC的过程中,这些对象都被回收了,内存占用量会会落到一个稳定的水平;
Ø 反之如果代码中存在没有释放对象引用的情况,则data object的Total Size值在每次GC后不会有明显的回落,随着操作次数的增多Total Size的值会越来越大,直到到达一个上限后导致进程被kill掉。
Ø 此处以system_process进程为例,在我的测试环境中system_process进程所占用的内存的data object的Total Size正常情况下会稳定在2.2~2.8之间,而当其值超过3.55后进程就会被kill。
总之,使用DDMS的Heap视图工具可以很方便的确认我们的程序是否存在内存泄漏的可能性。
allocation tracker
(三)allocation tracker 简介
allocation tracker是内存分配跟踪工具
步骤:
运行DDMS,只需简单的选择应用进程并单击Allocation tracker标签,就会打开一个新的窗口,单击“StartTracing”按钮;
然后,让应用运行你想分析的代码。运行完毕后,单击“GetAllocations”按钮,一个已分配对象的列表就会出现第一个表格中。
单击第一个表格中的任何一项,在表格二中就会出现导致该内存分配的栈跟踪信息。通过allocation tracker,不仅知道分配了哪类对象,还可以知道在哪个线程、哪个类、哪个文件的哪一行。
Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误。
内存溢出的几点原因:
(1)资源释放问题
程序代码的问题,长期保持某些资源,如Context、Cursor、IO流的引用,资源得不到释放造成内存泄露。
(2)对象内存过大问题
保存了多个耗用内存过大的对象(如Bitmap、XML文件),造成内存超出限制。
(3)static关键字的使用问题
static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。
public class ClassName {
private static ContextmContext;
//省略
}
以上的代码是很危险的,如果将Activity赋值到mContext的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放。
我们举Android文档中的一个例子。
private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this); //getApplicationContext
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
private static Drawable sBackground;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this); //getApplicationContext
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}
sBackground是一个静态的变量,但是我们发现,我们并没有显式的保存Contex的引用,但是,当Drawable与View连接之后,Drawable就将View设置为一个回调,由于View中是包含Context的引用的,所以,实际上我们依然保存了Context的引用。这个引用链如下:
Drawable->TextView->Context
所以,最终该Context也没有得到释放,发生了内存泄露。
针对static的解决方案
1) 应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。
2) Context尽量使用ApplicationContext,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。
3) 使用WeakReference代替强引用。比如可以使用WeakReference
1. 线程导致内存溢出
线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。
public class MyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new MyThread().start();
}
private class MyThread extends Thread{
@Override
public void run() {
super.run();
//do somthing while(true)
}
}
}
这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。
由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。
有些人喜欢用Android提供的AsyncTask,但事实上AsyncTask的问题更加严重,Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了ThreadPoolExcutor,该类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。
针对这种线程导致的内存泄露问题的解决方案:
(一) 将线程的内部类,改为静态内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。
(二) 在线程内部采用弱引用保存Context引用。
OOM内存溢出,想要避免OOM异常首先我们要知道什么情况下会导致OOM异常。
(1)图片过大导致OOM
Android 中用bitmap时很容易内存溢出,比如报如下错误:Java.lang.OutOfMemoryError : bitmap size exceeds VM budget。
解决方法:
方法1:等比例缩小图片
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
//Options 只保存图片尺寸大小,不保存图片到内存
BitmapFactory.Options opts = new BitmapFactory.Options();
opts.inSampleSize = 2;
Bitmap bmp = null;
bmp = BitmapFactory.decodeResource(getResources(), mImageIds[position],opts);
//回收
bmp.recycle();//
opts.inSampleSize = 2;
Bitmap bmp = null;
bmp = BitmapFactory.decodeResource(getResources(), mImageIds[position],opts);
//回收
bmp.recycle();//
以上代码可以优化内存溢出,但它只是改变图片大小,并不能彻底解决内存溢出。
方法2:对图片采用软引用,及时地进行recyle()操作
SoftReference bitmap = new SoftReference(pBitmap);
if(bitmap != null){
if(bitmap.get() != null && !bitmap.get().isRecycled()){
bitmap.get().recycle();
bitmap = null;
}
}
方法3:使用加载图片框架处理图片,如专业处理加载图片的ImageLoader图片加载框架,XUtils的BitMapUtils来做处理。
(2)界面切换导致OOM
一般情况下,开发中都会禁止横屏的。因为如果是来回切换话,activity的生命周期会重新销毁然后创建。
有时候我们会发现这样的问题,横竖屏切换N次后 OOM了。
这种问题没有固定的解决方法,但是我们可以从以下几个方面下手分析。
1、看看页面布局当中有没有大的图片,比如背景图之类的。
去除xml中相关设置,改在程序中设置背景图(放在onCreate()方法中):
Drawable drawable = getResources().getDrawable(R.drawable.id);
ImageView imageView = new ImageView(this);
imageView.setBackgroundDrawable(drawable);
在Activity destory时注意,drawable.setCallback(null); 防止Activity得不到及时的释放。
2、跟上面方法相似,直接把xml配置文件加载成view 再放到一个容器里,然后直接调用this.setContentView(View view);方法,避免xml的重复加载。
3、 在页面切换时尽可能少地重复使用一些代码
比如:重复调用数据库,反复使用某些对象等等......
(3)查询数据库没有关闭游标
程序中经常会进行查询数据库的操作,但是经常会有使用完毕Cursor后没有关闭的情况。如果我们的查询结果集比较小,对内存的消耗不容易被发现,只有在常时间大量操作的情况下才会出现内存问题,这样就会给以后的测试和问题排查带来困难和风险。
(4)构造Adapter时,没有使用缓存的 convertView
在使用ListView的时候通常会使用Adapter,那么我们应该尽可能的使用ConvertView。
为什么要使用convertView?
当convertView为空时,用setTag()方法为每个View绑定一个存放控件的ViewHolder对象。当convertView不为空,重复利用已经创建的view的时候,使用getTag()方法获取绑定的ViewHolder对象,这样就避免了findViewById对控件的层层查询,而是快速定位到控件。
(5)Bitmap对象不再使用时调用recycle()释放内存
有时我们会手工的操作Bitmap对象,如果一个Bitmap对象比较占内存,当它不再被使用的时候,可以调用Bitmap.recycle()方法回收此对象的像素所占用的内存,但这不是必须的,视情况而定。
(6)其他
Android应用程序中最典型的需要注意释放资源的情况是在Activity的生命周期中,在onPause()、onStop()、onDestroy()方法中需要适当的释放资源的情况。使用广播没有注销也会产生OOM。
(一)UncaughtExceptionHandler
1、自定义一个Application,比如叫MyApplication继承Application实现UncaughtExceptionHandler。
2、覆写UncaughtExceptionHandler的onCreate和uncaughtException方法。
@Override
public void onCreate() {
super.onCreate();
Thread.setDefaultUncaughtExceptionHandler(this);
}
@Override
public void uncaughtException(final Thread thread, final Throwable ex) {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
System.out.println(Thread.currentThread());
Toast.makeText(getApplicationContext(), "thread="+thread.getId()+" ex="+ex.toString(), 1).show();
Looper.loop();
}
}).start();
SystemClock.sleep(3000);
android.os.Process.killProcess(android.os.Process.myPid());
}
}
注意:上面的代码只是简单的将异常打印出来。
在onCreate方法中我们给Thread类设置默认异常处理handler,如果这句代码不执行则一切都是白搭。
在uncaughtException方法中我们必须新开辟个线程进行我们异常的收集工作,然后将系统给杀死。
3、在AndroidManifest中配置该Application
(二)Bug收集工具 Crashlytics
Crashlytics 是专门为移动应用开发者提供的保存和分析应用崩溃的工具。国内主要使用的是友盟做数据统计。
Crashlytics的好处:
1.Crashlytics不会漏掉任何应用崩溃信息。
2.Crashlytics可以象Bug管理工具那样,管理这些崩溃日志。
3.Crashlytics可以每天和每周将崩溃信息汇总发到你的邮箱,所有信息一目了然。
使用步骤:
1.注册需要审核通过才能使用,国内同类产品顶多发个邮箱激活链接;
2.支持Eclipse、Intellij IDEA和Android Studio等三大IDE;
3.Eclipse插件是iOS主题风格UI,跟其他plugin在一起简直是鹤立鸡群;
4.只要登录帐号并选择项目,会自动导入jar包并生成一个序列号,然后在AndroidManifest.xml
和启动Activity
的入口添加初始化代码,可以说是一键式操作,当然要使用除错误统计外的其他功能还是得自己添加代码;
5.不像友盟等国内同类产品,将固定的序列号直接写入xml文件,而是动态自动生成的;当然这个存放序列号的xml文件也是不能修改和提交到版本控制系统的;
6.后台可以设置邮件提醒,当然这个最好不要开启,Android开发那数量惊人、千奇百怪的错误信息你懂的。
7.不仅能统计到UncaughtException
这种未捕获的Crash异常信息,只要在try
/catch
代码块的catch
中添加一行代码就能统计到任何异常;
|
8.相当详细的错误信息,不仅仅是简单的打印StackTrace信息;并且能看到最近一次crash的机器可用内存等信息,而不仅仅是简单统计机型和版本号。
使用连接:http://blog.csdn.net/smking/article/details/39320695
在Android上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择让程序继续运行,但是,他们在使用你的应用程序时,并不希望每次都要处理这个对话框。因此,在程序里对响应性能的设计很重要,这样,系统不会显示ANR给用户。
Activity 5秒 broadcast10秒
耗时的操作 worker thread里面完成, handlermessage…AsynTask , intentservice.等…
ANR:Application Not Responding,即应用无响应
ANR一般有三种类型:
1:KeyDispatchTimeout(5seconds) --主要类型
按键或触摸事件在特定时间内无响应
2:BroadcastTimeout(10 seconds)
BroadcastReceiver在特定时间内无法处理完成
3:ServiceTimeout(20 seconds) --小概率类型
Service在特定的时间内无法处理完成
超时的原因一般有两种:
(1)当前的事件没有机会得到处理(UI线程正在处理前一个事件没有及时完成或者looper被某种原因阻塞住)
(2)当前的事件正在处理,但没有及时完成
UI线程尽量只做跟UI相关的工作,耗时的工作(数据库操作,I/O,连接网络或者其他可能阻碍UI线程的操作)放入单独的线程处理,尽量用Handler来处理UI thread和thread之间的交互。
UI线程主要包括如下:
Activity:onCreate(), onResume(), onDestroy(), onKeyDown(), onClick()
AsyncTask: onPreExecute(), onProgressUpdate(), onPostExecute(),onCancel()
Mainthread handler: handleMessage(), post(runnable r)
查找ANR的方式: 1. 导出/data/data/anr/traces.txt,找出函数和调用过程,分析代码 2. 通过性能LOG人肉查找
Ø 共享内存(变量);
Ø 文件,数据库;
Ø Handler;
Ø Java里的wait(),notify(),notifyAll()
Dalvik虚拟机运行在Linux操作系统之上。Linux操作系统并没有纯粹的线程概念,只要两个进程共享一个地址空间,那么就可以认为它们是同一个进程的两个线程。Linux系统提供了两个fork和clone调用,其中,前者是用来创建进程的,而后者是用来创建线程的。
一般来说,虚拟机的进程和线程都是和目标机器本地操作系统的进程和线程一一对应的,这样的好处是可以使本地操作系统来调度进程和线程。
每个Android应用程序进程都有一个Dalvik虚拟机实例。这样做得好处是Android应用程序进程之间不会互相影响,也就是说,一个Android应用程序进程的意外终止,不会影响到其他的应用程序进程的正常运行。
Ø 每个Android应用程序进程都是由一种称为Zygote的进程fork出来的。Zygote进程是由init进程启动起来的,也就是在系统启动的时候启动的。Zygnote进程在启动的时候,会创建一个虚拟机实例,并且在这个虚拟机实例将所有的Java核心库都加载起来。每当Zygote进程需要创建一个Android应用程序进程的时候,它就通过复制自身来实现,也就是通过fork系统调用来实现。这些被fork出来的Android应用程序进程,一方面是复制了Zygote进程中的虚拟机实例,另外一方面是与Zygote进程共享了同一套Java核心库。这样不仅Android程序进程的创建很快,而且所有的应用程序都共享同一套Java核心库而节省了内存空间。
1. android系统架构分从下往上为linux 内核层、运行库、应用程序框架层、和应用程序层。
2. linuxkernel:负责硬件的驱动程序、网络、电源、系统安全以及内存管理等功能。
3. libraries和 androidruntime:libraries:即c/c++函数库部分,大多数都是开放源代码的函数库,例如webkit,该函数库负责 android网页浏览器的运行,例如标准的c函数库libc、openssl、sqlite等,当然也包括支持游戏开发2dsgl和 3dopengles,在多媒体方面有mediaframework框架来支持各种影音和图形文件的播放与显示,例如mpeg4、h.264、mp3、 aac、amr、jpg和png等众多的多媒体文件格式。android的runtime负责解释和执行生成的dalvik格式的字节码。
4. applicationframework(应用软件架构),java应用程序开发人员主要是使用该层封装好的api进行快速开发。
5. applications:该层是java的应用程序层,android内置的googlemaps、e-mail、即时通信工具、浏览器、mp3播放 器等处于该层,java开发人员开发的程序也处于该层,而且和内置的应用程序具有平等的位置,可以调用内置的应用程序,也可以替换内置的应用程序。
如何限制的?
Android应用的开发语言为Java,每个应用最大可使用的堆内存受到Android系统的限制
•Android每一个应用的堆内存大小有限
•通常的情况为16M-48M
•通过ActivityManager的getMemoryClass()来查询可用堆内存限制
•3.0(HoneyComb)以上的版本可以通过largeHeap=“true”来申请更多的堆内存
•NexueHeap 512
•如果试图申请的内存大于当前余下的堆内存就会引发OutOfMemoryError()
•应用程序由于各方面的限制,需要注意减少内存占用,避免出现内存泄漏。
获取这个代码:
如何合理使用内存?
1、注意资源回收,像数据库,输入输出流,定位操作这样的对象,要在使用完及时关闭流。
2、少使用静态变量,因为系统将静态变量的优先级设定的很高,会最后回收。所以可能因为静态变量导致该回收的没有回收。而回收了不该回收的内存。
3、注意大图片的缩放,如果载入的图片很大,要先经过自己程序的处理,降低分辨率等。最好设置多种分辨率格式的图片,以减少内存消耗。
4、动态注册监听,把一些只有显示的时候才使用到的监听放进程序内部,而不是放在manifesat中去。
5、减少使用动画,或者适当减少动画的帧数。
6、注意自己的程序逻辑,在该关闭自己程序的控件的时候,主动关闭,不要交给系统去决定。(这个要自己把握好,也不是说都自己搞定,只有那些自己确定需要关闭的对象,自己将其关闭。)
Android应用程序结构也就是讲我们的工程结构:
src目录是源代码目录,所有允许用户修改的java文件和用户自己添加的java文件都保存在这个目录中
gen目录是1.5版本新增的目录,用来保存ADT自动生成的java文件,例如R.java或AIDL文件
注意:R.java文件(非常重要)
a) R.java文件是ADT自动生成的文件,包含对drawable、layout和values目录内的资源的引用指针,Android程序能够直接通过R类引用目录中的资源
b) R.java文件不能手工修改,如果向资源目录中增加或删除了资源文件,则需要在工程名称上右击,选择Refresh来更新R.java文件中的代码
c) R类包含的几个内部类,分别与资源类型相对应,资源ID便保存在这些内部类中,例如子类drawable表示图像资源,内部的静态变量icon表示资源名称,其资源ID为0x7f020000。一般情况下,资源名称与资源文件名相同
android.jar文件是Android程序所能引用的函数库文件,Android通过平台所支持API都包含在这个文件中
assets目录用来存放原始格式的文件,例如音频文件、视频文件等二进制格式文件。此目录中的资源不能被R.java文件索引。,所以只能以资截流的形式读取。一般情况下为空
layout目录用来存放我们为每个界面写的布局文件
Strings.xml文件是程序中的一些字符串的引用
AndroidManifest.xml是XML格式的Android程序声明文件,包含了Android系统运行Android程序前所必须掌握的重要信息,这些信息包含应用程序名称、图标、包名称、模块组成、授权和SDK最低版本等,而且每个Android程序必须在根目录下包含一个AndroidManifest.xml文件
注:AndroidMainfest.xml文件:
1) AndroidManifest.xml文件的根元素是manifest,包含了xmlns:android、package、android:versionCode和android:versionName共4个属性
2) xmlns:android定义了Android的命名空间,值为http://schemas.android.com/apk/res/android
3) package定义了应用程序的包名称
4) android:versionCode定义了应用程序的版本号,是一个整数值,数值越大说明版本越新,但仅在程序内部使用,并不提供给应用程序的使用者
5) android:versionName定义了应用程序的版本名称,是一个字符串,仅限于为用户提供一个版本标识
6) manifest元素仅能包含一个application元素,application元素中能够声明Android程序中最重要的四个组成部分,包括Activity、Service、BroadcastReceiver和ContentProvider,所定义的属性将影响所有组成部分
7) android:icon定义了Android应用程序的图标,其中@drawable/icon是一种资源引用方式,表示资源类型是图像,资源名称为icon,对应的资源文件为res/drawable目录下的icon.png
8) android:label则定义了Android应用程序的标签名称
default.properties文件记录Android工程的相关设置,该文件不能手动修改,需右键单击工程名称,选择“Properties”进行修改
apk程序是运行在虚拟机上的,对应的是Android独特的权限机制,只有体现到文件系统上时才使用linux的权限设置。
(一)linux文件系统上的权限
-rwxr-x--x system system 4156 2010-04-30 16:13 test.apk
代表的是相应的用户/用户组及其他人对此文件的访问权限,与此文件运行起来具有的权限完全不相关。比如上面的例子只能说明system用户拥有对此文件的读写执行权限;system组的用户对此文件拥有读、执行权限;其他人对此文件只具有执行权限。而test.apk运行起来后可以干哪些事情,跟这个就不相关了。千万不要看apk文件系统上属于system/system用户及用户组,或者root/root用户及用户组,就认为apk具有system或root权限
(二)Android的权限规则
(1)Android中的apk必须签名
(2)基于UserID的进程级别的安全机制
(3)默认apk生成的数据对外是不可见的
(4)AndroidManifest.xml中的显式权限声明
所有的框架都是基于反射 和 配置文件(manifest)的。
普通的情况:
Activity创建一个view是通过 ondraw 画出来的, 画这个view之前呢,还会调用onmeasure方法来计算显示的大小.
特殊情况:
Surfaceview是直接操作硬件的,因为 或者视频播放对帧数有要求,onDraw效率太低,不够使,Surfaceview直接把数据写到显存。
一、进程间的通信方式
#管道(pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
#有名管道(namedpipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
#信号量(semophore) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
#消息队列(messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
#信号(sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
#共享内存(sharedmemory ):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
#套接字(socket): 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
二、线程间的通信方式
#锁机制:包括互斥锁、条件变量、读写锁
*互斥锁提供了以排他方式防止数据结构被并发修改的方法。
*读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
*条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
#信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
#信号机制(Signal):类似进程间的信号处理
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
1.1 适配方式之dp
名词解释:
分辨率:eg:480*800,1280*720。表示物理屏幕区域内像素点的总和。(切记:跟屏幕适配没有任何关系)
因为我们既可以把1280*720的分辨率做到4.0的手机上面。我也可以把1280*720的分辨率做到5.0英寸的手机上面,如果分辨率相同,手机屏幕越小清晰。
px(pix):像素,就是屏幕中最小的一个显示单元
dpi(像素密度):即每英寸屏幕所拥有的像素数,像素密度越大,显示画面细节就越丰富。
计算公式:像素密度=√{(长度像素数^2+宽度像素数^2)}/ 屏幕尺寸
注:屏幕尺寸单位为英寸 例:分辨率为1280*720 屏幕宽度为6英寸 计算所得像素密度约等于245,屏幕尺寸指屏幕对角线的长度。
在Android手机中dpi分类:
|
Resources for low-density (ldpi) screens (~120dpi). |
|
Resources for medium-density (mdpi) screens (~160dpi). (This is the baseline density.) |
|
Resources for high-density (hdpi) screens (~240dpi). |
|
Resources for extra high-density (xhdpi) screens (~320dpi). |
在我们的Android工程目录中有如下drawable-*dpi目录,这些目录是用来适配不同分辨率手机的。
Android应用在查找图片资源时会根据其分辨率自动从不同的文件目录下查找(这本身就是Android系统的适配策略),如果在低分辨的文件目录中比如drawable-mdpi中没有图片资源,其他目录中都有,当我们将该应用部署到mdpi分辨率的手机上时,那么该应用会查找分辨率较高目录下的资源文件,如果较高分辨率目录下也没有资源则只好找较低目录中的资源了。
常见手机屏幕像素及对应分别率级别:
ldpi 320*240
mdpi 480*320
hdpi 800*480
xhdpi 1280*720
xxhdpi 1920*1080
dp和px之间的简单换算关系:
ldpi的手机 1dp=0.75px
mdpi的手机 1dp=1.0px
hdpi的手机 1dp=1.5px
xhdpi的手机 1dp=2.0px
xxhdpi的手机 1dp=3.0px
:根据上面的描述我们得出如下结论,对于mdpi的手机,我们的布局通过dp单位可以达到适配效果。
1.2 适配方式之dimens
跟drawable目录类似的,在Android工程的res目录下有values目录,这个是默认的目录,同时为了适配不同尺寸手机我们可以创建一个values-1280x720的文件夹,同时将dimens.xml文件拷贝到该目录下。
在dimens.xml中定义一个尺寸,如下图所示。
在values-1280x720目录中的dimens.xml中定义同样的尺寸名称,但是使用不同的尺寸,如下图所示。
当我们在布局文件中使用长或者宽度单位时,比如下图所示,应该使用@dimen/width来灵活的定义宽度。
:在values-1280x720中,中间的是大写字母X的小写形式x,而不是加减乘除的乘号。如果我们在values-1280x720中放置了dimens常量,一定记得也将该常量的对应值在values目录下的dimens.xml中放一份,因为该文件是默认配置,当用户的手机不是1280*720的情况下系统应用使用的是默认values目录中的dimens.xml。
1.3 适配方式之layout
跟values一样,在Android工程目录中layout目录也支持类似values目录一样的适配,在layout中我们可以针对不同手机的分辨率制定不同的布局,如下图所示。
1.4 适配方式之java代码适配
为了演示用java代码控制适配的效果,因此假设有这样的需求,让一个TextView控件的宽和高分别为屏幕的宽和高的一半。
我们新创建一个Android工程,修改main_activity.xml,布局文件清单如下:
在MainActivity.java类中完成用java代码控制TextView的布局效果,其代码清单如下:
public class MainActivity extends Activity {
private static final String tag = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//去掉title
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
//获取TextView控件
TextView tv = (TextView) findViewById(R.id.tv);
//找到当前控件的夫控件(父控件上给当前的子控件去设定一个规则)
DisplayMetrics metrics = new DisplayMetrics();
//给当前metrics去设置当前屏幕信息(宽(像素)高(像素))
getWindowManager().getDefaultDisplay().getMetrics(metrics);
//获取屏幕的高度和宽度
Constant.srceenHeight = metrics.heightPixels;
Constant.srceenWidth = metrics.widthPixels;
//日志输出屏幕的高度和宽度
Log.i(tag, "Constant.srceenHeight = "+Constant.srceenHeight);
Log.i(tag, "Constant.srceenWidth = "+Constant.srceenWidth);
//宽高各 50%
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
//数学角度上 四舍五入
(int)(Constant.srceenWidth*0.5+0.5),
(int)(Constant.srceenHeight*0.5+0.5));
//给tv控件设置布局参数
tv.setLayoutParams(layoutParams);
}
}
其中Constant类是一个常量类,很简单,只有两个常量用来记录屏幕的宽和高,其代码清单如下:
public class Constant {
public static int srceenHeight;
public static int srceenWidth;
}
1.5适配方式之weight权重适配
在控件中使用属性android:layout_weight="1"可以起到适配效果,但是该属性的使用有如下规则:
只能用在线性控件中,比如LinearLayout。
竖直方向上使用权重的控件高度必须为0dp(Google官方的推荐用法)
水平方向上使用权重的控件宽度必须为0dp(Google官方的推荐用法)
1.6适配方式之百分比适配
手机自适应主要分为两种情况:横屏和竖屏的切换,以及分辨率大小的不同。
2.1横屏和竖屏的切换
1、Android应用程序支持横竖屏幕的切换,Android中每次屏幕的切换动会重启Activity,所以应该在Activity销毁(执行onPause()方法和onDestroy()方法)前保存当前活动的状态;在Activity再次创建的时候载入配置,那样,进行中的游戏就不会自动重启了!有的程序适合从竖屏切换到横屏,或者反过来,这个时候怎么办呢?可以在配置Activity的地方进行如下的配置android:screenOrientation="portrait"(landscape是横向,portrait是纵向)。这样就可以保证是竖屏总是竖屏了。
2、而有的程序是适合横竖屏切换的。如何处理呢?首先要在配置Activity的时候进行如下的配置:
android:configChanges="keyboardHidden|orientation",另外需要重写Activity的onConfigurationChanged方法。实现方式如下:
@Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
if(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE){
//TODO
}else if(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT){
//TODO
}
}
super.onConfigurationChanged(newConfig);
if(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_LANDSCAPE){
//TODO
}else if(this.getResources().getConfiguration().orientation==Configuration.ORIENTATION_PORTRAIT){
//TODO
}
}
2.2分辨率大小不同
对于分辨率问题,官方给的解决办法是创建不同的layout文件夹,这就需要对每种分辨率的手机都要写一个布局文件,虽然看似解决了分辨率的问题,但是如果其中一处或多处有修改了,就要每个布局文件都要做出修改,这样就造成很大的麻烦。那么可以通过以下几种方式解决:
一)使用layout_weight
目前最为推荐的Android多屏幕自适应解决方案。
该属性的作用是决定控件在其父布局中的显示权重,一般用于线性布局中。其值越小,则对应的layout_width或layout_height的优先级就越高(一般到100作用就不太明显了);一般横向布局中,决定的是layout_width的优先级;纵向布局中,决定的是layout_height的优先级。
传统的layout_weight使用方法是将当前控件的layout_width和layout_height都设置成fill_parent,这样就可以把控件的显示比例完全交给layout_weight;这样使用的话,就出现了layout_weight越小,显示比例越大的情况(即权重越大,显示所占的效果越小)。不过对于2个控件还好,如果控件过多,且显示比例也不相同的时候,控制起来就比较麻烦了,毕竟反比不是那么好确定的。于是就有了现在最为流行的0px设值法。看似让人难以理解的layout_height=0px的写法,结合layout_weight,却可以使控件成正比例显示,轻松解决了当前Android开发最为头疼的碎片化问题之一。
二)清单文件配置:【不建议使用这种方式,需要对不同的界面写不同的布局】
需要在AndroidManifest.xml文件的
android:normalScreens="true" android:anyDensity="true" android:smallScreens="true" android:xlargeScreens="true"> 以上是为我们的屏幕设置多分辨率支持(更准确的说是适配大、中、小三种密度)。 Android:anyDensity="true",这一句对整个的屏幕都起着十分重要的作用,值为true,我们的应用程序当安装在不同密度的手机上时,程序会分别加载hdpi,mdpi,ldpi文件夹中的资源。相反,如果值设置为false,即使我们在hdpi,mdpi,ldpi,xdpi文件夹下拥有同一种资源,那么应用也不会自动地去相应文件夹下寻找资源。而是会在大密度和小密度手机上加载中密度mdpi文件中的资源。 有时候会根据需要在代码中动态地设置某个值,可以在代码中为这几种密度分别设置偏移量,但是这种方法最好不要使用,最好的方式是在xml文件中不同密度的手机进行分别设置。这里地图的偏移量可以在values-xpdi,values-hpdi,values-mdpi,values-ldpi四种文件夹中的dimens.xml文件进行设置。 三)、其他: 说明: 在不同分辨率的手机模拟器下,控件显示的位置会稍有不同 通过在layout中定义的布局设置的参数,使用dp(dip),会根据不同的屏幕分辨率进行适配 但是在代码中的各个参数值,都是使用的像素(px)为单位的 技巧: 1、尽量使用线性布局,相对布局,如果屏幕放不下了,可以使用ScrollView(可以上下拖动) ScrowView使用的注意: 在不同的屏幕上显示内容不同的情况,其实这个问题我们往往是用滚动视图来解决的,也就是ScrowView;需要注意的是ScrowView中使用layout_weight是无效的,既然使用ScrowView了,就把它里面的控件的大小都设成固定的吧。 2、指定宽高的时候,采用dip的单位,dp单位动态匹配 3、由于android代码中写的单位都是像素,所有需要通过工具类进行转化 4、尽量使用9-patch图,可以自动的依据图片上面显示的内容被拉伸和收缩。其中在编辑的时候,灰色区域是被拉伸的,上下两个点控制水平方向的拉伸,左右两点控制垂直方向的拉伸 dp:是dip的简写,指密度无关的像素。 指一个抽象意义上的像素,程序用它来定义界面元素。一个与密度无关的,在逻辑尺寸上,与一个位于像素密度为160dpi的屏幕上的像素是一致的。要把密度无关像素转换为屏幕像素,可以用这样一个简单的公式:pixels=dips*(density/160)。举个例子,在DPI为240的屏幕上,1个DIP等于1.5个物理像素。 布局时最好使用dp来定义我们程序的界面,因为这样可以保证我们的UI在各种分辨率的屏幕上都可以正常显示。 ①aidl是Android interface definition Language 的英文缩写,意思Android 接口定义语言。 ②使用aidl可以帮助我们发布以及调用远程服务,实现跨进程通信。 ③将服务的aidl放到对应的src目录,工程的gen目录会生成相应的接口类 我们通过bindService(Intent,ServiceConnect,int)方法绑定远程服务,在bindService中有一个ServiceConnec接口,我们需要覆写该类的onServiceConnected(ComponentName,IBinder)方法,这个方法的第二个参数IBinder对象其实就是已经在aidl中定义的接口,因此我们可以将IBinder对象强制转换为aidl中的接口类。 我们通过IBinder获取到的对象(也就是aidl文件生成的接口)其实是系统产生的代理对象,该代理对象既可以跟我们的进程通信,又可以跟远程进程通信,作为一个中间的角色实现了进程间通信。 AIDL全称Android Interface DefinitionLanguage(AndRoid接口描述语言) 是一种接口描述语言; 编译器可以通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程跨界对象访问的目的。需要完成2件事情: 1. 引入AIDL的相关类.; 2. 调用aidl产生的class.理论上, 参数可以传递基本数据类型和String, 还有就是Bundle的派生类, 不过在Eclipse中,目前的ADT不支持Bundle做为参数。 Android中主线程也叫UI线程,那么从名字上我们也知道主线程主要是用来创建、更新UI的,而其他耗时操作,比如网络访问,或者文件处理,多媒体处理等都需要在子线程中操作,之所以在子线程中操作是为了保证UI的流畅程度,手机显示的刷新频率是60Hz,也就是一秒钟刷新60次,每16.67毫秒刷新一次,为了不丢帧,那么主线程处理代码最好不要超过16毫秒。当子线程处理完数据后,为了防止UI处理逻辑的混乱,Android只允许主线程修改UI,那么这时候就需要Handler来充当子线程和主线程之间的桥梁了。 我们通常将Handler声明在Activity中,然后覆写Handler中的handleMessage方法,当子线程调用handler.sendMessage()方法后handleMessage方法就会在主线程中执行。 这里面除了Handler、Message外还有隐藏的Looper和MessageQueue对象。 在主线程中Android默认已经调用了Looper.preper()方法,调用该方法的目的是在Looper中创建MessageQueue成员变量并把Looper对象绑定到当前线程中。当调用Handler的sendMessage(对象)方法的时候就将Message对象添加到了Looper创建的MessageQueue队列中,同时给Message指定了target对象,其实这个target对象就是Handler对象。主线程默认执行了Looper.looper()方法,该方法从Looper的成员变量MessageQueue中取出Message,然后调用Message的target对象的handleMessage()方法。这样就完成了整个消息机制。 2.1 事件分发中的onTouch和onTouchEvent有什么区别,又该如何使用? 这两个方法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。 另外需要注意的是,onTouch能够得到执行需要两个前提条件,第一mOnTouchListener的值不能为空,第二当前点击的控件必须是enable的。因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。 2.2 请描述一下Android的事件分发机制 Android的事件分发机制主要是Touch事件分发,有两个主角:ViewGroup和View。Activity的Touch事件事实上是调用它内部的ViewGroup的Touch事件,可以直接当成ViewGroup处理。 View在ViewGroup内,ViewGroup也可以在其他ViewGroup内,这时候把内部的ViewGroup当成View来分析。 先分析ViewGroup的处理流程:首先得有个结构模型概念:ViewGroup和View组成了一棵树形结构,最顶层为Activity的ViewGroup,下面有若干的ViewGroup节点,每个节点之下又有若干的ViewGroup节点或者View节点,依次类推。如图: 当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。 1.Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中ViewGroup又继承于View。 2.ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。 3.触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。 4.当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。 5.当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。 6.当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。 7.onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。 1、用Activity对象的runOnUiThread方法更新 在子线程中通过runOnUiThread()方法更新UI: 如果在非上下文类中(Activity),可以通过传递上下文实现调用; 2、用View.post(Runnable r)方法更新UI 不能,如果在子线程中直接new Handler()会抛出异常java.lang.RuntimeException:Can't create handler inside thread that has not called 在没有调用Looper.prepare()的时候不能创建Handler,因为在创建Handler的源码中做了如下操作 Handler的构造方法中 public static Looper myLooper() { return sThreadLocal.get(); } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't createhandler inside thread that has not called Looper.prepare()"); } Android中动画分为两种,一种是Tween动画、还有一种是Frame动画。 Tween动画,这种实现方式可以使视图组件移动、放大、缩小以及产生透明度的变化; Frame动画,传统的动画方法,通过顺序的播放排列好的图片来实现,类似电影。 可以通过两种方式,一是通过定义Activity的主题,二是通过覆写Activity的overridePendingTransition方法。 通过设置主题样式 在styles.xml中编辑如下代码: 添加themes.xml文件: 在AndroidManifest.xml中给指定的Activity指定theme。 覆写overridePendingTransition方法 overridePendingTransition(R.anim.fade,R.anim.hold); 补间动画只是显示的位置变动,View的实际位置未改变,表现为View移动到其他地方,点击事件仍在原处才能响应。而属性动画控件移动后事件相应就在控件移动后本身进行处理 一、ContentObserver目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理。 它类似于数据库技术中的触发器(Trigger),当ContentObserver所观察的Uri发生变化时,便会触发它。触发器分为表触发器、行触发器,相应地ContentObserver也分为“表“ContentObserver、“行”ContentObserver,当然这是与它所监听的Uri MIME Type有关的。 1)注册ContentObserver方法 public final void registerContentObserver(Uri uri, booleannotifyForDescendents, ContentObserver observer) 功能:为指定的Uri注册一个ContentObserver派生类实例,当给定的Uri发生改变时,回调该实例对象去处理。 参数: uri 表示需要观察的Uri notifyForDescendents 为false 表示精确匹配,即只匹配该Uri。 为true 表示可以同时匹配其派生的Uri。 取消注册ContentObserver方法 public final void unregisterContentObserver(ContentObserverobserver) 功能:取消对给定Uri的观察 参数: observer ContentObserver的派生类实例 ContentObserver类介绍 构造方法 ContentObserver(Handler h) void onChange(boolean selfChange) 功能:当观察到的Uri发生变化时,回调该方法去处理。所有ContentObserver的派生类都需要重载该方法去处理逻辑。 3.观察特定Uri的步骤如下: 1、创建我们特定的ContentObserver派生类,必须重载父类构造方法,必须重载onChange()方法去处理回调后的功能实现 2、利用context.getContentResolover()获ContentResolover对象,接着调用registerContentObserver()方法去注册内容观察者 3、在不需要时,需要手动的调用 unregisterContentObserver()去取消注册。 一、网络篇 1.什么是socket?,TCP和 UDP每个数据包的大小? Socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信连的句柄,应用程序通常通过“套接字”向网络发送请求或者应答网络请求,它就是网络通信过程中端点的抽象表示。它主要包括TCP,UDP两个协议。 UDP 包的大小是 1492 - IP头(20) - UDP头(8) = 1464字节 TCP 包的大小是 1492 - IP头(20) - TCP头(20) = 1452字节 2.用 volley 如何下载数据较大的文件Volley 的二次封装是怎样做的 (1) Volley本身不支持大数据文件的下载,如果必须使用的volley进行下载,可进行断点下载;将每次的下载文件尽可能的切割成更小的文件进行下载。 (2) 二次封装 ① 直接封装一个数据接口,方便我们在Activity中直接得到网络请求的结果数据。 ② 封装一个JsonObject请求结果的泛型封装类,方便对不同的实体类的解析。 ③ 封装Volley请求方法,包括GET,POST等主流请求方式,及json文件的解析方法。最后,在Activity中的调用。 3.大文件上传到服务器是怎么做到的 利用文件分割将大文件分割为小文件可以解决问题;android端和服务端存在RandomAccessFile随机访问文件类,方便对文件进行分割。 4.如何实现文件断点上传 在Android中上传文件可以采用HTTP方式,也可以采用Socket方式,但是HTTP方式不能上传大文件,这里介绍一种通过Socket方式来进行断点续传的方式,服务端会记录下文件的上传进度,当某一次上传过程意外终止后,下一次可以继续上传,这里用到的其实还是J2SE里的知识。 这个上传程序的原理是:客户端第一次上传时向服务端发送“Content-Length=35;filename=WinRAR_3.90_SC.exe;sourceid=“这种格式的字符串,服务端收到后会查找该文件是否有上传记录,如果有就返回已经上传的位置,否则返回新生成的sourceid以及position为0,类似sourceid=2324838389;position=0“这样的字符串,客户端收到返回后的字符串后再从指定的位置开始上传文件。 5.XUtils 和 Volley 的区别在哪里? Volley: Volley是Andrdoid平台的网络通讯库,能使网络通讯更快,更简单,更健壮;默认Android2.3及以上基于HttpURLConnection,2.3以下使用基于HttpClient;适用于数据量小,网络通信频繁的操作。 Xutils:源于Afinal框架,对Afinal进行了大量重构,使得xUtils支持大文件上传,更全面的http请求协议支持,拥有更加灵活的ORM,更多的事件注解支持且不受混淆影响,同时xutils集成了较多的工具类。 6.Tcp/Ip 及 UDP 的差别在哪里? 平常中我们是如何来进行使用的? TCP协议和UDP协议特性区别总结: (1)TCP是面向连接(Connection oriented)的协议,UDP是无连接(Connection less)协议; (2)TCP可靠,UDP不可靠; (3)TCP有序,UDP无序; (4)TCP无界,UDP有界; (5)TCP有流量控制(拥塞控制),UDP没有; (6)TCP传输慢,UDP传输快; (7)TCP是重量级的,UDP是轻量级的 (8)TCP的头部比UDP大; 使用: TCP一般用于文件传输(FTP HTTP 对数据准确性要求高,速度可以相对慢),发送或接收邮件(POP IMAP SMTP 对数据准确性要求高,非紧急应用),远程登录(TELNETSSH 对数据准确性有一定要求,有连接的概念)等等,TCP可以用于网络数据库,分布式高精度计算系统的数据传输。 UDP一般用于即时通信(QQ聊天对数据准确性和丢包要求比较低,但速度必须快),在线视频(RTSP速度一定要快,保证视频连续,但是偶尔花了一个图像帧,人们还是能接受的),网络语音电话(VoIP 语音数据包一般比较小,需要高速发送,偶尔断音或串音也没有问题)等等;UDP还用于服务系统内部之间的数据传输,因为数据可能比较多,内部系统局域网内的丢包错包率又很低,即便丢包,顶多是操作无效,这种情况下,UDP经常被使用。 7.你了解 Android 网络的重试机制,以及Android 的重试机制,你怎么看? 因为经常需要跟网络编程打交道,并不是你的每次请求,服务端都会给你想要的结果。重试机制虽然并不能解决这种情况,但是却可以大大减少这种情况的发生。 8.给我说说Http? 网络由下往上分为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。我知道IP协议对应于网络层,TCP协议对应于传输层,而HTTP协议对应于应用层,那么http的是负责主要解决如何包装数据,我们用的是http1.1版本而不是1.0版本,因为1.0他是非持续链接,android App 一般是获取数据和展示数据,数据交互一般使用PUt 和Post请求,然后说一下二者区别。 9.sqlite数据库去重复怎么做? 在列名前加distinct即可去重复,示例如下: Selectdistinct * from XXX; 二、框架搭建 11.简述一下MVC和MVP这两种设计模式,说说各自的特点 在MVC里,View是可以直接访问Model的!从而,View里会包含Model信息,不可避免的还要包括一些业务逻辑。 在MVC模型里,更关注的Model的不变,而同时有多个对Model的不同显示,及View。所以,在MVC模型里,Model不依赖于View,但是 View是依赖于Model的。不仅如此,因为有一些业务逻辑在View里实现了,导致要更改View也是比较困难的,至少那些业务逻辑是无法重用的。 MVP的优点: 1、模型与视图完全分离,我们可以修改视图而不影响模型; 2、可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部; 3、我们可以将一个Presenter用于多个视图,而不需要改变Presenter的逻辑。这个特性非常的有用,因为视图的变化总是比模型的变化频繁; 4、如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试) MVP主要解决就是把逻辑层抽出来成P层,要是遇到需求逻辑上的更改就可以只需要修改P层了或者遇到逻辑上的大概我们可以直接从写一个P也可以,很 多开发人员把所有的东西都写在了Activity/Fragment里面这样一来遇到频繁改需求或者逻辑越来越复杂的时候,Activity /Fragment里面就会出现过多的混杂逻辑导致出错,所以MVP模式对于APP来对控制逻辑和UI的解耦来说是一个不错的选择! 12.说说mvc模式的原理,它在android中的运用 MVC英文即Model-View-Controller,即把一个应用的输入、处理、输出流程按照Model、View、Controller的方式进行分离,这样一个应用被分成三个层——模型层、视图层、控制层。 Android中界面部分也采用了当前比较流行的MVC框架,在Android中M就是应用程序中二进制的数据,V就是用户的界面。Android的界面直接采用XML文件保存的,界面开发变的很方便。在Android中C也是很简单的,一个Activity可以有多个界面,只需要将视图的ID传递到setContentView(),就指定了以哪个视图模型显示数据。 在AndroidSDK中的数据绑定,也都是采用了与MVC框架类似的方法来显示数据。在控制层上将数据按照视图模型的要求(也就是Android SDK中的Adapter)封装就可以直接在视图模型上显示了,从而实现了数据绑定。比如显示Cursor中所有数据的ListActivity,其视图层就是一个ListView,将数据封装为ListAdapter,并传递给ListView,数据就在ListView中显示。 另一种方式解答: a.模型(model)对象:是应用程序的主体部分,所有的业务逻辑都应该写在该层。 b. 视图(view)对象:是应用程序中负责生成用户界面的部分。也是在整个mvc架构中用户唯一可以看到的一层,接收用户的输入,显示处理结果。 c.控制器(control)对象:是根据用户的输入,控制用户界面数据显示及更新model对象状态的部分 View:自定义View或ViewGroup,负责将用户的请求通知Controller,并根据model更新界面; Controller:Activity或者Fragment,接收用户请求并更新model; Model:数据模型,负责数据处理相关的逻辑,封装应用程序状态,响应状态查询,通知View改变,对应Android中的datebase、SharePreference等。 14. 单例设计模式的缺点是什么 主要优点: 1、 提供了对唯一实例的受控访问。 2、 由于在系统内存中只存在一个对象, 因此可以节约系统资源, 对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。 3、 允许可变数目的实例。 主要缺点: 1、 由于单利模式中没有抽象层, 因此单例类的扩展有很大的困难。 2、 单例类的职责过重, 在一定程度上违背了“ 单一职责原则” 。 3、 滥用单例将带来一些负面问题, 如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出; 如果实例化的对象长时间不被利用, 系统会认为是垃圾而被回收, 这将导致对象状态的丢失。 15.说说观察者模式,里面主要有什么方法,各自的用途 notifyChange():观察数据库内容是否发生改变,如果改变,通知观察者 registerContentObserver():注册观察者 三、图片 16.图片的三级缓存 1、图片三级缓存就是内存-文件-网络 三层cache机制,其中内存缓存包括强引用缓存和软引用缓存(SoftReference)。通过url向网络获取图片的时候,先从内存中找,如果内存中没有,再从缓存文件中查找,如果缓存文件中也没有,再从网络上通过http请求获取图片。在键值对(key-value)中,图片缓存的key是图片url的hash值,value就是bitmap。所以,只要一个url被下载过,其图片就被缓存起来了。 2、从代码上来说,采用一个ImageManager来负责图片的管理和缓存,函数接口为public void loadBitmap(String url, Handler handler) ;其中url为要下载的图片地址,handler为图片下载成功后的回调,在handler中处理message,而message中包含了图片的信息以及bitmap对象。 17.LruCache 的怎么回收bitmap LruCache 每次添加Bitmap图片缓存的时候(put操作),都会调用sizeOf方法,返回Bitmap的内存大小给LruCache,然后循环增加这个size。 当这个Size内存大小超过初始化设定的cacheMemory大小时,则遍历map集合,把最近最少使用的元素remove掉 18.图片优化底层怎么实现 图片优化其实就是内存进行优化,比如bitmap中有方法recycle()进行回收。 从BitmapFactory的源代码可以看到,生成Bitmap对象最终都是通过JNI调用方式实现的。所以,加载Bitmap到内存里以后,是包含两部分内存区域的。简单的说,一部分是Java部分的,一部分是C部分的。这个Bitmap对象是由Java部分分配的,不用的时候系统就会自动回收了,但是那个对应的C可用的内存区域,虚拟机是不能直接回收的,这个只能调用底层的功能释放。所以需要调用recycle()方法来释放C部分的内存。从Bitmap类的源代码也可以看到,recycle()方法里也的确是调用了JNI方法了的。 19.图片压缩的详细过程 使用BitmapFactory.Options设置inSampleSize就可以缩小图片。属性值inSampleSize表示缩略图大小为原始图片大小的几分之一。即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片的大小就为原始大小的1/4。 如果知道图片的像素过大,就可以对其进行缩小。那么如何才知道图片过大呢? 使用BitmapFactory.Options设置inJustDecodeBounds为true后,再使用decodeFile()等方法,并不会真正的分配空间,即解码出来的Bitmap为null,但是可计算出原始图片的宽度和高度,即options.outWidth和options.outHeight。通过这两个值,就可以知道图片是否过大了。 20.当服务器上给我们传回来的图片过小的时候,我们应该怎么处理? 可以通过android中的bitmap对象获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。首先通过Bitmap获取图片的大小,根据你需要计算出缩放比例即可。 21.Volley框架中,关于图片的处理是如何处理的? Volley框架图片处理是用Volley里面的的ImageLoader来实现加载图片的功能,从源码角度来分析加载流程; 使用ImageLoader来加载图片步骤: (1)创建一个RequestQueue对象; (2)创建一个ImageLoader对象; 获取一个ImageListener对象(通过ImageLoader的getImageListener方法来获取); (4)调用ImageLoader的get方法来加载图片; 22.图片的URL传递,我们用的是javabean还是对象传递,还是map集合传递 图片的URL传递,我们一般用对象进行传递,比如用bitmap对象进行传递。用intent.putExtra("bitmap",bitmap);传递过去。因为intent只能传递实现序列化的类,即继承java 的Serializable接口或者android的Parcelable接口。故javabean没有实现该接口所以不能传递。 如果是从网上拉取的图片,可以定义一个下载的工具类,并定义一个static的静态Map集合,每拉取成功一张图片就把该图片存入Map中作为缓存。key是该图片的拉取地址。然后通过intent把地址传递过去。在新activity中通过该工具类取得静态map,并通过传递过来的地址中map中取出该图片 23.加载大图片的时候,如何防止OOM异常 在加载图片时候先检查一下图片的大小:用BitmapFactory.Options参数,参数中的inJustDecodeBounds属性设置为true就可以让解析图片方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。虽然Bitmap是null了,但是BitmapFactory.Options的outWidth、outHeight和outMimeType属性都会被赋值。从而可以得到图片的宽、高、大小。得到图片的大小后,我们就可以决定是否把整张图片加载到内存中还是加载一个压缩版的图片到内存中,从而就可以解决OOM异常。 24.谈谈你对Bitmap的理解, 什么时候应该手动调用bitmap.recycle() Bitmap是android中经常使用的一个类,它代表了一个图片资源。 Bitmap消耗内存很严重,如果不注意优化代码,经常会出现OOM问题,优化方式通常有这么几种: 1. 使用缓存; 2. 压缩图片;3. 及时回收; 至于什么时候需要手动调用recycle,这就看具体场景了,原则是当我们不再使用Bitmap时,需要回收之。另外,我们需要注意,2.3之前Bitmap对象与像素数据是分开存放的,Bitmap对象存在java Heap中而像素数据存放在Native Memory中,这时很有必要调用recycle回收内存。但是2.3之后,Bitmap对象和像素数据都是存在Heap中,GC可以回收其内存 四、控件篇 25.请问Gridview能添加头布局吗 GridView本身没有添加头布局的方法api 可以使用ScrollView与GridView结合,让GridView充满ScrollView,不让GridView滑动而只让ScrollView滑动;具体做法是重载GridView的onMeasure()方法。示例如下: publicclass MyGridView extends GridView { public MyGridView(Context context,AttributeSet attrs) { super(context, attrs); } public MyGridView(Context context) { super(context); } public MyGridView(Context context,AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void onMeasure(int widthMeasureSpec,int heightMeasureSpec) { int expandSpec =MeasureSpec.makeMeasureSpec( Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST); super.onMeasure(widthMeasureSpec,expandSpec); } } 26.新闻详细页WebView是怎么使用的? WebView是View的子类,可以让你在activity中显示网页。 (1)添加网络访问权限。 (2)调用webview的loadUrl加载网络地址。 27.如何清除webview的缓存 webview的缓存包括网页数据缓存(存储打开过的页面及资源)、H5缓存(即AppCache),webview 会将我们浏览过的网页url已经网页文件(css、图片、js等)保存到数据库表中,如下; /data/data/package_name/database/webview.db /data/data/package_name/database/webviewCache.db 所以,我们只需要根据数据库里的信息进行缓存的处理即可。 28.webview 播放视频, 5.0 以上没有全屏播放按钮 实现全屏的时候把webview里的视频放到一个View里面,然后把webview隐藏掉;即可实现全屏播放。 29.webview 调用js (1)android中利用webview调用网页上的js代码。 首先将webview控件的支持js的属性设置为true,,然后通过loadUrl就可以直接进行调用,如下所示: mWebView.getSettings().setJavaScriptEnabled(true); mWebView.loadUrl("javascript:test()"); (2)网页上调用android中java代码的方法 在网页中调用java代码,需要在webview控件中添加javascriptInterface。如下所示: mWebView.addJavascriptInterface(new Object() { publicvoid clickOnAndroid() { mHandler.post(newRunnable() { public voidrun() { Toast.makeText(Test.this,“测试调用java”, Toast.LENGTH_LONG).show(); } }); } },"demo"); 在网页中,只需要像调用js方法一样,进行调用就可以3、dp和px之间的关系
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
public static int px2dip(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
/**
* 根据手机的分辨率从 dip 的单位 转成为 px(像素)
*/
public static int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
三、AIDL
1、什么是AIDL以及如何使用
2、AIDL的全称是什么?如何工作?能处理哪些类型的数据?
四、Android中的事件处理
1、Handler机制
2、事件分发机制
3、子线程发消息到主线程进行更新UI,除了handler和AsyncTask,还有什么?
4、子线程中能不能newhandler?为什么?
五、Android中的动画
1、Android中的动画有哪几类,它们的特点和区别是什么
2、如何修改Activity进入和退出动画
3、属性动画,例如一个button从A移动到B点,B点还是可以响应点击事件,这个原理是什么?
六、ContentObserver 内容观察者作用及特点
例子:监听短信内容变化
在Activity中:
public class Day0108_contentobserverActivity extends Activity {
private Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case 100:
String body = (String) msg.obj;
TextView tv = (TextView) findViewById(R.id.tv);
tv.setText(body);
break;
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ContentResolver cr = getContentResolver();
ContentObserver smsObserver = new SmsContentObserver(this,handler);
//第二个参数,true表示观察所有有关短信的
cr.registerContentObserver(Uri.parse("content://sms"), true, smsObserver);
//content://sms/inbox //收件箱
//content://sms/sent //已发送
//content://sms/draft //草稿箱
//content://sms/outbox //发件箱
//content://sms/failed //失败短信
//content://sms/queued //代发队列
}
}
//SmsContentObserver代码如下:
public class SmsContentObserver extends ContentObserver {
private Handler handler;
private Context context;
public SmsContentObserver(Context context,Handler handler) {
super(handler);
this.handler = handler;
this.context = context;
}
@Override
public void onChange(boolean selfChange) {
ContentResolver cr = context.getContentResolver();
Cursor c = cr.query(Uri.parse("content://sms/inbox"), null, "0", null, "date desc");
StringBuilder sb = new StringBuilder();
while(c.moveToNext()){
//发件人手机号码
String sendNumber = c.getString(
c.getColumnIndex("address"));
//信息内容
String body = c.getString(c.getColumnIndex("body"));
//readType 表示是否已经读
int hasRead = c.getInt(c.getColumnIndex("read"));
if(hasRead == 0){//表示短信未读
System.out.println("短信未读"+sendNumber);
}
sb.append(sendNumber+":"+body+"\n");
}
handler.obtainMessage(100,sb.toString()).sendToTarget();
}
}
九、 常见面试题
(3)Java代码调用js并传参
首先需要带参数的js函数,如function test(str),然后只需在调用js时传入参数即可,如下所示:
mWebView.loadUrl("javascript:test('aa')");
(4)Js中调用java函数并传参
首先一样需要带参数的函数形式,但需注意此处的参数需要final类型,即得到以后不可修改,如果需要修改其中的值,可以先设置中间变量,然后进行修改。如下所示:
mWebView.addJavascriptInterface(new Object() {
publicvoid clickOnAndroid(final int i) {
mHandler.post(newRunnable() {
publicvoid run() {
intj = i;
j++;
Toast.makeText(Test.this,"测试调用java" + String.valueOf(j),
Toast.LENGTH_LONG).show();
}
});
}
},"demo");
然后在html页面中,利用如下代码
,即可实现调用
30.Webview 中是如何控制显示加载完成的进度条的
在WebView的setWebChromClient()中,重写WebChromClient的openDialog()和closeDialog()方法;实现监听进度条的显示与关闭。
31.你使用过 HTM5 开发过 Android 应用程序吗? 谈谈你对移动应用开发领域 webApp 开发与 NativeApp 开发之争的看法?
目前移动应用开发有三种方案,分别是Native原生App,Hybrid混合App,以及HTML5 Web App。
Native(原生):
丰富的用户体验,平台的指向性,久经考验的移动应用开发途;对于游戏应用这类对性能、图形处理要求较高,但不太在乎文件尺寸的软件来说,原生应用才是最理想的选择——不过大家其实也可以利用PhoneGap实现游戏开发。
HTML 5:
更快的开发周期,跨平台运行,实时更新。对于任何追求极致轻量化的网站(或者Web应用)都应该通过HTML 5进行创建,并使用Bootstrap或者Foundation等技术作为响应层。响应式Web设计为设备提供一套极度精简化的访问门户,技术人员还能够根据需求每天对其加以调整。
32.View, surfaceView, GLsurfaceView 他们的区别?
View:显示视图,内置画布,提供图形绘制函数、触屏事件、按键事件函数等;必须在UI主线程内更新画面,速度较慢。必须在UI的主线程中更新画面,用于被动更新画面。
SurfaceView:基于view视图进行拓展的视图类,更适合2D游戏的开发;是view的子类,类似使用双缓机制,在新的线程中更新画面所以刷新界面速度比view快。UI线程和子线程中都可以。在一个新启动的线程中重新绘制画面,主动更新画面。
GLSurfaceView:基于SurfaceView视图再次进行拓展的视图类,专用于3D游戏开发的视图;是SurfaceView的子类,openGL专用。
UI的主线程中更新画面 可能会引发问题,比如你更新画面的时间过长,那么你的主UI线程会被你正在画的函数阻塞。那么将无法响应按键,触屏等消息。
当使用surfaceView 由于是在新的线程中更新画面所以不会阻塞你的UI主线程。但这也带来了另外一个问题,就是事件同步,涉及到线程同步。
33.说说 RecycleView和 ListView 的区别
RecyclerView是ListView的增强版。整体上看RecyclerView架构,更加灵活,通过设置它提供的不同LayoutManager,ItemDecoration , ItemAnimator优质的效果。想要控制其显示的方式,可以通过布局管理器LayoutManager;想要控制Item间的间隔(可绘制),可以通过ItemDecoration;想要控制Item增删的动画,可以通过ItemAnimator;如果想要要控制点击、长按事件,需要自己写。
RecyclerView.LayoutManager是一个抽象类,系统为其提供了3个实现类:
LinearLayoutManager现行管理器,支持横向、纵向。
GridLayoutManager网格布局管理器
StaggeredGridLayoutManager瀑布就式布局管理器
五、系统篇
35.Android系统编程与Java编程有何异同之处,谈谈你的看法
Java编程 Android编程
1.执行入口点 main manifest.xml配置文件
2.UI frame Activity
3.是否给予配置文件 否 是
4.是否基于组件 否 是 activity,service, broadcastReceiver,contentprovider
5.布局文件 new findViewById
总的来说,区别在于android程序是基于组件给予配置的。
1:Android使用的开发包是J2EE包的一个子集。
2:在使用Android开发使用线程技术比较多,但使用J2EE开发常不用手动去创建线程
3:在Android经常使用到线程,而在线程中的处理是不能直接调用UI界面,所以就涉及到线程与UI的比较多,所以对Android的Handler用得比较多。
4:在使用Android开发时,有一些底层的网络通信就需要使用Socket通信,在J2EE开发中,几乎不会用到Socket技术
36.谈一谈java线程模型
线程是一个程序内部的顺序控制流,
线程生命周期:新建,就绪,运行,阻塞,终止
创建线程的两种方式:实现Runnable接口,继承Thread类
比较:
实现Runnable接口
可以将CPU,代码和数据分开,形成清晰的模型;
线程体run()方法所在的类还可以从其他类继承一些有用的属性和方法
有利于保持程序风格的一致性
继承Thread类
Thread子类无法再从其他类继承
编写简单,run()方法的当前对象就是线程对象,可直接操纵
线程优先级:
默认为5,最小为1,最大为10,不提供优先级低的在优先级高的执行结束后执行
线程串行化
join()
线程休眠
sleep()
临界资源问题
并发线程有机的交替,确保共享的数据在关键的时段被专用
多个线程间共享的数据称为临界资源
在Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任何一个时刻,只能有一个线程访问该对象
关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能有一个线程访问
并发运行的多个线程彼此间等待,都无法运行的状态称为线程死锁
线程同步通信
为避免死锁,在线程进入阻塞状态时应尽量释放其锁定的资源,以为其他的线程提供运行的机会
wait()
notify()/notifyAll()
线程间数据传输
使用管道流
类的同步性与线程安全
37.谈谈Android的GC
Java语言建立了垃圾收集机制,用以跟踪正在使用的对象和发现并回收不再使用(引用)的对象。该机制可以有效防范动态内存分配中可能发生的两个危险:因内存垃圾过多而引发的内存耗尽,以及不恰当的内存释放所造成的内存非法引用。
垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配。垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能,因此需要开发人员做比较深入的了解。
38.java多线程同步锁
1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。
39.线程和进程间的通讯都有哪些方式
1、一个 Android 程序开始运行时,会单独启动一个 Process 。
默认情况下,所有这个程序中的 Activity 或者 Service 都会跑在这个 Process 。
默认情况下,一个 Android 程序也只有一个 Process ,但一个 Process 下却可以有许多个 Thread。
2、一个 Android 程序开始运行时,就有一个主线程 Main Thread 被创建。该线程主要负责 UI 界面的显示、更新和控件交互,所以又叫 UI Thread 。
一个 Android 程序创建之初,一个 Process 呈现的是单线程模型 —即 Main Thread ,所有的任务都在一个线程中运行。所以, MainThread 所调用的每一个函数,其耗时应该越短越好。而对于比较费时的工作,应该设法交给子线程去做,以避免阻塞主线程(主线程被阻塞,会导致程序假死现象)。
3、 Android 单线程模型: Android UI 操作并不是线程安全的并且这些操作必须在 UI 线程中执行。如果在子线程中直接修改 UI ,会导致异常。
那么我们线程之间怎么通讯昵?
Handler,AsyncTask,广播,文件,sp
线程之间:意图,内容提供者,File,网络,广播,aidl
40.Android系统中GC什么情况下会出现内存泄露呢? 视频编解码/内存泄露
导致内存泄漏主要的原因是,先前申请了内存空间而忘记了释放。如果程序中存在对无用对象的引用,那么这些对象就会驻留内存,消耗内存,因为无法让垃圾回收器GC验证这些对象是否不再需要。如果存在对象的引用,这个对象就被定义为"有效的活动",同时不会被释放。要确定对象所占内存将被回收,我们就要务必确认该对象不再会被使用。典型的做法就是把对象数据成员设为null或者从集合中移除该对象。但当局部变量不需要时,不需明显的设为null,因为一个方法执行完毕时,这些引用会自动被清理。
Java带垃圾回收的机制,为什么还会内存泄露呢?
Vector v = new Vector(10);
for (int i = 1; i < 100;i++){
Object o = new Object();
v.add(o);
o = null;
}//此时,所有的Object对象都没有被释放,因为变量v引用这些对象。
Java 内存泄露的根本原因就是 保存了不可能再被访问的变量类型的引用
检测内存工具 heap
41.说说LruCache底层原理
LruCache使用一个LinkedHashMap简单的实现内存的缓存,没有软引用,都是强引用。如果添加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。
maxSize是通过构造方法初始化的值,他表示这个缓存能缓存的最大值是多少。
size在添加和移除缓存都被更新值,他通过safeSizeOf这个方法更新值。safeSizeOf默认返回1,但一般我们会根据maxSize重写这个方法,比如认为maxSize代表是KB的话,那么就以KB为单位返回该项所占的内存大小。
除异常外首先会判断size是否超过maxSize,如果超过了就取出最先插入的缓存,如果不为空就删掉,并把size减去该项所占的大小。这个操作将一直循环下去,直到size比maxSize小或者缓存为空。
42.jni的调用过程?
1. 安装和下载Cygwin,下载 Android NDK。
2. ndk项目中JNI接口的设计。
3. 使用C/C++实现本地方法。
4. JNI生成动态链接库.so文件。
5. 将动态链接库复制到java工程,在java工程中调用,运行java工程即可。
43.怎样保证App不被杀死?
强烈建议不要这么做,不仅仅从用户角度考虑,作为Android开发者也有责任去维护Android的生态环境。当然从可行性讲,谷歌也不会让容易的实现。同时这样的app一般属于流氓应用
通常为了保证自己app避免被杀死,我们一般使用以下方法:
1.Service设置成START_STICKY,kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样
2.通过startForeground将进程设置为前台进程,做前台服务,优先级和前台应用一个级别,除非在系统内存非常缺,否则此进程不会被 kill
3..双进程Service:让2个进程互相保护,其中一个Service被清理后,另外没被清理的进程可以立即重启进程
4.QQ黑科技:在应用退到后台后,另起一个只有 1 像素的页面停留在桌面上,让自己保持前台状态,保护自己不被后台清理工具杀死
5.在已经root的设备下,修改相应的权限文件,将App伪装成系统级的应用(Android4.0系列的一个漏洞,已经确认可行)
6.Android系统中当前进程(Process)fork出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响。鉴于目前提到的在Android-Service层做双守护都会失败,我们可以fork出c进程,多进程守护。死循环在那检查是否还存在,具体的思路如下(Android5.0以下可行)
1.用C编写守护进程(即子进程),守护进程做的事情就是循环检查目标进程是否存在,不存在则启动它。
2.在NDK环境中将1中编写的C代码编译打包成可执行文件(BUILD_EXECUTABLE)。
3.主进程启动时将守护进程放入私有目录下,赋予可执行权限,启动它即可。
7联系厂商,加入白名单
六、优化篇
44.性能优化有哪些
性能优化,是App开发的常态,也是一个好的App的基本需要,好的体验的一种方式,也是我们走向高级程序员的基本能力,我把性能优化归类为5类:
1.Reckon(计算)
app所消耗内存的情况
2.Reduce(减少)
消耗更少的资源
3.Reuse(重用)
当第一次使用完以后,尽量给其他的使用
Reuse重用,减少内存消耗的重要手段之一。
核心思路就是将已经存在的内存资源重新使用而避免去创建新的,最典型的使用就是缓存(Cache)和池(Pool)。
5.Recycle(回收)
返回资源给生产流
4.Review(检查)
回顾检查你的程序,看看设计或代码有什么不合理的地方。
具体的可以看文档《Android内存优化之5R法则》
45.我们的app发热很厉害,这块的话 你有什么解决方案?
不是安卓机的通病,其他系统的手机,如果电流没有优化好,器件布局不合理,一样会存在发热严重的问题。
对于那些在待机状态下也出现发热严重的,建议试试把移动数据关掉;手机上如果装的app多,发出接入移动网络的请求也多,会出现难以进入睡眠模式的情况,这个时候尽管屏幕是灭屏状态,但其实待机电流可能高达好几百毫安;手机不仅发热大,而且还特别耗电
在的安卓手机,硬件配置性能过剩,动辄8核,运行内存也不断变大,多任务运行也更加流畅,加之应用越做越大,cpu的运算压力太大,很容易发热。很多软件常驻后台,不断自启,使得手机发热严重!系统优化的不够也是原因之一!
46.android优化工具有哪些
代码优化:
lint for androidstudio工具
代码 在性能测试之前,首先要对工程源码进行排错和调优。AndroidLint 可以通过扫描和检查对Android工程可能存在的问题进行审查,其是一种静态测试工具,通过发现代码中可能存在的问题来在应用发布前保证程序质量优化。早期的lint工具可以与Eclipse集成,如今android-studio已经内置这一工具。通过在工程标题上右键->Analyze->InspectCode可以打开该工具。
Android Lint可以检查出的错误包括:
应用性能检测工具
每当处理或者排查性能问题的时候,我都遵循这些原则:
· 持续测量: 用你的眼睛做优化从来就不是一个好主意。同一个动画看了几遍之后,你会开始想像它运行地越来越快。数字从来都不说谎。使用我们即将讨论的工具,在你做改动的前后多次测量应用程序的性能。
· 使用慢速设备:如果你真的想让所有的薄弱环节都暴露出来,慢速设备会给你更多帮助。有的性能问题也许不会出现在更新更强大的设备上,但不是所有的用户都会使用最新和最好的设备。
· 权衡利弊 :性能优化完全是权衡的问题。你优化了一个东西 —— 往往是以损害另一个东西为代价的。很多情况下,损害的另一个东西可能是查找和修复问题的时间,也可能是位图的质量,或者是应该在一个特定数据结构中存储的大量数据。你要随时做好取舍的准备。
Android Studio 最近改进了很多,有越来越多的工具可以帮助我们找出和分析性能问题。Android窗口中的内存页告诉我们,随着时间的推移有多少数据在栈上分配。它看上去像这样:
我们在图中看到一个小的下降,这里发生了一次 GC 事件,移除了堆上不需要的对象和释放了空间。
图中的左边有两个工具可用:堆转储和分配跟踪器
47.说说ANR,怎样避免
ANR(ApplicationNot Responding) Android系统中应用无响应
是Android系统中比较常见的问题,当出现ANR时一般情况会弹出一个带有以下文字的对话框提示:
ActivityXXX(in XXXXX) is not responding. 比如:
从大体上分,两种情况导致ANR:
第一类:dispatchTimeout 输入事件分发超时,一般是由于主线程在5秒之内没有响应输入事件。
第二类:BroadcastReceiver没有在系统设定的时间(一般是10s)内完成并返回。
从细节上归纳(ANR可能是由主线程导致也可能是由非主线程导致):
由于主线程导致的情况:
1.耗时网络访问
2.当有大量数据读写操作时再请求数据读写
3.数据库操作(比如其他大数据量应用访问数据库导致数据库负载过重时)
4.硬件操作(比如Camera)
5.调用thread_join()/ Sleep() / Wait() 或者等待locker的时候
6.Servicebinder数量达到上限
7.在system_server中发生WatchDog ANR
8.Service忙导致超时无响应
由于非主线程导致的情况:
1.非主线程持有lock,导致主线程等待lock超时
2.非主线程终止或者崩溃导致主线程一直等待
既然我们知道了导致ANR是怎样引起的,那么我们该怎样避免ANR昵?
1、运行在主线程里的任何方法都尽可能少做事情。特别是,Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。(可以采用重新开启子线程的方式,然后使用Handler+Message的方式做一些操作,比如更新主线程中的ui等)
2、应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。
3、避免在IntentReceiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。
检测工具
总结:ANR异常也是在程序中自己经常遇到的问题,主要的解决办法自己最常用的就是不要在主线程中做耗时的操作,而应放在子线程中来实现,比如采用Handler+mesage的方式,或者是有时候需要做一些和网络相互交互的耗时操作就采用Asyntask异步任务的方式(它的底层其实Handler+mesage有所区别的是它是线程池)等,在主线程中更新UI。
48.怎样对android进行优化?
1) 对listview的优化。
2) 对图片的优化。
3) 对内存的优化。
Ø 尽量不要使用过多的静态类static
Ø 数据库使用完成后要记得关闭cursor
Ø 广播使用完之后要注销
49.请介绍下AsyncTask的内部实现,适用的场景是
AsyncTask内部也是Handler机制来完成的,只不过Android提供了执行框架来提供线程池来执行相应地任务,因为线程池的大小问题,所以AsyncTask只应该用来执行耗时时间较短的任务,比如HTTP请求,大规模的下载和数据库的更改不适用于AsyncTask,因为会导致线程池堵塞,没有线程来执行其他的任务,导致的情形是会发生AsyncTask根本执行不了的问题。
50.谈谈二叉树,说说他的遍历方式?
1、先序遍历:先序遍历是先输出根节点,再输出左子树,最后输出右子树。
2、中序遍历:中序遍历是先输出左子树,再输出根节点,最后输出右子树。
3、后序遍历:后序遍历是先输出左子树,再输出右子树,最后输出根节点。