您的位置:首页 > 职场人生

Android几个面试题的解答

2015-03-15 11:31 302 查看
一、如何将一个Activity设置成窗口的样式。

设置Activity窗口化:

android:theme="@android:style/Theme.Dialog"

设置Activity透明化:

android:theme="@android:style/Theme.Translucent"

二、请介绍下Android的数据存储方式

㈠.文件存储方式:

在Android中通常使用File存储方式是用 Context.openFileOutput(String fileName, int mode)和Context.openFileInput(String fileName)。

Context.openFileOutput(String fileName, int mode)生成的文件自动存储在/data/data/PackageName/file目录下,其全路径是/data/data/Package Name/files/fileName 。注意下,这里的参数fileName不可以包含路径分割符(如"/")。

通常来说,这种方式生成的文件只能在这个apk内访问。但这个结论是指使用Context.openFileInput(String fileName)的方式。使用这种方式,每个apk只可以访问自己的/data/data/Package Name/files目录下的文件,原因很简单,参数fileName中不可以包含路径分割符,Android会自动在/data/data /Package Name/files目录下寻找文件名为fileName的文件。

具体实例如下:

[java] view
plaincopy

String fn = “moandroid.log”;

FileInputStream fis = openFileInput(fn);

FileOutputStream fos = openFileOutput(fn.Context.MODE_PRIVATE);

㈡.使用SharedPreferences存储数据

首先说明SharedPreferences存储方式,它是 Android提供的用来存储一些简单配置信息的一种机制,例如:登录用户的用户名与密码。其采用了Map数据结构来存储数据,以键值的方式存储,可以简单的读取与写入,具体实例如下:

[java] view
plaincopy

void ReadSharedPreferences(){

String strName,strPassword;

SharedPreferences user = getSharedPreferences(“user_info”,0);

strName = user.getString(“NAME”,””);

strPassword = user getString(“PASSWORD”,””);

}

void WriteSharedPreferences(String strName,String strPassword){

SharedPreferences user = getSharedPreferences(“user_info”,0);

uer.edit()//获得编辑器;

user.putString(“NAME”, strName);

user.putString(“PASSWORD” ,strPassword);

user.commit();

}

数据读取与写入的方法都非常简单,只是在写入的时候有些区别:先调用edit()使其处于编辑状态,然后才能修改数据,最后使用commit()提交修改的数据。实际上SharedPreferences是采用了XML格式将数据存储到设备中,在DDMS中的File Explorer中的/data/data/<package name>/shares_prefs下。以上面的数据存储结果为例,打开后可以看到一个user_info.xml的文件,打开后可以看到:

[html] view
plaincopy

<?xml version=”1.0″ encoding=”UTF-8″?>

<map>

<string name=”NAME”>moandroid</string>

<string name=” PASSWORD”>SharedPreferences</string>

</map>

使用SharedPreferences是有些限制的:只能在同一个包内使用,不能在不同的包之间使用。

㈢:网络存储数据

网络存储方式,需要和Android网络数据包打交道。

㈣.ContentProvider数据存储

1、ContentProvider简介

当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。虽然使用其他方法也可以对外共享数据,但数据访问方式会因数据存储的方式而不同,如:采用文件方式对外共享数据,需要进行文件操作读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式。与之相关联的是uri,下面是uri的简介:

2、Uri类简介

Uri代表了要操作的数据,Uri主要包含了两部分信息:需要操作的ContentProvider和对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成:

1).scheme:ContentProvider(内容提供者)的scheme已经由Android所规定为:content://。

2).主机名(或Authority):用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。

3).路径(path):可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:

要操作contact表中id为10的记录,可以构建这样的路径:/contact/10

要操作contact表中id为10的记录的name字段, contact/10/name

要操作contact表中的所有记录,可以构建这样的路径:/contact?

要操作的数据不一定来自数据库,也可以是文件等他存储方式,如下:

要操作xml文件中contact节点下的name节点,可以构建这样的路径:/contact/name

如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:

Uri uri = Uri.parse("content://com.changcheng.provider.contactprovider/contact")

3、UriMatcher、ContentUrist和ContentResolver简介

因为Uri代表了要操作的数据,所以我们很经常需要解析Uri,并从 Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher 和ContentUris 。掌握它们的使用,会便于我们的开发工作。

? UriMatcher:用于匹配Uri,它的用法如下:

1).首先把你需要匹配Uri路径全部给注册上,如下:

//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码(-1)。

UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

//如果match()方法匹配content://com.changcheng.sqlite.provider.contactprovider /contact路径,返回匹配码为1

uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact”, 1);//添加需要匹配uri,如果匹配就会返回匹配码

//如果match()方法匹配 content://com.changcheng.sqlite.provider.contactprovider/contact/230路径,返回匹配码为2

uriMatcher.addURI(“com.changcheng.sqlite.provider.contactprovider”, “contact/#”, 2);//#号为通配符

2).注册完需要匹配的Uri后,就可以使用uriMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用 addURI()方法传入的第三个参数,假设匹配 content://com.changcheng.sqlite.provider.contactprovider/contact路径,返回的匹配码为1。

ContentUris:用于获取Uri路径后面的ID部分,它有两个比较实用的方法:

withAppendedId(uri, id)用于为路径加上ID部分

parseId(uri)方法用于从路径中获取ID部分

ContentResolver:当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用 ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver使用insert、delete、update、query方法,来操作数据。

㈤.数据库存储方式?

数据库存储方式,最长用的是SQLite

三、如何启用Service,如何停用Service

● 启动方式和停止方式:

㈠.Context.startService() / Context.stopService();

㈡. Context.bindService() / Context.unbindService();

四、 注册广播有几种方式,这些方式有何优缺点?请谈谈Android引入广播机制的用意。

在android下,要想接受广播信息,那么这个广播接收器就得我们自己来实现了,我们可以继承BroadcastReceiver,就可以有一个广播接受器了。有个接受器还不够,我们还得重写BroadcastReceiver里面的onReceiver方法,当来广播的时候我们要干什么,这就要我们自己来实现,不过我们可以搞一个信息防火墙。具体的代码:

[java] view
plaincopy

public class SmsBroadCastReceiver extends BroadcastReceiver

{

public void onReceive(Context context, Intent intent)

{

Bundle bundle = intent.getExtras();

Object[] object = (Object[])bundle.get("pdus");

SmsMessage sms[]=new SmsMessage[object.length];

for(int i=0;i<object.length;i++)

{

sms[0] = SmsMessage.createFromPdu((byte[])object[i]);

Toast.makeText(context, "来自 "+sms[i].getDisplayOriginatingAddress()+" 的消息是:"+sms[i].getDisplayMessageBody(), Toast.LENGTH_SHORT).show();

}

//终止广播,在这里我们可以稍微处理,根据用户输入的号码可以实现短信防火墙。

abortBroadcast();

}

}

当实现了广播接收器,还要设置广播接收器接收广播信息的类型,这里是信息:android.provider.Telephony.SMS_RECEIVED

我们就可以把广播接收器注册到系统里面,可以让系统知道我们有个广播接收器。这里有两种,

? 一种是代码动态注册:

[java] view
plaincopy

//生成广播处理

smsBroadCastReceiver = new SmsBroadCastReceiver();

//实例化过滤器并设置要过滤的广播

IntentFilter intentFilter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");

//注册广播

BroadCastReceiverActivity.this.registerReceiver(smsBroadCastReceiver, intentFilter);

? 一种是在AndroidManifest.xml中配置广播

[html] view
plaincopy

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="spl.broadCastReceiver"

android:versionCode="1"

android:versionName="1.0">

<application android:icon="@drawable/icon" android:label="@string/app_name">

<activity android:name=".BroadCastReceiverActivity"

android:label="@string/app_name">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<!--广播注册-->

<receiver android:name=".SmsBroadCastReceiver">

<intent-filter android:priority="20">

<action android:name="android.provider.Telephony.SMS_RECEIVED"/>

</intent-filter>

</receiver>

</application>

<uses-sdk android:minSdkVersion="7" />

<!-- 权限申请 -->

<uses-permission android:name="android.permission.RECEIVE_SMS"></uses-permission>

</manifest>

两种注册类型的区别是:

1)第一种不是常驻型广播,也就是说广播跟随程序的生命周期。

2)第二种是常驻型,也就是说当应用程序关闭后,如果有信息广播来,程序也会被系统调用自动运行

五、请解释下在单线程模型中Message、Handler、Message Queue、Looper之间的关系。

2.2 Message Queue

在单线程模型下,为了解决类似的问题,Android设计了一个Message Queue(消息队列), 线程间可以通过该Message Queue并结合Handler和Looper组件进行信息交换。下面将对它们进行分别介绍:

1. Message

Message消息,理解为线程间交流的信息,处理数据后台线程需要更新UI,则发送Message内含一些数据给UI线程。

2. Handler

Handler处理者,是Message的主要处理者,负责Message的发送,Message内容的执行处理。后台线程就是通过传进来的 Handler对象引用来sendMessage(Message)。而使用Handler,需要implement 该类的 handleMessage(Message)

方法,它是处理这些Message的操作内容,例如Update UI。通常需要子类化Handler来实现handleMessage方法。

3. Message Queue

Message Queue消息队列,用来存放通过Handler发布的消息,按照先进先出执行。

每个message queue都会有一个对应的Handler。Handler会向message queue通过两种方法发送消息:sendMessage或post。这两种消息都会插在message queue队尾并按先进先出执行。但通过这两种方法发送的消息执行的方式略有不同:通过sendMessage发送的是一个message对象,会被 Handler的handleMessage()函数处理;而通过post方法发送的是一个runnable对象,则会自己执行。

4. Looper

Looper是每条线程里的Message Queue的管家。Android没有Global的Message Queue,而Android会自动替主线程(UI线程)建立Message Queue,但在子线程里并没有建立Message Queue。所以调用Looper.getMainLooper()得到的主线程的Looper不为NULL,但调用Looper.myLooper() 得到当前线程的Looper就有可能为NULL。

对于子线程使用Looper,API Doc提供了正确的使用方法:

这个Message机制的大概流程:

1. 在Looper.loop()方法运行开始后,循环地按照接收顺序取出Message Queue里面的非NULL的Message。

2. 一开始Message Queue里面的Message都是NULL的。当Handler.sendMessage(Message)到Message Queue,该函数里面设置了那个Message对象的target属性是当前的Handler对象。随后Looper取出了那个Message,则调用该Message的target指向的Hander的dispatchMessage函数对Message进行处理。

在dispatchMessage方法里,如何处理Message则由用户指定,三个判断,优先级从高到低:

1) Message里面的Callback,一个实现了Runnable接口的对象,其中run函数做处理工作;

2) Handler里面的mCallback指向的一个实现了Callback接口的对象,由其handleMessage进行处理;

3) 处理消息Handler对象对应的类继承并实现了其中handleMessage函数,通过这个实现的handleMessage函数处理消息。

由此可见,我们实现的handleMessage方法是优先级最低的!

3. Handler处理完该Message (update UI) 后,Looper则设置该Message为NULL,以便回收!

在网上有很多文章讲述主线程和其他子线程如何交互,传送信息,最终谁来执行处理信息之类的,个人理解是最简单的方法——判断Handler对象里面的 Looper对象是属于哪条线程的,则由该线程来执行!

1. 当Handler对象的构造函数的参数为空,则为当前所在线程的Looper;

2. Looper.getMainLooper()得到的是主线程的Looper对象,Looper.myLooper()得到的是当前线程的Looper对象。

六、AIDL的全称是什么?如何工作?能处理哪些类型的数据?

AIDL(AndRoid接口描述语言)是一种接口描述语言; 编译器可以通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程的目的. 如果需要在一个Activity中, 访问另一个Service中的某个对象, 需要先将对象转化成AIDL可识别的参数(可能是多个参数), 然后使用AIDL来传递这些参数, 在消息的接收端, 使用这些参数组装成自己需要的对象.

AIDL的IPC的机制和COM或CORBA类似, 是基于接口的,但它是轻量级的。它使用代理类在客户端和实现层间传递值. 如果要使用AIDL, 需要完成2件事情: 1. 引入AIDL的相关类.; 2. 调用aidl产生的class.

AIDL的创建方法:

AIDL语法很简单,可以用来声明一个带一个或多个方法的接口,也可以传递参数和返回值。由于远程调用的需要, 这些参数和返回值并不是任何类型.

下面是些AIDL支持的数据类型:

1. 不需要import声明的简单Java编程语言类型(int,boolean等)

2. String, CharSequence不需要特殊声明

3. List, Map和Parcelables类型, 这些类型内所包含的数据成员也只能是简单数据类型, String等其他比支持的类型.

12. 请解释下Android程序运行时权限与文件系统权限的区别

要区分apk运行时的拥有的权限与在文件系统上被访问(读写执行)的权限两个概念。

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必须签名

这种签名不是基于权威证书的,不会决定某个应用允不允许安装,而是一种自签名证书。重要的是,android系统有的权限是基于签名的。比如:system等级的权限有专门对应的签名,签名不对,权限也就获取不到。

默认生成的APK文件是debug签名的。

(2)基于UserID的进程级别的安全机制

大家都知道,进程有独立的地址空间,进程与进程间默认是不能互相访问的,是一种很可靠的保护机制。

Android通过为每一个安装在设备上的包(apk)分配唯一的linux userID来实现,名称为"app_"加一个数字,比如app_43

不同的UserID,运行在不同的进程,所以apk之间默认便不能相互访问。

Android提供了如下的一种机制,可以使两个apk打破前面讲的这种壁垒。

在AndroidManifest.xml中利用sharedUserId属性给不同的package分配相同的userID,通过这样做,两个package可以被当做同一个程序,

系统会分配给两个程序相同的UserID。当然,基于安全考虑,两个package需要有相同的签名,否则没有验证也就没有意义了。

(这里补充一点:并不是说分配了同样的UserID,两程序就运行在同一进程, 下面为PS指令摘取的,

显然,system、app_2分别对应的两个进程的PID都不同,不知Android到底是怎样实现它的机制的)

User PID PPID

system 953 883 187340 55052 ffffffff afe0cbcc S system_server

app_2 1072 883 100264 19564 ffffffff afe0dcc4 S com.android.inputmethod.

system 1083 883 111808 23192 ffffffff afe0dcc4 S android.process.omsservi

app_2 1088 883 156464 45720 ffffffff afe0dcc4 S android.process.acore

(3)默认apk生成的数据对外是不可见的

实现方法是:Android会为程序存储的数据分配该程序的UserID。

借助于Linux严格的文件系统访问权限,便实现了apk之间不能相互访问似有数据的机制。

例:我的应用创建的一个文件,默认权限如下,可以看到只有UserID为app_21的程序才能读写该文件。

-rw------- app_21 app_21 87650 2000-01-01 09:48 test.txt

如何对外开放?

<1> 使用MODE_WORLD_READABLE and/or MODE_WORLD_WRITEABLE 标记。

When creating a new file with getSharedPreferences(String, int), openFileOutput(String, int), or openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory), you can use the MODE_WORLD_READABLE and/or MODE_WORLD_WRITEABLE flags to allow any other package
to read/write the file. When setting these flags, the file is still owned by your application, but its global read and/or write permissions have been set appropriately so any other application can see it.

(4)AndroidManifest.xml中的显式权限声明

Android默认应用是没有任何权限去操作其他应用或系统相关特性的,应用在进行某些操作时都需要显式地去申请相应的权限。

一般以下动作时都需要申请相应的权限:

A particular permission may be enforced at a number of places during your program's operation:

• At the time of a call into the system, to prevent an application from executing certain functions.

• When starting an activity, to prevent applications from launching activities of other applications.

• Both sending and receiving broadcasts, to control who can receive your broadcast or who can send a broadcast to you.

• When accessing and operating on a content provider.

• Binding or starting a service.

在应用安装的时候,package installer会检测该应用请求的权限,根据该应用的签名或者提示用户来分配相应的权限。

在程序运行期间是不检测权限的。如果安装时权限获取失败,那执行就会出错,不会提示用户权限不够。

大多数情况下,权限不足导致的失败会引发一个 SecurityException, 会在系统log(system log)中有相关记录。

(5)权限继承/UserID继承

当我们遇到apk权限不足时,我们有时会考虑写一个linux程序,然后由apk调用它去完成某个它没有权限完成的事情,很遗憾,这种方法是行不通的。

前面讲过,android权限是经营在进程层面的,也就是说一个apk应用启动的子进程的权限不可能超越其父进程的权限(即apk的权限),

即使单独运行某个应用有权限做某事,但如果它是由一个apk调用的,那权限就会被限制。

实际上,android是通过给子进程分配父进程的UserID实现这一机制的。

(三)常见权限不足问题分析

首先要知道,普通apk程序是运行在非root、非system层级的,也就是说看要访问的文件的权限时,看的是最后三位。

另外,通过system/app安装的apk的权限一般比直接安装或adb install安装的apk的权限要高一些。

言归正传,运行一个android应用程序过程中遇到权限不足,一般分为两种情况:

(1)Log中可明显看到权限不足的提示。

此种情况一般是AndroidManifest.xml中缺少相应的权限设置,好好查找一番权限列表,应该就可解决,是最易处理的情况。

有时权限都加上了,但还是报权限不足,是什么情况呢?

Android系统有一些API及权限是需要apk具有一定的等级才能运行的。

比如 SystemClock.setCurrentTimeMillis()修改系统时间,WRITE_SECURE_SETTINGS权限好像都是需要有system级的权限才行。

也就是说UserID是system.

(2)Log里没有报权限不足,而是一些其他Exception的提示,这也有可能是权限不足造成的。

比如:我们常会想读/写一个配置文件或其他一些不是自己创建的文件,常会报java.io.FileNotFoundException错误。

系统认为比较重要的文件一般权限设置的也会比较严格,特别是一些很重要的(配置)文件或目录。



-r--r----- bluetooth bluetooth 935 2010-07-09 20:21 dbus.conf

drwxrwx--x system system 2010-07-07 02:05 data

dbus.conf好像是蓝牙的配置文件,从权限上来看,根本就不可能改动,非bluetooth用户连读的权利都没有。

/data目录下存的是所有程序的私有数据,默认情况下android是不允许普通apk访问/data目录下内容的,通过data目录的权限设置可知,其他用户没有读的权限。

所以adb普通权限下在data目录下敲ls命令,会得到opendir failed, Permission denied的错误,通过代码file.listfiles()也无法获得data目录下的内容。

七、系统上安装了多种浏览器,能否指定某浏览器访问指定页面?请说明原由。

如果在你的android系统上安装了多种浏览器,能否指定某浏览器访问指定页面?答案当然是:肯定的。

问题的关键在于我们设置了class name,也就是我们想要跳转的pakcage的activity。如果你想要跳转到其它的浏览器,只需要修改一下这个函数就OK了。

好,我们现在来让刚刚的思路来指导我们的实践。假如我们现在要直接启动UC浏览器,那么我们该怎么做呢?让我们step by step吧。

1.下载UC apk:http://i-uc.net/read.php?2

2. 用7zip解压apk文件,得到classes.dex文件

3.下载反编译dex文件工具:http://nchc.dl.sourceforge.net/project/dedexer/dedexer/1.5/ddx1.5.jar(Dedexer 项目主页: http://dedexer.sourceforge.net/)
4.执行命令:java -jar ddx1.5.jar -o -D -d c:\ c:\classes.dex

5. 得到package name是:com.uc.browser,启动的activity是:com.uc.browser.ActivityUpdate(补充:当我在这里选择采用ActivityBrowser的时候发觉权限不够,报permiss denied 异常,而且也不是我们要的那个activity,幸运的是在第二次尝试用ActivityUpdate,刚好能满足要求)

6.修改上面的代码为intent.setClassName("com.uc.browser","com.uc.browser.ActivityUpdate");
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: