安卓四大组件

2016-07-31 fishedee 前端

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

只出现onPause,不出现onStop

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 生成签名文件

Screen Shot 2016-09-30 at 7.12.26 P
keytool -genkey -alias android.keystore -keyalg RSA -validity 20000 -keystore android.keystore

-validity 20000代表有效期天数,命令完成后,当前目录中会生成android.keystore

10.2 查看签名文件

Screen Shot 2016-09-30 at 7.15.47 P
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文件了

Screen Shot 2016-09-30 at 7.10.13 P

如果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

相关文章