1 概述
安卓生命周期
2 Application
Application就是安卓的顶层容器,是应用级别的生命周期
- onCreate,应用启动时回调,一般用作初始化全局变量
- onTerminate,应用关闭时回调
- onConfigurationChanged,应用配置发生改变时回调,一般是横竖屏切换时回调的
3 Activity
Activity就是安卓的ui容器,用户看到的一个页面就是一个activity
3.1 生命周期
3.1.1 概述
首先,放出大图,展示activity的生命周期,一般来说,activity只会停留在如下的几个状态:
- onResume,当前activity是前台activity,正在执行中
- onPause,当前activity不是前台activity,但是仍然被用户看到(前台activity是透明的)
- onStop,当前activity不是前台activity,并且不被用户看到,但是用户之后可能会返回到当前activity上,状态不能丢失。
- onDestroy,当前activity不是前台activity,并且不被用户看到,而且用户不可能再返回到当前activity上
一般来说,我们是在onCreate,onDestroy上控制ui元素的出现与消失,onResume,onPause上监控activity的真实停留时间。
3.1.2 切换到下一个页面
如果Activity的A跳转到Activity的B,会发生什么情况?
如果Activity的B是不透明的,那么:
- A: onPause->onStop
- B: onCreate->onStart->onResume
如果Activity的B是透明的,那么:
- A: onPause
- B: onCreate->onStart->onResume
3.1.2 返回到上一个页面
如果Activity的B返回到Activity的A,会发生什么情况?
如果Activity的B是不透明的,那么:
- A: onRestart->onStart->onResume
- B: onPause->onStop->onDestroy
如果Activity的B是透明的,那么:
- A: onResume
- B: onPause->onStop->onDestroy
3.1.3 最小化当前activity
如果用户正在看Activity的A,然后按下了Home键,会发生什么情况?
- A: onPause->onStop
然后用户点击App的图标来恢复Activity的A,会发生什么情况?
- A: onRestart->onStart->onResume
3.1.4 旋转屏幕
如果用户正在看Activity的A,然后旋转屏幕,会发生什么情况?
如果该activity没有添加configChanges属性
- A: onPause->onStop->onDestroy->onCreate->onStart->onResume,完全就是一个重建activity的过程
如果该activity添加configChanges属性
<activity android:name=".Test"
android:configChanges="orientation|keyboard">
</activity>
那么A的activity的生命周期没有触发,只会触发onConfigurationChanged属性
3.1.5 退出最后一个activity
如果用户正在看Activity的A(当前app的最后一个),然后按下了Back键,会发生什么情况?
- A: onPause->onStop->onDestroy
3.2 任务栈
每个activity都有一个附属的任务栈,默认整个app中只有一个activity的任务栈,当跳转activity时,向栈顶部中推入新建的activity,当退出activity时,向栈顶部中推出activity。但是,通过设置activity的launchMode属性,我们可以改变这个行为
launchMode的四种
- standard,每次激活Activity时都会创建Activity,并放入任务栈中。
- singleTop,如果在任务的栈顶正好存在该Activity的实例, 就重用该实例,否者就会创建新的实例并放入栈顶(即使栈中已经存在该Activity实例,只要不在栈顶,都会创建实例)。
- singleTask,如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的onNewIntent())。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移除栈。如果栈中不存在该实例,将会创建新的实例放入栈中。
- singleInstanse,在一个新栈中创建该Activity实例,并让多个应用共享改栈中的该Activity实例。一旦改模式的Activity的实例存在于某个栈中,任何应用再激活改Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中。
3.3 注意点
- activity的生命周期一般用来做资源的回收工作,例如监测onDestroy的回调来清除过期数据。但是onDestroy的触发并不是实时的,A的activity已经关闭了,A的onDestroy回调可能会在B的activity启动时才会触发。安卓只保证肯定会触发,但没有保证触发的时机。
4 Service
Service就是安卓的服务,相当于服务器的daemon,用来跑一些常驻任务的,例如是后台清理sd卡数据。
5 ContentProvider
ContentProvider就是安卓的本地数据服务,每个app在安卓中数据是独立,不能互通的,那么我们的a的app需要获取b的app数据怎么办?用ContentProvider,可以达成在不用启动b的activity的情况下,a的app就能悄悄地获得b的app数据了,当然这个需要b的app支持了。
6 BoradcastReceiver
BoradcastReceiver就是安卓的广播接收器,相当于服务器的消息接收器,用来接收消息,定时器等信息的,也被一些厂商利用定时器来做app的无限自启。
7 进程与线程
7.1 统一进程
默认情况下,同一个app的四大组件都是运行在同一个进程下,而且组件间互不依赖启动。例如即使用户没有启动一个Activity,该app的BoardcastReceiver仍然能启动的。并且,安卓中app不能控制进程的关闭时刻,即使该app的所有activity都被代码强制finish了,但是该app进程仍然会继续空跑的。直到用户强制关闭该app进程,或系统回收该进程资源。
<service android:enabled="true" android:name=".TestService" android:process=":remote"/>
当然,你可以通过代码强制控制某个service运行在另外一个主进程上。
7.2 统一线程
默认情况下,同一个app的四大组件不仅都是运行在同一个进程下,而且还是运行在同一个线程线程中!所以,组件间直接控制对方的逻辑是线程安全的,Receiver可以直接控制activity的组件,这是木有问题的。与此同时,带来的后果是,你需要小心翼翼组件的运行代码,避免一个长时间操作将整个进程都阻塞了。
7.3 最佳实践
7.3.1 单一业务线程操作UI原则
无论是控制业务数据,还是操控UI,请尽可能在UI线程上执行,避免多线程修改同一业务数据,造成数据冲突等难以调试的并发问题。
7.3.2 长阻塞任务异步操作
安卓中是禁止在UI线程直接进行网络操作的,否则会报出NetworkOnMainThreadException异常,这也很正常,毕竟在UI线程进行长阻塞任务操作,是会导致用户ANR问题发生的。其实,除了网络操作外,还会以下几种操作是十分不推荐在UI线程中执行的:
- sleep操作,延时执行
- io操作,读写网络,读写SD卡
- cpu操作,压缩解压图片
初学者的解决办法一般是一个任务一个Thread来解决,导致进程内的线程资源急剧增加,试想每个网络请求都开一个线程来单独处理是什么情况。好的办法应该是建立线程池,让处理扔到线程池中处理,然后回调到主线程中执行。
public static void GetAsync(final Context context, final String url, Map<String,String> params, final FinishListener<String> listener){
HttpUrl.Builder builder = HttpUrl.parse(url).newBuilder();
if( params != null ){
for(String singleName : params.keySet() ){
String singleValue = params.get(singleName);
builder.addQueryParameter(singleName,singleValue);
}
}
String httpUrl = builder.build().toString();
Request request = new Request.Builder()
.url(httpUrl)
.build();
getInstance().newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
new Handler(context.getMainLooper()).post(new Runnable() {
@Override
public void run() {
listener.onFinish(1,"获取"+url+"数据失败",null);
}
});
}
@Override
public void onResponse(Call call, final Response response) {
if( response.code() != 200 ){
new Handler(context.getMainLooper()).post(new Runnable() {
@Override
public void run() {
listener.onFinish(1,"获取"+url+"数据失败,状态码为:"+response.code(),null);
}
});
}
try {
final String data = response.body().string();
new Handler(context.getMainLooper()).post(new Runnable() {
@Override
public void run() {
listener.onFinish(0, "", data);
}
});
}catch(final Exception e){
e.printStackTrace();
new Handler(context.getMainLooper()).post(new Runnable() {
@Override
public void run() {
listener.onFinish(1,"获取"+url+"数据失败:"+e.getMessage(),null);
}
});
}
}
});
}
封装OkHttp,来实现异步的网络请求,注意回调时是进入到主线程中的
public static void getBitmapFromUrlAsync(Context context, final String url, int maxWidth, int maxHeight,final FinishListener<Bitmap> listener){
if( maxWidth == 0 ){
maxWidth = SimpleTarget.SIZE_ORIGINAL;
}
if( maxHeight == 0 ){
maxHeight = SimpleTarget.SIZE_ORIGINAL;
}
Glide.with(context).load(url).asBitmap().centerCrop().into(new SimpleTarget<Bitmap>(maxWidth,maxHeight) {
@Override
public void onLoadFailed(Exception e, Drawable errorDrawable){
listener.onFinish(1,"加载图片"+url+"失败"+e.getMessage(),null);
}
@Override
public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
listener.onFinish(0,"",resource);
}
});
}
封装Glide,来异步的图片压缩与处理
sleep的话就可以用new Handler().postDelayed()来实现了,这里不多说了。其实不仅仅是android,基本上所有ui程序都是这样,长阻塞操作都在单独的线程池来处理,然后回调给主线程来执行业务逻辑。Chromium架构,ios上的Network操作都是如此。只是这么写多了,又导致了回调地狱的问题,什么时候java也有async,await就能完美解决了。
8 Intent与PendingIntent
区别与应用 Filter RegisterService
9 AndroidManifest.xml
9.1 图标与名字
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:hardwareAccelerated="true"
android:debuggable="true">
</application>
Application上的图标是icon属性,注意小米的桌面有bug,更改程序图标后需要重启手机才能刷新。
9.2 权限
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECEIVE_USER_PRESENT" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
一般都需要这些权限的,全加上就对了
10 签名
10.1 生成签名文件
keytool -genkey -alias android.keystore -keyalg RSA -validity 20000 -keystore android.keystore
-validity 20000代表有效期天数,命令完成后,当前目录中会生成android.keystore
10.2 查看签名文件
keytool -list -v -keystore android.keystore
根据签名文件来查看签名信息,包括签名的MD5,SHA1,SHA256,签名算法等等
10.3 签名
jarsigner -digestalg SHA1 -sigalg MD5withRSA -verbose -tsa https://timestamp.geotrust.com/tsa -keystore /Users/fishedee/Project/Crossapi.wiki/file/xiaoyusan.keystore -signedjar Magick.apk origin.apk xiaoyusan.keystore
输入后就会提示你输入密码来签名apk文件了
如果apk文件未签名就安装,就会提示以上的这个错误
11 FAQ
11.1 activity的onDestroy有时候不触发
安卓中,activity关闭时会触发onDestroy回调,不过这个回调的时机不一定准确,可能会在下一个activity新建时才会触发上一个activity的onDestroy,不过回调始终都会发生的
11.2 apk第一次安装时最小化后恢复会新建activity
这是安卓的bug,在这里和这里都有提及,目前没有万能的解决办法。我解决的方法是,因为每次最小化再新建的activity都是任务栈里最底部的activity,所以我会在新建这个最底部的activity的时候加一些标识位到intent的extra中,如果下次再启动这个activity还是有这个extra属性的,直接finih掉这个activity。
11.3 切换activity时很慢有黑屏体验
切换activity时,intent会等待新activity的onCreate与onResume都执行完毕后,才会完整显示这个新的activity。如果新activity的onCreate与onResume的操作比较多,切换时动画就会滞留。解决办法是
- 数据的操作尽可能放在SplashActivity完成,或者使用异步操作
- 页面的操作使用分段式载入,先显示第一屏页面的组件,在onResume后使用100ms延后再加载第二屏以后的组件
11.4 android 5以上的权限问题
android m以后分为普通权限和危险权限,光在manifest加还不行,每次启动进程后使用都要问一下用户才可以。看这里,觉得写代码的话,可以用android的easypermissions
- 本文作者: fishedee
- 版权声明: 本博客所有文章均采用 CC BY-NC-SA 3.0 CN 许可协议,转载必须注明出处!