- Activity
Activity的概念
一个Activity通常对应一个能与用户交互的UI(用户界面)
Activity分类
FragmentActivity
ListActivity
TabActivity...
还会自己定义一个Basectivity.
Activity与View的区别
- Activity是四大组件中唯一一个用来和用户进行交互的组件。可以说Activity就是android的视图层。
- 如果再细化,Activity相当于视图层中的控制层,是用来控制和管理View的,真正用来显示和处理事件的实际上是View。
- 每个Activity内部都有一个Window对象, Window对象包含了一个DecorView(实际上就是FrameLayout),我们通过setContentView给Activity设置显示的View实际上都是加到了DecorView中。
Activity生命周期
生命周期
:
完整生命周期
onCreate
onStart
onResume
onPause
onStop
onDestroy
onRestart
可视化生命周期
onStart
onResume
onPause
onStop
onRestart
前台生命周期
onResume
onPause
-
-
- 生命周期主干
-
方法 | 描述 | 可被杀死 | 下一个 |
onCreate() | 在Activity第一次被创建的时候被调用。 这里是你做所有初始化设置的地方—创建视图、绑定数据至列表等。 如果曾经有状态记录(参阅后述Saving Activity State。),则调用此方法时会传入一个包含着此activity以前状态的包对象做为参数 | 否 | onStart() |
onStart() | 在Activity正要变得为用户所见时被调用。 数据刷新时候调用!!! 当Activity转向前台时接着调用onResume() 在Activity变为隐藏时接着知行onStop() | 否 | onResume() onStop() |
onResume() | 在Activity开始与用户进行交互之前被调用。 此时Activity位于堆栈顶部,并接收用户输入。 | 否 | onPause() |
Activity run…… | Activity开始运行…………………… |
|
|
onPasue() | 当系统将要启动另一个 Activity时调用。 此方法主要用来将未保存的变化进行持久化,停止类似动画这样耗费CPU的动作等。 这一切动作应该在短时间内完成,因为下一个Activity必须等到此方法返回后才会继续。
当Activity重新回到前台会执行onResume()【弹出对话框】。 当Activity变为用户不可见时会执行onStop()。 | 是 | onResume() onStop() |
onStop() | 当Activity不再为用户可见时调用此方法。 这可能发生在它被销毁或者另一个Activity(可能是现存的或者是新的)回到运行状态并覆盖了它。
如果Activity再次回到前台跟用户交互则执行onRestart(). 如果关闭Activity则执行onDestroy() | 是 | onRestart() onDestroy() |
onDestroy() | 在Activity销毁前调用。 这是Activity接收的最后一个调用。 这可能发生在Activity结束(调用了它的finish()方法)或因为系统需要空间所以临时的销毁了此Activity的实例时。 可以用isFinishing()方法来区分这两种情况。 | 是 | 已经完蛋了,还调用毛线!!! |
-
-
- 其他中转方法
-
onRestart() | 在Activity停止后,在再次启动之前被调用。 之后执行onStart(). | 否 | onStart() |
-
-
- 从一个界面跳转到第二个界面
-
第一个界面
OnCreate();
OnStart();
OnResume();
第二个界面
OnPause();
OnCreate();
Onstart();
OnResume();
Onstop();
返回到第一个界面
OnPause();
OnRestart();
OnStart();
OnResume();
OnStop();
OnDestroy();
-
-
横竖屏切换
-
设置Activity android:configChanges="orientation|keyboardHidden|screenSize"时,切
屏不会重新调用各个生命周期,只会执行onConfigurationChanged 方法。
通常在游戏开发, 屏幕的朝向都是写死的。
app竖着的话必须设置属性:android:screenOrientation="portrait"(landscape 是横向,portrait 是纵向)
Activity启动模式
Standard
| 默认模式,可以不用写配置。在这个模式下,都会默认创建一个新的实例。因此,在这种模式下,可以有多个相同的实例,也允许多个相同Activity叠加。 |
|
singleTop
| 可以有多个实例,但是不允许多个相同Activity叠加。即,如果Activity在栈顶的时候,启动相同的Activity,不会创建新的实例,而会调用其onNewIntent方法。 |
|
singleTask
| 只有一个实例。在同一个应用程序中启动它的时候,若Activity不存在,则会在当前task创建一个新的实例,若存在,则会把task中在其之上的其它Activity destory掉并调用它的onNewIntent方法。 如果是在别的应用程序中启动它,则会新建一个task,并在该task中启动这个Activity,singleTask允许别的Activity与其在一个task中共存,也就是说,如果我在这个singleTask的实例中再打开新的Activity,这个新的Activity还是会在singleTask的实例的task中 |
|
singleInstance
| 只有一个实例,并且这个实例独立运行在一个task中,这个task只有这个实例,不允许有别的Activity存在。 |
|
onNewIntent |
|
|
Activity 设置成窗口的样式
只需要给我们的Activity 配置如下属性即可。
android:theme="@android:style/Theme.Dialog"
配置样例
<activity android:name="com.example.mobilesafe.WatchDogActivity" android:launchMode="singleInstance"/> //也可以通过Intent.FLAG_ACTIVITY_SINGLE_TOP标志启动Activity |
退出多个Activity 的Application?
-
-
- 退出一个用finish();
- 关闭每一个已经打开的Activity
-
//伪代码
List<Activity> lists ;// 在application 全局的变量里面
lists = new ArrayList<Activity>();
lists.add(this);
for(Activity activity: lists)
{
activity.finish();
}l
ists.remove(this);
-
-
- 发送特定广播:
-
registerReceiver(receiver, filter)
-
-
- 递归退出
-
在打开新的Activity 时使用startActivityForResult,然后自己加标志,在onActivityResult 中
处理,递归关闭。
-
-
- 通过intent 的flag
-
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
Activity启动方法
- 在一个Activity中调用startActivity()方法。
直接启动Activity,不带请求码。
- 在一个Activity中调用startActivityForResult()方法。
带请求码启动Activity。
如何保存Activity状态
onSaveInstanceState()方法接受一个Bundle 类型的参数, 开发者可以将状态数据存储到这个
Bundle 对象中, 这样即使activity 被系统摧毁, 当用户重新启动这个activity 而调用它的onCreate()
方法时, 上述的Bundle 对象会作为实参传递给onCreate()方法, 开发者可以从Bundle 对象中取出
保存的数据, 然后利用这些数据将activity 恢复到被摧毁之前的状态。
@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
}
请求码与响应码
请求码(RequestCode) | 在一个业务中可能在两个按钮被单击事件中打开同一个Activity,但我们在onActivityResult事件中如何判断是哪个按钮打开了新的Activity?请求码就是为解决这个问题的,那么我们打开新的Activity时应该使用startActivityForResult(intent, 1);。其中的第二个参数就是请求码。
|
结果码(ResultCode) | 在一个业务中可能要打开多个不同的Activity,那关闭Activity时在onActivityResult事件中我们如何知道关闭的是哪一个Activity呢?结果码就是为解决这个问题的,那么我们打开新的Activity时应该使用setResult(2, intent);或在关闭Activity前调用ActivityObj.setResult(2);。其中的第一个参数就是结果码。 在onActivityResult事件中,通过判断resultCode更可知道是哪个Activity被关闭了。
|
- Intent
介绍
Intent是一种在不同组件之间传递的请求消息,是应用程序发出的请求和意图。作为一个完整的消息传递机制,Intent不仅需要发送端,还需要接收端
Intent的几种方式
-
-
- 显式意图:
-
对于明确指出了目标组件名称的Intent,我们称之为显式Intent。
-
-
- 隐式意图
-
对于没有明确指出目标组件名称的Intent,则称之为隐式Intent。
-
-
- 回传数据
-
startActivityForResult
setResult(resultCode,intent Data)
onActivityResult
requstCode 区分跳转的页面
resultCode 结果返回码
Intent
-
-
- 数据传递
-
Intent 可以传递的数据类型非常的丰富,java 的基本数据类型和String 以及他们的数组形式都可以,除此之外还可以传递实现了Serializable 和Parcelable 接口的对象
Serializable 和Parcelable 的区别
Parcelable 类比Serializable 性能高,所以推荐使用Parcelable 类。
- Serializable 在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
2.Parcelable 不能使用在要将数据存储在磁盘上的情况。尽管Serializable 效率低点,但在这
种情况下,还是建议你用Serializable 。
实现:
1.Serializable 的实现,只需要继承Serializable 即可。这只是给对象打了一个标记,系统会
自动将其序列化。
2.Parcelabel 的实现,需要在类中添加一个静态成员变量CREATOR,这个变量需要继承
Parcelable.Creator 接口。
Intent和IntentFilter的区别
- Intent 对象来表示一条消息,一个Intent 对象不仅包含有这个消息的目的地,还可以包含消息的内容,这好比一封Email,其中不仅应该包含收件地址,还可以包含具体的内容。对于一个Intent 对象,消息“目的地”是必须的,而内容则是可选项。通过Intent 可以实现各种系统组件的调用与激活.
- IntentFilter 来寻找与隐式Intent相关的对象,也可以理解为邮局或者是一个信笺的分拣系统…
这个分拣系统通过3 个参数来识别
Action: 动作view
Data: 数据uri uri
Category : 而外的附加信息
Action 匹配
- BroadcastReceiver广播接收器(收音机)
介绍
一个能接收到系统发生一些事件时发出的广播的组件
Eg:
在我们的项目中经常使用广播接收者接收系统通知,比如开机启动、
sd
挂载、低电量、外播电话、
锁屏等。如果我们做的是播放器,那么监听到用户锁屏后我们应该将我们的播放之暂停等。
分类
-
-
- 有序广播
- 无序广播
-
是完全异步的
启动方式
sendBroadcast(intent);
sendBroadcast(intent,String);
sendOrderedBroadcast(Intent,String);
内部通信实现机制
通过Android 系统的Binder 机制实现通信。
。
应用场景
在Android系统中,广播体现在方方面面:
eg:
- 当开机完成后系统会产生一条广播,接收到这条广播就能实现开机启动服务的功能;
- 当锁屏或者点亮屏幕时就会产生一条广播,接收这条广播就可以实现一些暂停或者开启一些耗电进程的功能。
- 当网络状态改变时系统会产生一条广播,接收到这条广播就能及时地做出提示和保存数据等操作;
- 当电池电量改变时,系统会产生一条广播,接收到这条广播就能在电量低时告知用户及时保存进度;
注册
-
-
- 静态注册
-
静态注册是在AndroidManifest.xml文件中配置的。
-
-
- 动态注册
-
动态注册需要在代码中动态的指定广播地址并注册。
需要注意的事项:
RegisterReceiver是android.content.ContextWrapper类中的方法,Activity和Service都继承了ContextWrapper,所以可以直接调用。在实际应用中,我们在Activity或Service中注册了一个BroadcastReceiver,当这个Activity或Service被销毁时如果没有解除注册,系统会报一个异常,提示我们是否忘记解除注册了。所以,需要在特定的地方执行解除注册操作:生命周期的onDestroy()。
有部分广播接受者,涉及到用户的敏感内容,需要在权限文件中声明。如开机完成的广播,用户电量变化的广播,用户网络状态发生改变的广播
生命周期
- 广播接收者的生命周期是非常短暂的,在接收到广播的时候创建,onReceive()方法结束之后销毁
- 广播接收者中不要做一些耗时的工作,否则会弹出Application No Response错误对话框
- 最好也不要在广播接收者中创建子线程做耗时的工作,因为广播接收者被销毁后进程就成为了空进程,很容易被系统杀掉
- 耗时的较长的工作最好放在服务中完成
- Service
需要注意的
- 多次调用bindService 的话,service 只会调用一次onBind 方法。多次调用unbindService 的话会抛出异常。
- 多次调用startService 的话,service 会多次调用onStart 方法。多次调用stopService 的话,service 只会调用一次onDestroyed 方法
- 一个原则是Service 的onCreate 的方法只会被调用一次
Service里可以弹土司么
可以. 弹吐司有个条件就是得有一个Context 上下文,而Service 本身就是Context 的子类
概念
在后台干活的一个组件
,
不与任何
UI
对应
。
为什么用服务
进程优先级, 回收时是从5~1, 从低到高
Foreground process 前台进程
Visible process 可视进程
Service process 服务进程
Background process 后台进程
Empty process 空进程
回收的优先级: 先回收空进程, 一个一个回收的, 当内存够用时, 不再回收空进程. 如果空进程回收完毕后, 内存还是不够用时, 继续向上一个一个的回收后台进程. 依次类推.
当系统内存不够用时, 需要回收服务进程时, 当系统内存又够用时, 会重新启动服务. 当用户去手动的把服务关闭时, 服务不会再重启了
作用
由于ANR对Activity和BroadcastReceiver响应时间的限制(Activity对事件响应不超过5秒,BroadcastReceiver执行不超过10秒,Service 20秒),使得在其中都不适合执行较耗时操作,这样像网络、数据库、复杂计算这类耗时操作的执行就需要一个组件来承担。Service作为Android四大组件之一,其功能之一就是耗时操作的执行,主要功能如下:
a. 执行需要长时间运行的操作,这个操作不与用户进行交互,如网络下载、大文件I/O、复杂计算、监听手机状态。
b. 应用内或应用间数据通信,Android每个应用程序都在自己的dalvik虚拟机中运行,一个应用是不允许访问其他应用的内存信息的,为此Android引入了Content Provider在不同应用间共享数据,BroadcastReceiver广播信息给不同应用程序,但Content Provider更多用于数据的共享,BroadcastReceiver广播的信息会被所有应用接收较耗费系统资源,对于两个应用间动态的进行交互还需要通过Service来完成。
启动方式
startService(intent);
bindService(Intent ,Service,int);
-
-
- 直接启动
-
Activity开启完服务后就不管服务了. Activity和服务没有关系. startService开启的服务, 只有stopService可以关闭
-
-
- 绑定启动
-
绑定服务, 生命周期方法会执行: onUnbind -> onDestory 服务销毁了.
在activity中调用service中的方法.
步骤:
调用bindService方法绑定服务
- 在Activity中定义一个连接桥的内部类, 会在bindService方法传递给service.
- 在service服务中onBind方法中返回一个IBinder接口对象.
- 在service类中定义一个IBinder的内部实现类, 在onBind方法返回.
- 当onBinder方法返回完对象后, activity中连接桥里的onServiceConnected会备调用, 其中形参IBinder service就是service类中onBind返回的对象.
- activity得到了service中的内部类对象, 点击按钮是调用内部类中的forwardBuyTicket方法, 此方法会转调服务中buyTicket方法.
生命周期
Service的生命周期 (适用于2.1及以上)
1. 被startService的
无论是否有任何活动绑定到该Service,都在后台运行。
onCreate(若需要) -> onStart(int id, Bundle args)-> onDestory()
多次startService,则onStart调用多次,但不会创建多个Service实例,只需要一次stop。该Service一直后台运行,直到stopService或者自己的stopSelf()或者资源不足由平台结束。
2. 被bindService的
onCreate() -> onBind() -> onUnBind -> onDestory()
调用bindService绑定,连接建立服务一直运行。未被startService只是BindService,则onCreate()执行,onStart(int,Bund,le)不被调用;这种情况下绑定被解除,平台就可以清除该Service(连接销毁后,会导致解除,解除后就会销毁)。
3. 被启动又被绑定
类似startService的生命周期,onCreate onStart都会调用。
4. 停止服务时
stopService时显式onDestroy()。或不再有绑定(没有启动时)时隐式调用。有bind情况下stopService()不起作用。
IntentService
-
-
- 介绍
-
IntentService 是Service 的子类,比普通的Service 增加了额外的功能
-
-
- 特征
-
- 会创建独立的worker 线程来处理所有的Intent 请求;
- 会创建独立的worker 线程来处理onHandleIntent()方法实现的代码,无需处理多线程问题
- 所有请求处理完成后,IntentService 会自动停止,无需调用stopSelf()方法停止Service;
- 为Service 的onBind()提供默认实现,返回null;
- 为Service 的onStartCommand 提供默认实现,将请求Intent 添加到队列中;
- ContentProvider
ContentProvider
-
-
- 介绍
-
一个能将某个应用的数据库数据暴露给其它应用的组件
,
可以实现不同
app
之间共享。
数据库在Android当中是私有的,当然这些数据包括文件数据和数据库数据以及一些其他类型的数据。
不能将数据库设为WORLD_READABLE,每个数据库都只能创建它的包访问,
这意味着只有由创建数据库的进程可访问它。如果需要在进程间传递数据,
则可以使用AIDL/Binder或创建一个ContentProvider,但是不能跨越进程/包边界直接来使用数据库。
一个Content Provider类实现了一组标准的方法接口,从而能够让其他的应用保存或读取此Content Provider的各种数据类型。
也就是说,一个程序可以通过实现一个Content Provider的抽象接口将自己的数据暴露出去。
外界根本看不到,也不用看到这个应用暴露的数据在应用当中是如何存储的,或者是用数据库存储还是用文件存储,还是通过网上获得,这些一切都不重要,
重要的是外界可以通过这一套标准及统一的接口和程序里的数据打交道,可以读取程序的数据,也可以删除程序的数据,
当然,中间也会涉及一些权限的问题。下边列举一些较常见的接口,这些接口如下所示。
· query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder):通过Uri进行查询,返回一个Cursor。
· insert(Uri url, ContentValues values):将一组数据插入到Uri 指定的地方。
· update(Uri uri, ContentValues values, String where, String[] selectionArgs):更新Uri指定位置的数据。
· delete(Uri url, String where, String[] selectionArgs):删除指定Uri并且符合一定条件的数据。
ContentResolver
外界的程序通过ContentResolver接口可以访问ContentProvider提供的数据,在Activity当中通过getContentResolver()可以得到当前应用的 ContentResolver实例。
ContentResolver提供的接口和ContentProvider中需要实现的接口对应,主要有以下几个。
- query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder):通过Uri进行查询,返回一个Cursor。
- insert(Uri url, ContentValues values):将一组数据插入到Uri 指定的地方。
- update(Uri uri, ContentValues values, String where, String[] selectionArgs):更新Uri指定位置的数据。
- delete(Uri url, String where, String[] selectionArgs):删除指定Uri并且符合一定条件的数据。
ContentObserver
在注册,翻译成中文就是内容观察者,目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理。ContentObserver一般和系统或第三方程序提供的Provider一起使用,这些Provider一般情况下会有一个Uri,然后ContentObserver就去监听这些Uri数据的变化,然后做出相应的处理。
- 注册ContentObserver 方法
public final void registerContentObserver(Uri uri, boolean notifyForDescendents,
ContentObserver observer)
- 取消注册ContentObserver 方法
public final void unregisterContentObserver(ContentObserver observer)
- 观察特定Uri 的步骤:
必须重载onChange()方法去处理回调后的功能实现
ContentProvider和ContentResolver中用到的Uri
在ContentProvider和 ContentResolver当中用到了Uri的形式通常有两种,一种是指定全部数据,另一种是指定某个ID的数据。
我们看下面的例子。
· content://contacts/people/ 这个Uri指定的就是全部的联系人数据。
· content://contacts/people/1 这个Uri指定的是ID为1的联系人的数据。
在上边两个类中用到的Uri一般由3部分组成。
· 第一部分是方案:"content://" 这部分永远不变
· 第二部分是授权:"contacts"
· 第二部分是路径:"people/","people/1"(如果没有指定ID,那么表示返回全部)。
由于URI通常比较长,而且有时候容易出错,且难以理解。所以,在Android当中定义了一些辅助类,并且定义了一些常量来代替这些长字符串的使用,例如下边的代码:
Contacts.People.CONTENT_URI (联系人的URI)。
在我们的实例MyProvider中是如下定义的:
public static final String AUTHORITY="com.teleca.PeopleProvider";
public static final String PATH_SINGLE="people/#";
public static final String PATH_MULTIPLE="people";
public static final Uri content_URI=Uri.parse("content://"+AUTHORITY+"/"+PATH_MULTIPLE);
ContentProvider、ContentResolver、ContentObserver 之间的关系
a. ContentProvider 内容提供者,用于对外提供数据
b. ContentResolver.notifyChange(uri)发出消息
c. ContentResolver 内容解析者,用于获取内容提供者提供的数据
d. ContentObserver 内容监听器,可以监听数据的改变状态
e. ContentResolver.registerContentObserver()监听消息。
- Service如何向Activity传递数据
一个Android程序可以由多个Activity和Servier组成,在这些程序组件之间传递数据的方法有以下几种,每种方法都有其特定的使用途径。
原始数据类型
在Activity/Servier之间传递临时性的原始数据,可以使用Intent的putExtras方法来传递数据。
若传递的数据需要长久保存,则使用SharedPreference类来完成。
传递对象
当在Activity/Servier之间传递不需要长久保存的对象时,可以使用以下几种途径:
(1)通过Application类,每个Android应用程序都有一个Application类。当你在程序的AndroidManifest.xml中给Application设定一个名字时,你的程序中就必须有一个Application的子类。这个Application子类会被Android自动实例化,并且是一个全局性的类,它的生命周期和程序的生命周期相同,你可以把一些全局性的对象保存在Application类中。Application类可以通过getApplication()获得。
(2)通过HashMap of WeakReferences传递对象。当一个Activity需要向另外一个Activity传递对象时,可以使用一个关键字把对象存在一个HashMap中,并把这个关键字通过Internt的Extras发给目标Activity,目标Activity接到该关键字后使用该关键字把对象从HashMap中取出。
在Activity/Service之间传递需要长久保存的对象时,可以使用以下的方式
- Application Preferences
- Files
- contentProviders
- SQLite DB
- Android中五种数据存储方式以及他们的特点
- SharedPreference,存放较少的五种类型的数据,只能在同一个包内使用,生成XML的格式存放在设备中
- 一种轻型的数据存储方式
- 是key-value键值对数据
- 其存储位置在/data/data/<包名>/shared_prefs目录下
- 只能存储boolean,int,float,long和String五种简单的数据类型
- 无法进行条件查询等
- SQLite数据库,存放各种数据,是一个轻量级的嵌入式数据库
- File文件,通过读取写入方式生成文件存放数据
- ContentProvider,主要用于让其他应用程序使用保存的数据
- 通过网络获取数据和写入数据到网络存储空间
- MVC,MVP,MVVM
- MVC: mvc是model,view,controller的缩写,mvc包含三个部分:
模型(model)对象:是应用程序的主体部分,所有的业务逻辑都应该写在该层。//对数据库的操作、对网络等的操作都应该在model里面处理,当然对业务计算等操作也是必须放在的该层的。
视图(view)对象:是应用程序中负责生成用户界面的部分。也是在整个mvc架构中用户唯一可以看到的一层,接收用户的输入,显示处理结果。//一般采用xml文件进行界面的描述,使用的时候可以非常方便的引入。
控制器(control)对象:是根据用户的输入,控制用户界面数据显示及更新model对象状态的部分,控制器更重要的一种导航功能,响应用户出发的相关事件,交给m层处理。// android的控制层的重任通常落在了众多的acitvity的肩上,这句话也就暗含了不要在acitivity中写过多的代码,要通过activity交割model业务逻辑层处理,这样做的另外一个原因是android中的acitivity的响应时间是5s,如果耗时的操作放在这里,程序就很容易被回收掉。
- Handler
什么是Handler?
- Handler是用来实现子线程和主线程消息传递的
- Handler消息机制用来实现子线程和主线程通信传递数据的,子线程获取到数据后不直接修改UI,面是把 数据发到主线程,主线程有一个轮询器,会立刻收到子线发过来的消息,然后拿到消息后在主线程修改UI,
- 因为ui线程主要负责执行UI的渲染、view的绘制,这些操作需要非常高的时效性,以确保界面不会卡顿,甚至无ANR,而子线程有可能需要执行比较耗时的操作,比如链接网络,获取数据库数据等,如果ui操作放到子线程,则ui操作必须等待耗时操作执行完毕才能出来,这样便会引起界面无响应 (Application Not Responding)(Activity 5秒, BroadcastReciver 10秒, Service 20秒);
做法
在主线程new一个handler
在子线程通过handler发送消息, 最终通过handler的handlermessage方法中处理子线程,直接修改UI
关系
- Handler: 是处理的意思,主要用于发消息,处理消息, 用Handler对象的sendMessage()发送消息;最终传送到Handler对象的handlerMessage()方法中,你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从Message Queue取出)所送来的消息。
- Message Queue(消息队列): 每个线程只有一个,用来存放Handler发送的消息。
- Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。
- prapare()方法:保证每个线程最多只有一个Looper对象。
- looper()方法:调用next();
- 用到 Looper.prepare();Looper.loop();就不会崩.一旦SystemClock.sleep(2000)就会崩.会报: "only the original thread that create a view hierarchy touch tis views"
- 子线程可以初使化UI,子线程不可以更新UI(这是系统的限制),是因为子线程要一个prepare(),Loop(); Toast是个特例,
- Looper.getMainLooper().post();//子线程吐Toast
- new Handler(getMainLooper()){...}//虽然在子线和调用了Loop,但是调用了getMainLooper,所以开启的是主线程
- Thread建立一个MessageQueue, 是消息循环的执行场所。
- Message是在线程之间传递信息 what携带一些数据 obj携带对象
Handler异步实现的原理和优缺点
在Handler 异步实现时,涉及到 Handler, Looper, Message, MessageQueue四个对象,实现异步的流程是主线程启动Thread(子线程)运行并生成Message放到MessageQueue,Looper从MessageQueue中获取Message并传递给Handler,Handler逐个获取Looper中的Message,并进行UI变更。
使用的优点:
结构清晰,功能定义明确
对于多个后台任务时,简单,清晰
使用的缺点:
在单个后台异步
处理时,显得代码过多,结构过于复杂(相对性)
Handler的方法
Handler可以分发Message对象和Runnable对象到主线程中, 每个Handler实例,都会绑定到创建他的线程中,
它有两个作用:
(1)安排消息或Runnable 在某个主线程中某个地方执行
(2)安排一个动作在不同的线程中执行
Handler中分发消息的一些方法
post(Runnable)
postAtTime(Runnable,long)
postDelayed(Runnable long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message,long)
sendMessageDelayed(Message,long)
dispatchMessaeg; (Looper.getMainLooper(),new Handler.Callback()...)
sThreadLocal.set(k,v); "only one Looper may be created per thread"
以上post类方法允许你排列一个Runnable对象到主线程队列中,
sendMessage类方法, 允许你安排一个带数据的Message对象到队列中,等待更新.
总结
数据简单使用
AsyncTask:实现代码简单,数据量多且复杂使用
handler+thread :相比较AsyncTask来说能更好的利用系统资源且高效
handler不加static为什么会有内存泄露.
同一个线程下的handler共享一个looper对象,消息中保留了对handler的引用,只要有消息在队列中,那么handler便无法被回收,如果handler不是static,那么使用Handler的Service和Activity就也无法被回收,即便它们的ondestroy方法被调用。这就可能导致内存泄露。当然这通常不会发生,除非你发送了一个延时很长的消息。
但把hanlder添加为static后,会发现在handler中调用外部类的方法和成员变量需要它们都定义为final,这显然是不大可能的。这里建议在你的Service或Activity中的增加一个内部static Handler类,
这个内部类持有Service或Activity的弱引用,这样就可以解决final的问题。
一是在Activity的onDestroy方法中调用handler.removeCallbacksAndMessages(null)
二是声明handler的内部类为static。
handler中遇到不加static会出现警告,而我们还要引用成员变量,该怎样解决
外部类的方法和成员变量需要它们都定义为final,
子线程发消息到主线程进行更新UI,有几几方式?
- Handler
- AsyncTask
- runOnUiThread();
- View.post(Runnable r);
子线程中能不能new handler?为什么?
- 不能,如果在子线程中直接new Handler() 会抛出异常java.lang.RuntimeException: Can'tcreate handler inside thread that has not called
- 在没有调用Looper.prepare()的时候不能创建Handler,因为在创建Handler 的源码中做了判空处理
- ListView
简介
在android开发中Listview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候,会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建。
ListView加载数据都是在public View getView(int position, View convertView, ViewGroup parent) {}方法中进行的(要自定义listview都需要重写listadapter:如BaseAdapter,SimpleAdapter,CursorAdapter的等的getvView方法),优化listview的加载速度就要让convertView匹配列表类型,并最大程度上的重新使用convertView。
Listview真充父窗体本身就是AsnycTask加载
1.getview的加载方法一般有以下三种种方式:
- 最慢的加载方式是每一次都重新定义一个View载入布局,再加载数据
- 正确的加载方式是当convertView不为空的时候直接重新使用convertView从而减少了很多不必要的View的创建,然后加载数据
- 最快的方式是定义一个ViewHolder,将convetView的tag设置为ViewHolder,不为空时重新使用即可
2.补充
当处理一些耗时的资源加载的时候需要做到以下几点,以使你的加载更快更平滑:
- 适配器在界面主线程中进行修改
- 可以在任何地方获取数据但应该在另外一个地方请求数据
- 在主界面的线程中提交适配器的变化并调用notifyDataSetChanged()方法
异步加载图片思想:
1.先从内存缓存中获取图片显示(内存缓冲)
2.获取不到的话从SD卡里获取(SD卡缓冲)
3.都获取不到的话从网络下载图片并保存到SD卡同时加入内存并显示(视情况看是否要显示)
getView(); 加载图片的方法
- 不要直接拿路径就去循环BitmapFactory.decodeFile;使用Options 保存图片大小、不要加载图片到内存去。
- 对图片一定要经过边界压缩尤其是比较大的图片,如果你的图片是后台服务器处理好的那就不需要了
- 不要直接拿个路径去取图片,而是以WeakReference(使用WeakReference 代替强引用。比如可以使用WeakReference mContextRef)、SoftReference、WeakHashMap 等的来存储图片信息。
- 在getView 中做图片转换时,产生的中间变量一定及时释放
- ImageLoader 里的线程使用了线程池,从而避免了过多线程频繁创建和销毁,如果每次总是new 一个线程去执行这是非常不可取的,好一点的用的AsyncTask 类,其实内部也是用到了线程池。在从网络获取图片时,先是将其保存到sd 卡,然后再加载到内存,这么做的好处是在加载到内存时可以做个压缩处理,以减少图片所占内存。
优化
-
-
- listView优化:
-
- 复用ConvertView,使用历史的View
- 使用静态ViewHolder,减小findViewById()的次数
- 图片错乱(智慧北京)
- 图片内存优化(另监听滚动事件,滚动不加载,静止加载)
- 异步,分页加载
View view = Layout.inflate(mContext,R.layout,null);//加载布局
View view = LayutInflater.from(mContext).inflate(R.layout.recylcerview,parent,false);
-
-
- 代码优化
- 减少不必要的全局变量
- 代码优化
-
尽量避免static成员变量引用资源耗费过多的实例,比如Context。
因为Context的引用超过它本身的生命周期,会导致Context泄漏。所以尽量使用Application这种Context类型。
你可以通过调用Context.getApplicationContext()或 Activity.getApplication()轻松得到Application对象。
-
-
-
- Cursor(游标)回收
-
-
Cursor是Android查询数据后得到的一个管理数据集合的类,在使用结束以后。应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉,因为在源代码中我们发现,如果等到垃圾回收器来回收时,会给用户以错误提示。
-
-
-
- Receiver(接收器)回收
-
-
调用registerReceiver()后未调用unregisterReceiver().
当我们Activity中使用了registerReceiver()方法注册了BroadcastReceiver,一定要在Activity的生命周期内调用 unregisterReceiver()方法取消注册
也就是说registerReceiver()和unregisterReceiver()方法一定要成对出现,通常我们可以重写Activity的onDestory()方法,在onDestory里进行unregisterReceiver操作
-
-
-
- Stream/File(流/文件)回收
-
-
主要针对各种流,文件资源等等如:InputStream/OutputStream,SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O,Bitmap图片等操作等都应该记得显示关闭。
-
-
-
- 避免创建不必要的对象
-
-
最常见的例子就是当你要频繁操作一个字符串时,使用StringBuffer代替String。还比如:使用int数组而不是Integer数组。避免创建短命的临时对象,减少对象的创建就能减少垃圾收集,进而减少对用户体验的影响。
-
-
-
- Android中慎用枚举
-
-
Android中慎用枚举,能用静态常量表示的用静态常量表示,因为枚举所需的资源开销比静态常量最少大两倍
-
-
-
- Android中避免使用Getters/Setters
-
-
在Android中,虚方法调用的代价比直接字段访问高昂许多。通常根据面向对象语言的实践,在公共接口中使用Getters和Setters是有道理的,但在一个字段经常被访问的类中宜采用直接访问。JIT情况下,直接访问速度比Getters/Setters快7倍
-
-
-
- for循环
-
-
永远不要在for的第二个条件中调用任何方法,如下面一段代码:for(int i = 0; i < this.getCount ; i++){}
Int N = this.getCount();for(int i =0; i <N; i++) {}
-
-
-
- SparseArray使用
-
-
性能优化工具的使用:
http://blog.youkuaiyun.com/yanbober/article/details/48394201
ListView 数据集改变后,如何更新ListView
使用该ListView 的adapter 的notifyDataSetChanged()方法。该方法会使ListView 重新绘制。
ListView 可以显示多种类型的条目吗
这个当然可以的
adapter 还提供了getViewTypeCount()和getItemViewType(int position)两个方法。在getView 方法中我们可以根据不同的viewtype 加载不同的布局文件
ListView 如何定位到指定位置
可以通过ListView 提供的lv.setSelection(listView.getPosition());方法。
Scrollview里面能不能嵌套listview,有什么问题出现?
能,
listview显示不全,只显示一行,listview无法滑动,焦点呗scrollcview抢占了。
1.计算每个item的高度,调用measure计算,然后用,view,setparams.height。每个item的布局必须是linearlyout,因为其他的布局里面没有
重写onmesure的方法。
- 重写listview的ontounchevent方法。当按下的时候拦截scrollview的滚动。
//重写该方法,达到listview适应scrollview的效果
procteed void onMeasure(int widthMeasureSpec,int heightMeasureSpc){
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,MeasureSpec.AT_MOST);
}
ListView 中图片错位的问题是如何产生的
图片错位问题的本质源于我们的listview 使用了缓存convertView,解决之道在于可见则显示,不可见则不显示
- JNI
快速智能开发步骤
- 配置NDK目录结构(只需一次)
- 添加本地支持:右键选中工程,Android Tools—>Add native support
- 将cpp代码改成c代码,注意Android.mk文件也要修改,刷新工程,删除obj目录
- 声明本地方法,实现对应c代码实现:Javah+全类名 生成本地方法标头文件,把头文件剪切到jni目录下,c代码引用头文件,实现头文件里的方法。
- 实现对应的c代码
- 交叉编译,一锤子敲下去
- 使用静态代码块,引用库函数,调用方法
开发中常见的JNI问题
-
-
- 错误一
-
10-31 06:42:33.645: E/AndroidRuntime(805): java.lang.UnsatisfiedLinkError: Native method not found: com.example.ndk2.MainActivity.hello_From_C:()Ljava/lang/String;
- 引入的函数库名字不对,或者没有引入
- java方法和c方法不对应
- 部署的平台有问题
-
- 错误二
-
当前工程报错,但是没有任何文件有错误,有可能Android.mk有问题
在Android.mk文件中 不要用到全角空格或者回车
-
-
- 错误3
-
在C代码中有编译时异常 , 在控制台上会提示
-
-
- 错误4
-
10-31 06:53:23.165: A/libc(2075): Fatal signal 11 (SIGSEGV) at 0x476a415c (code=2), thread 2075 (om.example.ndk2)
下面打印一大堆debug信息
C代码中 有运行时异常
应用场景
- 输出日志
- 收集用户反馈信息
- 用户登录
- 加密算法
- 电商数据加密
- 区别
padding和margin,
margin是距离父控件的边距,padding是距离文本的边距 手往里抓
Gravity和layout_Gravity
Gravity针对子控件(son)
layout_Gravity相对父控件(RelativeLayoutj里没有
rawX和X
rawX是相对于屏幕左边的距离,
X是相对当前控件左边的距离
Post和Get
Get:
1.使用方便,不安全,在url后面组拼数据
2.数据在url组拼不安全,有长度限制
Post:
1.安全,数据是通过流的方式写给服务器,数据不受限制
2.必须指定请求的数据类型
3.必须要指定请求提交的数据长度
context
getApplicationContext() 返回应用的上下文,生命周期是整个应用,应用摧毁它才摧毁
Activity.this的context 返回当前activity的上下文,属于activity ,activity 摧毁他就摧毁
getBaseContext() 返回由构造函数指定或setBaseContext()设置的上下文
visible和invisible和 gone
VISIBLE:设置控件可见
INVISIBLE:设置控件不可见 (有阴影)
GONE:设置控件隐藏 (完全隐藏)
sliddingmenu和DrawerLayout的区别
DrawerLayout是官方提供的侧滑菜单(ActionBar不动,滑过来,覆盖过去)相比SliddingMenu,它更加轻量级。
Sliddingmenu(智慧北京用到的,都在动,把A顶运去了)
rui,rul,run
URI:Uniform Resource Identifier,统一资源标识符;
URL:Uniform Resource Locator,统一资源定位符;
URN:Uniform Resource Name,统一资源名称
Https和http?
- Android中的上下文
上下文
Application
Activity
Service
参数
getContext(); //虚拟的假的上下文
构造方法; 自己写类
getApplicationContext(); //需要一个活的时间非常长的上下文
activity.this //活的短的上下文
getActivity();//在Fragment里得到Activity的方法
- 静默安装
方式一:定制ROM
Google的安全策略要求任何应用应该提示APK安装包的权限,对于一些内置特定厂商应用,可以跳过安装过程的信息加快安装,或者运营商强制安装。
方式二:查看系统源码
-
-
- 基本原理
-
在窗口中点击一个APK时,触发单击事件,PackageInstaller接收系统服务PackageManagerService传来的intent信息,传来的Intent信息中有APK的一些参数。实现的关键是区分一般APK和特定APK。
通过传给PackageManagerService的intent中添加特别的参数,PackageInstaller接收后进行判断,进行特别的隐藏安装流程。这个实现只能通过程序调用的方式安装。
安装过程的信息窗口在PackageInstallActivity.java中实现的。安装过程的信息窗口有4个:需要实现一个PakkageInstallActivityHide.JAVA的文件,去掉下面的dialog和窗口
安装权限确认窗口:installPermissionConfirm
安装进度条:installProgress
安装结果窗口:installResult
安装错误提示对话框
-
-
- 具体实现(方法一)
-
- 在Androidmainfest.xml声明一个特定的intent:android.intent.action.VIEW.HIDE,由PackageInstallActivityHide.java来接受
注意:
- 实现PakkageInstallActivityHide.java,UninstallerActivityHide.java。 只需把PakkageInstallActivity.java修改去掉dialog和对话框。
- 安装程序调用者发一个上面定义的intent即可。如下例子,静默安装/sdcard/hello.apk
- 卸载的方法类似。
- 注意,这个方法需要PackageInstall这个apk必须与系统一起编译。这个apk在/system/app/目录下面;android.intent.action.VIEW.HIDE 这个静默安装的接口需要开放给第三方。
-
- 具体实现(方法二)
-
- 从模拟器System\framework目录下提取framework.jar
- 将framework.jar后缀名改为zip,解压后提取其中的classes.dex文件
- 用dex2jar工具将classes.dex转成classes.dex.dex2jar.jar(注意新版本的dex2jar工具无法转换Android2.2的framework,建议使用dex2jar-0.0.7.8-SNAPSHOT,该工具可以从google官方站上下载到)
- 将classes.dex.dex2jar.jar改名为classes.dex.dex2jar.zip解压取出android/content/pm/目录下的PackageManager.class,IPackageInstallObserver.class,IPackageDeleteObserver.class及相关的几个class文件备用
- 找到android-sdk目录下的android.jar,改名为android.zip(注意改名前先备份一下),解压后将步骤4中取得的class文件覆盖到android对应的目录下,
- 这个时候你的android.jar已经是一个更新过的SDK了,重新打开eclipse工程,你已经可以调用方法:
void android.content.pm.PackageManager.installPackage(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName)
- Android中数据的存储方式
方式
- Shared Preferences:主要用于保存程序的系统配置信息。用来存储“key-values paires”。一般用于保存程序启动时设定的信息,以便在程序下一次启动时继续保留前一次设定的信息。
- XML解析
DOM 消耗内存
SAX 只能读取XML 无法进行修改
PULL 可以用于Android,JavaEE,
实流化/实流解
newSerializer();
setOutput(fileOutputStream,"utf-8");
startDocumet("utf-8",ture):
- Files:(内置,外置)用文件的形式保存信息。可以通过对文件的读写来获取或保存相关信息。
- SQLite:用数据库的形式保存信息。SQLite是一个开源的数据库 系统。
- NetWork:将数据保存于网络。
区别
- Shared Preferences:
Android提供用来存储一些简单的配置信息的一种机制,例如,一些默认欢迎语、登录的用户名和密码等。其以键值对的方式存储,
SharedPreferences是以XML的格式以文件的方式自动保存的,在DDMS中的File Explorer中展开到/data/data/<packagename>/shared_prefs下,以上面这个为例,可以看到一个叫做SETTING_Infos.xml的文件
- Files
(内部存储)
在Android中,其提供了openFileInput 和 openFileOuput 方法读取设备上的文件,下面看个例子代码,具体如下所示:
String FILE_NAME = "tempfile.tmp"; //确定要操作文件的文件名
FileOutputStream fos = openFileOutput(FILE_NAME, Context.MODE_PRIVATE); //初始化
FileInputStream fis = openFileInput(FILE_NAME); //创建写入流
上述代码中两个方法只支持读取该应用目录下的文件,读取非其自身目录下的文件将会抛出异常。需要提醒的是,如果调用
FileOutputStream 时指定的文件不存在,Android 会自动创建它。另外,在默认情况下,写入的时候会覆盖原文件内容,如果想把新写入的内容附加到原文件内容后,则可以指定其模式为Context.MODE_APPEND
(外部存储)
getExternalStorangeState();
getExternalStorangeDirectory();
- SQLite
SQLite是Android所带的一个标准的数据库,它支持SQL语句,它是一个轻量级的嵌入式数据库
- NetWork:
将数据上传到网络
补充:
- Shared Preferences底层使用xml,xml也可以保存数据,但是Shared Preferences只能保存键值对方式,xml能保存复杂数据
- Content provider底部还是使用了Sqlite数据库,也是算一种方式。
- 什么是异步,什么是同步?分别在什么情况下使用?
- 比如:在子线程中执行数据加载,主线程中实现界面显示就是异步,这样界面可以先显示出来,在后台加载完数据再填充界面,而同步则是在一个线程中,如果数据没有加载完,那么界面就不会展示,两个任务必须按顺序执行.
- 异步适合加载网络数据,或者大量数据
- 同步适合多线程访问或修改同一个数据,要保证数据修改前后的一致性的时候使用.
- 开发中遇到过哪些异常?是如何处理的?
- java.lang.NullPointerException 异常的解释是"程序遇上了空指针",简单地说就是调用了未经初始化的对象或者是不存在的对象,即把数组的初始化和数组元素的初始化混淆起来了。数组的初始化是对数组分配需要的空间,而初始化后的数组,其中的元素并没有实例化,依然是空的,所以还需要对每个元素都进行初始化(如果要调用的话)
- java.lang.OutOfMemoryError: bitmap size exceeds VM budget
- java.lang.ClassNotFoundException 异常的解释是"指定的类不存在"。
- java.lang.ArrayIndexOutOfBoundsException
算法异常,一般是被0除,logcat中一般有提示;
- SQLException:操作数据库异常类,日志中一般会写出来执行什么语句有问题,直接从代码中查找即可;
- java.lang.IllegalArgumentException 这个异常的解释是"方法的参数错误",比如g.setColor(int red,int green,int blue)这个方法中的三个值,如果有超过255的也会出现这个异常,因此一旦发现这个异常,我们要做的,就是赶紧去检查一下方法调用中的参数传递是不是出现了错误。
- java.lang.IllegalAccessException 这个异常的解释是"没有访问权限",当应用程序要调用一个类,但当前的方法即没有对该类的访问权限便会出现这个异常。对程序中用了Package的情况下要注意这个异常
- java.io.FileNotFoundException文件找不到异常,找到与自己写得类有关的行号,双击进入找到相关代码,文件读写异常,一般是使用完流没有关流会产生的异常,检查流是否关闭…..
- 开发时是如何做屏幕适配的?
开发一般切几套图
切一套图就够了,切图太多会造apk成打包太大
屏幕适配的处理技巧都有哪些
手机自适应主要分为两种情况:横屏和竖屏的切换,以及分辨率大小的不同。
屏幕适配
-
-
- 尺寸Dimens
- 图片适配
-
不同的手机分辨率会找不同的drawable对应的目录, 在做屏幕适配时, 会把一些对应分辨率的图片放在drawable-某个手机的分支下.
-
-
- 布局适配(Layout)
-
不同手机屏幕的大小不一样, 我们想让程序运行时, 布局也展示的不一样.
在res目录下创建layout-1280x720文件夹, 里边创建的布局文件就会加载在1280x720的手机屏幕上.
-
-
- 尺寸适配(dp)
-
在res下创建values目录, 跟着对应手机的分辨率.
android下单位: px像素, dip/dp与像素无关的单位密度.
密度比值: 密度比值 = 开跟(宽的平方 + 高的平方) / 屏幕的尺寸.
= 开跟(57600 + 102400) / 屏幕的尺寸.
= 开跟(160000) / 屏幕的尺寸.
= 400 / 2.7
= 148.14814814814814814814814814815
= 0.75
= 开跟(1638400 + 518400) / 屏幕的尺寸.
= 开跟(2156800) / 屏幕的尺寸.
= 1468.6047800548655438793375614966 / 4.7
= 312.46910213933309444241224712693
= 2.
换算: px = dip * 密度比值;
在240*320手机上,
160dip * 0.75 = 120px;
在1280*720手机上,
160dip * 2 = 320px; 320dip * 2 = 640px
代码获取密度比值: density
Resources resources = getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
System.out.println("密度比值: " + metrics.density);
-
-
- 权重适配(weight)
- 代码适配(java)
-
代码中的默认没有dp这个尺寸,而像素不会根据尺寸匹配屏幕,可以通过工具类转换dp到px.
-
-
- 其他
-
- 在自定义控件中可以根据图片原有比例算出适合屏幕的尺寸来显示.
- 提供高清图片,放在一个屏幕比较适中偏小的文件夹中,如mdpi.(这样可以解决图片过多,应用臃肿的现象)
- 开发时,是如何访问网络的?
一般在子线程中访问网络.
使用AsyncTask在doInBackground中访问网络.
URLConnection,HttpClient,AsyncHttpClient,Xutils使用get或post方法发送请求,接收响应.获得流对象取得数据.
使用HttpURLConnection(URLConnection子类)
1)调用URL对象的openConnection()方法来创建URLConnection对象
2)设置URLConnection的参数和普通请求属性
3)发送GET或POST请求
4)通过输入流读取远程资源
5)关闭释放资源
HttpClient: Apache提供了一个HttpClient,用于发送HTTP请求,接收HTTP响应。
使用HttpClient发送请求、接收响应,只需要以下几步:
1)创建HttpClient对象
2)如果需要发送get请求,创建HttpGet对象;如果发送post请求,创建HttpPost对象
3)发送参数,setParams(HttpParams params), 对于HttpPost,也可以调用setEntity(HttpEntity entity)来进行设置
4)httpClient.execute()发送请求,执行此方法返回HttpResponse对象
5)处理响应对象,调用HttpResponse的getEntity()获取HttpEntity对象,该对象封装了服务器响应内容
使用Socket通过Tcp或Udp访问网络.
1.基于TCP协议的Socket
服务器端首先声明一个ServerSocket对象并且指定端口号,然后调用Serversocket的accept()方法接收客户端的数据。accept()方法在没有数据进行接收的处于堵塞状态。(Socketsocket=serversocket.accept()),一旦接收到数据,通过inputstream读取接收的数据。
客户端创建一个Socket对象,指定服务器端的ip地址和端口号(Socketsocket=newSocket("172.168.10.108",8080);),通过inputstream读取数据,获取服务器发出的数据(OutputStreamoutputstream=socket.getOutputStream()),最后将要发送的数据写入到outputstream即可进行TCP协议的socket数据传输。
2. 基于UDP协议的数据传输
服务器端首先创建一个DatagramSocket对象,并且指点监听的端口。接下来创建一个空的DatagramSocket对象用于接收数据(bytedata[]=newbyte[1024;]DatagramSocketpacket=newDatagramSocket(data,data.length)),使用DatagramSocket的receive方法接收客户端发送的数据,receive()与serversocket的accepet()类似,在没有数据进行接收的处于堵塞状态。
客户端也创建个DatagramSocket对象,并且指点监听的端口。接下来创建一个InetAddress对象,这个对象类似与一个网络的发送地址(InetAddress serveraddress=InetAddress.getByName("172.168.1.120")).定义要发送的一个字符串,创建一个DatagramPacket对象,并制定要讲这个数据报包发送到网络的那个地址以及端口号,最后使用DatagramSocket的对象的send()发送数据。*(Stringstr="hello";bytedata[]=str.getByte();DatagramPacketpacket=new DatagramPacket(data,data.length,serveraddress,4567);socket.send(packet);)
- 大图片是如何必免OOM?图片是怎么进行缓存的?
介绍OOM:
加载大图片时会报java.lang.OutOfMemoryError :
Android 虚拟机默认为每个应用分配的堆内存空间是16M
按装程序会在/data/dalvlk-cache解决莫名消失的20几M
常见加载图片框架
Univeral ImageLoader
- 多线程下载图片,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等
- 支持随意的配置ImageLoader,例如线程池,图片下载器,内存缓存策略,硬盘缓存策略,图片显示选项以及其他的一些配置
- 支持图片的内存缓存,文件系统缓存或者SD卡缓存
- 支持图片下载过程的监听
- 根据控件(ImageView)的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存
- 较好的控制图片的加载过程,例如暂停图片加载,重新开始加载图片,一般使用在ListView,GridView中,滑动过程中暂停加载图片,停止滑动的时候去加载图片
- 提供在较慢的网络下对图片进行加载
注意:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
2个权限必须要有
Picasso
- 使用ListView,GridView的时候,自动检测Adapter的重用,取消下载,使用缓存
- 将图像进行变换,以更好的适应布局控件等,减小内存开销
- 进行图形变换,也可以写自己的变换类,但是必须实现Transformation接口
- 支持设置加载之前的图片,和加载失败后的图片
- 支持加载资源文件的图片
- 支持加载sdcard中的图片文件
Fresco
FaceBook出品
Fresco综合了之前图片加载库的优点的基础上利用本地代码做了性能上的优化
Glide (Google推荐的图片加载库)
Volley
XUtils –>BitmapUtil
方法3:使用加载图片框架处理图片,如专业处理加载图片的ImageLoader 图片加载框架。还有我
们学的XUtils 的BitMapUtils 来做处理。
内存溢出解决办法
图片处理
如何避免OOM异常?OOM异常应该如何处理?
- 缓存图像到内存,采用软引用缓存到内存,而不是在每次使用的时候都从新加载到内存,eg:
Bitmap bitmap = BitmapFactory.decodeFile(path);mImageCache.put(path, new SoftReference<Bitmap>(bitmap));
- 调整图像大小,onLoadingComplete()手机屏幕尺寸有限,分配给图像的显示区域本身就更小,有时图像大小可以做适当调整;
- 采用低内存占用量的编码方式,比如Bitmap.Config.ARGB_4444比Bitmap.Config.ARGB_8888更省内存;
- 及时回收图像,如果引用了大量Bitmap对象,而应用又不需要同时显示所有图片,可以将暂时用不到的Bitmap对象及时回收掉;bitmap.recycle();mImageCache.remove(path);
- 自定义堆内存分配大小,优化Dalvik虚拟机的堆内存分配;
图片像素
Android中图片有四种属性,分别是:
ALPHA_8: 每个像素占用1byte内存
ARGB_4444:每个像素占用2byte内存
ARGB_8888:每个像素占用4byte内存 (默认)
RGB_565: 每个像素占用2byte内存
Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。 所以在对图片效果不是特别高的情况下使用RGB_565(565没有透明度属性)
BitmapFactory.Options opt = new BitmapFactory.Options(); opt.inPreferredConfig = Bitmap.Config.RGB_565; // 图像以RGB_565读取
|
图片大小边界压缩(采样率)
方法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();//
直接使用ImageView显示bitmap会占用较多资源,特别是图片较大的时候,可能导致崩溃。
使用BitmapFactory.Options设置inSampleSize, 这样做可以减少对系统资源的要求。
属性值inSampleSize表示缩略图大小为原始图片大小的几分之一,即如果这个值为2,则取出的缩略图的宽和高都是原始图片的1/2,图片大小就为原始大小的1/4。
图片OOM的原理:
假设一张图片的宽高为2600 * 1800 像素,每个像素是ARGB_8888,则其直接拉进内 存,占用的内存大小为:
2600 * 1800 * 4byte = 18720000byte = 17.8M
若展示此图片的ImageView大小仅为260 * 180px,则加载这么大的图片是没什么意义的,经计算,压缩比为10(2600 / 260),即inSampleSize = 10;
经过边界压缩后,图片的大小为:
260 * 180 * 4byte = 0.18M
图片压缩范例代码:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; // 设置inJustDecodeBounds为true后,decodeFile并不分配空间 Bitmap bitmap = BitmapFactory.decodeFile("/sdcard/test.jpg", options); //此时返回bitmap为空, 但加载到了原始图片的长度和宽度 options.inJustDecodeBounds = false; // 这里一定要将其设置回false,因为之前我们将其设置成了true // 计算缩放比 (仅以计算高度缩放举例) int be = (int)(options.outHeight / iv.getHeight()); if (be <= 0) { be = 1; } options.inSampleSize = be; // 设置压缩比 //重新读入图片,注意这次要把options.inJustDecodeBounds 设为 false bitmap=BitmapFactory.decodeFile("/sdcard/test.jpg",options); iv.setImageBitmap(bitmap); // 设置图片到ImageView
|
图片回收
使用Bitmap过后,就需要及时的调用Bitmap.recycle()方法来释放Bitmap占用的内存空间,而不要等Android系统来进行释放。
下面是释放Bitmap的示例代码片段:
// 先判断是否已经回收 if(bitmap != null && !bitmap.isRecycled()){ // 回收并且置为null bitmap.recycle(); bitmap = null; } System.gc(); |
捕获异常
经过上面这些优化后还会存在报OOM的风险,所以下面需要一道最后的关卡——捕获OOM异常:
Bitmap bitmap = null; try { // 实例化Bitmap bitmap = BitmapFactory.decodeFile(path); } catch (OutOfMemoryError e) { // 捕获OutOfMemoryError,避免直接崩溃 } if (bitmap == null) { // 如果实例化失败 返回默认的Bitmap对象 return defaultBitmapMap; } |
三级缓存
-
-
-
- LruCache内存缓存
-
-
方法2:对图片采用软引用,及时地进行recyle()操作
SoftReference<Bitmap> bitmap = new SoftReference<Bitmap>(pBitmap);
if(bitmap != null){
if(bitmap.get() != null && !bitmap.get().isRecycled()){
bitmap.get().recycle();
bitmap = null;
}
}
这个类非常适合用来缓存图片,
-
-
-
- 它的主要算法原理是把最近最少使用的对象用强引用存储在 LinkedHashMap 中,//没有软引用,都是强引用。如果添加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。
- 必须要重写sizeOf();/设置上限,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
-
-
三级缓存解决:
-
-
-
- 内存缓存,本地缓存,网络缓存
- 在内部了个LruCache(list)
- 强,软,弱,虚(2.3,API9)就没用了
- 只能用到LruCache它其实就是HashMap封装(key值是图片的url的hash值,value是
-
-
bitmap资源,)
在过去,我们经常会使用一种非常流行的内存缓存技术的实现:
- 强引用: StrongReference垃圾回收机制就程序崩溃都不会回收.
- 软引用: SoftReference保证软件能够运行的情况, 可以回收软引用的对象.Map<String, SoftReference> map;
- 弱引用: WeakReference内存达到一定程序, 就会回收掉.
- 虚引用: PhantomReference只要垃圾回收一跑, 就会回收.
(1)因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。
(2)另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃,
所以我们这里用得是LruCache来缓存图片(三方框架),当存储Image的大小大于LruCache设定的值,系统自动释放内存.
我们应该考虑到底应该为缓存分配多大的空间,一般建议为可用内存最大值的1/8左右:
// 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。 // LruCache通过构造函数传入缓存值,以KB为单位。 int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
LruCache mBitmapCache = new LruCache(maxMemory); |
|
-
-
-
- DiskLruCache本地缓存
-
-
?
-
-
-
- 网络缓存
-
-
约定俗成的,不算缓存
优化分类
内存泄露工具
LeakCanary
布局优化
减少视图层级
减少视图层级可以有效的减少内存消耗,因为视图是一个树形结构,每次刷新和渲染都会遍历一次。(RL与LL)
ViewStub标签(懒加载)
此标签可以使UI在特殊情况下,直观效果类似于设置View的不可见性,但是其更大的意义在于被这个标签所包裹的Views在默认状态下不会占用任何内存空间。
(用的时候再去加载,第一次用刷新布局的并不会真的加载进来,只有在用的时候才真的加载进来)
include标签
可以通过这个标签直接加载外部的xml到当前结构中,是复用UI资源的常用标签。
merge标签
它在优化UI结构时起到很重要的作用。目的是通过删减多余或者额外的层级,从而优化整个Android Layout的结构。
()
过渡绘制问题
避免过渡绘制,去除不必要的background
(怎么产生的? A view覆盖了B view,A有背景,需要绘制,B也有背景需要绘制,但是这个时候B的背景就是绘制了也看不见,这就出现了过度绘制问题)
线程优化
尽量使用线程池机制(说到这儿,就得说下线程池的机制) //谷歌电子市场里讲到
核心线程数
最大线程队列
缓存线程数
使用ThreadPool而不是每次new Thread
AsyncTask相关
代码优化
无关引用
Bitmap, Thread, AsyncTask
一些对象产生后不会自动释放或需要完全执行完了才释放
申请了太多的对象. 比如: 使用ListView时, 不复用convertView,
页面背景和图片加载
在布局和代码中设置背景和图片的时候,如果是纯色,尽量使用color;如果是规则图形,尽量使用shape画图;如果稍微复杂点,可以使用9patch图;如果不能使用9patch的情况下,针对几种主流分辨率的机型进行切图。
ListView和GridView的item缓存
BroadCastReceiver、Service
绑定广播和服务,一定要记得在不需要的时候给解绑
static
长生命周期的对象持有短生命周期对象的引用: Activity中使用Handler
必免使用String 当有较多的字符创需要拼接的时候,推荐使用StringBuffer
对象内存过大问题
保存了多个耗用内存过大的对象(如Bitmap、XML 文件),造成内存超出限制。
资源释放问题
程序代码的问题,长期保持某些资源,如Context、Cursor、IO , connection流的引用,资源得不到释放造成内存泄露。
-
-
-
- 用Application代替Content.因为Application生命周期长.
- 使用WeakReference 代替强引用
-
-
Infate parmentInfate
BitmapRegionDecoder (使用有局限,支持的图片格式有限)à不压缩,避免OOM (地图,画)
BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is,false);
decoder.getHeight(); //获取原图高度
decoder.getWidth(); //获取原图宽度
decoder.decodeRegion(rect,null); //rectà可以指定创建出来的图片大小,不压缩。
ListView优化
Layout.inflate打气筒布局使用
图片乱序使用tag标记(监听滚动事件,在滚动的时候,不去加载网络)
converView
Viewholder
分页加载???(怎么处理?)
图片乱序
Inflate加载???
减少不必要的全局变量
尽量避免static成员变量引用资源耗费过多的实例,比如Context。
因为Context的引用超过它本身的生命周期,会导致Context泄漏。所以尽量使用Application这种Context类型。
你可以通过调用Context.getApplicationContext()或 Activity.getApplication()轻松得到Application对象。
Cursor(游标)回收
Cursor是Android查询数据后得到的一个管理数据集合的类,在使用结束以后。应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉,因为在源代码中我们发现,如果等到垃圾回收器来回收时,会给用户以错误提示。
Receiver(接收器)回收
调用registerReceiver()后未调用unregisterReceiver().
当我们Activity中使用了registerReceiver()方法注册了BroadcastReceiver,一定要在Activity的生命周期内调用unregisterReceiver()方法取消注册
也就是说registerReceiver()和unregisterReceiver()方法一定要成对出现,通常我们可以重写Activity的onDestory()方法,在onDestory里进行unregisterReceiver操作
Stream/File(流/文件)回收
主要针对各种流,文件资源等等如:
InputStream/OutputStream,SQLiteOpenHelper,SQLiteDatabase,Cursor,文件,I/O,Bitmap图片等操作等都应该记得显示关闭。
避免创建不必要的对象
最常见的例子就是当你要频繁操作一个字符串时,使用StringBuffer代替String。
还比如:使用int数组而不是Integer数组。
避免创建短命的临时对象,减少对象的创建就能减少垃圾收集,进而减少对用户体验的影响。
Android中慎用枚举
Android中慎用枚举,能用静态常量表示的用静态常量表示,因为枚举所需的资源开销比静态常量最少大两倍
Android中避免使用Getters/Setters
在Android中,虚方法调用的代价比直接字段访问高昂许多。通常根据面向对象语言的实践,在公共接口中使用Getters和Setters是有道理的,但在一个字段经常被访问的类中宜采用直接访问。JIT情况下,直接访问速度比Getters/Setters快7倍
for循环
永远不要在for的第二个条件中调用任何方法,如下面一段代码:
for(int i = 0; i < this.getCount ; i++){
}
=============
Int N = this.getCount();
for(int i =0; i <N; i++) {}
SparseArray使用
设计的初衷:
在效率上,比hashmap要高
但是后来使用时,还没有hashmap低好多
替代hashmap
如果hashmap的key是integer的时候,可以替换。如果不是integer,就不能替换
sparseArray的底层给key做了维护,从0到.....
性能优化工具的使用:
http://blog.youkuaiyun.com/yanbober/article/details/48394201
内存泄露
-
-
- 内存泄露的根本原因
-
Java 内存泄露的根本原因就是保存了不可能再被访问的变量类型的引用
-
-
- 内存泄露的解决办法
-
- 将线程的内部类,改为静态内部类(因为非静态内部类拥有外部类对象的强引用,而静态类则不拥有)。
- 在线程内部采用弱引用保存Context 引用。
-
- 检测内存工具heap
-
Everything:内存泄露检测
-
-
- 界面切换导致OOM
-
一般情况下,开发中都会禁止横屏的。因为如果是来回切换话,activity 的生命周期会重新销毁然后创建。
- 看看页面布局当中有没有大的图片,比如背景图之类的。去除xml 中相关设置,改在程序中设置背景图(放在onCreate()方法中):在Activity destory 时注意,drawable.setCallback(null); 防止Activity 得不到及时的释放
- 跟上面方法相似,直接把xml 配置文件加载成view 再放到一个容器里,然后直接调用this.setContentView(View view);方法,避免xml 的重复加载。
- 在页面切换时尽可能少地重复使用一些代码比如:重复调用数据库,反复使用某些对象等等......
- 做过哪些自定义控件?如何实现的?
- 自定义组合控件
- DragDrop框架实现拖放手势
- 流式布局//左右参差不齐
- 瀑布流
- ToggleButton
- pull2RefreshListView
- LazyViewPager
- SmoothProgressBar
- stickylistView
- 自定义吐司(Toast)
- Slidingmenu //Bar跟着一起动
- ViewPagerIndicator
- PullToRefresh
- ScrollTo,ScrollBy,Scroller
To是到哪里,by是至哪里.er模拟数据
- DrawerLayout实现侧滑菜单
- ActionBar标题栏
- ViewPager Indicator标题栏下面的小条
- PagerSlidingTab内部实现 (¥1000)?
- PullToRefreshLayout实现下拉刷新,上拉加载
- SwipeRefreshLayout实现下拉刷新
- GridLayout可拖拽排序// GridView 必然需要一个Adapter,GridLayout 的孩子可以横向、纵向地合并单元格
- Viewstub,include,merge标签
自定义控件的实现步骤
mearsure -- layout -- draw
-
- 写一个类继承View或你想扩展功能的控件(比如TextView)
- 在/res/value下创建一个attr.xml文件
- 重写父类的构造方法(我一般把三个都写上)在构造方法中获取到自定义是属性的值。
- 重写onDraw()方法
- 新建一个Activity(此处不再给出),在其使用的布局文件中添加自定义控件,并且可以引入自定义的命名空间,使用attr中定义的属性
具体实现
- 自定义SlidingMenu,仿ViewPager,自定义栏目定制功能,自定义扇形进度条,自定义开关.仿优酷旋转菜单.
-
- 仿VIewPager技术要点
-
- 新建MyViewpager继承ViewGroup
- 注册OnTouch事件根据用户手指移动距离判断是否需要跳到下一页
- 注册手势事件,实现用户手指滑动页面跟着用户手指滑动的效果
- 处理事件分发机制,判断ViewPager的内部子控件是否需要处理滑动事件,按照预想的效果,将事件合理分发(如上下滑动事件教给子View处理,左右滑动事件交给ViewPager处理)
- 在需要的Activity中new出来,按照需要添加子View,进行操作和显示
-
- 自定义优化菜单技术要点
-
- 用相对布局先绘制好界面
- 按照需要的效果为每层界面添加动画效果(补间动画使用时需要注意在控件消失后设置相应控件不可用或者不可点击,属性动画不需要设置)
- 重写OnKeyDown事件,对用户按下返回键也实现相应的动画
- 为菜单上每个按钮添加事件(setOnclick时最好让本类实现点击事件接口,因为菜单的点击事件可能有点多,这样代码更有条理性)
-
- 自定义ToggleButton技术要点
-
- 新建MyToggleButton类集成View
- 初始化各种图片资源
- 为自身设置OnclickEvent
- 为自身设置OntouchEvent
- 设置Touch边界,防止用户将控件滑出边界
- 在OntouchEvent里根据用户手指移动绝对值距离判断是点击事件or移动事件,定义变量区别,让两个Event分别响应,然后刷新界面
事件分发
-
-
- 描述一下Android 的事件分发机制
-
- Android 的事件分发机制主要是Touch 事件分发,有两个主角:ViewGroup 和View。Activity的Touch 事件事实上是调用它内部的ViewGroup 的Touch 事件,可以直接当成ViewGroup 处理。
- View 在ViewGroup 内,ViewGroup 也可以在其他ViewGroup 内,这时候把内部的ViewGroup当成View 来分析。
- ViewGroup 和View 组成了一棵树形结构,最顶层为Activity 的ViewGroup,下面有若干ViewGroup 节点或者View 节点,依次类推。如图:
- ViewGroup 遍历它包含着的子View,调用每个View 的dispatchTouchEven方法,①-②-⑤-⑥-⑦-③-④。
- 触摸事件由Action_Down、Action_Move、Aciton_UP 组成,其中一次完整的触摸事件中,Down 和Up 都只有一个,Move 有若干个,可以为0 个。
- onInterceptTouchEvent 有两个作用:1.拦截Down 事件的分发。2.中止Up 和Move 事件向目标View 传递,使得目标View 所在的ViewGroup 捕获Up 和Move 事件
形象图片如下:
-
-
- onTouch 和onTouchEvent 有什么区别,又该如何使用?
-
- 这两个方法都是在View 的dispatchTouchEvent 中调用的,onTouch 优先于onTouchEvent执行。如果在onTouch 方法中通过返回true 将事件消费掉,onTouchEvent 将不会再执行。
- onTouch 能够得到执行需要两个前提条件,第一mOnTouchListener 的值不能为空,第二当前点击的控件必须是enable 的
- 视频播放
芯片框架
ARM
X86
MIPS
解码
M3u8 一段时间缓存一次
F4v play插件
MP4 文件大,优质不好
Ogg 蓝光
Avi
第三方的开源的框架
-
-
- Vlc
- ijkPlayer
- Vitamio(收费的)
-
API
-
-
-
- VideoView
- 使用MediaPlayer类和SurfaceView控件来播放视频
-
-
三种方式来实现视频的播放
Android三种播放视频的方式 http://www.3lian.com/edu/2013/05-03/68151.html
ServiceView的基本语法 http://blog.youkuaiyun.com/hellogv/article/details/5985090
http://www.cnblogs.com/technology-fans/archive/2012/02/29/2373928.html
-
-
- 使用其自带的播放器。指定Action为ACTION_VIEW,Data为Uri,Type为其MIME类型。
-
Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath()+"/Test_Movie.m4v");
//调用系统自带的播放器
Intent intent = new Intent(Intent.ACTION_VIEW);
Log.v("URI:::::::::", uri.toString());
intent.setDataAndType(uri, "video/mp4");
startActivity(intent);
-
-
- 使用VideoView来播放。在布局文件中使用VideoView结合MediaController来实现对其控制。
-
Uri uri = Uri.parse(Environment.getExternalStorageDirectory().getPath()+"/Test_Movie.m4v");
VideoView videoView = (VideoView)this.findViewById(R.id.video_view);
videoView.setMediaController(new MediaController(this));
videoView.setVideoURI(uri);
videoView.start();
videoView.requestFocus();
-
-
- 使用MediaPlayer类和SurfaceView来实现,这种方式很灵活。( 使用MediaPlayer完成播放,同时界面使用SurfaceView来实现 )
-
Serfaceview 的使用步骤如下:
1、定义
可以直接从内存或者DMA等硬件接口取得图像数据,是个非常重要的绘图容器。
它的特性是:可以在主线程之外的线程中向屏幕绘图上。这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。在游戏开发中多用到SurfaceView,游戏中的背景、人物、动画等等尽量在画布canvas中画出。
2、实现
首先继承SurfaceView并实现SurfaceHolder.Callback接口
使用接口的原因:因为使用SurfaceView 有一个原则,所有的绘图工作必须得在Surface 被创建之后才能开始(Surface—表面,这个概念在 图形编程中常常被提到。基本上我们可以把它当作显存的一个映射,写入到Surface 的内容可以被直接复制到显存从而显示出来,这使得显示速度会非常快),而在Surface 被销毁之前必须结束。所以Callback 中的surfaceCreated 和surfaceDestroyed 就成了绘图处理代码的边界。
需要重写的方法
(1)public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}
//在surface的大小发生改变时激发
(2)public void surfaceCreated(SurfaceHolder holder){}
//在创建时激发,一般在这里调用画图的线程。
(3)public void surfaceDestroyed(SurfaceHolder holder) {}
//销毁时激发,一般在这里将画图的线程停止、释放。
整个过程:继承SurfaceView并实现SurfaceHolder.Callback接口 ----> SurfaceView.getHolder()获得SurfaceHolder对象 ---->SurfaceHolder.addCallback(callback) 添加回调函数---->SurfaceHolder.lockCanvas()获得Canvas对象并锁定画布----> Canvas绘画 ---->SurfaceHolder.unlockCanvasAndPost(Canvas canvas)结束锁定画图,并提交改变,将图形显示。
3、SurfaceHolder
这里用到了一个类SurfaceHolder,可以把它当成surface的控制器,用来操纵surface。处理它的Canvas上画的效果和动画,控制表面,大小,像素等。
几个需要注意的方法:
(1)、abstract void addCallback(SurfaceHolder.Callback callback);
// 给SurfaceView当前的持有者一个回调对象。
(2)、abstract Canvas lockCanvas();
// 锁定画布,一般在锁定后就可以通过其返回的画布对象Canvas,在其上面画图等操作了。
(3)、abstract Canvas lockCanvas(Rect dirty);
// 锁定画布的某个区域进行画图等..因为画完图后,会调用下面的unlockCanvasAndPost来改变显示内容。
// 相对部分内存要求比较高的游戏来说,可以不用重画dirty外的其它区域的像素,可以提高速度。
(4)、abstract void unlockCanvasAndPost(Canvas canvas);
// 结束锁定画图,并提交改变。
4、实例
这里的例子实现了一个矩形和一个计时器
- 动画
补间动画:
指的是TranslationAnimation,AlpahAnimation,ScaleAnimation,RotateAnimation;
缺点:并没有真正持久改变View的属性,就是说它内部没有一个去记录动画行为的机制;
帧动画:
Drawable Animation指的是一帧一帧播放的动画(经常用)
实现:通过animation-list来实现,写法如下:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:duration="200" android:drawable="@drawable/ic_launcher"/>
<item android:duration="200" android:drawable="@drawable/ic_launcher"/>
<item android:duration="200" android:drawable="@drawable/ic_launcher"/>
</animation-list>
属性动画(为了解决补间动画的缺点)
ObjectAnimator
Android 3.0以后引入了属性动画,属性动画可以轻而易举的办到许多View动画做不到的事。但是Android3.0以下是无法使用属性动画的,如果需要在Android3.0以下版本中使用属性动画,需要使用一个开源的jar包:(nineoldandroids.jar)
translationX,translationY;
scaleX,scaleY;
rotationX,rotationY,rotation;
alpha;
ObjectAnimtor animtor = ObjectAnimtor.ofFloat(text,"transltionY",100);
animator.setDuration(1000);
animtator.start();
问题是:ObjectAnimator只能是3.0之后才有,
那么我们如果想让属性动画兼容低版本,那么一般使用NineOldAnidroid.jar来实现属性动画
ViewPropertyAnimator
(NineOldAnidroid.jar):主要封装了属性动画和View相关的操作类,该类库是JackWharton来写的;
用法:ViewPropertyAnimator.animate(text).rotationBy(180)
.setDuration(500)
.start();
ViewHelper
直接操作view的属性来更改view的形态:
text.setTransltionY(-100);
view.setTranslationX();
view.setRotationX();
view.setAlpha();
view.setScaleX();
//如果想在低版本直接操作view的属性,则用如下方法
ViewHelper.setScaleX(text, 0);
速度插值器(弹的感觉)
改变动画的运动轨迹:
Interpolator (android.view.animation) -- 顶层接口
AccelerateInterpolator | 加速度插值器 |
AccelerateDecelerateInterpolator | 先加速后减速的插值器 |
AnticipateInterpolator | 先往变化的相反方向回退,然后甩到尾值的插值器 |
AnticipateOvershootInterpolator | 先往变化的相反方向回退,然后甩到尾值并越过尾值,再返回到尾值 |
弹跳插值器 | |
CycleInterpolator | 指定重复次数的插值器 |
DecelerateInterpolator | 减速插值器 |
LinearInterpolator | 线性(匀速)插值器 |
OvershootInterpolator | 超过尾值再返回到尾值的插值器 |
PathInterpolator | 路径插值器,可以定义路径坐标,然后可以按照路径坐标来跑动;注意其坐标并不是 XY,而是单方向,也就是可以从0~1,然后弹回0.5 然后又弹到0.7 有到0.3,直到最后时间结束。 |
自定义动画 ValueAnimator(不变型的操作)
ValueAnimator: 它只是帮我们定义了动画的执行流程,但是没有帮我们实行具体的动画逻辑,
我们需要监听动画的进度,然后在回调方法中进行自定义的动画逻辑;
用法:ValueAnimator animator = ValueAnimator.ofInt(100,1000);
//监听动画执行的进度
animator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
int animatedValue = (Integer) animator.getAnimatedValue();
// Log.e("tag", "animatorValue:"+animatedValue);
//根据动画值的变化进行我们的动画逻辑
// LayoutParams params = text.getLayoutParams();
// params.height = animatedValue;
// text.setLayoutParams(params);
text.setText(animatedValue+"");
}
});
animator.setDuration(1500);
animator.setStartDelay(1000);
animator.start();
如何修改Activity 进入和退出动画
- 通过定义Activity 的主题
- 通过覆写Activity 的overridePendingTransition 方法
一个button 从A 移动到B 点,B 点还是可以响应点击事件,这个原理是什么?
补间动画只是显示的位置变动,View 的实际位置未改变,而属性动画控件移动后事件相应就在控件移动后本身进行处理
- 设计模式,写个单利设计模式、观察者模式.
(everyThing:DesignPatterns)
总体来说设计模式分为三大类:
创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
单例模式:Appllication,SliteOpenHelper //禁用this,建议用Application
1.初始化一个该类的私有成员变量.
2.私有构造方法.
3.提供一个方法返回该类的成员变量.
工厂模式:需要啥取啥,别人帮你来做
Fragment工厂
线程工厂
建造者模式:Aliog().Builder();
结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
适配模式:A implement B, C extentd B
装饰模式:InputStream(FileInputStream(“dub.txt”))
行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
责任链模式:责任链最后必须轮到一个人, Touch分发,没人消费最后会作费
观察者模式:依懒关系,依懒对象得到通知并更新
1.写一个回调接口.
2.设置一个该接口的成员变量
3.提供一个set..Listener(该接口)方法,给另一个类来实现此接口.将实现的对象赋值给成员变量
4.在数据改变时调用接口的回调方法.
- 静态代码块、构造代码块、构造函数执行顺序?(子类父类都有,使用多态创建对象)静态方法的覆盖问题?
1.静态代码块->构造代码块->构造函数.
2.静态方法不会被覆盖,如果父类的引用指向了子类的对象,如:Parent sub = new Sub(); 这时调用sub的静态方法会执行父类的,如果子类引用只想子类对象,如:Sub sub = new Sub(); 这是调用sub的静态方法会执行子类的.
- 开启线程有哪些方法?是么情况下使用Thread?什么情况下使用Runnable?
- 写一个类继承Thread,复写run方法,使用start开启线程.
- 实现runnable接口,将实现的对象传递给Thread的构造方法,再使用start开启线程.
在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下好处:
(1)避免点继承的局限,一个类可以实现多个接口。
(2)适合于资源的共享
- 协议
HTTP
无状态,请求头,响应体
XMPP(及时通讯)
webservice 穿墙效果非常好
Socket
-
-
- Socket套接字(通讯模块)
-
用于描述IP 地址和端口,向网络发送请求或者应答网络请求,它包含TCP,UDP
网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字
通信的两端都有Socket
网络通信其实就是Socket间的通信
数据在两个Socket间通过IO流传输
Socket在应用程序中创建,通过一种绑定机制与驱动程序建立关系,告诉自己所对应的IP和port
-
-
- TCP
-
Socket ServerSocket
TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。
一个TCP连接必须要经过三次“对话”才能建立起来
- A向B发送请求 “瞅我干啥”
- B向A同意连接 “我瞅你杂地”
- A确认主机B的要求:“开始唠吧..”
-
- UDP
-
DatagramSocket, DatagramPacket
UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去!
UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。
比如,我们经常使用“ping”命令来测试两台主机之间TCP/IP通信是否正常,其实“ping”命令的原理就是向对方主机发送UDP数据包,然后对方主机确认收到数据包,如果数据包是否到达的消息及时反馈回来,那么网络就是通的。例如,在默认状态下,一次“ping”操作发送4个数据包(如图2所示)。大家可以看到,发送的数据包数量是4包,收到的也是4包(因为对方主机收到后会发回一个确认收到的数据包)。这充分说明了UDP协议是面向非连接的协议,没有建立连接的过程。正因为UDP协议没有连接的过程,所以它的通信效果高;但也正因为如此,它的可靠性不如TCP协议高。QQ就使用UDP发消息,因此有时会出现收不到消息的情况。
-
-
- TCP/IP 协议分哪几层?
- TCP协议和UDP协议的区别
-
| TCP | UDP |
是否可连接 | 面向连接 | 面向非链接 |
传输可靠性 | 可靠 | 不可靠 |
应用场合 | 传输大量数据 | 少量数据 |
速度 | 慢 | 快 |
- Fragment
生命周期
Fragment 在你们项目中的使用
-
-
- 谈谈Fragment定义以及调用方式
-
- Android 3.0出现,基于pad的
- 有两种方式:activity可以包含多个fragment
- 另一种是动态加载fragment
-
- Fragment 的好处:
-
-
-
- Fragment 可以使你能够将activity 分离成多个可重用的组件,每个都有它自己的生命周期和UI。
- Fragment 可以轻松得创建动态灵活的UI 设计,可以适应于不同的屏幕尺寸。从手机到平板电脑。
- Fragment 是一个独立的模块,紧紧地与activity 绑定在一起。可以运行中动态地移除、加入、交换等。
- Fragment 提供一个新的方式让你在不同的安卓设备上统一你的UI。
- Fragment 解决Activity 间的切换不流畅,轻量切换。
- Fragment 替代TabActivity 做导航,性能更好。
- Fragment 在4.2.版本中新增嵌套fragment 使用方法,能够生成更好的界面效果。
-
-
-
- 谈谈Fragment定义以及调用方式
-
如何切换fragement,不重新实例化
翻看了Android 官方Doc,和一些组件的源代码,发现replace()这个方法只是在上一个Fragment不再需要时采用的简便方法.
正确的切换方式是add(),切换时hide(),add()另一个Fragment;再次切换时,只需hide()当前,show()另一个。
Fragment 的replace 和add 方法的区别
Fragment 本身并没有replace 和add 方法,这里的理解应该为使用FragmentManager 的replace
和add 两种方法切换Fragment 时有什么不同。
我们经常使用的一个架构就是通过RadioGroup 切换Fragment,每个Fragment 就是一个功能模块
Fragment 如何实现类似Activity 栈的压栈和出栈效果的?
Fragment 的事物管理器内部维持了一个双向链表结构,该结构可以记录我们每次add 的
Fragment 和replace 的Fragment,然后当我们点击back 按钮的时候会自动帮我们实现退栈操作。
- application的framework,library分别有什么
ramework是程序的结构.类库是构建程序的部件.
- 为什么不能在broadcastreceiver中开启子线程
BroadcastReceiver的生命周期很短,子线程可能还没有结束BroadcastReceiver就先结束了。
BroadcastReceiver一旦结束,此时BroadcastReceiver的所在进程很容易在系统需要内存时被优先杀死,
因为它属于空进程(没有任何活动组件的进程)。如果它的宿主进程被杀死,那么正在工作的子线程也会被杀死。
所以采用子线程来解决是不可靠的。
- jvm与dalvik的区别
| Jvm | Dalvik |
编译后文件格式 | .java->.class->.jar | .java->.class->.dex->.odex |
基于架构 | 基于class内存的架构 | 基于寄存器的架构 |
- 架构不同:JVM使用栈架构;Dalvik使用的是寄存器,数据是加载到CUP的寄存器上的。
- JVM加载的.class文件,Dalvik加载的是.dex文件,对内存的分配情况做了优化。
- jvm是怎样工作的
- 当前的activity是singletop的,而且在栈的最顶端,如果我们从service中执行startAcitvity来开启这个activity,会执行哪些方法
onResume()-onRestart()
- Android框架
开源框架是指一些库和架包,譬如gson, 就是拿来用的.
第三方框架得需要集成进去,得需要获取appkey, 是别人的框架.
二维码生成识别:
Zxing
数据库
-
-
-
-
-
-
- Greendao
- Relam
-
-
-
-
-
注入库
- Butterknife
- Dgger2
事件总线
- EventBus
- Otto
JS和原生互调
1.JSBright
网络交互
-
-
- HttpUrlContent
- HttpClient
- Okhttp
- Retrofit
-
是用动态代理,注解实现的,底层是okhttp
-
-
- Volley
-
用过volley吗,主要提供哪些功能
-
-
-
-
-
-
- 自动高度网络请求
- 支持标准的HTTP缓存协议
- 支持请求优先级设置(4级)
- 支持取消单个或多个请求
- 易于定制,扩展性强,比如Retry & Backoff
- 强大的网络请求能力让你轻松的发送异步请求填充UI数据
- 提供高度和跟踪工具
-
-
-
-
-
数据转换
Gson,fastjson,jackson
UI绑定
Butterknife,dageer2
业务辅助
EventBus,OTTO
登录注册分享支付统计推送地图
ShareSDK,友盟,百度,谷歌,高德,云巴,极光,支付宝,银联 ,诸葛io
- 跨进程通信
原理
Android中的跨进程通信采用的是Binder机制,其底层原理是共享内存。
为什么不能直接跨进程通信?
为了安全考虑,应用之间的内存是无法互相访问的,各自的数据都存在于自身的内存区域内。
如何跨进程通信?
要想跨进程通信,就要找到一个大家都能访问的地方,例如硬盘上的文件,多个进程都可以读写该文件,通过对该文件进行读写约定好的数据,来达到通信的目的。
- Binder在linux层面属于一个驱动,但是这个驱动不是去驱动一个硬件,而且驱动一小段内存。
- 不同的应用通过对一块内存区域进行数据的读、写操作来达到通信的目的。
- 不同的应用在同一内存区域读写数据,为了告知其他应用如何理解写入的数据,就需要一个说明文件,这就是AIDL。当两个应用持有相同的AIDL文件,就能互相理解对方的的意图,就能做出相应的回应,达到通信的目的。
-
- AIDL
-
- aidl 是Android interface definition Language 的英文缩写,意思Android 接口定义语言。
- 使用aidl 可以帮助我们发布以及调用远程服务,实现跨进程通信。
- 将服务的aidl 放到对应的src 目录,工程的gen 目录会生成相应的接口类
我们通过bindService(Intent,ServiceConnect,int)方法绑定远程服务
-
-
- AIDL 的全称是什么,如何工作,能处理哪些类型的数据
-
- 引入AIDL 的相关类
- 调用aidl 产生的class.理论上, 参数可以传递基本数据类型和String, 还有就是Bundle 的派生类, 不过在Eclipse 中,目前的ADT 不支持Bundle 做为参数。
- 支付宝
支付宝付款的实现流程:
1.去支付宝sdk网站注册账号,然后设置程序包名,它会根据包名生成appKey,就是加密的字符串
2.将appKey配置到manifesst中,如何配置看demo,
3.支付的时候调用对应的pay方法,
4.支付的结果会在监听里面得到state值,根据state判断是否成功和取消支付等,然后进行UI提示
- 响应式编程
- Rxjava
- RxAndroid
- 常用总结
Andall和add区别:Add是存入一个item, addall存入一个list
- 所有线程
Android 线程间通信有哪几种方式(重要)
- 共享内存(变量)
- 文件,数据库
- Handler
- Java 里的wait(),notify(),notifyAll()
进程间的通信方式
- 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
- 有名管道(namedpipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 信号量(semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 消息队列( messagequeue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 信号(sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 共享内存(shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
- 套接字(socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
Thread
Runnable
AsyncTask 更轻量级
-
-
- 底层处理
-
底层使用本地线程池机制:
- AsyncTask 内部也是Handler 机制来完成的,只不过Android 提供了执行框架来提供线程池来执行相应地任务
- AsyncTask 只应该用来执行耗时时间较短的任务
- 核心线程数:线程池中保存的线程数,包括空闲线程,默认为5个,线程池中允许的最大线程数,固定为128个+10个阻塞线程,当线程数大于核心线程数时,如果线程池中中线程数大于核心线程数5超过一秒事,终止多余的线程,保留五个核心线程数。执行前用于保持任务的队列,此队列仅保持execute方法提交的Runnable任务,固定容量为10,执行程序创建新线程时使用的工厂
-
- AsyncTask介绍
-
Android的AsyncTask比Handler更轻量级一些(只是代码上轻量一些,而实际上要比handler更耗资源),适用于简单的异步处理。
首先明确Android之所以有Handler和AsyncTask,都是为了不阻塞主线程(UI线程),且UI的更新只能在主线程中完成,因此异步处理是不可避免的。
Android为了降低这个开发难度,提供了AsyncTask。AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务。
AsyncTask直接继承于Object类,位置为android.os.AsyncTask。要使用AsyncTask工作我们要提供三个泛型参数,并重载几个方法(至少重载一个)。
AsyncTask定义了三种泛型类型 Params,Progress和Result。
Params 启动任务执行的输入参数,比如HTTP请求的URL。
Progress 后台任务执行的百分比。
Result 后台执行任务最终返回的结果,比如String。
使用过AsyncTask 的同学都知道一个异步加载数据最少要重写以下这两个方法:
doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。
onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回
有必要的话你还得重写以下这三个方法,但不是必须的:
onProgressUpdate(Progress…) 可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。
onPreExecute()这里是最终用户调用Excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。
onCancelled() 用户调用取消时,要做的操作
使用AsyncTask类,以下是几条必须遵守的准则:
Task的实例必须在UI thread中创建;
execute方法必须在UI thread中调用;
不要手动的调用onPreExecute(), onPostExecute(Result),doInBackground(Params...), onProgressUpdate(Progress...)这几个方法;
该task只能被执行一次,否则多次调用时将会出现异常;
-
-
- AsyncTask实现的原理和适用的优缺点
-
AsyncTask,是android提供的轻量级的异步类,可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度(可以通过接口实现UI进度更新),最后反馈执行的结果给UI主线程.
使用的优点:简单,快捷,过程可控
使用的缺点:在使用多个异步操作和并需要进行Ui变更时,就变得复杂起来.
Handler 轻量级
-
-
-
-
- Messagequeue
- Looper
- HandlerThread
- HandlerMessage
-
-
-
线程池
-
-
- 线程池概述
-
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
个数 = 线程池% 2 + 1 ?
-
-
- 用Executors工厂类来产生线程池
-
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
这些方法的返回值是ExecutorService对象,该对象表示一个线程池
- Android高级
- 性能优化
Bug 收集工具Crashlytics
Crashlytics 是专门为移动应用开发者提供的保存和分析应用崩溃的工具。国内主要使用的是友盟做数据统计。
- WebView
WebView 是View 的子类,可以让你在activity 中显示网页。
- Android 应用中验证码登陆都有哪些实现方案
从服务器端获取图片, 通过短信服务,将验证码发送给客户端这两种
- 定位项目中,如何选取定位方案,如何平衡耗电与实时位置的精度?
始定位,Application 持有一个全局的公共位置对象,然后隔一定时间自动刷新位置,每次刷新成功
都把新的位置信息赋值到全局的位置对象,然后每个需要使用位置请求的地方都使用全局的位置信息
进行请求。该方案好处:请求的时候无需再反复定位,每次请求都使用全局的位置对象,节省时间。
该方案弊端:耗电,每隔一定时间自动刷新位置,对电量的消耗比较大。
方案2:按需定位,每次请求前都进行定位。这样做的好处是比较省电,而且节省资源,但是请求时
间会变得相对较长。
- andorid 应用第二次登录实现自动登录?
我来说一个实际案例的流程吧。前置条件是所有用户相关接口都走https,非用户相关列表类数据
走http。1. 第一次登陆getUserInfo 里带有一个长效token,该长效token 用来判断用户是否登
陆和换取短token 2. 把长效token 保存到SharedPreferences 3. 接口请求用长效token 换取短
token,短token 服务端可以根据你的接口最后一次请求作为标示,超时时间为一天。4. 所有接口
都用短效token 5. 如果返回短效token 失效,执行3 再直接当前接口6. 如果长效token 失效(用
户换设备或超过两周),提示用户登录。
- jni 的调用过程?
1.安装和下载Cygwin,下载Android NDK。
-
- ndk 项目中JNI 接口的设计
- 使用C/C++实现本地方法
- JNI 生成动态链接库.so 文件
- 将动态链接库复制到java 工程,在java 工程中调用,运行java 工程即可
- 手机APP 安全登录的几种方式?
密码加密传输、动态密码、验证码等
- 即时通讯是是怎么做的??
- 第三方登陆??
- 第三方支付??