- 介绍下实现一个自定义view的基本流程
1.明确需求,确定你想实现的效果 2.确定是使用组合控件的形式还是全新自定义的形式,组合控件即使用多个系统控件来合成一个新控件,你比如titilebar,这种形式相对简单,参考 3.如果是完全自定义一个view的话,你首先需要考虑继承哪个类,是View呢,还是ImageView等子类。 4.根据需要去复写View#onDraw、View#onMeasure、View#onLayout方法 5.根据需要去复写dispatchTouchEvent、onTouchEvent方法 6.根据需要为你的自定义view提供自定义属性,即编写attr.xml,然后在代码中通过TypedArray等类获取到自定义属性值 7.需要处理滑动冲突、像素转换等问题
另外一个回答
- 编写attr.xml文件
- 在layout布局文件中引用,同时引用命名空间
- 在自定义控件中进行读取(构造方法拿到attr.xml文件值)
- 覆写onMeasure()方法
- 覆写onLayout()方法
- Android中touch事件的传递机制是怎样的?
基础知识
(1) 所有Touch事件都被封装成了MotionEvent对象,包括Touch的位置、时间、历史记录以及第几个手指(多指触摸)等。
(2) 事件类型分为ACTION_DOWN, ACTION_UP, ACTION_MOVE, ACTION_POINTER_DOWN, ACTION_POINTER_UP, ACTION_CANCEL,每个事件都是以ACTION_DOWN开始ACTION_UP结束。
(3) 对事件的处理包括三类,分别为传递——dispatchTouchEvent()函数、拦截——onInterceptTouchEvent()函数、消费——onTouchEvent()函数和OnTouchListener
传递流程
(1) 事件从Activity.dispatchTouchEvent()开始传递,只要没有被停止或拦截,从最上层的View(ViewGroup)开始一直往下(子View)传递。子View可以通过onTouchEvent()对事件进行处理。
(2) 事件由父View(ViewGroup)传递给子View,ViewGroup可以通过onInterceptTouchEvent()对事件做拦截,停止其往下传递。
(3) 如果事件从上往下传递过程中一直没有被停止,且最底层子View没有消费事件,事件会反向往上传递,这时父View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到Activity的onTouchEvent()函数。
(4) 如果View没有对ACTION_DOWN进行消费,之后的其他事件不会传递过来。
(5) OnTouchListener优先于onTouchEvent()对事件进行消费。 上面的消费即表示相应函数返回值为true
- android中有哪几种解析xml的类,官方推荐哪种?以及它们的原理和区别
- DOM解析
优点:
1.XML树在内存中完整存储,因此可以直接修改其数据和结构.
2.可以通过该解析器随时访问XML树中的任何一个节点.
3.DOM解析器的API在使用上也相对比较简单.
缺点:如果XML文档体积比较大时,将文档读入内存是非常消耗系统资源的.
使用场景:DOM 是用与平台和语言无关的方式表示 XML 文档的官方 W3C 标准.DOM 是以层次结构组织的节点的集合.这个层次结构允许开发人员在树中寻找特定信息.分析该结构通常需要加载整个文档和构造层次结构,然后才能进行任何工作.DOM是基于对象层次结构的. - SAX解析
优点:
SAX 对内存的要求比较低,因为它让开发人员自己来决定所要处理的标签.特别是当开发人员只需要处理文档中所包含的部分数据时,SAX 这种扩展能力得到了更好的体现.
缺点:
用SAX方式进行XML解析时,需要顺序执行,所以很难访问到同一文档中的不同数据.此外,在基于该方式的解析编码过程也相对复杂.
使用场景:
对于含有数据量十分巨大,而又不用对文档的所有数据进行遍历或者分析的时候,使用该方法十分有效.该方法不用将整个文档读入内存,而只需读取到程序所需的文档标签处即可. - Xmlpull解析
android SDK提供了xmlpull api,xmlpull和sax类似,是基于流(stream)操作文件,然后根据节点事件回调开发者编写的处理程序.因为是基于流的处理,因此xmlpull和sax都比较节约内存资源,不会象dom那样要把所有节点以对橡树的形式展现在内存中.xmlpull比sax更简明,而且不需要扫描完整个流.
webView.addJavaScriptInterface(new Object(){xxx}, "xxx");
1
答案:可以使用WebView控件执行JavaScript脚本,并且可以在JavaScript中执行Java代码。要想让WebView控件执行JavaScript,需要调用WebSettings.setJavaScriptEnabled方法,代码如下:
WebView webView = (WebView)findViewById(R.id.webview);
WebSettings webSettings = webView.getSettings();
//设置WebView支持JavaScript
webSettings.setJavaScriptEnabled(true);
webView.setWebChromeClient(new WebChromeClient());
JavaScript调用Java方法需要使用WebView.addJavascriptInterface方法设置JavaScript调用的Java方法,代码如下:
webView.addJavascriptInterface(new Object()
{
//JavaScript调用的方法
public String process(String value)
{
//处理代码
return result;
}
}, "demo"); //demo是Java对象映射到JavaScript中的对象名
可以使用下面的JavaScript代码调用process方法,代码如下:
<script language="javascript">
function search()
{
//调用searchWord方法
result.innerHTML = "<font color='red'>" + window.demo.process('data') + "</font>";
}
- SurfaceView和View的最本质的区别
SurfaceView是在一个新起的单独线程中可以重新绘制画面,而view必须在UI的主线程中更新画面。
在UI的主线程中更新画面可能会引发问题,比如你更新的时间过长,那么你的主UI线程就会被你正在画的函数阻塞。那么将无法响应按键、触屏等消息。当使用SurfaceView由于是在新的线程中更新画面所以不会阻塞你的UI主线程。但这也带来了另外一个问题,就是事件同步。比如你触屏了一下,你需要SurfaceView中thread处理,一般就需要有一个event queue的设计来保存touchevent,这会稍稍复杂一点,因为涉及到线程安全。
- 启动一个程序,可以主界面点击图标进入,也可以从一个程序中跳转过去,二者有什么区别?
是因为启动程序(主界面也是一个app),发现了在这个程序中存在一个设置为
<category android:name="android.intent.category.LAUNCHER" />
的activity,
所以这个launcher会把icon提出来,放在主界面上。当用户点击icon的时候,发出一个Intent:
Intent intent = mActivity.getPackageManager().getLaunchIntentForPackage(packageName);
mActivity.startActivity(intent);
跳过去可以跳到任意允许的页面,如一个程序可以下载,那么真正下载的页面可能不是首页(也有可能是首页),这时还是构造一个Intent,startActivity.
这个intent中的action可能有多种view,download都有可能。系统会根据第三方程序向系统注册的功能,为你的Intent选择可以打开的程序或者页面。所以唯一的一点
不同的是从icon的点击启动的intent的action是相对单一的,从程序中跳转或者启动可能样式更多一些。本质是相同的。
- AIDL的全称是什么?如何工作?能处理哪些类型的数据?
AIDL全称Android Interface Definition Language(Android接口描述语言)是一种接口描述语言; 编译器可以通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程跨界访问对象的目的.AIDL的IPC的机制和COM或CORBA类似, 是基于接口的,但它是轻量级的。它使用代理类在客户端和实现层间传递值. 如果要使用AIDL, 需要完成2件事情: 1. 引入AIDL的相关类.; 2. 调用aidl产生的class.
理论上, 参数可以传递基本数据类型和String, 还有就是Bundle的派生类, 不过在Eclipse中,目前的ADT不支持Bundle做为参数,
具体实现步骤如下:
1、创建AIDL文件, 在这个文件里面定义接口, 该接口定义了可供客户端访问的方法和属性。
2、编译AIDL文件, 用Ant的话, 可能需要手动, 使用Eclipse plugin的话,可以根据adil文件自动生产java文件并编译, 不需要人为介入.
3、在Java文件中, 实现AIDL中定义的接口. 编译器会根据AIDL接口, 产生一个JAVA接口。这个接口有一个名为Stub的内部抽象类,它继承扩展了接口并实现了远程调用需要的几个方法。接下来就需要自己去实现自定义的几个接口了.
4、向客户端提供接口ITaskBinder, 如果写的是service,扩展该Service并重载onBind ()方法来返回一个实现上述接口的类的实例。
5、在服务器端回调客户端的函数. 前提是当客户端获取的IBinder接口的时候,要去注册回调函数, 只有这样, 服务器端才知道该调用那些函数
AIDL语法很简单,可以用来声明一个带一个或多个方法的接口,也可以传递参数和返回值。 由于远程调用的需要, 这些参数和返回值并不是任何类型.下面是些AIDL支持的数据类型:
- 不需要import声明的简单Java编程语言类型(int,boolean等)
- String, CharSequence不需要特殊声明
- List, Map和Parcelables类型, 这些类型内所包含的数据成员也只能是简单数据类型, String等其他比支持的类型.
(另外: 我没尝试Parcelables, 在Eclipse+ADT下编译不过, 或许以后会有所支持).
实现接口时有几个原则:
1.抛出的异常不要返回给调用者. 跨进程抛异常处理是不可取的.
2.IPC调用是同步的。如果你知道一个IPC服务需要超过几毫秒的时间才能完成地话,你应该避免在Activity的主线程中调用。也就是IPC调用会挂起应用程序导致界面失去响应. 这种情况应该考虑单起一个线程来处理.
3.不能在AIDL接口中声明静态属性。
IPC的调用步骤: - 声明一个接口类型的变量,该接口类型在.aidl文件中定义。
- 实现ServiceConnection。
- 调用ApplicationContext.bindService(),并在ServiceConnection实现中进行传递.
- 在ServiceConnection.onServiceConnected()实现中,你会接收一个IBinder实例(被调用的Service). 调用
YourInterfaceName.Stub.asInterface((IBinder)service)将参数转换为YourInterface类型。 - 调用接口中定义的方法。你总要检测到DeadObjectException异常,该异常在连接断开时被抛出。它只会被远程方法抛出。
- 断开连接,调用接口实例中的ApplicationContext.unbindService()
参考:http://buaadallas.blog.51cto.com/399160/372090
aidl主要就是帮助我们完成了包装数据和解包的过程,并调用了transact过程,而用来传递的数据包我们就称为parcel
AIDL: xxx.aidl->xxx.java,注册service
用aidl定义需要被调用方法接口
实现这些方法
调用这些方法
- ANR是什么?怎样避免和解决ANR
ANR:Application Not Responding,即应用无响应
ANR一般有三种类型:
1:KeyDispatchTimeout(5 seconds)
--主要类型
按键或触摸事件在特定时间内无响应
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人肉查找
- Android注册广播有几种方式? 它们的适用情况是什么?
Android中有两种广播的注册方式: 1、静态注册:AndroidManifest.xml配置文件中; 2、动态注册:Java代码; 静态注册适用于全局广播;动态注册适用于局部广播;
补充一点:有些广播只能通过动态方式注册,比如时间变化事件、屏幕亮灭事件、电量变更事件,因为这些事件触发频率通常很高,如果允许后台监听,会导致进程频繁创建和销毁,从而影响系统整体性能。
- 如何调试Android应用程序
- log
- 断点调试
- android在线程中创建handler应注意什么
Looper.prepare()
,主线程使用handler,系统默认prepare了,子线程中创建handler必须在前面Looper.prepare()
,后面加上Looper.loop();
源码中: 主线程: 在程序启动的时候,系统已经帮我们自动调用了Looper.prepare()方法。查看ActivityThread中的main()
public static void main(String[] args) {
SamplingProfilerIntegration.start();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
EventLogger.setReporter(new EventLoggingReporter());
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
if (false) {
Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
请注意Looper.prepareMainLooper()
:
public static final void prepareMainLooper() {
prepare();
setMainLooper(myLooper());
if (Process.supportsProcesses()) {
myLooper().mQueue.mQuitAllowed = false;
}
}
子线程:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare()
handler2 = new Handler();
Looper.loop()
}
}).start();
如果没有Looper.prepare().会报错:
Can't create handler inside thread that has not called Looper.prepare()
因为没looper对象创建
looper.prepare()源码:
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
---------我是优雅的分割线---------
在Android开发中经常会使用到线程,一想到线程,一般都会想到
new Thread(){...}.start();
这样的方式。这样如果在一个Activity中多次调用上面的代码,那么将创建多个匿名线程,如果这些线程的没有被销毁,那肯定会影响性能呢。这个时候我么就想到了android提供的一个异步处理线程的类HandlerThread
。
一般Handler的用法
Handler handler = new Handler(){...};
这样创建的handler是在主线程即UI线程下的Handler
,即这个Handler是与UI线程下的默认Looper绑定的
(当然也只有主线程才能这么干,子线程是干不了的,除非自己创建个looper)。因此,有些时候会占用ui主线程,引起一些问题,所以我们就想到了重新创建个子线程,来处理handler。。。。 使用HandlerThread解决问题
HandlerThread实际上继承于Thread,只不过它比普通的Thread多了一个Looper。我们可以使用下面的例子创建Handler
HandlerThread thread = new HandlerThread("MyHandlerThread");
thread.start();
创建HandlerThread时要把它启动了,即调用start()方法。
接着就是handler的使用,如下:
mHandler = new Handler(thread.getLooper());
//TODO:you can post or send something....
创建Handler时将HandlerThread中的looper对象传入。那么这个mHandler对象就是与HandlerThread这个线程绑定了(这时就不再是与UI线程绑定了,这样它处理耗时操作将不会阻塞UI)。