Android阶段性总结🦄

阶段性总结一下基本控件、常用布局还有Menu、Notifaction、Resource以及Activity、Fragment

基本控件🌈

OverView概览

TextView:文本框控件
EditText:编辑框控件
TextInputLayout:文本输入布局
Button:按钮控件
ImageView:图片控件
CheckBox:选择框控件
RadioButton:单选框控件
Snackbar:消息框控件
Notifaction:通知消息
Toast:吐司消息
SnackBar:底部提示消息
Menu:菜单控件
Spinner:下拉框控件

View基类

最基础的构件,所有widget类的基类。
重要属性属性描述
android:id设置一个在当前layout中的唯一编号
android:background设置背景色/背景图片
android:visibility设置是否显示View(visible,invisible,gone)

继承自View的常用组件在LayoutParams下的常用属性

android:gravity    内部内容对齐
layout_gravity     控件相对于其所在容器的位置
padding相关属性     内边距
margin相关属性      外边距

TextView文本控件

TextView继承自View,用于向用户展示文本内容,但不允许编辑

重要属性描述

重要属性属性描述
android:text文本内容
android:textColor文本颜色
android:textSize设置文本大小
android:maxLength文本最大长度,超出此长度的文本不显示
android:ems设置文本的字符个数
android:lines设置文本的行数,超出此行数的文本不显示
android:textAppearance设置文字外观
android:ellipsize设置当文本超出TextView规定的范围的显示方式
android:textStyle设置文本样式,如:bold(粗体)、italic(斜体)、normal(正常)

常用方法

tv.setText(String);
tv.append(String);

textView.addTextChangedListener(new TextWatcher() {     //文本更改监听器
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        //文字改变后
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        //文本改变时
    }

    @Override
    public void afterTextChanged(Editable s) {
        //文本改变之前
    }
 });

EditText编辑框控件

编辑框控件,TextView的子类,用户可在此控件中输入信息。一般与TextInputLayout搭配使用。

重要属性描述

重要属性重要描述
android:inputType输入类型设置(textPassword,phone)
android:hint编辑框内容为空时显示的提示信息
android:maxLines设置文本的最大行数
android:ems设置控件的宽度为N个字符的宽度
android:digits设置允许输入哪些字符

常用方法

//获取编辑框内容
editText.getText().toString();
editText.setText(String);
//监听输入变化
editText.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

        }

        @Override
        public void afterTextChanged(Editable editable) {

        }
});

TextInputLayout容器

EditText容器,只能包含一个EditText,可以实现多种多种动画、提示信息等等。

示例布局代码

<com.google.android.material.textfield.TextInputLayout
    android:id="@+id/inputLayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <EditText 
        android:id="@+id/et_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:saveEnabled="false"
        android:maxLength="24"
        android:hint="输入email"/>
</com.google.android.material.textfield.TextInputLayout>

重要属性描述

重要属性属性描述
app:counterEnabled是否显示计数器,默认false
app:counterMaxLength设置计数器的最大值,与counterEnabled同时使用
app:errorEnabled是否显示错误信息
app:errorTextAppearance错误信息的字体样式
app:hintTextAppearance设置hint的文字样式
app:hintAnimationEnabled是否显示hint的动画,默认true

常用方法

inputLayout.setError("请输入正确的邮箱地址");

Button按钮控件

按钮,继承自TextView,既可以显示文本,也可以显示图片,允许用户通过点击执行操作。常用子类:CheckBox、RadioButton、ToggleButton。

常用方法

//Button点击事件1
//xml设置onClick属性
android:onClick="showToast"
//在Activity类实现这个事件处理方法
public void showToast(View view) {
    String msg = "Hello Toast!";
    Toast.makeText(this, msg,duration).show();
}

//Button点击事件2
btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        // 添加逻辑代码
    }
});

//Button点击事件3
//Activity实现OnClickListener接口
public class <? extends AppCompatActivity> implements View.OnClickListener
    
@Override
public void onClick(View v) {
    // 添加逻辑代码
}

ImageView图片控件

用于展示图片,继承自View

重要属性描述

重要属性属性描述
android:src设置展示图片的资源路径
android:scaleType设置图片的填充方式
android:adjustViewBounds是否保持宽高比,需要与maxWidth、maxHeight一起使用
android:cropToPadding是否截取指定区域用空白代替,需要与scrollY一起使用
android:tint将图片渲染成指定的颜色
android:maxHeight置View最大高度,需要与setAdjustViewBounds一起使用
android:maxWidth设置View最大宽度,需要与setAdjustViewBounds一起使用

缩放种类scaleType

fitXY:图片拉伸或收缩填满ImageView,不保持比例,最常用;
fitStart:图片按比例扩大、缩小至ImageView宽度,在ImageView上方显示;
fitEnd:图片按比例扩大、缩小至ImageView宽度,在ImageView下方显示;
fitCenter:图片按比例扩大、缩小至ImageView宽度,在ImageView居中显示;
center:以图片和ImageView的中心点居中显示图片原尺寸,裁剪超出ImageView的部分;
centerCrop:以图片和ImageView的中心点居中显示按比例扩大或缩小的图片,图片的宽高有一边等于ImageView的宽高,长出的部分被裁剪;centerInside:以图片和ImageView的中心点居中显示按比例缩小图片,使图片的宽高小于等于ImageView的宽高,直到将图片的内容完整居中显示;
matrix:不改变原图大小,从ImageView左上角开始绘制,超过ImageView的部分被裁剪

常用方法

imageView.setImageResource(@ResId);

CheckBox复选框控件

CheckBox是复选框,继承自CompoundButton类。用于多项选择的场景

重要属性描述

重要属性属性描述
android:textCheckBox控件提示文字
android:checked设置此标签的初始状态为选中

常用方法

//判断按钮是否处于被选中状态
cb.isChecked();

//设置按钮是否选中
cb.setChecked(Boolean flag);

//设置多选组件的触摸监听器
cb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {

    }
});

RadioButton

RadioButton为单选按钮,继承自CompoundButton,主要用于单项选择的场景,需要与RadioGroup控件一起使用,可以实现单选效果。RadioGroup是单选组合框,可容纳多个RadioButton,用于将RadioButton组合成选择互斥的选项。

示例布局代码

        <RadioGroup
            android:id="@+id/rg_gender"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <RadioButton
                android:id="@+id/man"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:checked="true"
                android:text="男" />

            <RadioButton
                android:id="@+id/woman"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="女" />
        </RadioGroup>

示例方法

rgGender.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(RadioGroup group, int checkedId) {
        Boolean isMan = checkedId == R.id.man;
    }
});

轻量消息提醒组件

Toast吐司消息
Toast.makeText(<Context>, <String>, Toast.LENGTH_LONG).show();
Snacker底部弹出消息
Android5.0推出的Material Design控件,用于替代Toast,在界面底部弹出提示信息
//gradle
implementation 'com.google.android.material:material:1.2.1'
//usage in activity
Snackbar.make(<View>, <String>, Snackbar.LENGTH_LONG).setAction("确定", new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(<Context>,"信息已确认", Toast.LENGTH_SHORT).show();
    }
}).show();

Menu菜单组件

Menu菜单组件,定义于/res/menu,可以在Activity中设置为溢出菜单,也可以设置为ContextMenu弹窗菜单,也可以设置为Navigation组件菜单。app:showAsAction属性设置菜单项的显示动作。
<?xml version="1.0" encoding="utf-8"?>
<!--define in xml-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/action_call"
        android:orderInCategory="100"
        android:title="Devloper"
        android:icon="@drawable/ic_baseline_search_24"
        app:showAsAction="always" />
    <item
        android:id="@+id/action_settings"
        android:orderInCategory="100"
        android:title="About"
        app:showAsAction="never" />
</menu>
溢出菜单Menu
//usage in activity
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_action, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
       case R.id.action_call:
            //action
            return true;
       case R.id.action_settings:
            //action
            return true;
        ...
     }
     return super.onOptionsItemSelected(item);
}
上下文菜单ContextMenu
//usage in activity
registerForContextMenu(<View>);

@Override
public boolean onContextItemSelected(@NonNull MenuItem item) {
    switch (item.getItemId()) {
        case R.id.action_call:
             //todo
             return true;
        case R.id.action_settings:
             //todo
             return true;
    }
    return super.onContextItemSelected(item);
}

Spinner下拉菜单

Spinner下拉菜单提供了从一个数据集合中快速选择一项值的办法。默认情况下Spinner显示的是当前选择的值,点击Spinner会弹出一个包含所有可选值的dropdown菜单,从该菜单中可以为Spinner选择一个新值。

res/values中添加str_array_services.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="services_array">
        <item>@string/str_select_service</item>
        <item>@string/str_health_self</item>
        <item>@string/str_hs_project</item>
        <item>@string/str_com_care</item>
        <item>@string/str_find_people</item>
        <item>@string/str_dormitory</item>
    </string-array>
</resources>
public class LoginActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener {
    private Spinner mSpinner;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        mSpinner = findViewById(R.id.spinner_service);
        mSpinner.setOnItemSelectedListener(this);
        ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
                R.array.services_array, android.R.layout.simple_spinner_item);
        adapter.setDropDownViewResource(android.R.layout.simple_dropdown_item_1line);
        mSpinner.setAdapter(adapter);
    }

    @Override
    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {

    }

    @Override
    public void onNothingSelected(AdapterView<?> parent) {
        // Another interface callback
    }
}

Notifaction通知

Android使用Notification实现消息提醒,提供通知栏和通知抽屉查看通知及通知详情。从Android 8.0开始,必须为所有通知分配一个或多个渠道,否则通知将不会显示。Android 8.0及以上版本通过渠道的importance属性设置重要程度,Android 8.0以下版本每条通知通过调用set Priority ()方法进行设置

image-20210425000548243

①小图标,必须提供,调用setSmallIcon()设置;

②应用名称,由系统提供;

③时间戳,由系统提供,可以调用setWhen()替换或者调用setShowWhen(false)隐藏;

④大图标,可选内容,调用setLargeIcon()进行设置;

⑤消息标题,可选内容,调用setContentTitle()设置;

⑥消息文本,可选内容,调用setContentText()设置。

image-20210425000753561

//安卓8以上必须创建通知渠道
@TargetApi(Build.VERSION_CODES.O)
private void createNotificationChannel(String channelId, String channelName, int importance) {
    NotificationChannel channel = new NotificationChannel(channelId, channelName, importance);
    NotificationManager notificationManager = (NotificationManager) getSystemService(
            NOTIFICATION_SERVICE);
    notificationManager.createNotificationChannel(channel);
}

//usage
String channelId = "chat";
String channelName = "聊天消息";
int importance = NotificationManager.IMPORTANCE_HIGH;
createNotificationChannel(channelId, channelName, importance);

channelId = "subscribe";
channelName = "订阅消息";
importance = NotificationManager.IMPORTANCE_DEFAULT;
createNotificationChannel(channelId, channelName, importance);
Intent intent1 = new Intent(this, BmiActivity.class);
intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    NotificationChannel channel = manager.getNotificationChannel("chat");
    if (channel.getImportance() == NotificationManager.IMPORTANCE_NONE) {
        Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
        intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
        intent.putExtra(Settings.EXTRA_CHANNEL_ID, channel.getId());
        startActivity(intent);
        Toast.makeText(this, "请手动将通知打开", Toast.LENGTH_SHORT).show();
    }
}

Notification notification = new NotificationCompat.Builder(this, "chat")
        .setContentTitle("收到一条聊天消息")
        .setContentText("今天中午吃什么?")
        .setWhen(System.currentTimeMillis())
        .setSmallIcon(R.drawable.ic_launcher_foreground)
        .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.nicklogo))
        .setAutoCancel(true)
        .setContentIntent(PendingIntent.getActivity(this, 1, intent1, PendingIntent.FLAG_UPDATE_CURRENT))
        .build();
manager.notify(1, notification);

常用布局🍀

OverView概览

LinearLayout:线性布局,子View水平或垂直方向进行排列
FrameLayout:帧布局,子View以左上角为起点堆叠在一起
RelativeLayout:相对布局,子View之间的相对定位进行排列
GridLayout:网格布局,子View以行、列方式进行排列
ConstraintLayout:约束布局

ViewGroup

常用布局直接或者间接继承自ViewGroup。ViewGroup提供了对其子View的管理功能,包括布局、动画等。子组件可以是View、也可以是ViewGroup。

20191012211908559

LinearLayout

线性布局,内部View以水平或垂直方式排列

FrameLayout

帧布局会在屏幕上创建一块空白区域,添加到该区域中的每个子控件占一帧,这些帧会一个一个叠加在一起,后加入的控件会叠加在上一个控件上层,默认显示在屏幕左上角。案例参照

image-20210425020137080

RelativeLayout

相对布局,控制子View以相对定位的方式进行布局显示,即以其它控件或父容器为参照物,摆放控件位置。非常灵活但个人觉得完全可以抛弃他用ConstraintLayout约束布局代替。案例参照

image-20210425015723535

相对父容器位置的属性
属性名称属性描述
android:layout_centerInParent在父视图中正中心
android:layout_centerHorizontal在父视图的水平中心线
android:layout_centerVertical在父视图的垂直中心线
android:layout_alignParentTop紧贴父视图顶部
android:layout_alignParentBottom紧贴父视图底部
android:layout_alignParentLeft紧贴父视图左部
android:layout_alignParentRight紧贴父视图右部
使用id描述相对其它子View的属性
属性名称属性描述
android:layout_alignTop与指定视图顶部对齐
android:layout_alignBottom与指定视图底部对齐
android:layout_alignLeft与指定视图左部对齐
android:layout_alignRight与指定视图右部对齐
android:layout_above在指定视图上方
android:layout_below在指定视图下方
android:layout_toLeftOf在指定视图左方
android:layout_toRightOf在指定视图右方
相对其它子View的具体像素值的属性
属性名称属性描述
android:layout_marginBottom离某元素底边缘的距离
android:layout_marginLeft离某元素左边缘的距离
android:layout_marginRight离某元素右边缘的距离
android:layout_marginTop离某元素上边缘的距离

GridLayout

网格布局,Android 4.0以上版本新增的布局。使用虚细线将布局划分为行、列和单元格,可以同时在x、y轴方向进行控件的对齐,也分为垂直和水平(默认)布局。案例参照
自身属性
属性描述取值说明
android:alignmentMode当设置alignMargins,使视图的外边界之间进行校准alignBounds–对齐子视图边界
alignMargins–对齐子视距内容
android:columnCount最大列数值为4表示每行有4列
android:rowCount最大行数值为5表示总共有5行
android:orientation所含子元素的布局方向水平:Horizontal垂直:vertical
子元素属性
属性描述取值说明
android:layout_column显示该子元素所在的列值为0表示在第1列
android:layout_row显示该子元素所在的行值为2表示在第3行
android:layout_columnSpan该子元素所占的列数值为2表示占两列
android:layout_rowSpan该子元素所占的行数值为2表示占两行
android:layout_columnWeight该子元素的列权重若一行有2列,值为1表示平分
android:layout_rowWeight该子元素的行权重若共有3行,值为1表示占1/3

include重用布局

在开发布局的时候会碰见一些布局重复利用的情况。比如两个不相干的的类,但是他们的布局有部分是一样的,那时候可以使用include标签来重复利用相同的layout,来优化我们的xml文档,提高可读性。也能节省代码冗余。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
 
    ...
  
    <include layout="@layout/demo_layout"/>
    
    ...
 
</LinearLayout>

如果打算用RelateLayout或Linearlayout作为界面根布局,<include>导入的布局可以考虑用<merge>作为根节点,而<merge>根节点内的控件布局取决于<include>这个布局的父布局是哪个布局。

img

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
 
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_gravity="center_horizontal"
        android:text="第一个标签" />
 
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_gravity="center_horizontal"
        android:text="第二个标签" />
</merge>

ViewBinding⚡

View Binding是Android Studio 3.6推出的新特性,目的是为了替代findViewById(内部实现还是使用findViewById)。在启动视图绑定后,系统会为改模块中的每个xml文件生成一个绑定类,绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用。还可以针对某布局关闭ViewBinding。

Usage

android {
        ...
        viewBinding {
            enabled = true
        }
    }
} 
//activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="100dp"
        android:layout_height="40dp"
        android:text="这是按钮"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView" />


    <TextView
        android:id="@+id/textView"
        android:layout_width="100dp"
        android:layout_height="40dp"
        android:gravity="center"
        android:text="Hello World!"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>  
//MainActivity.java
public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //关键代码
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        View rootView = binding.getRoot();
        setContentView(rootView);
        //如何使用
        binding.textView.setText("这是修改的");
        binding.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Log.d("Main","点击了按钮");
            }
        });

    }
}

Activity基类封装ViewBinding

public class BaseActivity<V extends ViewBinding> extends AppCompatActivity {
    protected V v;
    protected Activity mContext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this;
        ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
        Class cls = (Class) type.getActualTypeArguments()[0];
        try {
            Method inflate = cls.getDeclaredMethod("inflate", LayoutInflater.class);
            v = (V) inflate.invoke(null, getLayoutInflater());
            setContentView(v.getRoot());
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

Fragment基类封装ViewBinding

public class BaseFragment<T extends ViewBinding> extends Fragment {
    protected T v;
    protected Context mContext;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mContext = getActivity();
        ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
        Class cls = (Class) type.getActualTypeArguments()[0];
        try {
            Method inflate = cls.getDeclaredMethod("inflate", LayoutInflater.class, ViewGroup.class, boolean.class);
            v = (T) inflate.invoke(null, inflater, container, false);
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        return v.getRoot();
    }

    @Override
    public void onDestroyView() {
        if (v != null) v = null;
        super.onDestroyView();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        v = null;
    }
}

MVP架构中的封装了ViewBinding的Activity基类

public abstract class BaseActivityCompat<P extends BasePresenter, V extends ViewBinding> extends AppCompatActivity implements BaseView, Toolbar.OnMenuItemClickListener {
    Handler mainHandler;
    protected P mPresenter;
    protected V v;
    protected Activity mContext;
    private Bundle savedInstance;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        savedInstance = savedInstanceState;
        mContext = this;
        beforeInitLayout();

        ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
        Class cls = (Class) type.getActualTypeArguments()[1];
        try {
            Method inflate = cls.getDeclaredMethod("inflate", LayoutInflater.class);
            v = (V) inflate.invoke(null, getLayoutInflater());
            setContentView(v.getRoot());
        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
        createPresenter();

        if (mPresenter != null) mPresenter.attachView(this);
        initViews();
        initParams();
    }

    public Bundle getSavedInstance() {
        return savedInstance;
    }

    protected abstract void beforeInitLayout();

    protected abstract void initViews();  //初始化控件

    protected abstract void initParams(); //初始化参数


    private void createPresenter(){
        ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
        Class cls = (Class) type.getActualTypeArguments()[0];
        try {
            Constructor constructor = cls.getConstructor(Activity.class);
            mPresenter = (P) constructor.newInstance(mContext);
        } catch (IllegalAccessException e) {
            //e.printStackTrace();
        } catch (InstantiationException e) {
            //e.printStackTrace();
        } catch (NoSuchMethodException e) {
            //e.printStackTrace();
            try {
                mPresenter = (P) cls.newInstance();
                LOGE("重新创建了无参Presenter");
            } catch (IllegalAccessException illegalAccessException) {
                illegalAccessException.printStackTrace();
            } catch (InstantiationException instantiationException) {
                instantiationException.printStackTrace();
            }
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    @Override
    public boolean onMenuItemClick(MenuItem item) {
        return false;
    }


    @Override
    protected void onDestroy() {
        if (mPresenter != null) mPresenter.detachView();
        super.onDestroy();
    }

    @Override
    public void jumpPage(Class<?> activity) {
        mContext.startActivity(new Intent(this, activity));
    }
    @Override
    public void finishself() {
        finish();
    }

    public void runOnUI(Runnable runnable) {
        mainHandler = new Handler(Looper.getMainLooper());
        mainHandler.post(runnable);
    }

    public void toast(String message) {
        Toast toast = Toast.makeText(mContext, message, Toast.LENGTH_SHORT);
        runOnUI(toast::show);
    }


    public void LOGE(String log) {
        Log.e("==" + this.getClass().getName() + "==>", log);
    }

    public void LOGW(String log) {
        Log.w("==" + this.getClass().getName() + "==>", log);
    }

    @Override
    public Snackbar showSnackBar(String message) {
        Snackbar snackbar = Snackbar.make(v.getRoot(), message, Snackbar.LENGTH_SHORT);
        runOnUI(snackbar::show);
        return snackbar;
    }

    @Override
    public void showToast(String message) {
        runOnUI(() -> {
            Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
        });
    }
}

MVP架构中的封装了ViewBinding的Fragment基类

public abstract class BaseFragmentCompat<T extends BasePresenter,V extends ViewBinding> extends Fragment implements BaseView {

    Handler mainHandler;
    protected T mPresenter;
    protected V v;
    protected FragmentActivity mContext;
    private View mRootView;


    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
        Class cls = (Class) type.getActualTypeArguments()[1];
        try {
            Method inflate = cls.getDeclaredMethod("inflate", LayoutInflater.class, ViewGroup.class, boolean.class);
            v = (V) inflate.invoke(null, inflater, container, false);
        }  catch (NoSuchMethodException | IllegalAccessException| InvocationTargetException e) {
            e.printStackTrace();
        }
        mRootView = v.getRoot();
        mContext = getActivity();
        createPresenter();
        if (mPresenter != null) mPresenter.attachView(this);
        initParams();
        initViews();
        return mRootView;
    }

    protected abstract void initViews();

    protected abstract void initParams();

    protected abstract void createPresenter();


    @Override
    public void onDestroyView() {
        if (mPresenter != null) mPresenter.detachView();
        if (v != null) v = null;
        super.onDestroyView();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        v = null;
    }

    protected void runOnUI(Runnable runnable) {
        mainHandler = new Handler(Looper.getMainLooper());
        mainHandler.post(runnable);
    }

    @Override
    public Snackbar showSnackBar(String message) {
        Snackbar snackbar = Snackbar.make(v.getRoot(), message, Snackbar.LENGTH_SHORT);
        runOnUI(snackbar::show);
        return snackbar;
    }
}

Resource🌵

Android资源目录一般分为两大部分。一是主要的资源文件,是放在res文件夹里面的,他会由aapt检查资源合法性并自动生成R class。二是assets文件夹是存放不进行编译加工的原生文件。

资源目录及解释

目录资源类型Resource Types
res/animator/用于定义属性动画的 XML 文件。
res/anim/用于定义渐变动画的 XML 文件。(属性动画也可保存在此目录中,但为了区分这两种类型,属性动画首选 animator/ 目录。)
res/drawable/位图文件(.png.9.png.jpg.gif)或编译为可绘制对象资源子类型的 XML 文件。
res/mipmap/适用于不同启动器图标密度的可绘制对象文件。
res/color/用于定义颜色状态列表的 XML 文件
res/layout/用于定义用户界面布局的 XML 文件
res/values/包含字符串、整型数和颜色等简单值的 XML 文件。其他 res/ 子目录中的 XML 资源文件会根据 XML 文件名定义单个资源,而 values/ 目录中的文件可描述多个资源。对于此目录中的文件,<resources> 元素的每个子元素均会定义一个资源。例如,<string> 元素会创建 R.string 资源,<color> 元素会创建 R.color 资源。
res/xml/可在运行时通过调用 Resources.getXML() 读取的任意 XML 文件。各种 XML 配置文件(如可搜索配置)都必须保存在此处。
res/raw/由于每个资源均使用自己的 XML 元素进行定义,因此您可以随意命名文件,并在某个文件中放入不同的资源类型。但是,您可能需要将独特的资源类型放在不同的文件中,使其一目了然。例如,对于可在此目录中创建的资源,下面给出了相应的文件名约定:需以原始形式保存的任意文件。如要使用原始 InputStream 打开这些资源,请使用资源 ID(即 R.raw.*filename*)调用 Resources.openRawResource()
res/font/带有扩展名的字体文件(如 .ttf.otf.ttc),或包含 <font-family> 元素的 XML 文件。
assests资源打包到应用程序中的静态文件,这些文件不会被编译,最终会直接部署到目标设备中;另外,不能直接通过 R 资源类读取,只能使用流的形式读取。

代码读取res文件夹下资源

Color Value语法: #color_value 可以保存在res/values/colors.xml (文件名可以任意)。
xml引用:android:textColor="@color/color_name"
Java引用: int color = Resources.getColor(R.color.color_name)

Drawables语法: Drawable 可以保存在res/drawable/some_file。
xml引用:android:background="@drawable/name"
java引用:Drawable redDrawable = Resources.getDrawable(R.drawable.name)


dimension语法: dimen_value单位 一般保存为res/values/dimen.xml。
XML: android:textSize="@dimen/some_name"
Java: float dimen = Resources.getDimension(R.dimen.some_name)

Context.getResources();

<Resources>.openRawResource(R.raw.somefilename);
<Resources>.getXml(R.xml.xx);
<Resources>.getString(R.)或者Resources.getText()获取String。
<Resources>.getStringArray(R.array.category);

代码读取res/raw和assets文件夹资源

InputStream is = getResources().openRawResource(R.raw.filename);
InputStream is = getResources().getAssets().open("filename");

public String getFromRaw(){
    try {
        InputStreamReader inputReader = new InputStreamReader( getResources().openRawResource(R.raw.test1));
        BufferedReader bufReader = new BufferedReader(inputReader);
        String line="";
        String Result="";
        while((line = bufReader.readLine()) != null)
                            Result += line;
        return Result;
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

public String getFromAssets(String fileName){
    try {
        InputStreamReader inputReader = new InputStreamReader( getResources().getAssets().open(fileName) );
        BufferedReader bufReader = new BufferedReader(inputReader);
        String line="";
        String Result="";
        while((line = bufReader.readLine()) != null)
                            Result += line;
        return Result;
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

private void copyFileFromAssets(Context context, String fileName) {
    InputStream in = null;
    FileOutputStream out = null;
    String path = context.getFilesDir().getAbsolutePath();
    File file = new File(path + fileName);
    File filePath = new File(path);
    if (!filePath.exists())
                filePath.mkdirs();
    if (file.exists())
                return;
    try {
        in = context.getAssets().open(fileName);
        out = new FileOutputStream(file);
        int length = -1;
        byte[] buf = new byte[1024];
        while ((length = in.read(buf)) != -1) {
            out.write(buf, 0, length);
        }
        out.flush();
    }
    catch (Exception e) {
        e.printStackTrace();
    }
    finally {
        try {
            if (in != null) in.close();
            if (out != null) out.close();
        }
        catch (IOException e1) {
            e1.printStackTrace();
        }
    }
}

四大组件之Activity🌊

Activity是表示一个视图的层次结构的窗口,是应用程序的一个组件,Activity需要在AndroidManifest配置文件中注册。

一般继承自AppCompatActivity,拥有一个布局文件,并且在创建时使用setContentView初始化页面布局。

        <activity android:name=".ui.single.SplashPage">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".ui.mvp.MainFrame.MainFrame"
            android:theme="@style/AppTheme.MD.NoActionBar"
            android:windowSoftInputMode="adjustPan">
            <intent-filter tools:ignore="AppLinkUrlError">
                <action android:name="android.intent.action.VIEW" />

                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:scheme="freya"
                    android:host="fastassist" />
            </intent-filter>
        </activity>
android:windowSoftInputMode      表示Activity如何与包含屏幕软键盘的窗口交互。该属性会影响软键盘的状态,对Activity主窗口的调整。
android:label                    描述该Activity的一个标签,通常是随着Activity图标一起显示出来的
android:icon                     代表Activity和图标。在Activity被显示的时候,就用该图标显示给用户
android:launchMode               描述了该Activity应该如何被启动。standard,singleTop,singleTask和singleInstance
android:noHistory                设置在用户离开该Activity,并且它在屏幕上不再可见的时候,是否应该从Activity的堆栈中删除
android:excludeFromRecents       表示应用程序是否应该将Activity从最近运行的应用程序列表排除
android:screenOrientation        表示Activity显示的方向(比如纵向,横向)landscape,portrait
android:hardwareAccelerated      是否应为该Activity启动硬件加速
android:exported                 表示Activity是否可以由其他应用程序中的组件来启动

Activity生命周期

img

方法名称功能描述
onCreateActivity实例被Android系统创建时调用,通常在该方法中完成初始化操作,如:加载布局、初始化数据、初始化控件、绑定控件的响应事件等
onStartActivity由不可见变为可见时调用,但还不能与用户交互
onResumeActivity初始化完成后,与用户交互时调用,此时Activity位于栈顶,并且处于运行状态
onPauseActivity由可见变为不可见时调用,新Activity的onResume()必须在这个方法执行完才能调用,所以该方法只能执行一些耗时少的操作,如:保存一些关键数据
onStopActivity完全不可见时调用,它与onPause()的区别在于:onPause()只在启动一个对话框式的Activity时才会调用
onDestroyActivity被销毁之前调用,完成回收和资源释放等操作
onRestartActivity由不可见返回可见状态时调用,如:Home键切换界面,重新回到界面的过程
生命周期中各个方法的含义和作用

onCreate:create表示创建,这是Activity生命周期的第一个方法,也是我们在android开发中接触的最多的生命周期方法。它本身的作用是进行Activity的一些初始化工作,比如使用setContentView加载布局,对一些控件和变量进行初始化等。但也有很多人将很多与初始化无关的代码放在这,其实这是不规范的。此时Activity还在后台,不可见。所以动画不应该在这里初始化,因为看不到……

onStart:start表示启动,这是Activity生命周期的第二个方法。此时Activity已经可见了,但是还没出现在前台,我们还看不到,无法与Activity交互。其实将Activity的初始化工作放在这也没有什么问题,放在onCreate中是由于官方推荐的以及我们开发的习惯。

onResume:resume表示继续、重新开始,这名字和它的职责也相同。此时Activity经过前两个阶段的初始化已经蓄势待发。Activity在这个阶段已经出现在前台并且可见了。这个阶段可以打开独占设备

onPause:pause表示暂停,当Activity要跳到另一个Activity或应用正常退出时都会执行这个方法。此时Activity在前台并可见,我们可以进行一些轻量级的存储数据和去初始化的工作,不能太耗时,因为在跳转Activity时只有当一个Activity执行完了onPause方法后另一个Activity才会启动,而且android中指定如果onPause在500ms即0.5秒内没有执行完毕的话就会强制关闭Activity。从生命周期图中发现可以在这快速重启,但这种情况其实很罕见,比如用户切到下一个Activity的途中按back键快速得切回来。

onStop:stop表示停止,此时Activity已经不可见了,但是Activity对象还在内存中,没有被销毁。这个阶段的主要工作也是做一些资源的回收工作。

onDestroy:destroy表示毁灭,这个阶段Activity被销毁,不可见,我们可以将还没释放的资源释放,以及进行一些回收工作。

onRestart:restart表示重新开始,Activity在这时可见,当用户按Home键切换到桌面后又切回来或者从后一个Activity切回前一个Activity就会触发这个方法。这里一般不做什么操作。

常见问题
1、第一次运行程序时调用的生命周期方法为:onCreate() —> onStart() —> onResume()
2、退出时调用的生命周期方法为:onPause() —> onStop() —> onDestory()
3、当它在屏幕前台时,它是激活或运行状态(Running),能响应用户操作
4、当它上面有另外一个Activity,使它失去了焦点但仍然对用户可见时,处于暂停状态(Pause),在它之上的Activity没有完全覆盖屏幕,或者是透明的,被暂停的Activity仍然对用户可见,并且是存活状态,如果系统处于内存不足时会杀死这个Activity
5、当它完全被另一个Activity覆盖时则处于停止状态(stop),它仍然保留所有的状态和成员信息,然而对用户是不可见的,所以它的窗口将被隐藏,如果其它地方需要内存,则系统经常会杀死这个Activity
避免横竖屏切换重建Activity
设置configChanges属性
当进行横竖屏切换时不会再执行其它的生命周期方法,如果希望某一个界面一直处于竖屏或者横屏状态,不随手机的晃动而改变,可以在清单文件中通过设置Activity的screenOrientation属性完成
<activity android:name=".MainActivity"
          android:configChanges="orientation|keyboardHidden|screenSize">
设置Activity的screenOrientation属性
如果希望某一个界面一直处于竖屏或者横屏状态,不随手机的晃动而改变,可以在清单文件中通过设置Activity的screenOrientation属性完成
竖屏:android:screenOrientation="portrait"
横屏:android:screenOrientation="landscape"

Activity启动模式

standard模式
默认模式,每启动一个Activity就会在栈顶创建一个新的实例,ctivity的onCreate、onStart、onResume方法都会被调用,并放入Back Stack中

image-20210425035608284

singleTop模式
会判断要启动的Activity实例是否位于栈顶,如果位于栈顶则直接复用,否则创建新的实例。适用于接收到通知启动的内容显示界面,如:QQ接收到消息后弹出的界面、新闻推送打开的界面

image-20210425035704085

singleTask模式
每次启动该Activity时,系统首先会检查栈中是否存在当前Activity实例,如果存在则直接使用,并把当前Activity之上的所有实例全部出栈。适合作为程序的入口点,例如:浏览器的主界面

image-20210425035728886

singleInstance模式
启动一个新的任务栈来管理Activity实例,无论从哪个任务栈中启动该Activity,该实例在整个系统中只有一个。其效果相当于多个应用共享同一个应用。适合需要与程序分离开的页面,例如:闹铃提醒,将闹铃提醒与闹铃设置分离

image-20210425035822195

启动模式的设置
启动模式有2种设置方式:在AndroidMainifest设置、通过Intent设置标志位。

Intent设置方式的优先级> Manifest设置方式

Manifest设置方式无法设定FLAG_ACTIVITY_CLEAR_TOP;Intent设置方式无法设置单例模式(SingleInstance)

在AndroidMainifest设置
<activity 
    android:name="com.demo.Main4Activity"
    //通过android:launchMode属性设置
    android:launchMode="singleTask"/>
通过Intent设置标志位
Intent inten = new Intent (ActivityA.this,ActivityB.class);
//通过Intent的Flag设置
intent,addFlags(Intent,FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); 
标记位属性
标记位属性含义
FLAG_ACTIVITY_SINGLE_TOP指定启动模式为栈顶复用模式(SingleTop
FLAG_ACTIVITY_NEW_TASK指定启动模式为栈内复用模式(SingleTask
FLAG_ACTIVITY_CLEAR_TOP所有位于其上层的Activity都要移除,SingleTask模式默认具有此标记效果
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS具有该标记的Activity不会出现在历史Activity的列表中,
即无法通过历史列表回到该Activity上

Activity事件处理

Activity使用Intent

显示意图
显式意图可以直接通过名称开启指定的目标组件
Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);
隐式意图
隐式意图通过指定action和category等属性,系统根据这些信息寻找目标目标组件,如:打电话、发短信等
<activity android:name=".SecondActivity">
    <intent-filter>
        <action android:name="com.kirito666.invite" />
        <category android:name="android.intent.category.DEFAULT" />
    </ intent-filter>
</ activity>
Intent intent =  new Intent();
intent.setAction("com.kirito666.invite");
intent.addCategory(Intent.CATEGORY_DEFAULT);
startActivity(intent);
系统提供的常用隐式Intent
Intent类的系统动作常量名说明
ACTION_MAINApp启动时的入口
ACTION_VIEW显示数据
ACTION_EDIT显示可编辑的数据
ACTION_SEND分享内容
ACTION_CALL直接拨号
ACTION_DIAL准备拨号
ACTION_SENDTO发送短信
ACTION_ANSWER接听电话
ACTION_SEARCH导航栏SearchView的搜索动作
public void openBaidu() {
    Intent intent = new Intent();
    intent.setData(Uri.parse("http://baidu.com"));
    intent.setAction(Intent.ACTION_VIEW);
    this.startActivity(intent); //启动浏览器
}
Intent传递数据
使用setData()方法传递Uri类型的数据
intent.setData(Uri.parse("http://www.baidu.com"));
intent.setData(Uri.fromFile(new File("/sdcard/sample.jpg")));
//接收数据
Uri locationUri = intent.getData();
使用putExtra()传递Key-Value数据对
intent.putExtra(String name, int value);
intent.putExtra(String name, String[] value);
intent.putExtras(Bundle bundle);
//接收数据
int level = intent.getIntExtra("level", 0);
Bundle bundle = intent.getExtras();
数据回传
onActivityResult
① 调用startActivityForResult()启动第2个Activity
② 在第2个Activity返回数据创建一个Intent对象
    调用putExtra()传递回传的数据
    设置返回结果为Activity_RESULT_OK或Activity_CANCELED
    调用finish()关闭这个Activity
③ 在第1个Activity实现onActivityResult()方法接收回传的数据
Activity Results
在androidx中的Activity中使用Activity Results API替代onActivityResult
//gradle
implementation "androidx.activity:activity:lastversion"
implementation "androidx.fragment:fragment:lastversion
public class JumpActivity extends BaseActivity<ActivityJumpBinding> {

    private final ActivityResultLauncher<String> launcher = registerForActivityResult(new ResultContract(), new ActivityResultCallback<String>() {
        @Override
        public void onActivityResult(String result) {
            Toast.makeText(JumpActivity.this, result, Toast.LENGTH_SHORT).show();
        }
    });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_jump);
        v.button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(JumpActivity.this,Jump2Activity.class);
                User user = new User();
                user.name = "唐钱进";
                user.age = 22;
                //intent.putExtra("user",user);
                Bundle bundle = new Bundle();
                bundle.putSerializable("user",user);
                intent.putExtras(bundle);
                startActivity(intent);
                launcher.launch("传递字符串");
            }
        });
    }

    static class ResultContract extends ActivityResultContract<String, String> {

        @NonNull
        @Override
        public Intent createIntent(@NonNull Context context, String input) {
            return null;
        }

        @Override
        public String parseResult(int resultCode, @Nullable Intent intent) {
            return intent.getStringExtra("s");
        }
    }
}

Activity常用方法

Context

Context环境上下文其实就是指与android服务(AMS,WMS)交互的通信接口。

v2-2fcc5646c25b15a9a8ca708925758114_720w

Activity、Service、Application间接的继承Context。应用程序进程中Context的数量为Activity的数量加Service的数量加Application的数量。一个应用程序进程的Application只有一个。

- ContextImpl
继承自Context,是Context的实现类
  
- ContextWrapper
继承自Context,是ContextImpl的包装类,内部将ContextImpl对象保存在类型为Context的成员变量mBase,大部分方法通过调用ContextImpl的相应方法实现。
  
- ContextThemeWrapper,Service,Application
继承自ContextWrapper,通过ContextImpl对象调用Context方法,在ContextWrapper的基础上添加了不同的功能。

- Activity
继承自ContextThemeWrapper,ContextThemeWrapper中包含与主题相关的方法,因此Activity继承ContextThemeWrapper。

Context的应用场景

20150104183450879

Activity Context

Activity Context 的创建可以定位到 Activity 启动过程,在 ActivityThread 中。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

        ...
        ContextImpl appContext = ContextImpl 作为参数
关联ContextImpl(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());

        ...
        appContext.setOuterContext(activity);

        activity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.configCallback);

    }
这里分为 4 步来看: - 创建 ContextImpl 对象 - 创建 Activity 实例 - ContextImpl 关联 Activity - Activity 关联 ContextImpl

STEP1:创建的是对应的 Activity 的 ContextImpl

private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
       ...
        ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.loadedApk, r.activityInfo, r.token, displayId, r.overrideConfig);
      ...


     static ContextImpl createActivityContext(ActivityThread mainThread,
        ...
        ContextImpl context = new ContextImpl(null, mainThread, loadedApk, activityInfo.splitName,
                activityToken, null, 0, classLoader);
        ...

STEP2:接着实例化 Activity,通过类加载创建 Activity

public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
    }

STEP3/4:通过 ContextImpl 和 Activity 的相互关联,Activity Context 的创建也就完成了,以后 Activity 的任务大多就是通过 ContextImpl 实现。

//第三步,在ContextImpl 中
    final void setOuterContext(Context context) {
        mOuterContext = context;
    }
    //第四步,在 Activity中
     final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
        ...
        attachBaseContext(context);
        ...    

    }

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(newBase);
        ...
    }

    //在 ContextWrapper 中
     protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

Activity Context 的使用

Activity Context 的实现就是 Activity ,因此如果对 Context 使用不当的话很容易发生内存泄漏,比如下面这两段代码

public class A
{
    private static Context mContext;

    public static void setContext(Context context){
        mContext = context;
    }
}
public class B
{
    private static B sInstance;
    private Context mContext;

    private B(Context context)
    {
        this.mContext = context;
    }

    public static synchronized B getInstance(Context context)
    {
        if (sInstance == null)
        {
            sInstance = new B(context);
        }
        return sInstance;
    }


}

在第一段代码中有一个静态的 Context ,在第二段是一个单例模式,一个静态实例拥有一个 Context 变量。在这两种情况下,Context 的周期就和应用程序一样,这是如果赋值时 Activity就会使得 Activity 在退出后不能正常被回收,因为还有 Context 引用。因此建议尽量 Application Context ,因为 Application Context 的周期就是整个应用程序,所以不用担心内存泄漏。但是在某些情况比如创建 Dialog, 或者启动组建的时候就只能使用 Activity Context 或者 Service Context ,这个时候就要注意内存泄漏问题了。

Fragment

Fragment是嵌在Activity中使用的UI片段 ,一 个Activity中可以组合多个Fragment,多 个Activity可重复使用某个Fragment。Fragment可以视为Activity的模块化组成部分,它具有自己的生命周期,能处理用户事件,并能在Activity运行时动态添加或移除。

创建Fragment

public class ProfileFragment extends Fragment {
    
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_profile, container, false);
    }
}

Fragment生命周期

Activity创建时,与之关联的Fragment也被创建,并处于启动状态。

Activity调用Fragment的onAttach()方法使得Fragment附属于Activity,然 后Activity通过调用onAttachFragment()方法接收到Fragment的引用。

Fragment调用onCreate()方法进行创建,接着调用onCreateView()方法构建界面视图,当Activity包含多个Fragment时,这个过程就会进行多遍,确保Fragment都被创建。

当Activity处于运行状态时,可以动态添加、删除或替换Fragment,当Fragment被添加时处于启动状态,被删除时则处于销毁状态。当Activity暂停时,该Activity关联的所有Fragment都会被暂停,当Activity被销毁时,Activity中所有的Fragment也会被销毁。

image-20210425050355544

-Fragment有与Activity相同的生命周期方法,但比Activity多出几个方法,说明如下:
-onAttach():Fragment与Activity关联时调用;
-onCreateView():Fragment创建界面视图时调用;可能被多次执行
-onActivityCreated():Fragment关联的Activity已经创建完成时调用;
-onDestroyView():Fragment的视图被销毁时调用;
-onDetach():Fragment与Activity解除关联时调用;

Fragment与Activity数据传递

使用实例方法
通过FragmentManager实例的findFragmentById()方法获取Fragment实例,从而调用Fragment public的方法
//Activity向Fragment传数据
TitleFragment titleFragment = (TitleFragment) getSupportFragmentManager()
                .findFragmentById(R.id.fragment_title); 
通过Fragment实例的getActivity()方法获取关联的Activity对象,从而调用Activity public的方法。getActivity()还可以获取Fragment的上下文。
//Fragment向Activity传数据
MainActivity activity = (MainActivity) getActivity();
使用Bundle传递数据
通过初始化Fragment时调用setArgument()方法将Bundle对象传递给Fragment
// 创建Fragment对象
public static ContentFragment newInstance(String param) {    
    ContentFragment fragment = new ContentFragment();
    // 使用Bundle对象装载数据
    Bundle args = new Bundle();
    args.putString("param", param);
    // 传递bundle对象
    fragment.setArguments(args);
    return fragment;
}
使用Listener监听器
首先在Fragment类中定义事件监听的监听器接口,Activity类实现 该监听器。Fragment 在其onAttach() 生命周期方法中捕获接口实现,然后调用接口方法,触发监听方法与 Activity 通信。
Fragment使用FragmentResultOwner接口互传数据
从 Fragment 1.3.0-alpha04开始,每一个FragmentManager实现了FragmentResultOwner接口。这意味着FragmentManager可以像一个贮存库作为Fragment间通讯的媒介。

image-20210425052303540

implementation "androidx.fragment:fragment:1.3.0-rc02"
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    getParentFragmentManager().setFragmentResultListener("key", this, new FragmentResultListener() {
        @Override
        public void onFragmentResult(@NonNull String key, @NonNull Bundle bundle) {
            // 这里使用的是String,但是任何其他能够被放在Bundle中的数据类型都是支持的
            String result = bundle.getString("bundleKey");
            //做一些其他事情
        }
    });
}
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Bundle result = new Bundle();
        result.putString("bundleKey", "result");
        getParentFragmentManager().setFragmentResult("requestKey", result);
    }
});
原创文章采用 CC BY-NC-SA 4.0协议 进行许可,转载请著名转自: Android阶段性总结

4 评论

  1. Finger 4月25日 回复

    来人!

  2. chen 12月23日 回复

    哼哼哼

  3. lzgggazbde 9月22日 回复

    叼茂SEO.bfbikes.com

  4. vqlsrayvaf 9月23日 回复

    看的我热血沸腾啊