小丁的屋舍
Android Studio 近期学习复盘&心得
2023-07-17
查看标签
0次浏览

利用暑假在“BiliBili 开放大学”来学习一下 Java 以及 Android Studio ,跟着 longway777 老师从新手的角度下学习了 Android 开发的基本内容,目前已经学到 P13 了,今天晚上就不继续学了,来复盘一下最近学的东西,我将按照 内容优先>集数顺序 由表及里的进行回顾复盘。

安装并对 AS 的基础组件、布局进行认识

p1 Android Studio下载和安装

俗话说要致富,先下载编译工具!在 Android Studio 官网 下载并安装编译工具,Android Studio 也是用于开发 Android 应用的官方 IDE,提供 Compose 设计工具、灵活的构建系统和 Android 模拟器。
image.png
刚下载这个就感觉头有点大,他的环境真的难得搭建,光是 Gradle 的配置与拉取就要花费很长的时间,每次新建一个 demo 项目就会要重新下载一次 gradle 下载倒还是小事,问题是下载的像乌龟一样,明明挂了加速器,仍然下的好慢,在网上一查说是要用阿里云的加速库就会快一些,结果到现在我都不知道配置正确没有,配置了以后仍然在 gradle 的官网上面给我拉去,真的有一种吐血的想法,好在现在已经下载过许多次了,直接复制之前项目的 .gradle 与 gradle 目录就好了 因为目前我使用的 gradle 版本都是 8.0 的保证环境的一定性是学习的前提!

p2 Hello World! 创建第一个项目

Hello World! 是编程爱好者新接触一个语言比接触的一个 Demo AS的 Hello World!

注意 Hardcoded 警告的处理

需要以界面的形式存在,在命名的时候就需要注意[code] Hardcoded string [/code]问题,我们要用@string 的方式将静态资源存放到 resource 里面就不会报提示三角形了
image.png
我们可以将这些常量信息保存到 values 里面,values 文件夹也分为 string.xml color.xml theme.xml 默认是这三个,就可以将数据在里面定义 调用再使用 [code]@string/id[/code] 取出来!

学习使用 ConstraintLayout 组件

在之前使用 iApp 编程的时候,我无不赞叹线性布局是个好东西,相对布局有时候也有妙用,但是在 Android Studio 里面 ConstraintLayout 瞬间吸引到我了他可以创建百分比布局,可以解决不同尺寸的设备界面显示问题
image.png
说到 ConstraintLayout 布局就不能不提到他的 guideline 组件了 完美的辅助线来对控件进行排布,而不会是单纯限于线性布局的 dp 大法 十分的银杏😁

附录 各种语言的 Hello World!

1. 编程语言之首——Java

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

2. 嵌入式领域的王者——C语言

#include<stdio.h>
    int main(void) {
        printf("Hello World!\n");
        return 0;
}

3. 通用型脚本语言——Python

print("Hello World")

4. Web前端开发主流语言——JavaScript

console.log("Hello, World")

5. 世界范围内网站运用率最高的编程语言——PHP

<?php
echo "Hello World";
?>

6. 多范式编程语言——C++

#include<iostream>
Using namespace std;
int main(){
    cout << "\nHello World!";
    return 0;
}

7. 高级程序编程语言——C#

using System;
namespace helloWorld{
    class HelloWorld
    {
        static void Main(String[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

8. 基于对象的程序设计语言——Visual Basic

    subsub main
msgbox "Hello World!"
end sub
}

9. 统计分析的可编程语言——R

cat("helloworld")

10. 静态强类型、编译型、并发型、并具有垃圾回收功能的编程语言——GO

package main
import "fmt"
func main() {
    fmt.Println("Hello World!")
}

p3 使用 JetPack 对 App 进行开发架构

在Google I/O 2018发布了JetPack之后,Android应用架构和开发技术都发生了很大的变化。

Activity LifeCycle(理解Android应用的运行机制)

Lifecycles 其实从名字看肯定是与生命周期相关,那它与生命周期又有什么联系?先参考一下官方文档:

Lifecycles 是一个生命周期感知组件,当 Activity 或者 Fragment 的生命周期发生改变的时会,Lifecycles也会做出相应的生命周期状态的改变,它保存关于组件生命周期状态的信息(比如活动或片段),并允许其他对象观察这种状态。

可以看出 Lifecycles 是一个组件,具有感知生命周期的功能,既然是个组件,那就说明可以嵌入到其他地方,比如 VewModel 便是其中之一。
我们知道,当我们需要处理与生命周期相关的组件的时候,在没有Lifecycles提供的时候,需要设置各种回调,如下所示:

class MyLocationListener {
    public MyLocationListener(Context context, Callback callback) {
        // ...
    }

    void start() {
        // connect to system location service
    }

    void stop() {
        // disconnect from system location service
    }
}

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    @Override
    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, (location) -> {
            // update UI
        });
    }

    @Override
    public void onStart() {
        super.onStart();
        myLocationListener.start();
        // manage other components that need to respond
        // to the activity lifecycle
    }

    @Override
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
        // manage other components that need to respond
        // to the activity lifecycle
    }
}

当组件数目过多的时候,便会需要在生命周期回调中管理多个组件从而徒增代码量,使得项目难以维护,而且在生命周期中执行过多的耗时操作极易引起内存泄漏,而这些都可以通过Lifecycles来解决。
看下Lifecycles的源码吧,反正也不长

public abstract class Lifecycle {

    /**
     * Lifecycle coroutines extensions stashes the CoroutineScope into this field.
     *
     * @hide used by lifecycle-common-ktx
     */
    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
    @NonNull
    AtomicReference<Object> mInternalScopeRef = new AtomicReference<>();

    //添加生命周期状态的观察者
    @MainThread
    public abstract void addObserver(@NonNull LifecycleObserver observer);

    //这个也就是移除观察者
    @MainThread
    public abstract void removeObserver(@NonNull LifecycleObserver observer);

    //获得当前生命周期状态
    @MainThread
    @NonNull
    public abstract State getCurrentState();

    //再熟悉不过的生命周期回调
    //是从框架和生命周期类分派的生命周期事件,这些事件映射到活动和片段中的回调事件。
    @SuppressWarnings("WeakerAccess")
    public enum Event {
        /**
         * Constant for onCreate event of the {@link LifecycleOwner}.
         */
        ON_CREATE,
        /**
         * Constant for onStart event of the {@link LifecycleOwner}.
         */
        ON_START,
        /**
         * Constant for onResume event of the {@link LifecycleOwner}.
         */
        ON_RESUME,
        /**
         * Constant for onPause event of the {@link LifecycleOwner}.
         */
        ON_PAUSE,
        /**
         * Constant for onStop event of the {@link LifecycleOwner}.
         */
        ON_STOP,
        /**
         * Constant for onDestroy event of the {@link LifecycleOwner}.
         */
        ON_DESTROY,
        /**
         * An {@link Event Event} constant that can be used to match all events.
         */
        ON_ANY
    }

    //这个也就此当前所处于的状态了
    @SuppressWarnings("WeakerAccess")
    public enum State {
        /**
         * Destroyed state for a LifecycleOwner. After this event, this Lifecycle will not dispatch
         * any more events. For instance, for an {@link android.app.Activity}, this state is reached
         * <b>right before</b> Activity's {@link android.app.Activity#onDestroy() onDestroy} call.
         */
        DESTROYED,

        /**
         * Initialized state for a LifecycleOwner. For an {@link android.app.Activity}, this is
         * the state when it is constructed but has not received
         * {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} yet.
         */
        INITIALIZED,

        /**
         * Created state for a LifecycleOwner. For an {@link android.app.Activity}, this state
         * is reached in two cases:
         * <ul>
         *     <li>after {@link android.app.Activity#onCreate(android.os.Bundle) onCreate} call;
         *     <li><b>right before</b> {@link android.app.Activity#onStop() onStop} call.
         * </ul>
         */
        CREATED,

        /**
         * Started state for a LifecycleOwner. For an {@link android.app.Activity}, this state
         * is reached in two cases:
         * <ul>
         *     <li>after {@link android.app.Activity#onStart() onStart} call;
         *     <li><b>right before</b> {@link android.app.Activity#onPause() onPause} call.
         * </ul>
         */
        STARTED,

        /**
        * Resumed state for a LifecycleOwner. For an {@link android.app.Activity}, this state
        * is reached after {@link android.app.Activity#onResume() onResume} is called.
        */
        RESUMED;

        /**
        * Compares if this State is greater or equal to the given {@code state}.
        *
        * @param state State to compare with
        * @return true if this State is greater or equal to the given {@code state}
        */
        public boolean isAtLeast(@NonNull State state) {
        return compareTo(state) >= 0;
        }
    }
}

例子 以Chronometer为例子来示范生命周期监听

public class MyChronometer extends Chronometer implements LifecycleObserver {

    private long time;


    public MyChronometer(Context context) {
        super(context);
    }

    public MyChronometer(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyChronometer(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    //以注解的形式完成监听
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    private void stopMeter(){
        time = SystemClock.elapsedRealtime() - getBase();
        stop();
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    private void resumeMeter(){
        setBase(SystemClock.elapsedRealtime() - time);
        start();
    }
}

然后Activity如下:只需要添加观察者即可

public class LifecyclesActivity extends AppCompatActivity {

    MyChronometer chronometer;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lifecycles);

        chronometer = findViewById(R.id.meter);
        getLifecycle().addObserver(chronometer);
    }

}

是不是感觉Activity与Chronometer之间解耦了,Chrononmeter自我实现了良好的封装,也方便了移植。
这也就是使用Lifecycles带来的好处。

LifeCycle 生命周期图解

  • 控制视频缓冲的开始与停止:启动App的时候可以更快的开始缓冲视频,App销毁时停止缓冲。
  • 开始和停止网络连接:应用位于前台时可能需要实时传输数据,并在应用进入后台时自动暂停。
  • 控制页面动画的启动与停止:动画在页面可见时进行播放,不可见时停止。

ViewModel 数据管理小帮手

ViewModel的出现主要为了解决两个问题:
1.当Actvitiy销毁重建过程中的数据恢复问题,虽然原来可以使用onSaveInstanceState()来完成,但是只支持能被序列化的数据而且是小量数据,对于大量数据则显得有点无力。
2.UI控制器的工作繁忙,UI控制器主要用于处理显示,交互,其他的额外操作可以委托给其他类完成,将不应该分配给UI的任务分离出来是必要的,这也就是上面所说的分离关注点原则

ViewModel是一个负责为Fragment/Activity配置和管理数据的类,同时处理Fragment/Activity与Application其余部分的通信。
ViewModel始终与Scope(Fragment/Activity)相关联创建,同时一直保留当Scope存在时,例如Activity被finish。换句话说,ViewModel不会随着其拥有者因配置更改被销毁而销毁(旋转,横竖屏切换),新的拥有者会重新建立与ViewModel的关联。
ViewModel旨在获取和保存Activity或Fragment必要的信息,Activity或Fragment能够观察到ViewModel的数据改变,ViewModel通常通过LiveData或Android Data Binding暴露数据,也可以在你喜欢的框架中使用可观察构造。
ViewModel只负责管理UI数据,不应访问视图结构和持有Activity或Fragment的引用。

下面则是示意图

ViewModel实例

ViewModel在配置更改期间能自动保留其对象,以便它们所持有的数据可立即用于下一个 Activity 或片段Fragment
下面通过一个具体的实例:
MainActivity.java

public class MainActivity extends AppCompatActivity {

    private MyViewModel myViewModel;

    private TextView tv;
    private Button button1, button2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);

        tv = findViewById(R.id.textView);
        tv.setText(String.valueOf(myViewModel.num));
        button1 = findViewById(R.id.button1);
        button2 = findViewById(R.id.button2);

        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myViewModel.num++;
                tv.setText(String.valueOf(myViewModel.num));
            }
        });

        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                myViewModel.num += 2;
                tv.setText(String.valueOf(myViewModel.num));
            }
        });
    }
}

MyViewModel.java

public class MyViewModel extends ViewModel {

    public int num = 0;

}

布局如下:Welcome对应TextView,+1对象button1,+2对应button2

加到7,当设备旋转

数据仍能得到保留,即使更改系统语言都可以,也就是说如果Activity被重建,将接收到第一个Activity创建的相同的MyViewModel实例。当所有者Activity完成后,会调用ViewModel对象的onCleared()方法,以便它能够清理资源。
同时这里也需要注意一个问题,当ViewModel持有外部引用的时候会阻止回收,所以ViewModel绝不能引用View、Lifecycle(也是Jetpack组件之一)或任何可能包含对Activity上下文的引用的类。
回到最上面的那个图,图说明了ViewModel的作用域涉及到整个生命周期,当获取ViewModel时,ViewModel的生命周期限定为传入ViewModelProvider的对象的生命周期。也就是对于以下场景(引用官方示例)

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData<Item> getSelected() {
        return selected;
    }
}


public class MasterFragment extends Fragment {
    private SharedViewModel model;
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
        model.getSelected().observe(this, { item ->
                                    // Update the UI.
                                    });
    }
}

由于传入的是Activity,所以其作用域为整个Activity,不同的Fragment可以通过ViewModelProviders获取到同一个ViewModel,这样有以下的好处:

  • Activity无须参与Fragment之间的交互。Activity与Fragment无关
  • Fragment之间也无需互相建立联系,Fragment与Fragment无关
  • 每个Fragment都有自己的生命周期,即使被替换也不会有任何影响。

    加强ViewModel,支持异常生命周期

    我们知道,有些时候在Activity被意外杀死,如清理后台等会直接跳过onDestory()而是回调onSaveInstanceState()异常杀死下的生命周期,这个时候ViewModel也会被杀死,再次恢复的时候便会被重建,这样,原来的数据也就丢失了,因此我们需要改进一下ViewModel以支持异常退出情况下的重建。
    首先很容易是想到通过onSaveInstanceState() 来保存,然后通过SaveInstanceState 来恢复,虽然也是一种可行的方法,但是好像全程跟ViewModel没什么关联,未免优点没有物尽其用,所以ViewModel也提供了类似SavedInstanceState的方法。
    SavedStateHandle :用于保存状态的数据类型,是一个key-value的map,其实也就是类似Bundle。采用Hashmap来实现的。
    具体使用:

    public class ViewModelWithData extends ViewModel {
    
      private MutableLiveData<Integer> number;
      private SavedStateHandle handle;
    
      private static final String KEY_NUMBER = "number";
    
      public ViewModelWithData(SavedStateHandle handle) {
          this.handle = handle;
          number = new MutableLiveData<>();
          number.setValue(0);
      }
    
      public MutableLiveData<Integer> getNumber() {//每次都通过SavedStateHandle来获取相应的值
          if (!handle.contains(KEY_NUMBER)) {
              handle.set(KEY_NUMBER, 0);
          }
          return handle.getLiveData(KEY_NUMBER);
      }
    
      public void addNumber(int n) {
          getNumber().setValue(getNumber().getValue() + n);
      }
    }
    
    public class LiveDataActivity extends AppCompatActivity {
    
      private ViewModelWithData viewModelWithData;
    
      ActivityLiveDataBinding binding;
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          binding = DataBindingUtil.setContentView(this, R.layout.activity_live_data);
          //需要将创建的SavedStateVMFactory传入
          viewModelWithData = ViewModelProviders.of(this, new SavedStateVMFactory(this)).get(ViewModelWithData.class);
    
          binding.setData(viewModelWithData);
          binding.setLifecycleOwner(this);
      }
    
    }

    好了,这样就完成了将VIewModel的生命周期与整个Activity的同步,包括异常情况下。

    支持SharedPreference等使用到Application的相关

    因为SharedPreference需要使用到Application来获取到,所以要想配合ViewModel还需要传入Application作为参数,当然,Jetpack已经为我们准备好了
    AndroidViewModel:感知应用上下文的ViewModel,继承自ViewModel。
    其实实现也很简单,传入Application就行了,需要时获取。

    public class AndroidViewModel extends ViewModel {
      @SuppressLint("StaticFieldLeak")
      private Application mApplication;
    
      public AndroidViewModel(@NonNull Application application) {
          mApplication = application;
      }
    
      /**
       * Return the application.
       */
      @SuppressWarnings("TypeParameterUnusedInFormals")
      @NonNull
      public <T extends Application> T getApplication() {
          //noinspection unchecked
          return (T) mApplication;
      }
    }
    

    然后剩下的便可以通过获取SharedPreference来存储数据,再配合LiveData,SaveStataHandler实现跨越整个App的生命周期同步。当然,数据的持久化存储不仅仅时这一种方式,配合任意方式都可。

    使用ViewModel取代Loader

    特别对于数据库这类数据,由于需要保持UI与数据库间的数据同步,常常导致在UI控制器(Activity等)进行数据加载,这有违分离关注点的原则。
    使用VIewModel配合Room和LiveData能够方便的取代CusorLoader的操作,VIewModel保证数据的稳定性,Room通知数据更改,LiveData则自动使用更新的数据更新UI。

    以上便是ViewModel在整个Jetpack的作用和功能了,现在只是冰山一角,慢慢学习。

    LiveData 做好一个数据观察员

    LiveData是一个可观察的数据持有者类,不过它和其他的可观察对象不同,它会与生命周期相关联,比如Activity的生命周期,LiveData能确保仅在Activity处于活动状态下才会更新。也就是说当观察者处于活动状态,才会去通知数据更新。
    个人觉得这是为了避免内存泄漏,可以说是很实用了,因为要想避免内存泄漏,必须要感知到生命周期,而原本并没有提供额外的方法,像Glide采用了一个透明的Fragment来感知Activity的生命周期,这虽然是一个可行的方法,但总共感觉并不是一个最优的方法。
    下面是官方说明的使用LiveData的优点

  • 确保UI与数据同步
  • 不会产生内存泄漏
  • 不会因为Activity停止而Crash
  • 不需要手动控制生命周期
    下面便是LiveData在MVVM中的角色

    LiveData使用

    LiveData是配合ViewModel使用的
    ViewModelWithData.java

    public class ViewModelWithData extends ViewModel {
    
      private MutableLiveData<Integer> number;
    
      //注意这个构造方法需要public,因为他是通过方式来创建实例的
      public ViewModelWithData(){
          number = new MutableLiveData<>();
          number.setValue(0);
      }
    
      public MutableLiveData<Integer> getNumber() {
          return number;
      }
    
      public void setNumber(MutableLiveData<Integer> number) {
          this.number = number;
      }
    
      public void addNumber(int n){
          number.setValue(number.getValue() + n);
      }
    }

    LiveDataActivity.java

    public class LiveDataActivity extends AppCompatActivity {
    
      private ViewModelWithData viewModelWithData;
      private TextView textView;
      private ImageButton imageButton1, imageButton2;
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_live_data);
    
          textView = findViewById(R.id.tv1);
          imageButton1 = findViewById(R.id.ib1);
          imageButton2 = findViewById(R.id.ib2);
    
          viewModelWithData = ViewModelProviders.of(this).get(ViewModelWithData.class);
          //这里添加对viewModelWithData的对象的观察者
          viewModelWithData.getNumber().observe(this, new Observer<Integer>() {
              @Override
              public void onChanged(Integer integer) {
                  textView.setText(String.valueOf(integer));//响应数据变化
              }
          });
    
          imageButton1.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  viewModelWithData.addNumber(1);
              }
          });
    
          imageButton2.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  viewModelWithData.addNumber(-1);
              }
          });
      }
    }
    

LiveData还支持map的映射转换

其实感觉这个liveData和rxJava大同小异
官方示例代码

LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});