Android 常见面试 知识点小结

NanKinsella 8年前

来自: http://blog.csdn.net//never_cxb/article/details/50457410


前言

根据笔者自己的阅读以及项目经验总结而言,不同于网上的copy来copy去。很多内容加上了自己的理解,难免有错误不当之处,烦请指出。

Handler

在子线程里面创建 Handler 对象会抛出异常Can't create handler inside thread that has not called Looper.prepare()

但是加上Looper.prepare();不会抛出异常, 这个因为 Handler 对应一个 Looper,一个 Looper 对应一个线程。

new Thread(new Runnable() {      @Override      public void run() {          Looper.prepare();          handler2 = new Handler();      }  }).start();

用 ThreadLocal 保存 Looper 对象
ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

通过Looper.prepare()来创建Looper对象(消息队列封装在Looper对象中),并且保存在sThreadLoal中,然后通过Looper.loop()来执行消息循环,这两步通常是成对出现的!!

xml解析

个人理解:
Dom把xml全部加载,比较耗内存。
Sax是事件驱动,但是不支持随机读取,不能只处理部分元素。
Pull是Sax的改进,可以提前结束读取,Sax不能提前结束。

基本的解析方式包含两种,一种是事件驱动的(代表SAX),另一种方式是基于文档结构(代表DOM)。其他的只不过语法不一样而已。

Dom 方式解析xml,比较简单,并可以访问兄弟元素,但是需要将整个xml文档加载到内存中,对于android设备来说,不推荐使用dom的方式解析xml。

// 接收一个xml的字符串来解析xml,Document代表整个xml文档  Document document = builder.parse(inputStream);  // 得到xml文档的根元素节点  Element personsElement = document.getDocumentElement();  // 得到标签为person的Node对象的集合NodeList  NodeList nodeList = personsElement.getElementsByTagName("person");    Dom解析加载整个xml文档的树形结构,可以随时访问兄弟节点,父节点等。Sax只能向前遍历(单遍解析),使它不能支持随机访问。

sax和pull都是基于事件驱动的xml解析器,在解析xml时并不会加载整个的xml文档,占用内存较少,因此在android开发中建议使用sax或者pull来解析xml文档。

SAX,全称Simple API for XML,既是一种接口,也是一种软件包。它是一种XML解析的替代方法。SAX不同于DOM解析,它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中,这对于大型文档的解析是个巨大优势。

继承 extends DefaultHandler 需要复写下面几个方法。这几个方法是回调方法,解析时,若发生事件(文档开头结尾 元素开头结尾)的话会被调用这些回调方法。

  • startDocument() and endDocument() – Method called at the start and end of an XML document.
  • startElement() and endElement() – Method called at the start and end of a document element.
  • characters() – Method called with the text contents in between the start and end tags of an XML document element.

pull和sax的区别

Pull解析器和SAX解析器虽有区别但也有相似性。他们的区别为:SAX解析器的工作方式是自动将事件推入注册的事件处理器进行处理,因此你不能控制事件的处理主动结束;

而Pull解析器的工作方式为允许你的应用程序代码主动从解析器中获取事件,正因为是主动获取事件,因此可以在满足了需要的条件后不再获取事件,结束解析。这是他们主要的区别。

Android 系统架构

  1. Linux 内核层,为 Android 设备提供了底层的驱动,如蓝牙驱动、照相机驱动等等
  2. 系统运行库层,这些曾通过一些 C/C++ 库为 Android 系统提供了主要的特性支持,如 SQlite 提宫数据库,Webkit 提高了浏览器内核
    需要注意的是这一层也有 Android 运行时候需要的核心库
    Android 运行时库包括了 Dalvik 虚拟机
  3. 应用框架层,这一层提供了编写 App 时候需要用到的 Api
  4. 应用层,所有安装在手机上的 App 都属于这一层,包括短信、QQ 等

四大组件

activity和service默认是运行在应用进程的主线程中,四大组件默认都是和activity运行在同一个主线程中的

  • Activity 活动
  • Service 服务
    Service的Oncreate()方法只有第一次执行,而OnStartCommand()每次启动服务都会调用
  • Broadcast 广播接收器
    最常用的广播就是android.provider.Telephony.SMS_RECEIVED
// AndroidManifest.xml  <uses-permission android:name="android.permission.RECEIVE_SMS" />  ...  <application ...>      <receiver android:name=".SMSReceiver">         <intent-filter>           <action android:name="android.provider.Telephony.SMS_RECEIVED" />         </intent-filter>       </receiver>    </application>    // SMSReceiver.java  public class SMSReceiver extends BroadcastReceiver   {       @Override       public void onReceive(Context context, Intent intent) {           Log.i(TAG, "SMS received.");          ....      }  }
  • ContentProvider 内容提供者

    注意如果自己定义一个ContentProvider暴露为其他 App 使用,通常用SQLite或者文件实现,也可以用 Json 或者 Xml 实现等等。


4大组件都需要在 xml 文件里注册,注意它们是平级的关系。

存储化技术

  • 文件存储 存储一些图片、视频、doc 等等,保密性不强
  • SharedPreference 多用于存储用户配置、密码等等
    也可在里面存储视频当前播放的位置,按home键返回后重新从当前位置读取
  • 数据库 Sqlite 存储一些格式复杂的数据,比如短信中 联系人和对应的短信记录

文件的操作模式

  • MODE_APPEND 追加内容
  • MODE_PRIVATE 覆盖文件中的内容
  • MODE_WORLD_READABLE 已经在4.2废弃
  • MODE_WORLD_WRITEABLE 已经在4.2废弃

打开文件示例代码

FileOutputStream out  = null;  BufferedWriter writer = null;  try {      out = openFileOutput("data", Context.MODE_PRIVATE);      writer = new BufferedWriter(new OutputStreamWriter(out));    } catch (FileNotFoundException e) {      e.printStackTrace();  } finally {      try {          if (writer != null)          writer.close();      } catch (IOException e) {          e.printStackTrace();      }  }

读取文件 代码示例

FileInputStream in = null;   BufferedReader reader = null;   StringBuilder content = null;     try {       in = openFileInput("data");       reader = new BufferedReader(new InputStreamReader(in));       String line = "";       if ((line = reader.readLine()) != null) {           content.append(line);       }   } catch (FileNotFoundException e) {       e.printStackTrace();   } catch (IOException e) {       e.printStackTrace();   } finally {       try {           if(reader != null)           reader.close();       } catch (IOException e) {           e.printStackTrace();       }   }

TestUtils

String x = "test";  TextUtils.isEmpty(x);

这儿的 isEmpty 方法可以判断 x 为 null 或者长度为 0

SQlite

使用adb shell中的sqlite3打开数据库

getWritableDatabase VS getReadableDatabase

getWritableDatabasegetReadableDatabase 取得的实例对数据库进行读和写的功能,不是像字面意思上一个读写权限和只读权限

两者的区别在于
- getWritableDatabase取得的实例是以读写的方式打开数据库,如果打开的数据库磁盘满了,此时只能读不能写,此时调用了getWritableDatabase的实例,那么将会发生错误(异常)
- getReadableDatabase取得的实例是先调用getWritableDatabase以读写的方式打开数据库,如果数据库的磁盘满了,此时返回打开失败,继而用getReadableDatabase的实例以只读的方式去打开数据库

intent-filter

intent-filter 可以动态注册也可以静态注册
下面是静态注册的代码,直接写在xml文件里

<application>      <activity name=""/>      <receiver android:name=".MyBroadcastReceiver">          <!-- intent过滤器,指定可以匹配哪些intent, 一般需要定义action 可以是自定义的也可是系统的 -->           <intent-filter>              <action android:name="com.app.bc.test"/>          </intent-filter>      </receiver>  </application>    //  // java code 这儿的intent也叫隐式intent  Intent intent = new Intent(“com.app.bc.test”);  sendBroadcast(intent);//发送广播事件

动态注册

//生成一个BroadcastReceiver对象  SMSReceiver smsReceiver = new SMSReceiver();  //生成一个IntentFilter对象  IntentFilter filter = new IntentFilter();         filter.addAction(“android.provider.Telephony.SMS_RECEIVED”);  //将BroadcastReceiver对象注册到系统当中  //此处表示该接收器会处理短信事件  TestBC1Activity.this.registerReceiver(smsReceiver, filter); 
  • 静态注册:在AndroidManifest.xml注册,android不能自动销毁广播接收器,也就是说当应用程序关闭后,还是会接收广播。
  • 动态注册:在代码中通过registerReceiver()手工注册.当程序关闭时,该接收器也会随之销毁。当然,也可手工调用unregisterReceiver()进行销毁。

显示intent 和隐式intent

显示

Intent intent = new Intent(this, SecondActivity.class);    startActivity(intent);  

隐式,它会自动寻找能处理 intent-filter 里面设置了处理该的 action 的Activity。

Intent intent = new Intent();  intent.setAction(“android.provider.Telephony.SMS_RECEIVED”);  startActivity(intent);

比如有手机装了多个浏览器,使用隐式intent会弹出让用户选择哪个浏览器。

Timer 和 Alarm

Timer 不适用需要长期在后台运行的定时任务,长时间不操作,CPU会睡眠状态,这会影响Timer的定时任务无法执行
Alarm 机制具有唤醒CPU的功能

工具方法

訪問Url,利用get請求,請返回的數據轉化為字符串

HttpURLConnection connection = null;  ...  connection.setReadTimeOut(8000);  InputStream in = connection.getInputStream();  BufferedReader reader = new BufferedReader(new InputStreamReader(in));  StringBuilder response = new StringBuilder();  String line = "";  while( (line = reader.readLine()) !=null ) {   response.append(line);  }

怎么更新UI

注意不能使用子线程更新 UI,可以用 Handler 或者 Activity.runOnUiThread

public final void runOnUiThread (Runnable action)

Added in API level 1  Runs the specified action on the UI thread. If the current thread is the UI thread, then the action is executed immediately. If the current thread is not the UI thread, the action is posted to the event queue of the UI thread.    Parameters  action  the action to run on the UI thread

Adapter 4个重写方法

BaseAdapter基本结构

  • public int getCount() :适配器中数据集中 数据的个数,即ListView需要显示的数据个数
  • public Object getItem(int position) : 获取数据集中与指定索引对应的数据项
  • public long getItemId(int position) : 获取指定行对应的ID
  • public View getView(int position, View convertView, ViewGroup parent) :获取每一个Item的显示内容
public class MyBaseAdapter extends BaseAdapter {        ArrayList myList = new ArrayList();      LayoutInflater inflater;      Context context;          public MyBaseAdapter(Context context, ArrayList myList) {          this.myList = myList;          this.context = context;          inflater = LayoutInflater.from(this.context);      }        @Override      public int getCount() {          return myList.size();      }        @Override      public ListData getItem(int position) {          return myList.get(position);      }        @Override      public long getItemId(int position) {          return 0;      }        @Override      public View getView(int position, View convertView, ViewGroup parent) {          MyViewHolder mViewHolder;            if (convertView == null) {              convertView = inflater.inflate(R.layout.layout_list_item, parent, false);              mViewHolder = new MyViewHolder(convertView);              convertView.setTag(mViewHolder);          } else {              mViewHolder = (MyViewHolder) convertView.getTag();          }            ListData currentListData = getItem(position);            mViewHolder.tvTitle.setText(currentListData.getTitle());          mViewHolder.tvDesc.setText(currentListData.getDescription());          mViewHolder.ivIcon.setImageResource(currentListData.getImgResId());            return convertView;      }        private class MyViewHolder {          TextView tvTitle, tvDesc;          ImageView ivIcon;            public MyViewHolder(View item) {              tvTitle = (TextView) item.findViewById(R.id.tvTitle);              tvDesc = (TextView) item.findViewById(R.id.tvDesc);              ivIcon = (ImageView) item.findViewById(R.id.ivIcon);          }      }  }

在使用BaseAdapter时,我们需要自己创建一个类继承BaseAdapter,然后Eclipse会提醒我们实现上述四个方法,当给ListView设置了我们自己写的Adapter后,ListView内部会调用上述四个方法。

参考文章

【Android】BroadCast广播机制应用与实例

ListView using BaseAdapter – Android http://www.pcsalt.com/android/listview-using-baseadapter-android/