Android 高级工程师面试(一)

java基础知识准备:
java基础篇,这是我自己整理好的:
(1)ArrayList,Vector,LinkedList的区别
(2)设计模式的分类
(3)算法
(4)深入探索Java工作原理:JVM内存回收及其他
(5)架构详解 等等

http://www.itlanbao.com/ns/news.aspx?s=600030
http://www.itlanbao.com/ns/news.aspx?s=600031
http://www.itlanbao.com/ns/news.aspx?s=600034

补充:网络编程、多线程

android基础篇:
android重要知识点:
(1):android性能加载
(2):基于UDP协议的数据传输
(3):android 实现socket简单通信
(4):三级缓存
(5):图片的处理和优化
(6):第三方框架:xUtils,Gson 极光推送 第三方登录等等

http://www.itlanbao.com/ns/news.aspx?s=600032
http://www.itlanbao.com/ns/news.aspx?s=600033
http://www.itlanbao.com/ns/news.aspx?s=600036

补充:handler的机制、自定义View、进程通信、事件处理、网络处理、内存优化、插件化思想、设计模式、类加载器等等

 

一、android 性能优化

1. 如何对 android 进行性能分析

2. 什么情况下会导致内存泄漏

3. 如何避免 oom

4. android 中如何捕获未捕获的异常

5. ANR 是什么,怎样避免和解决

6. android 线程间的通信有哪几种方式

7. devik 进程、Linux进程、线程的区别

8. 描述一下 android 的系统架构

9. android 应用对内存是如何限制的?应该如何合理使用内存

10. 简述 android 应用程序结构

11. 解释 android 程序运行时权限和文件系统权限的区别

12. framework 工作方式及原理,activity 是如何生成一个view的,机制是什么

13. 多线程之间通信和多进程之间通信有什么不同,分别怎么实现

 

二、android 屏幕适配

1. 屏幕适配方式都有哪些

2. 屏幕适配的处理技术都有哪些

3. dp和px之间的关系

 

三、AIDL

1. 什么是AIDL 及如何使用

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

 

四、android 中的时间处理

1. handler 机制

2. 事件分发机制

3. 子线程发消息到主线程进程 UI 更新,除了handler 和 AsyncTask,还有什么

4. 子线程中能不能 new handler, 为什么

 

五、android中动画

1. android中的动画有哪几种,他们的特点和区别是什么?

2. 如何修改activity 进入和退出动画

3. 属性动画, 如一个button 从A 点移动到B 点,B点还是可以响应点击事件,这个原理是什么

 

六、 ContentObserver 内容观察者作用及特点

1.  自我介绍
2.  开发中都使用过哪些框架、平台
3.  都使用过哪些自定义控件
4.  自定义控件:绘制圆环的实现过程
5.  自定义控件:摩天轮的实现过程
6.  自定义控件:可拖拽排序的 GridLayout 的实现过程
7.  流式布局的实现过程
8.  项目的流程
9.  项目中常见的问题(11.9更新)
10.  即时通讯是是怎么做的?
11.  设计模式六大原则
12.  第三方登陆
13.  第三方支付
14.  常见框架分析

 

 

1、如何对 Android应用进行性能分析
一款 App 流畅与否安装在自己的真机里,玩几天就能有个大概的感性认识。不过通过专业的分析工
具可以使我们更好的分析我们的应用。而在实际开发中,我们解决完当前应用所有 bug 后,就会开
始考虑到性能的优化。
如果不考虑使用其他第三方性能分析工具的话,我们可以直接使用 ddms 中的工具,其实 ddms工
具已经非常的强大了。ddms中有 traceview、heap、allocation tracker 等工具都可以帮助我们分
析应用的方法执行时间效率和内存使用情况。
(一)TraceView简介
Traceview 是 Android 平台特有的数据采集和分析工具,它主要用于分析 Android 中应用程
序的 hotspot(瓶颈)。Traceview 本身只是一个数据分析工具,而数据的采集则需要使用 Android
SDK中的 Debug 类或者利用 DDMS工具。
(二)heap简介
heap工具可以帮助我们检查代码中是否存在会造成内存泄漏的地方。
(三)allocation tracker 简介
allocation tracker是内存分配跟踪工具


2、什么情况下会导致内存泄露
Android 的虚拟机是基于寄存器的 Dalvik,它的最大堆大小一般是16M,有的机器为 24M。因
此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现
OutOfMemory 的错误。
内存溢出的几点原因:
1. 资源释放问题
程序代码的问题,长期保持某些资源,如 Context、Cursor、IO 流的引用,资源得不到释放
造成内存泄露。
2. 对象内存过大问题
保存了多个耗用内存过大的对象(如Bitmap、XML文件),造成内存超出限制。
3. static关键字的使用问题
static是 Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是
该类的实例。所以用 static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费
过多的实例(Context的情况最多),这时就要谨慎对待了。
public class ClassName {
private static Context mContext;
//省略
}
以上的代码是很危险的,如果将 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);
}
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<Context>
mContextRef;
4. 线程导致内存溢出
线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。
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引用。


3、如何避免 OOM异常
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();//
以上代码可以优化内存溢出,但它只是改变图片大小,并不能彻底解决内存溢出。
方法2:对图片采用软引用,及时地进行recyle()操作
SoftReference<Bitmap> bitmap = new SoftReference<Bitmap>(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。
4、Android 中如何捕获未捕获的异常
(一)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
<application
android:name="com.example.uncatchexception.MyApplication"
---------------------------------------------------------------------------------
(二)Bug收集工具
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 中添加一行代码就能统计到任何异常;
try{ myMethodThatThrows(); }catch(Exception
e){ Crashlytics.logException(e); //handle your exception here! }
8.相当详细的错误信息,不仅仅是简单的打印StackTrace 信息;并且能看到最近一次crash 的机器
可用内存等信息,而不仅仅是简单统计机型和版本号。
使用连接:http://blog.csdn.net/smking/article/details/39320695


5、 ANR 是什么?怎样避免和解决 ANR(重要)
在 Android 上,如果你的应用程序有一段时间响应不够灵敏,系统会向用户显示一个对话框,
这个对话框称作应用程序无响应(ANR:Application Not Responding)对话框。用户可以选择让
程序继续运行,但是,他们在使用你的应用程序时,并不希望每次都要处理这个对话框。因此,在程
序里对响应性能的设计很重要,这样,系统不会显示ANR 给用户。
Activity 5 秒 broadcast10 秒
耗时的操作 worker thread里面完成, handler message…AsynTask , intentservice.等…
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 人肉查找
6、Android 线程间通信有哪几种方式(重要)
共享内存(变量);
文件,数据库;
Handler;
Java里的 wait(),notify(),notifyAll()


7、Devik 进程,linux 进程,线程的区别
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核心库而节省了内存空间。


8、描述一下android 的系统架构?
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 开发人员开发的程序也处于该层,而且和
内置的应用程序具有平等的位置,可以调用内置的应用程序,也可以替换内置的应用程序。


9、android 应用对内存是如何限制的?我们应该如何合理使用内存?如何限制的?
Android应用的开发语言为Java,每个应用最大可使用的堆内存受到Android系统的限制
•Android每一个应用的堆内存大小有限
•通常的情况为 16M-48M
•通过 ActivityManager的 getMemoryClass()来查询可用堆内存限制
•3.0(HoneyComb)以上的版本可以通过largeHeap=“true”来申请更多的堆内存
•NexusS(4.2.1):normal 192, largeHeap 512
•如果试图申请的内存大于当前余下的堆内存就会引发OutOfMemoryError()
•应用程序由于各方面的限制,需要注意减少内存占用,避免出现内存泄漏。
获取这个代码:
如何合理使用内存?
1、注意资源回收,像数据库,输入输出流,定位操作这样的对象,要在使用完及时关闭流。
2、少使用静态变量,因为系统将静态变量的优先级设定的很高,会最后回收。所以可能因为静
态变量导致该回收的没有回收。而回收了不该回收的内存。
3、注意大图片的缩放,如果载入的图片很大,要先经过自己程序的处理,降低分辨率等。最好
设置多种分辨率格式的图片,以减少内存消耗。
4、动态注册监听,把一些只有显示的时候才使用到的监听放进程序内部,而不是放在 manifesat
中去。
5、减少使用动画,或者适当减少动画的帧数。
6、注意自己的程序逻辑,在该关闭自己程序的控件的时候,主动关闭,不要交给系统去决定。
(这个要自己把握好, 也不是说都自己搞定, 只有那些自己确定需要关闭的对象, 自己将其关闭)


10、简述 android 应用程序结构是哪些?
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”进行修改


11、请解释下 Android 程序运行时权限与文件系统权限的区别?
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 中的显式权限声明


12、Framework 工作方式及原理,Activity 是如何生成一个 view 的,机制是什么?
所有的框架都是基于反射 和 配置文件(manifest)的。
普通的情况:
Activity 创建一个 view 是通过 ondraw 画出来的, 画这个 view 之前呢,还会调用 onmeasure
方法来计算显示的大小.
特殊情况:
Surfaceview 是直接操作硬件的,因为 或者视频播放对帧数有要求,onDraw 效率太低,不够
使,Surfaceview 直接把数据写到显存。


13、多线程间通信和多进程之间通信有什么不同,分别怎么实现?
一、进程间的通信方式
# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的
进程间使用。进程的亲缘关系通常是指父子进程关系。
# 有名管道 (namedpipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的
通信。
# 信号量(semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它
常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进
程间以及同一进程内不同线程之间的同步手段。
# 消息队列( messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符
标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
# 信号 (sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
# 共享内存(shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内
存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间
通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间
的同步和通信。
# 套接字(socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同
及其间的进程通信。


二、线程间的通信方式
# 锁机制:包括互斥锁、条件变量、读写锁
*互斥锁提供了以排他方式防止数据结构被并发修改的方法。
*读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
*条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁
的保护下进行的。条件变量始终与互斥锁一起使用。
# 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
# 信号机制(Signal):类似进程间的信号处理
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机
制。
二、 Android屏幕适配
1、屏幕适配方式都有哪些
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 分类:
ldpi Resources for low-density (ldpi) screens (~120dpi).
mdpi Resources for medium-density (mdpi) screens (~160dpi). (This is the baseline density.)
hdpi Resources for high-density (hdpi) screens (~240dpi).
xhdpi 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,布局文件清单如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<!-- 当前控件宽高为屏幕宽度的各50% -->
<TextView
android:id="@+id/tv"
android:background="#000000"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
</RelativeLayout>
在 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官方的推荐用法)


2、屏幕适配的处理技巧都有哪些
手机自适应主要分为两种情况:横屏和竖屏的切换,以及分辨率大小的不同。
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
}
}
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文件的<manifest>元素如下添加子元素
<supports-screensandroid:largeScreens="true"
android:normalScreens="true"
android:anyDensity="true"
android:smallScreens="true"
android:xlargeScreens="true">
</supports-screens>
以上是为我们的屏幕设置多分辨率支持(更准确的说是适配大、中、小三种密度)。
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 图,可以自动的依据图片上面显示的内容被拉伸和收缩。其中在编辑的时
候,灰色区域是被拉伸的,上下两个点控制水平方向的拉伸,左右两点控制垂直方向的拉伸


3、dp 和px之间的关系
dp:是 dip 的简写,指密度无关的像素。
指一个抽象意义上的像素,程序用它来定义界面元素。一个与密度无关的,在逻辑尺寸上,
与一个位于像素密度为160dpi 的屏幕上的像素是一致的。要把密度无关像素转换为屏幕像素,可以
用这样一个简单的公式:pixels=dips*(density/160)。举个例子,在 DPI 为 240 的屏幕上,1 个 DIP
等于 1.5个物理像素。
布局时最好使用dp 来定义我们程序的界面, 因为这样可以保证我们的UI在各种分辨率的屏幕上
都可以正常显示。
/**
* 根据手机的分辨率从 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 以及如何使用
①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文件生成的接口)其实是系统产生的代理对象,该
代理对象既可以跟我们的进程通信, 又可以跟远程进程通信, 作为一个中间的角色实现了进程间通信。


2、AIDL 的全称是什么?如何工作?能处理哪些类型的数据?
AIDL 全称 Android Interface Definition Language(AndRoid 接口描述语言) 是一种接口描述
语言; 编译器可以通过aidl 文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程跨界
对象访问的目的。需要完成2件事情: 1. 引入 AIDL 的相关类.; 2. 调用 aidl 产生的 class.理论上, 参
数可以传递基本数据类型和String, 还有就是Bundle的派生类, 不过在Eclipse中,目前的ADT不支
持 Bundle做为参数。


四、Android中的事件处理
1、Handler 机制
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、事件分发机制
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事件。


3、子线程发消息到主线程进行更新 UI,除了 handler 和 AsyncTask,还有什么?
1、用Activity 对象的 runOnUiThread 方法更新
在子线程中通过runOnUiThread()方法更新UI:
如果在非上下文类中(Activity),可以通过传递上下文实现调用;
2、用View.post(Runnable r)方法更新UI


4、子线程中能不能new handler?为什么?
不能,如果在子线程中直接 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 create handler inside thread that has not called Looper.prepare()");
}

 

转载于:https://my.oschina.net/u/1377772/blog/783963

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值