面试题整理
地址1
1. Android 应用的结构是什么?
- src 目录
是源代码目录 - gen 目录
保存 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 目录
用来存放我们为每个界面写的布局文件 - AndroidManifest.xml
是 XML 格式的 Android 程序声明文件,包含了 Android 系统运行Android 程序前所必须掌握的重要信息,这些信息包含应用程序名称、图标、包名称、模块组成、授权和 SDK 最低版本等,而且每个 Android 程序必须在根目录下包含一个 AndroidManifest.xml文件- AndroidManifest.xml 文件的根元素是 manifest,包含了 xmlns:android、package、
android:versionCode 和 android:versionName 共 4 个属性
- AndroidManifest.xml 文件的根元素是 manifest,包含了 xmlns:android、package、
- xmlns:android 定义了 Android 的命名空间,值为http://schemas.android.com/apk/res/android
- package 定义了应用程序的包名称
- android:versionCode 定义了应用程序的版本号,是一个整数值,数值越大说明版本越新,但仅在程序内部使用,并不提供给应用程序的使用者
- android:versionName 定义了应用程序的版本名称,是一个字符串,仅限于为用户提供一个版本标识
- manifest 元素仅能包含一个 application 元素, application 元素中能够声明 Android 程序中最重要的四个组成部分,包括 Activity、Service、BroadcastReceiver 和 ContentProvider,所定义的属性将影响所有组成部分
- android:icon 定义了 Android 应用程序的图标,其中@drawable/icon 是一种资源引用方式,表示资源类型是图像,资源名称为 icon,对应的资源文件为 res/drawable 目录下的 icon.png
- android:label 则定义了 Android 应用程序的标签名称default.properties 文件记录 Android 工程的相关设置,该文件不能手动修改,需右键单击工程名称,选择“Properties”进行修改
2. Android 应用中如何保存数据。
-
- 使用SharedPreferences存储数据
SharedPreference 可以保存和检索的任何基本数据类( boolean, float, int, long, string)的持久键-值对(基于XML文件存储的“key-value”键值对数据)。
其存储在“data/data/程序包名/shared_prefs目录下。
- 获取 SharedPreferences :
getSharedPreferences (String name, int mode)
getPreferences (int mode) - Context.MODE_PRIVATE
该SharedPreferences数据只能被本应用程序读、写。
Context.MODE_WORLD_READABLE
该SharedPreferences数据能被其他应用程序读,但不能写。
Context.MODE_WORLD_WRITEABLE
该SharedPreferences数据能被其他应用程序读和写。
Context.MODE_MULTI_PROCESS
sdk2.3后添加的选项,当多个进程同时读写同一个SharedPreferences时它会检查文件是否修改。 - 向Shared Preferences中写入值
通过 SharedPreferences.Editor 获取到 Editor 对象;通过 Editor 的 putBoolean() 或 putString()等方法存入值;
最后调用 Editor 的 commit() 方法提交;
-从 SharedPreferences 中读取值
读取值使用 SharedPreference 对象的 getBoolean() 或 getString() 等方法就行了 - Preferences 是很轻量级的应用,使用起来也很方便,简洁。但存储数据类型比较单一(只有基本数据类型),无法进行条件查询,只能在不复杂的存储需求下使用,比如保存配置信息等。
- 使用SharedPreferences存储数据
-
- 文件存储数据
- 默认存储位置:/data/data/包名/files/文件名
- 创建和写入一个内部存储的私有文件:
String FILENAME = "a.txt"; String string = "fanrunqi"; try { //Context.MODE_PRIVATE = 0, 文件是私有数据,只能被应用本身访问,写入的内容会覆盖原文件的内容。 //Context.MODE_APPEND = 32768, 该模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。 //Context.MODE_WORLD_READABLE = 1,表示当前文件可以被其他应用读取。 //MODE_WORLD_WRITEABLE, 表示当前文件可以被其他应用写入。 //调用Context的openFileOutput()函数,填入文件名和操作模式,它会返回一个FileOutputStream对象。 FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE); //通过FileOutputStream对象的write()函数写入数据。 fos.write(string.getBytes()); //FileOutputStream对象的close ()函数关闭流。 fos.close(); } catch (Exception e) { e.printStackTrace(); }
- 读取一个内部存储的私有文件:
//调用openFileInput( ),参数中填入文件名,会返回一个FileInputStream对象。 //使用流对象的 read() 方法读取字节 //调用流的 close() 方法关闭流 String FILENAME = "a.txt"; try { FileInputStream inStream = openFileInput(FILENAME); int len = 0; byte[] buf = new byte[1024]; StringBuilder sb = new StringBuilder(); while ((len = inStream.read(buf)) != -1) { sb.append(new String(buf, 0, len)); } inStream.close(); } catch (Exception e) { e.printStackTrace(); }
- 保存内存缓存文件:
getCacheDir() 去打开一个文件,文件的存储目录( /data/data/包名/cache )是一个应用专门来保存临时缓存文件的内存目录。 - 使用外部存储(sdcard)
<!-- 在SDCard中创建与删除文件权限 --> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <!-- 往SDCard写入数据权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
检测外部存储的可用性:
//获取外存储的状态 String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { // 可读可写 mExternalStorageAvailable = mExternalStorageWriteable = true; } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { // 可读 } else { // 可能有很多其他的状态,但是我们只需要知道,不能读也不能写 }
访问外部存储器中的文件
如果 API 版本大于或等于8,使用// 类型参数DIRECTORY_MUSIC 和 DIRECTORY_RINGTONES(传null就是你应用程序的文件目录的根目录)通过指定目录的类型,确保Android的媒体扫描仪将扫描分类系统中的文件(例如,铃声被确定为铃声)。如果用户卸载应用程序,这个目录及其所有内容将被删除。 getExternalFilesDir (String type) File file = new File(getExternalFilesDir(null), "fanrunqi.jpg"); getExternalFilesDir (String type)
如果API 版本小于 8 (7或者更低)
getExternalStorageDirectory () 通过该方法打开外存储的根目录,你应该在以下目录下写入你的应用数据,这样当卸载应用程序时该目录及其所有内容也将被删除。 /Android/data/<package_name>/files/
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ File sdCardDir = Environment.getExternalStorageDirectory();//获取SDCard目录 "/sdcard" File saveFile = new File(sdCardDir,"a.txt"); //写数据 try { FileOutputStream fos= new FileOutputStream(saveFile); fos.write("fanrunqi".getBytes()); fos.close(); } catch (Exception e) { e.printStackTrace(); } //读数据 try { FileInputStream fis= new FileInputStream(saveFile); int len =0; byte[] buf = new byte[1024]; StringBuffer sb = new StringBuffer(); while((len=fis.read(buf))!=-1){ sb.append(new String(buf, 0, len)); } fis.close(); } catch (Exception e) { e.printStackTrace(); } }
- 其他一些经常用到的方法:
getFilesDir(): 得到内存储文件的绝对路径
getDir(): 在内存储空间中创建或打开一个已经存在的目录
deleteFile(): 删除保存在内部存储的文件。
fileList(): 返回当前由应用程序保存的文件的数组(内存储目录下的全部文件)。
- SQLite数据库存储数据
http://blog.csdn.net/amazing7/article/details/51375012
- SQLite数据库存储数据
- 使用ContentProvider存储数据
http://blog.csdn.net/amazing7/article/details/51324022
- 使用ContentProvider存储数据
- 网络存储数据
3. 如何在 Android 应用中执行耗时操作。
Android 耗时操作放到非 UI 线程中执行,例如:AsyncTask,Handler,Thread,Loaders。
-
AsyncTask 的问题:
- cancel方法实现不是很好,一旦执行了 doInBackground,就算调用取消方法,也会将 doInBackground里面的代码执行完毕,才会停止。
- 内存泄露问题,在 Activity 中使用非静态匿名内部类,由于 AsyncTask 的生命周期可能会比 Activity 长,当 Activity 销毁时,AsyncTask 还在执行,由于 AsyncTask 持有 Activity 的应用,导致 Activity 无法回收,产生内存泄漏。
- 结果丢失:屏幕旋转的时候,Activity 重新创建,还在运行的 AsyncTask 会持有之前 Activity 的非法引用,导致 onPostExecute() 不起作用。
4. 两个 Fragment 之间如何通信。
参考
两个 Fragment 无法直接进行通信,只能通过他们所寄托的 Activity 来进通信。
两个方式:方式一,Fragment 内部直接互相调用(不推荐)。方式二,通过回调实现 Fragment 之间的通信。
方式一:
// 在一个 Fragment 中
Button Btn1 = (Button) view.findViewById(R.id.btn_home);// 获取按钮资源
Btn1.setOnClickListener(new Button.OnClickListener() {// 创建监听
public void onClick(View v) {
// 得到当前绑定的 Activity 实例
MainTab activity = ((MainTab) getActivity());
// 拿到 Fragment 实例
FragmentManager manager = activity.getSupportFragmentManager();
// 拿到需要的 Fragment 实例
FragmentMessage fragment = (FragmentMessager) manager.findFragmentByTag("tab2");
if (null != fragment) {
View vw = fragment.getView();
if (null != vw) {
TextView txt = (TextView) vw.findViewById(R.id.tv);
T.showShort(getActivity(), txt.getText().toString());
}
} else {
T.showShort(getActivity(), "FragmentMessage还未创建");
}
}
});
方式二:
分两步:
- 在 Fragment1 里定义接口,在 Activity 中实现接口。通过回调实现 Fragment1 与 Activity 的通信。
- 在 Activity 接口回调中与 Fragment2 通信,将参数传递给 Fragment2。
定义 Fragmen1 接口:
public interface OnArticleClickedListener{
public void onArticleSelected(int position);
}
private void setOnArticleClickedListener(OnArticleClickedListener listener){
this.listener = listener;
}
public void onListItemClick(ListView l, View v, int position, long id) {
this.listener.onArticleSelected(position);
}
Activity :
fragment1.setOnArticleClickedListener(new Fragment1.OnArticleClickedListener(){
onArticleSelected(int position){
//调用f2的方法,与进行通信
fragment2.showArticleDetail(position);
}
);
5. 阐述一下 Android 的通知系统。
ServiceManager、SystemSerer和SystemUI是android系统三个非常重要的进程,相信熟悉Android系统框架的亲们已经比较了解了。所有的系统服务都是运行在SystemServer这个进程中,并且将自身注册到ServiceManager中。应用层通过Framework API发送通知时,会通过Binder驱动相应地调用NotificationManagerService的enqueueNotification方法。enqueueNotification方法记录通知内容,调用StatusBarManangerService的updateNotifcation接口,并且执行响铃、震动和呼吸灯效果,最后,如果有注册相应的辅助服务(AccessibilityService),则给辅助服务发送通知事件。StatusBarManagerService会将通知事件再次转发给SystemUI中负责通知栏显示的StatusBar。注意到Android系统内建有两个通知栏,一个是PhoneStatusBar,一个是TabletStatusBar,根据系统配置的类型决定用哪一种。
6. 两个不同的 app 之间如何交互。
- 使用 componentName。
componentName 与 Intent 同处在 android.content 包下,用来定义一个应用程序的组件(Activity,Service,BroadcastReceiver或者ContentProvide)。
构造函数 ComponentName(String pkg,String cls)
demo: app1 启动 app1
Intent intent = new Intent();
ComponentName cn = new ComponentName("com.example.app2","com.example.app2.MainActivity");
startActivity(intent);