Android项目结构和文件间关联
版本选择
Android 开发 SDK一般选择用最新的SDK版本, 这是Google官方建议的, App能运行的Android版本不是由SDK决定的, 是由每一个项目的minSDK决定的. SDK都是向下兼容的, SDK在不断改进中, 新的SDK会提供更强大开发工具, 而且用4.0的SDK编译的2.1的apk的执行效率会比用2.1的SDK编译的更高.
至于每个app应该用什么 minSDK, 应该根据应用具体的API来, 如果app没有用到1.6以上SDK新提供的API, 那么用1.6会在提供相同体验下兼容更多机型.
最外层项目结构
android-BasicNetworking$ tree . ├── app ├── build ├── build.gradle ├── gradle │ └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── local.properties ├── packaging.yaml └── settings.gradle
app: 实际开发的应用所在目录, gradle需要构建的子目录
build: 构建产生的文件存放目录
build.gradle: 放置全局的gradle构建配置, 这里只会指定少量配置
gradle: gradle wrapper目录
gradlew, gradlew.bat: gradle构建脚本
local.properties: 用于存放开发者本地的gradle路径信息. This file must *NOT* be checked into Version Control Systems, as it contains information specific to your local configuration. Location of the SDK. This is only used by Gradle.
packaging.yaml: 可以忽略
settings.gradle: 用于指示gradle 构建时应该包含哪个子目录, 本例中是 Application
内层项目结构
.
android-BasicNetworking$ tree . ├── app │ ├── build │ ├── build.gradle │ ├── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ └── example │ │ │ └── android │ │ │ ├── basicnetworking │ │ │ │ ├── MainActivity.java │ │ │ │ └── SimpleTextFragment.java │ │ │ └── common │ │ │ └── logger │ │ │ ├── LogFragment.java │ │ │ ├── Log.java │ │ │ ├── LogNode.java │ │ │ ├── LogView.java │ │ │ ├── LogWrapper.java │ │ │ └── MessageOnlyLogFilter.java │ │ └── res │ │ ├── drawable-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── tile.9.png │ │ ├── drawable-mdpi │ │ │ └── ic_launcher.png │ │ ├── drawable-xhdpi │ │ │ └── ic_launcher.png │ │ ├── drawable-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── layout │ │ │ └── sample_main.xml │ │ ├── menu │ │ │ └── main.xml │ │ ├── values │ │ │ ├── base-strings.xml │ │ │ ├── strings.xml │ │ │ ├── styles.xml │ │ │ ├── template-dimens.xml │ │ │ └── template-styles.xml │ │ ├── values-sw600dp │ │ │ ├── template-dimens.xml │ │ │ └── template-styles.xml │ │ ├── values-v11 │ │ │ └── template-styles.xml │ │ └── values-v21 │ │ ├── base-colors.xml │ │ └── base-template-styles.xml │ └── tests │ ├── AndroidManifest.xml │ └── src │ └── com │ └── example │ └── android │ └── basicnetworking │ └── tests │ └── SampleTests.java
build: 构建产生文件的目录
build.gradle: gradle的构建配置
src: 源文件, 其中
src.main.java
src.main.res.drawable-*: 针对不同分辨率的图片素材
src.main.res.layout: 布局文件
src.main.res.menu: 菜单配置
src.main.res.values*: string, style等文本
src.main.AndroidManifest.xml 应用的入口
tests: 测试文件
应用冷启动
冷启动是指app从0启动, 此时app进程还没有被系统进程创建. 如果系统刚启动或app进程被kill时的启动都是冷启动, 这种启动会花费最多的时间.
在冷启动期间, 系统会处理三件事:
- 载入并启动app.
- 在启动后立即显示空白的开始界面
- 创建app进程
一旦app进程已经创建, app进程就会负责下一阶段的启动, 其工作主要包含
- 创建app对象 Application.onCreate
- 启动main thread
- 创建main activity: Activity.init
- 填充view: Activity.onCreate
- 对屏幕进行布局
- 执行初始化绘制
当app进程完成第一次绘制后, 系统进程切换掉正在显示的后台窗口, 替换成main activity, 这时候用户就可以开始使用app了.
创建Application
当应用启动时, 空白启动界面会一直保持到系统完成第一次绘制. 这时候系统会切换到app窗口并允许用户开始与app交互. 如果你重载了Application.onCreate()方法, 系统将调用你的onCreate()方法, 然后生成main thread(即UI thread), 并让其创建main activity. 从这时开始, system- 和 app- 级别的进程将对应地处理app的生命周期.
创建Activity
在app创建activity时, 将处理以下任务:
- 初始化values
- 调用构造方法(constructors)
- 根据当前的生命周期状态调用各个回调方法, 例如Activity.onCreate()
一般情况下onCreate()方法对载入时间影响最大, 因为处理的事情比较多, 载入和填充视图, 填充activity运行需要的对象等.
应用热启动
热启动比冷启动要简单得多, 在热启动时, 系统需要做的就是把应用的activity放到前台. 如果应用的activities都还在内存里, 那么应用就不需要再重复对象初始化, layout inflation和rendering.
但是如果内存已经被trimming事件删除, 例如onTrimMemory(), 那么这些对象需要被重新创建. 热启动和冷启动在显示上是一样的: 系统先显示一个空白界面直到app完成第一次渲染.
应用暖启动
暖启动包含热启动中的一部分工作, 但是要少很多. 有很多状态都可以是暖启动, 例如:
用户离开app, 但是又重新打开了app. 那么app进程会继续运行, 但是app会通过调用onCreate()重新创建activity.
系统从内存中关闭了app, 然后用户又重新打开了app. 那么进程和activity都需要重新启动, 但是之前保存的状态信息可以帮助onCreate()的执行.
AndroidManifest, MainActivity 和 template-styles(Theme), layout
AndroidManifest.xml是整个应用的配置入口, 这里配置应用相关的信息:
- permission:
- users-permission:
- application
- android
- name, allowBackup, icon, label, supportsRtl, theme
- meta-data 多个
- service 多个
- receiver 多个
- activity 多个
- android
其中,
application.andorid:theme 可以通过template-styles.xml自定义, 实际一般使用 @style/Theme.AppCompat.Light.NoActionBar
application.activity: 每个activity都需要在这里注册, 而app启动时打开的activity, 需要增加 intent-filter.action:MAIN, intent-filter.category:DEFAULT和intent-filter.category:LAUNCHER, 因为启动界面的存在, 现在大多数app入口activity使用的是Splash作为MainActivity
MainActivity: 在onCreate时选择layout, layout中会根据对应的fragment对应的类调用其onCreate方法, 然后在MainActivity的onCreate中, 根据id拿到这些fragment, 对fragment里的元素进行操作.
layout: 这个layout是嵌入Theme中的主体部分的布局设计, 可以配置LinearLayout, RelativeLayout
等, Layout里面可以再嵌入Layout, Button, TextView, ImageView等元素
界面元素异步刷新
1. 在Activity里声明一个BroadcastReceiver对象实现onReceive方法, 并将这个对象通过registerReceiver注册到系统中, 在onDestroy阶段通过unregisterReceiver解除接收
2. 在Activity或Fragment里集成Listener接口, 实现onXxxx方法, 通过view实例的set方法, 将自己set成view实例的变量, 在view的操作中, 通过调用这个变量的onXxxx方法实现刷新
启动图标及生成
启动图标配置入口在AndroidManifest.xml , 分两部分,一个是ic_launcher, 一个是ic_launcher_round, 分别代表正方形圆角图标和圆形图标
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round"
上面这两个配置,对应的配置文件为 /res/mipmap-anydpi-v26/ 目录下的 ic_launcher.xml 和 ic_launcher_round.xml,这两个文件的内容都是一样的。
在GIMP等作图软件上制作图标文件,图标分辨率建议在600以上,然后File->New->Image Asset, 在Source Asset下选择Asset Type为Image, 然后在Path中选择图标文件
在下面的Scaling中选择Trim: Yes, 设置Resize比例让图标完整显示, 然后Background Layer下, Source Asset的Asset Type选择Color, 用提色器选择颜色。
点击Next后,会提示哪些文件将被覆盖。
在安装到测试机上时,因为有缓存,可能图标显示不会更新,需要通过修改手机桌面设置等手段清理缓存查看实际变化。
Service的两种模式
模式1: Where you start and stop. This can be started and stopped only once in an application.
模式2: You Bind and Unbind as required N number of times. But just bind and unbind doesn't do the job. The service first needs to be started then only it can be bound and unbound. So on start of app or whereever appropriate, Start the service. Then Bind when required. When don with it, Unbind it. That Bind-Unbind circle can go on. And Finally when you are sure you don't need it or at the end of the app Stop the Service. So the flow comes as Start -> Bind -> Unbind -> Stop <-
通过bind的方式连接Service
1 定义一个类继承Service
1.1 在里面创建一个内部类继承Binder, 里面只需要一个方法getInstance(), 用于返回这个service自己
1.2 在里面创建一个成员变量类型为IBinder, 实例就是上面的内部类
1.3 覆写onBind(Intent intent)方法, 返回上面这个成员变量
以上这些步骤, 都是为了在Activity里bindService后, 能拿到这个service的实例并把各种系统资源和配置传过去
2 在manifest.xml文件中声明上面定义的service
3 在Activity里, 初始化一个成员变量serverConnection, 为 ServiceConnection类型的一个实例, 覆写它的两个方法
3.1 覆写方法 onServiceConnected(ComponentName name, IBinder binder), 在里面从binder拿到service实例, 可以把service赋值到自己的成员变量里, 然后把context(自己), 资源和配置传进去,
3.2 覆写方法 onServiceDisconnected(ComponentName name), 在里面将service设为null
4 在Activity的onStart()方法里连接service
4.1 创建一个intent, 传进去的参数一个是this, 另一个是service的类名
4.2 在调用 bindService()之前, 要用上面的intent 调用一次 startService.
4.3 调用 bindService(serverIntent, serverConnection, BIND_AUTO_CREATE), 其中第一个参数就是刚才创建的intent, 第二个参数就是上一步初始化的成员变量
调用完bindService后, 就已经将service创建好并执行其onBind()方法了
5 之后就可以在Activity里调用service里的各个方法
6 不再使用时,调用unbindService(ServiceConnection)方法停止该服务
使用这种bind方式启动的service的生命周期如下:
onCreate() -- > onBind() --> (ServiceConnection的onServiceConnected)-->onUnbind() -- > onDestory()
注意1: 绑定服务不会调用onStart()或者onStartCommand()方法
注意2: 绑定服务由哪个context绑定后,就只能由这个context进行解绑,否则会报java.lang.IllegalArgumentException: Service not registered异常。同理,没被绑定的service,调用解绑同样回报这个异常.
注意3: Service中需要创建一个实现IBinder的内部类(这个内部类不一定在Service中实现,但必须在Service中创建它). 然后在OnBind()的方法中需返回一个IBinder实例,不然onServiceConnected方法不会调用.
注意4: ServiceConnection 的回调方法onServiceDisconnected() 在连接正常关闭的情况下是不会被调用的, 该方法只在Service 被破坏了或者被杀死的时候调用. 例如, 系统资源不足, 要关闭一些Services, 刚好连接绑定的 Service 是被关闭者之一, 这个时候onServiceDisconnected() 就会被调用.
bindService特点:bind的方式开启服务,绑定服务后如果调用者挂了,服务也会跟着挂掉。绑定者可以调用服务里面的方法。
动画Animator
通过声明一个Animator来实现动画效果.
ValueAnimator的使用
在Activity中声明ValueAnimator变量
private ValueAnimator indicatorAnimator;
在onCreate中初始化, 这里的drawable是view里的一个自定义shape, 注意这个ArgbEvaluator, 如果是颜色渐变必须要选这个, 不然按数字渐变的话, 体现到颜色上实际是乱的.
indicatorAnimator = ValueAnimator.ofInt( ContextCompat.getColor(this, R.color.colorLightGreen), ContextCompat.getColor(this, R.color.colorGreen)); indicatorAnimator.setDuration(500); indicatorAnimator.setEvaluator(new ArgbEvaluator()); indicatorAnimator.addUpdateListener((animation)-> { int value = (int) animation.getAnimatedValue(); GradientDrawable drawable = (GradientDrawable)txtMainH01T03.getBackground(); drawable.setColor(value); });
然后在对应的按钮动作或界面更新中使用
indicatorAnimator.start(); # or indicatorAnimator.end();
ObjectAnimator的使用
同样的功能也可以通过ObjectAnimator实现, 在Activity中声明变量
private ObjectAnimator indicatorAnimator;
在onCreate中初始化, 和ValueAnimator不同之处在于, 不需要再设置UpdateListener了
indicatorAnimator = ObjectAnimator.ofInt((GradientDrawable)txtMainH01T03.getBackground(), "Color", Color.RED,Color.BLUE,Color.GRAY,Color.GREEN); indicatorAnimator.setDuration(500); indicatorAnimator.setEvaluator(new ArgbEvaluator());
然后在对应的按钮动作或界面更新中使用
indicatorAnimator.start(); # or indicatorAnimator.end();
Animator的方法
animator对象的方法有
Animator.start() // start the animation from the beginning Animator.end() // end the animation Animator.cancel() // cancel the animation Animator.pause() // added in API 19; pause the animation Animator.resume() // added in API 19; resume a paused animation
start()会从开始状态启动动画, 如果有startDelay, 那么会在暂停对应时间后启动. 通过cancel() 和 end() 都可以停止动画, 区别在于cancel()会让动画停留在中间状态, 而end() 会让动画直接跳到最终状态.
异步处理Message的Handler
在安卓里不能通过子线程更新UI, 如果从UI线程以外更新, 会报ViewRootImpl$CalledFromWrongThreadException, 可以使用Handler处理.
Handler的形式很灵活, 可以作为Activity的成员变量, 然后赋值给Service及其他实例去调用, 也可以作为Service的成员变量, 由Activity去调用.
使用Activity的Handler更新UI
在MainActivity里创建Handler
# 声明变量 private MainActivityHandler handler; ... # 在onCreate里初始化 handler = new MainActivityHandler(Looper.getMainLooper()); handler.setWeakReference(this); # 类定义 public static class MainActivityHandler extends Handler { public static final int UPDATE_TEXT_01 = 0x01; public static final int START_ANIMATOR = 0x02; private WeakReference<MainActivity> weakReference; public MainActivityHandler(@NonNull Looper looper) { super(looper); } public void setWeakReference(MainActivity activity) { weakReference = new WeakReference<>(activity); } @Override public void handleMessage(@NonNull Message message) { super.handleMessage(message); MainActivity activity = weakReference.get(); switch (message.what) { case UPDATE_TEXT_01: TextView txtMain01 = activity.findViewById(R.id.main_text_01); TextView txtMainH01T02 = activity.findViewById(R.id.main_h01_text_02); GradientDrawable drawable = (GradientDrawable)txtMainH01T02.getBackground(); Button btnService = activity.findViewById(R.id.main_button_02); if (message.arg1 == 0) { txtMain01.setText(""); drawable.setColor(ContextCompat.getColor(activity, R.color.colorGray)); btnService.setText("Start Service"); } else { txtMain01.setText(StringUtil.implode(activity.pocketService.getWebServerUrls(), "\n")); drawable.setColor(ContextCompat.getColor(activity, R.color.colorGreen)); btnService.setText("Stop Service"); } break; case START_ANIMATOR: if (activity != null) { activity.indicatorAnimator.start(); } break; default: } } }
在Service里调用
msg.what = MainActivity.MainActivityHandler.UPDATE_TEXT_01; msg.arg1 = 1; handler.sendMessage(msg);