Android基础知识点学习总结

Android基础知识点学习总结

安卓基础知识个人学习笔记分享~

一、Android系统架构

Linux内核层→系统运行层→应用框架层→应用层

1、Linux内核层:Android系统是基于Linux内核的,这一层为硬件提供了底层的驱动,例如显示驱动,音频驱动,电源管理等等

2、系统运行库层:这一层主要通过C/C++实现,为Android系统提供支持,例如SQLite库提供了数据库的支持,Webkit提供了浏览器内核的支持(SQLite数据库:轻量级,嵌入式关系型数据库)

3、应用框架层:这一层提供了应用程序可能用到的各种API,我们开发者可以使用这些API来构建自己的应用程序

4、应用层:所有安装在手机上的应用程序都是属于这一层,当然也包括我们自己开发的应用程序

二、四大组件

Activity→Service→BroadcastReceiver→ContentProvider

1、Activity:所有看的到东西都是放在Activity中的

2、Service:我们无法看见他,他只会在后台默默运行,即使我们退出了应用,也是可以继续运行的

3、BroadcastReceiver:他允许应用接收来自各处的广播消息,例如电话,短信等,我们开发的程序也可以通过他向外发出广播消息

4、ContentProvider:为应用程序之间共享数据提供了可能,例如读取系统通讯录中的联系人,我们可以使用他来实现

三、目录结构各文件含义

.gradle和.idea

放置的都是Android Studio自动生成的一些文件

gradlew和gradle.bat

这两个文件是用来在命令行界面中执行gradle命令的,其中gradlew是在Linux或Mac系统中使用的,gradlew.bat是在Windows中使用的

HelloWorld.iml

这个文件是IntelliJ IDEA自动生成的(Android Studio是基于IntelliJ IDEA的),用于标识这是一个IntelliJ IDEA项目

local.properties

用于指定本机中的Android SDK路径,通常是自动生成的,如果SDK位置改变了,我们需要将文件中的路径改成新的位置即可。

setting.gradle

用于指定项目中所有引入的模块,通常都会自动完成

app目录下

libs目录

如果我们使用了第三方jar包,就需要将这些jar包都放在这个目录下,然后他会自动添加到项目的构建路径里

AndroidManifest.xml

这是整个Android项目的配置文件,我们在程序中定义的四大组件都需要在这个文件里注册,还可以在这个文件中给应用程序添加权限声明。****没有在这个文件中注册的组件是不能使用的!****MainActivity是我们编写的门面,然后将这个MainActivity注册进AndroidManifest.xml中才可以使用。

android.intent.action.MAIN决定应用程序最先启动的Activity ,android.intent.category.LAUNCHER决定应用程序是否显示在程序列表里。Main和LAUNCHER同时设定才有意义。

Proguard-rules.pro

用于指定项目代码的混淆规则,让破解者难以阅读

res目录

以drawable开头的都是用来放图片的

以mipmap开头的都是用来放应用图标的(一般放在drawable-xxhdpi目录下)

以layout开头的都是用来放布局文件的

以values开头的都是用来放字符串,样式,颜色等配置的

String.xml中的字符串引用方式有两种:

代码中:R.string.name名

Xml中:@string/name名

如果引用的是图片资源可以替换成drawable,应用图标替换成mipmap,布局文件替换成layout,等等以此类推(相同类型不同目录下的xml文件引用前缀都是一样的,不一样的只有文件名)

四、日志工具

Android有五个日志级别,分别是log.v()、log.d()、log.i()、log.w()、log.e().级别依次递增

Log.v():打印最琐碎的日志信息,对应级别是verbose

Log.d():打印一些调试信息,对应级别debug

Log.i():打印一些比较重要的信息,对应级别info

Log.w():打印一些警告信息,对应级别warn

Log.e():打印一些错误信息,对应级别error

使用方法:例如Log.d(“MainActivity”,”onCreate execute”);

这个方法中有两个参数,第一个参数是tag,一般传入当前的类名,用于对打印出来的信息过滤,第二个参数是msg,即我们想要打印的内容。

五、Activity

1、Activity的启动方式分为两种:显式启动和隐式启动

显式启动:使用Intent对象,直接在Intent构造函数里指明要启动的activity

例如Intent intent = new Intent(getApplicationContext(),SecondActivity.class);

startActivity(intent);

隐式启动:匹配action和category

例如:Intent intent = new Intent(“com.example.activitytest.ACTION_START”);

intent.addCategory(“com.example.activitytest.MY_CATEGORY”);

startActivity(intent);

Intent可以添加多个category,但是只能指定一个action

2、使用intent在activity传递数据:

FirstActivity中的代码:

Intent intent = new Intent(getApplicationContext(),SecondActivity.class);

​ String data = “Hello SecondActivity!”;

​ intent.putExtra(“extra_data”,data);

​ startActivity(intent);

//使用putExtra方法,将信息装进intent中

SecondActivity中的代码:

Intent intent = super.getIntent();

​ String extraData = intent.getStringExtra(“extra_data”);

​ Log.d(“SecondActivity”,“extra data is :”+extraData);

//首先调用父类的方法得到intent,然后使用getStringExtra方法解析键为"extra_data"的字符串

3、返回数据给上一个activity:

Firstactivity中:

Intent intent = new Intent(SecondActivity.class);

​ startActivityForResult(intent,1);

//使用 startActivityForResult(intent,1);这个方法来启动activity,并传入一个请求码(唯一值)

@Override

protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {

​ super.onActivityResult(requestCode, resultCode, data);

​ while (requestCode = 1){//判断这个是不是我们之前传出去的请求码

​ if (resultCode == RESULT_OK){//判断返回数据时传入的处理结果

​ String returnData = data.getStringExtra(data_return);

​ Log.d(“FirstActivity”,“return data is :”+returnData);

​ }

​ }

}

//然后重写onActivityResult方法,然后调用父类的这个方法,在这个方法里面有3个参数,第一个就是请求码,第二个是第二个activity返回的处理结果,第三个就是响应的数据了,我们需要对请求码和处理结果进行判断,再来接收数据

SeconActivity中:

Intent intent1 = new Intent();

​ intent1.putExtra(“data_return”,“Hello FirstActivity”);

​ setResult(RESULT_OK,intent1);//第一个参数用于向上一个activity返回处理结果即resultCode,一般只使用RESULT_OK或RESULT_CANCELED

​ finish();

//使用putExtra方法往intent里传值,使用setResult方法返回处理结果和intent,最后调用finish()方法来结束这个activity

/*如果是按返回键来返回到上一个activity,我们可以重写onBackPressed()方法/

4、Activity的4种状态:

1、*运行状态*:当activity位于栈顶的时候,activity就是运行状态,系统最不愿意回收的就是运行状态的activity

2、*暂停状态*:当activity不处于栈顶的时候,但仍然可见的时候,activity就是暂停状态(例如对话框形式的activity),系统也不愿意去回收他,只有在内存极低的时候才会去回收他

3、*停止状态*:当activity不处于栈顶,且完全不可见的时候,就是停止状态,此时系统仍然会保留这种activity的状态和成员变量,但是并不一定,当系统需要内存的时候,这中activity就很有可能会被回收。

4、*销毁状态*:当activity从栈中被移出的时候,就是销毁状态,系统最喜欢回收这种状态的activity,以保证内存的充足

5、Activity的生存期:

Activity类中定义了7个回调方法,对应了Activity生命周期的每一个环节,分别是

onCreat()、onStart()、onResume()、onPause()、onStop()、onDestroy()、onRestart()方法

1、onCreat():activity第一次创建的时候被调用

2、onStart():在activity由不可见为可见的时候被调用

3、onResume():在activity准备与用户交互的时候被调用,并且此时Activity一定位于栈顶,且处于运行状态

4、onPause():准备去启动或者恢复另一个activity时候调用,这时候系统会将一些消耗CPU的资源释放掉,以及保存一些关键数据,这个方法执行要快,不然会影响新的栈顶activity的使用

5、onStop():在activity完全不可见的时候调用(如果启动的是对话框式的新activity则不会执行,但onPause()会执行,这是他俩的区别)

6、onDestroy():在activity被销毁之前调用,然后activity状态变成销毁状态

7、onRestart():在activity又停止状态变为运行状态的时候调用

6、Activity的启动模式:

Standard、singleTop、singleTask、singleInstance

1、Standard:标准的启动模式,activity每次启动都是一个新的,然后再入栈

2、singleTop:当activity位于栈顶的时候,再去启动这个activity不会再创建一个新的activity,而是直接去使用之前那个activity

3、singleTask:当去启动一个activity的时候,回去检查栈中是否有这个activity,如果有则将这个activity以上的activity全部出栈

4、singleInstance:这个模式下的activity会启用一个新的返回栈来管理这个activity(使用案例:使用这个启动模式的activity可以被其他程序共享,因为这个activity的返回栈是独立出来的)

UID进程之间可以相同,但是PID不一样,他是唯一标识的,UID相同的进程可以实现相互之间的数据共享,但是其他的比如类还是不可以共享的

六、AndroidManifest.xml文件

权限声明

Android将将权限分成了三类,普通权限和危险权限,以及以一类特殊权限(暂时不讨论),

普通权限:系统会帮用户自动申请此类权限(例如广播权限等)

危险权限:需要用户自己手动授权(联系人信息,地理位置等)

权限是按组划分的,有可能好几个权限名在一个组里,*原则上,用户同意某一个权限申请之后,同组其他的权限也会被自动授权*,但是Android系统可能随时调整权限的分组,所以我们不能基于此规则来实现功能逻辑。

使用危险权限时必须进行运行时权限处理

首先我们需要在AndroidManifest.xml文件中声明这个权限

首先我们需要判断用户是不是以及对应用受过权了

借助ContextCompat.checkSelfPermission(ThirdActivity.this,Manifest.permission.CALL_PHONE) 方法,传入两个参数,其中第一个参数为当前activity,第二个参数是我们请求的权限名(常量),然后将这个方法的返回值与PackageManager.PERMISSION_GRANTED做比较,如果相等则已授权,否则就是还没有授权,

如果没授权的话我们需要向用户申请权限,调用 ActivityCompat.requestPermissions(ThirdActivity.this,new String[]{Manifest.permission.CALL_PHONE},2);方法,这个方法传入三个参数,第一个参数是activity实例,第二个参数是string数组,我们可以把权限名放在这个数组里面,第三个参数是请求码,要求是唯一值就行。

如果我们需要请求用户授权,调用requestPermissions方法后,需要重写 onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)方法,并在这个方法里判断用户是否授权

清单注册

四大组件Activity,Service,BroadcastReceiver,ContentProvider的使用必须在Androidmanifest.xml中注册,其中BroadcastReceiver分为动态注册和静态注册

Activity注册:

Service注册:<service android:name=“.SecondService”

BroadcastReceiver注册:

静态注册:

动态注册:

通过继承 BroadcastReceiver在activity内建立一个内部动态广播接收器类(TimeChangeReceiver),然后重写onReceive方法

然后在activity的onCreate()方法中实例化一个IntentFilter()对象,通过addAction()往这个对象传入一个常量,比如我们这里可以传入一个”android.intent.action.TIME_TICK”值,当系统时间发生变化的时候,系统发的正是一个值为这个的广播,也就是说我们想要监听什么广播,就往这里面添加相应的action,然后再把我们之前建立的广播接收器类实例化,最后调用registerReceiver(timeChangeReceiver,intentFilter)方法,这个方法需要接收两个参数,第一个就是我们自定义的广播接收器类的实例,第二个就是intentFilter实例。

*动态注册一定要记得取消注册!*

这里我们可以在onDestroy()中调用unregisterReceiver(timeChangeRe ceiver)

ContentProvider注册:注意android:authorities代表了权限,要和你设置ContentProvider的uri路径格式中:中的authority一致,否者会出现问题。

七、Service服务

1、了解Service在Android的定位

Service是Android中实现程序后台运行的解决方案,他不依赖于任何用户界面,他也不是运行在一个独立的进程中,而是依赖于创建service时所在的应用程序进程,当这个应用程序进程被杀掉时,service也会停止运行

Service不会自动开启线程,默认是运行在主线程中的,我们需要在service内部创建子线程,将执行的任务放在子线程中执行,否则容易导致主线程被阻塞

2、startService()和bindService()区别
  1. 启动方式:都是用来启动service的,但是bindService()获得的是一个持久连接

  2. 和activity联系:startService()启动的service如果activity销毁,仍可以保持运行,除非整个应用程序被杀掉,bindService()启动的service会跟activity一起销毁

  3. 数据交换方面:startService()启动的service不可以和activity进行数据交换,而bindService()可以

  4. 回调函数方面:startService()启动的service的回调函数是onStartCommand(),而bindService()是onBind()

  5. 结束service的方式:startService()是启动的service是调用stopService()或者stopSelf()方法,而bindService()是调用unBindService()方法。当同时调用startservice()和bindService()的时候,要同时满足上面两种结束方式,这个service才会被停止

3、Service生命周期

startService()→onCreat()(service创建)→onstartCommand()(service运行)→stopService()(service停止运行)→onDestroy()(service销毁)

bindService()→onCreat()(service创建)→onBind()(绑定service)→unBindService()→onUnBind()(解除绑定,service停止运行)→onDestroy()(service销毁)

生命周期理解

StartService,然后oncreate,onstartcommand,再绑定bindServic,然后onbind,onServiceConnection,再stopService方法,此时不回调任何方法,再unBindService,然后onDestroy

*StartService,然后oncreate,onstartcommand,,再绑定bindServic,然后onbind,onServiceConnection,再unBindService方法,此时不回调任何方法,再stopService,然后onDestroy

bindService,然后onCreat,onBind,onServiceConnection,再startService,然后onstartcommand,再stopService方法,此时不回调任何方法,再unBindService,然后onDestroy

bindService,然后onCreat,onBind,onServiceConnection,再startService,然后onstartcommand,再unBindService方法,此时不回调任何方法,再stopService,然后onDestroy

4、service的使用

1.关于startService的使用

首先在Androidmanifest.xml文件中注册这个service

首次创建时的代码放进MyService中的onCreat()方法中,要执行的业务逻辑代码放进onStartCommand()方法里

在TestActivity里使用这个service

//开启这个服务

Intent intent = new Intent(TestActivity.this,MyService.class);

startService(intent);

//关闭这个服务

Intent intent = new Intent(TestActivity.this,MyService.class);

stopService(intent);

\2. bindService的使用

同样需要在Androidmanifest.xml中注册这个service

然后在MyService中新建一个内部类MyBinder继承Binder,在这个类里面执行业务代码,然后在onBind()方法中返回这个MyBinder内部类的实例

在TestActivity里使用这个service

首先创建一个匿名类new ServiceConnection()实现,并重写onServiceConnected(),onServiceDisconnected()方法,在onServiceConnected()方法中先获取MyBinder的实例,然后调用MyBinder内部类中的业务代码,这个匿名类的返回值类型为ServiceConnection

然后将这个类型的变量conn传进bindService方法和unBindService方法中,实现绑定和解绑操作

八、广播机制(broadcast)

标准广播和有序广播

标准广播:无序,异步执行,发送出去广播所有可以接收的broadcastReceiver几乎同时收到,效率高,且无法被拦截,

有序广播:有序,同步执行,一次只能被一个接收器接收,只有这个接收器逻辑执行完毕才会向下一个broadcastReceiver传送,效率低,可以被拦截

\1. 学习broadcast的注册方式。

静态注册和动态注册

静态注册:在AndroidManifest.xml中注册,

在Androidmanifest.xml中的intentFilter标签中添加一个要监听的action标签,如果是“危险”的广播还需要进行权限申明

动态注册:在代码中注册

使用intentFilter过滤器,然后添加一个action值,然后实例化一个broadcastReceiver对象(此处测试类中重写的onReceive方法中只实现了一个Toast),再调用registerReceiver()方法将这两个参数传递进去。然后就实现了动态广播注册

2. broadcast使用的注意事项

标准广播和有序广播的实现方式基本一致,只是最后发送调用的方法不同

发送标准广播:sendBoardcast(intent)

发送有序广播:sendOrderedBroadcast(intent,null)

在标签里面设置优先级,android:priority越高的接收器越先收到,同等优先级情况下,谁先启动的快谁先收到广播

静态注册的广播接收者无法收到隐式广播,此时我们需要指定包名,将这条隐式广播变成显式广播

*动态注册的广播要手动移出广播接收器*

3. 外部broadcast以及本地广播的使用

外部broadcast:程序之外的广播,例如系统广播等等,如前面动态注册和静态注册的使用

本地广播:需要使用到LocalBroadcastManager的实例(instance = LocalBroadcastManager.getInstance(this);),然后通过这个实例再去调用registerReceiver,即instance.registerReceiver(),里面传递的参数跟前面动态注册的参数是一样的。

4. SharePrefence(轻量级存储)的使用方式

适合单进程,小批量的数据存储与访问,因为他是基于单个xml文件的,一次性加载进内存的。属于全局可读写。

使用方式:我们首先需要获取一个SharedPreferences对象,有两种方式可以得到这个对象

Context类中的getsharedPreferences()方法,两个参数,第一个指定文件名(若文件不存在则会创建一个),第二个指定操作模式

Activity类中的getPreferences()方法,只有一个操作模式参数,文件名默认使用当前activity的类名

存储数据:

使用以上方式获得一个sharedPreferences对象后,调用edit()方法,然后获得一个SharedPreferences.Editor对象,这个对象中提供了putString,putInt,putBoolean等一系列方法,都是以键值对的形式存储数据,然后使用apply()或者commit()方式提交,apply()是异步的,commit()是同步的,推荐使用apply()方法

文件放在了Device File Explorer文件管理器下面的data/data//shared_prefs下了

取出数据:

也是先获得一个sharedPreferences对象,然后再调用这个对象的getString(),getInt(),getBoolean()方法,这些方法需要两个参数,第一个参数是键,第二参数指定若是取出数据失败返回的默认值

5.SharePrefence数据共享

两个应用(A和B)数据共享的前提是有 相同的sharedUserId,即首先我们需要在manifest.xml文件中配置这个属性android:sharedUserId=“com.xxx”.

然后在A应用中使用sharedPreferences存储数据(参考上面的存储数据)

接着在B应用中取出这些数据

首先创建一个Context 类型的对象packageText,然后调用createPackageContext(“com.netease.nim.demo”, CONTEXT_IGNORE_SECURITY);方法创建一个包上下文,把返回值赋值给packageText(此处操作需要在try catch中捕获异常)

然后通过这个packageText去调用getSharedPreferences(“userlists”, Context.MODE_MULTI_PROCESS);这个方法,返回值是SharedPreferences类型的,然后再通过这个返回值调用getString(),getBoolean()等方法得到A应用中存储的值(一样需要传入一个键,即A中存数据使用的键)

九、ContentProvider

ContentProvider概念:

翻译成中文是内容提供者,他可以实现应用之间的内容共享,可以指定哪部分具体的数据进行共享,而文件存储和sharePreferences是两种全局的可读写模式,相比较来说ContentProvider就可以保证数据没有泄露的风险。

ContentProvider使用表的形式来组织数据,类似一个单数据库表

每个ContentProvider都有一个唯一的公开的内容URI,用来指定他的数据集

一个标准的URI格式如下:

Content://com.example.contentProvider.accessProvider/table

1. contentProvider的使用

用法:用法一般有两种

①一种是使用现有的ContentProvider读取和操作相应的应用程序中的数据

查询:

ContentResolver.query( URI, projection, selection, selectionArgs, sortArgs)或者Activity.managedQuery(URI, projection, selection, selectionArgs, sortArgs)方法,这两个方法的参数一模一样,返回值也是相同的,不同的是activity可以对cursor对象管理,比如activity暂停的时候卸载cursor对象,restart的时候再重新查询。还可以调用Activity.startManaginCursor()方法来管理一个没有activity管理的cursor对象

参数解读:

第一个参数:URI指定某个应用程序下的某一张表

第二个参数:projection指定查询的列名

第三个参数:selection指定约束条件(对应SQL中where的约束条件)

第四个参数:selectionArgs给where中的占位符提供具体的值

第五个参数:sortOrder指定查询结果的排序方式

查询指定行的值,需要用到ID值,如果我们不想手动去拼接URI可以使用ContentUris.withAppendedId(URI,ID)方法

示例代码如下:


public class ForthActivity extends AppCompatActivity {

    private List<String> contactList = new ArrayList<String>();
    private ArrayAdapter<String> adapter;

    private static final String ForthActivity = "ForthActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.forth_activity);
        Log.d(ForthActivity, "执行了ForthActivity中的onCreate方法");
        Button readContacts = findViewById(R.id.readContacts);
        readContacts.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (ContextCompat.checkSelfPermission(ForthActivity.this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){
                    ActivityCompat.requestPermissions(ForthActivity.this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
                }else{
                    readContacts();
                    }
                }
        });

        Button accessProvider = findViewById(R.id.accessProvider);
        accessProvider.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                ContentResolver contentResolver = ForthActivity.this.getContentResolver();

                Uri uri = Uri.parse("com.example.contentProvider.provider1/table1/1");
                Cursor cursor = contentResolver.query(uri, null, null, null, null);
                while (cursor.moveToNext()){
                    int num = cursor.getColumnIndex("1");
                    Log.d("forthActivity", "num"+num);
                }
                cursor.close();
            }
        });
    }

    private void  readContacts(){
        ContentResolver contentResolver =getContentResolver();
        Uri uri1 = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
        Cursor cursor = contentResolver.query(uri1, null, null, null, null);

        while (cursor.moveToNext()) {
            @SuppressLint("Range") String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
            @SuppressLint("Range") String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
            Log.d("forthActivity", "name:"+name+"电话号码:"+number);
        }

        cursor.close();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode){
            case 1:
            {
                if (permissions.length!=0 && grantResults[0] != PackageManager.PERMISSION_GRANTED){
                    Toast.makeText(ForthActivity.this, "获取权限后重试", Toast.LENGTH_SHORT).show();
                }
            }break;
        }
    }
}

readContacts()方法解读:首先获得一个URI,然后再获得一个contenResolver对象,再通过这个对象调用相关的查询方法,将URI和其他参数传进这个查询方法里,返回一个cursor对象(游标、指针),然后通过这个指针去遍历这个结果集

增加:

调用contentResolver().insert(URI, values)方法

ContentValues values = new ContentValues();

values.put(“ID”, “Abraham Lincoln”);

Uri uri = ContentResolver().insert(URI, values);

解读:创建一个contentvalue对象,然后以键值对的形式往里面put值,键是列名,然后调用insert()方法,传入URI和这个put了值的values对象作为参数,这个插入方法会有一个返回结果,我们可以根据这个返回结果来判断是否插入成功,一般插入成功会返回该行的URI

修改:

Update()方法

int rowsUpdated = contentResolver().update(

URI,

updateValues,

selectionClause,

selectionArgs);

参数含义:URI,更新的值,约束条件,给where中的占位符提供具体的值

删除:delete()方法

②另一种是创建自己的ContentProvider,给程序的数据提供外部访问接口

在自定义的ContentProvider中有六个需要重写的方法,其他五个比较常见,分别是创建和增删改查,最后还有一个getType()方法,这个方法用来返回uri对象所对应的MIME类型

指定对应的uri地址,然后这写uri地址做逻辑判断,等到其他程序来调用的时候,通过这些逻辑判断来进行安全控制和访问内容的限制

public class AccessProvider extends ContentProvider {
    public AccessProvider() {
    }

    private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        uriMatcher.addURI("com.example.contentProvider.provider1",
                "table1", 1);
        uriMatcher.addURI("com.example.contentProvider.provider1",
                "table1/#", 2);
        uriMatcher.addURI("com.example.contentProvider.provider1",
                "table2", 3);
        uriMatcher.addURI("com.example.contentProvider.provider1",
                "table2/#", 4);
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // Implement this to handle requests to delete one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO: Implement this to handle requests to insert a new row.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public boolean onCreate() {
        // TODO: Implement this to initialize your content provider on startup.
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        switch (uriMatcher.match(uri)){
            case 1:
                Log.d("AccessProvider", "进入了第一个判定条件");break;
            case 2:
                Log.d("AccessProvider", "进入了第二个判定条件");break;
            case 3:
                Log.d("AccessProvider", "进入了第三个判定条件");break;
            case 4:
                Log.d("AccessProvider", "进入了第四个判定条件");break;
            default:
                break;
        }
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @RequiresApi(api = Build.VERSION_CODES.O)


    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

如何使用,见①中代码

  1. contentProvider进程间通信

访问危险权限需要在manifest.xml中申明,提供接口和访问数据上面有提到

十、五大布局的使用以及约束布局的使用

1、了解各个布局的区别以及如何使用

布局包含控件,布局与布局之间是可以嵌套使用的,可以理解成控件是布局里面的最小组成单元。

Android的五大布局+后来的constraintLayou(约束布局)

五大布局分别是:LinearLayout,relativeLayout,frameLayout,tableLayou(淘汰),absoluteLayou(淘汰)

2、LinearLayout(线性布局):

这个布局有horizontal(水平排列)和vertical(垂直排列)两种方式,默认就是垂直排列,水平排列的时候,控件的宽度不能指定为match_parent,垂直排列的时候控件的高度不能指定为match_parent

里面比较常用的属性:

layout_gravity:他是用来指定控件在布局中的对齐方式的,值得注意的是当linearLayout的布局方式为水平的时候,这个属性只有垂直对齐才有效,当linearLayout的布局方式为垂直的时候,这个属性只有水平对齐才有效

Layout_weight=”1”:以LinearLayout的水平布局为例,如果有三个按钮,我们可以将这三个按钮的宽度属性指定为”0dp”,然后再使用这个属性,意思是说这三个按钮水平分布平分水平的宽度,这个1其实就是权重

3、relativeLayout(相对布局):

这个布局他可以通过相对定位的方式来让控件出现在任何位置

比较常用的属性:

layout_alignParentleft,layout_alignParenttop…和哪边一直这里是左边和上边一致

Layout_above=”@id/button1” 在哪个控件的上面

Layout_below=”@id/button1” 在哪个控件的下面

Layout_toLeftOf=”id/button1” 在哪个控件的左边

Layout_toRightOf=”@id/button1” 在哪个控件的右边

4、frameLayout(帧布局):

这里面的控件默认会摆放在布局的左上角,他的定位方式比较少,比如:

Layout_gravity=”left”:指定控件居左对齐

Layout_gravity=”right”:指定控件居右对齐

十一、自定义Shape资源,drawable资源等

1、了解drawable文件夹中可自定义的文件种类

Drawable是什么,翻译成中文意思是说能画的东西,专业的解释是:一种可以在Canvas上进行绘制的抽象的概念,颜色,图片都可以说是drawable,可以通过xml文件定义,也可以通过代码创建,所有的实现的drawable都是Drawable这个抽象类的子类。

drawable文件下可以存放图片资源,例如*.jpg,.png,.gif,

还可以放xml文件,其中常用xml类型的资源文件有:

ShapeDrawable:通过颜色构造的图形,可以是纯色的图形,也可以是有渐变效果的图形,shape标签创建的Drawable实体是GradientDrawable

LayerDrawable:XML标签为layer-list,层次化的Drawable合集,可以包含多个item,每个item表示一个Drawable,item中可以通过android:drawable直接引用资源,android:top等表示Drawable相当于View上下左右的偏移量。

StateListDrawable:对应于selector标签,用于View根据状态选择不同的Drawable

2、自定义实现Shape,drawable等属性。

十二、ListView,GridView学习使用

Listview:列表视图,我们可以使用上下滚动的方式来将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据会滚动出屏幕

使用方式:我们需要借助适配器来完成,一般推荐使用ArrayAdapter

首先我们创建一个activity,他会有一个对应的布局文件(这里使用linearlayout布局),然后在这个布局文件中使用一个控件,宽高都设置成match_parent,

然后再创建一个linearlayout,水平布局,在里面可以创建一个图片控件,引用一张图片,宽高都设置成固定值,再创建两个,依次水平放在这个图片控件后面,这个布局文件相当于是一个item样式,其中的三个控件是样式的组成小模块

然后创建一个水果实体类,这个实体类的属性对应着上面那三个控件的id,然后使用这个实体类创建一堆数据

然后再创建一个水果适配器类,让他继承ArrayAdapter,重写getView方法,

//构造方法

public class FruitAdapter extends ArrayAdapter<Fruit> {
    public FruitAdapter(@NonNull Context context, int resource, List<Fruit> data) {
        super(context, resource,data);
    }
    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        //得到当前项的实例
        Fruit fruit = getItem(position);
        //为每一个子项加载设定的布局
        @SuppressLint("ViewHolder") View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item,parent, false);
        //获取图片,水果名字和价格的实例
        ImageView imageView = view.findViewById(R.id.fruit_image);
        TextView fruitName = view.findViewById(R.id.textView_name);
        TextView fruitPrice = view.findViewById(R.id.textView_price);
        //设置要显示的图片和文字
        imageView.setImageResource(fruit.getImageId());
        fruitName.setText(fruit.getName());
        fruitPrice.setText(fruit.getPrice());
        return view;
    }

    @Nullable
    @Override
    public Fruit getItem(int position) {
        return super.getItem(position);
    }

    @Override
    public long getItemId(int position) {
        return super.getItemId(position);
    }
}

然后在activity里面通过listview的标签id得到这个标签,然后实例化这个水果适配器得到一个实例,往构造方法里面传入三个参数,分别context,还有一个自定义的布局,还有一个数据源。

最后再将这个适配器的实例传进这个listview中,setOnItemClickListener方法是给控件设置监听事件

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.list_view);
        addData();
        listView = findViewById(R.id.listView);
        FruitAdapter fruitAdapter = new FruitAdapter(SixActivity.this, R.layout.fruit_item, dataNum);
        listView.setAdapter(fruitAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                String fruitName = ((TextView) view).getText().toString();
                Toast.makeText(SixActivity.this, "你选择水果是"+fruitName, Toast.LENGTH_SHORT).show();
            }
        });


    }

GridView:GridView的使用跟Listview的使用差不多,只是自定义的适配器去继承的适配器有所不同,ListView去继承的是ArrayAdapter,而GridView去继承的适配器是BaseAdapter类,他俩为数据设置样式时,都是要获得这个项的实例,再通过这个实例去获得里面的子实例,然后得到里面的属性值,这些属性值则对应着自定义样式文件中的控件id

十三、RecycleView学习使用

RecyclerView可以实现ListView的效果,还可以实现横向布局,还可以实现瀑布流式布局

实现LIstView的效果:

首先我们需要导包 implementation ‘androidx.recyclerview:recyclerview:1.2.1’,然后在activity的布局文件中引用这个布局

然后我们自定义的数据样式文件可以直接使用之前的listView的样式文件,样式文件中的控件id所对应的那个实体类也可以直接使用之前的Fruit实体类

然后我们再自定义一个适配器去继承RecyclerView.Adapter<RecyclerFruitAdapter.ViewHolder>,

package com.example.recyclerView;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import com.example.applicationtest1.R;
import com.example.listView.Fruit;

import java.util.ArrayList;
public class RecyclerFruitAdapter extends RecyclerView.Adapter<RecyclerFruitAdapter.MyViewHolder> {
    private ArrayList<Fruit> fruitList;
    public RecyclerFruitAdapter(ArrayList<Fruit> fruitList) {
        this.fruitList = fruitList;
    }
    class MyViewHolder extends RecyclerView.ViewHolder {
        ImageView fruitImage;
        TextView textView_name;
        TextView textView_price;

        public MyViewHolder(@NonNull View view) {
            super(view);
            fruitImage = view.findViewById(R.id.fruit_image);
            textView_name = view.findViewById(R.id.textView_name);
            textView_price = view.findViewById(R.id.textView_price);
        }
    }
    @Override
    public int getItemViewType(int position) {
        return super.getItemViewType(position);
    }
    
    @NonNull
    @Override
    public RecyclerFruitAdapter.MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerFruitAdapter.MyViewHolder holder, int position) {
        //得到当前item项所包含的实例
        Fruit fruit = fruitList.get(position);
        //给这个实例的组成单元即子实例设置参数
            holder.fruitImage.setImageResource(fruit.getImageId());
            holder.textView_name.setText(fruit.getName());
            holder.textView_price.setText(fruit.getPrice());
    }

    @Override
    public int getItemCount() {
        return fruitList == null ? 0 : fruitList.size();
    }
}

注意这里的RecyclerFruitAdapter就是我们自定义的适配器名字,然后再写一个有参构造方法去接收数据,然后重写里面的方法onCreateViewHolder,onBindViewHolder,getItemCount

然后我们还需要自定义一个内部类,然后去继承RecyclerView.ViewHolder,在这个内部类里面我们需要重写ViewHolder方法,然后在这个方法里面通过view去调用自定义控件的id去获得相应的控件实例,

然后在外部类的onCreateViewHolder方法中得到一个view,创建一个ViewHolder的实例后,再将这个view传进去

然后在外部类的onBindViewHolder方法中去绑定这个holder,在这个方法里面通过position参数得到FruitList数据源的一个个item实例——fruit,然后得到fruit实例中的属性,再然后通过holder去设置控件的参数

最后getItemCount方法中设置项的数量,一般通过fruitList.size()来获取他的长度

到此适配器的实现就完毕了

接着在activity中调用这个适配器,

…//这上面做一些添加数据啥的

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.recycler_activity);
        addData();
        //得到这个recycler的控件
        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        //布局管理器里面添加布局的样式,比如线性布局,网格布局,瀑布流布局等等
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);//得到线性布局管理器
//        layoutManager.setOrientation(RecyclerView.HORIZONTAL);//设置布局为水平布局,然后就实现了横向滚动的最后一步
        /*GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3);
        recyclerView.setLayoutManager(gridLayoutManager);*/
//        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);//瀑布流布局
        //在这个控件里面设置布局管理器,
        recyclerView.setLayoutManager(layoutManager);
        //创建一个自定义的适配器实例,并传入要展现的数据
        RecyclerFruitAdapter recyclerFruitAdapter = new RecyclerFruitAdapter(fruitArrayList);
        //在这个控件里面设置适配器
        recyclerView.setAdapter(recyclerFruitAdapter);

    }

RecyclerView和ListView,GridView的区别

1.RecyclerView使用布局管理器管理子view的位置

2.封装了viewholder的回收复用

3.recyclerView中不能使用setOnItemClickListener()简单的设置监听事件,而是需要我们自己给子项具体的view去注册点击事件

十四、Dialog & PopWindow的学习以及使用

Activity与Dialog都属于应用窗口,而PopWindow属于子窗口,Toast、输入法等属于系统窗口。应用窗口与系统窗口可以作为父窗口,子窗口不能作为子窗口的父窗口,也就说Activity与Dialog或者系统窗口中可以弹出PopupWindow,但是PopupWindow不能在自己内部弹出PopupWindow子窗口。

1、原生Dialog的学习以及使用

比较常用的有以下10种对话框

普通弹窗、确定和取消对话框、多按钮对话框、列表对话框、自定义对话框

单选对话框、多选对话框、日期对话框、时间对话框、带Adapter的对话框

一般来说:

使用AlertDialog.Builder builder1 = new AlertDialog.Builder(NineActivity.this);来创建一个对话弹窗实例,然后通过这个实例来设置标题,信息,图标等等

通过这个builder设置确定,取消,暂停按钮的方法如下

setPositiveButton(“确定”, new DialogInterface.OnClickListener())

setNegativeButton(“取消”, new DialogInterface.OnClickListener())

setNeutralButton(“暂停”, new DialogInterface.OnClickListener())

设置列表对话框

setItems(stringArray, new DialogInterface.OnClickListener() )

自定义对话框中需要加载布局然后得到一个view,最后要setView(view)

单选对话框

使用setSingleChoiceItems()方法,需要传入数据源,checkItem,匿名内部类

多选对话框

使用setMultiChoiceItems()方法,需要传入数据源,checkItem,匿名内部类

日期对话框

DatePickerDialog datePickerDialog =

new DatePickerDialog(NineActivity.this,new DatePickerDialog.OnDateSetListener() {

@Override

​ public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {

​ Toast.makeText(NineActivity.this, year+“年”+month+“月”+dayOfMonth+“日”, Toast.LENGTH_SHORT).show(); }},2022,4,20);

datePickerDialog.show();

使用DatePickerDialog()构造函数,然后传入一个context,以及一个日期监听器

时间对话框

TimePickerDialog timePickerDialog =

new TimePickerDialog(NineActivity.this, new TimePickerDialog.OnTimeSetListener() {

@Override

public void onTimeSet(TimePicker view, int hourOfDay, int minute) {

​ Toast.makeText(NineActivity.this, hourOfDay + “时” + minute + “分钟”, Toast.LENGTH_SHORT).show();}}, 9, 56, true);

timePickerDialog.show();

使用TimePickerDialog()构造函数,并传入一个context,以及一个时间监听器

带Adapter的对话框

除了要使用new AlertDialog.Builder创建一个Builder意外,还要new一个适配器,这个适配器可以是我们自定义的也可以是官方提供的,然后使用这个builder调用setAdapter将这个适配器实例传进去

总结以上的Dialog有三种创建方式,分别是普通弹窗,进度条对话框、日期/时间

new AlertDialog.Builder(NineActivity.this);

new ProgresssDialog(NineActivity.this);

new TimePickerDialog(),new DatePickerDialog(),

详细代码如下所示:

测试的按钮有点多

    @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.b1://普通对话框
                AlertDialog.Builder builder1 = new AlertDialog.Builder(NineActivity.this);
                builder1.setTitle("普通对话框");
                builder1.setMessage("这是一个普通的对话框");
                builder1.setIcon(R.mipmap.ic_launcher);
                builder1.create().show();
                break;
            case R.id.b2://确定和取消对话框
                AlertDialog.Builder builder2 = new AlertDialog.Builder(NineActivity.this);
                builder2.setTitle("确定和取消对话框");
                builder2.setMessage("这是一个确定和取消对话框");
                builder2.setIcon(R.mipmap.ic_launcher);
                builder2.setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        Toast.makeText(NineActivity.this, "点击了确定按钮", Toast.LENGTH_SHORT).show();
                    }
                });
                builder2.setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        Toast.makeText(NineActivity.this, "点击了取消按钮", Toast.LENGTH_SHORT).show();
                    }
                });
                builder2.create();
                builder2.show();
                break;

            case R.id.b3://多按钮对话框
                AlertDialog.Builder builder3 = new AlertDialog.Builder(NineActivity.this);
                builder3.setTitle("多按钮对话框");
                builder3.setMessage("这是多按钮对话框,请选择:");
                builder3.setIcon(R.mipmap.ic_launcher);
                builder3.setPositiveButton("继续浏览", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        Toast.makeText(NineActivity.this,"点击了继续浏览选项",Toast.LENGTH_SHORT).show();
                    }
                });
                builder3.setNeutralButton("暂停", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        Toast.makeText(NineActivity.this, "点击了暂停选项", Toast.LENGTH_SHORT).show();
                    }
                });
                builder3.setNegativeButton("离开", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        Toast.makeText(NineActivity.this, "点击了离开选项", Toast.LENGTH_SHORT).show();
                    }
                });
                builder3.create();
                builder3.show();
                break;

            case R.id.b4://列表对话框
                String[] stringArray = getResources().getStringArray(R.array.品牌);
                AlertDialog.Builder builder4 = new AlertDialog.Builder(NineActivity.this);
                builder4.setTitle("列表对话框");
                builder4.setIcon(R.mipmap.ic_launcher);
                builder4.setItems(stringArray, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        Toast.makeText(NineActivity.this, "你选择了" + stringArray[i], Toast.LENGTH_SHORT).show();
                    }
                });
                builder4.create().show();
                break;

            case R.id.b5://自定义对话框
               /* AlertDialog.Builder builder5 = new AlertDialog.Builder(NineActivity.this);
                builder5.setTitle("自定义对话框");
                builder5.setIcon(R.mipmap.ic_launcher);
                View view1 = LayoutInflater.from(NineActivity.this).inflate(R.layout.alert_layout, null);
                EditText Id = view1.findViewById(R.id.alert_editText);
                EditText pwd = view1.findViewById(R.id.alert_pwd);
                builder5.setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        Toast.makeText(NineActivity.this, "账号为:"+Id.getText().toString()+"密码为:"+pwd.getText().toString(),Toast.LENGTH_SHORT).show();
                    }
                });
                builder5.setView(view1).create().show();*/
                MyDialog dialog = new MyDialog(NineActivity.this, R.style.MyDialog);

                dialog.setTitle("MyDialog");
                dialog.setMessage("hello world");

                dialog.setNoOnclickListener(new MyDialog.onNoOnclickListener() {
                    @Override
                    public void onNoOnclick() {
                        Toast.makeText(NineActivity.this, "点击了确定按钮", Toast.LENGTH_SHORT).show();
                        dialog.dismiss();
                    }
                });
                dialog.setYesOnclickListener(new MyDialog.onYesOnclickListener() {
                    @Override
                    public void onYesOnclick() {
                        Toast.makeText(NineActivity.this, "点击了取消按钮", Toast.LENGTH_SHORT).show();
                        dialog.dismiss();
                    }
                });


                dialog.show();

                break;

            case R.id.b6://单选对话框
                AlertDialog.Builder builder6 = new AlertDialog.Builder(NineActivity.this);
                builder6.setTitle("单选对话框");
                builder6.setIcon(R.mipmap.ic_launcher);
                builder6.setSingleChoiceItems(R.array.品牌, 0, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        Toast.makeText(NineActivity.this, i + "", Toast.LENGTH_SHORT).show();
                    }
                });
                builder6.setPositiveButton("确定", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        Toast.makeText(NineActivity.this, "点击了确定按钮", Toast.LENGTH_SHORT).show();
                    }
                });
                builder6.create().show();
                break;

            case R.id.b7://多选对话框
                AlertDialog.Builder builder7 = new AlertDialog.Builder(NineActivity.this);
                builder7.setTitle("多选对话框");
                builder7.setIcon(R.mipmap.ic_launcher);
                builder7.setMultiChoiceItems(R.array.品牌, null, new DialogInterface.OnMultiChoiceClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which, boolean isChecked) {
                        Toast.makeText(NineActivity.this, which+""+isChecked, Toast.LENGTH_SHORT).show();
                    }
                });
                builder7.create().show();
                break;

            case R.id.b8://日期对话框
              /*  AlertDialog.Builder builder8 = new AlertDialog.Builder(NineActivity.this);
                builder8.setTitle("日期对话框");
                builder8.setIcon(R.mipmap.ic_launcher);*/
                DatePickerDialog datePickerDialog = new DatePickerDialog(NineActivity.this, new DatePickerDialog.OnDateSetListener() {
                    @Override
                    public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
                        Toast.makeText(NineActivity.this, year+"年"+month+"月"+dayOfMonth+"日", Toast.LENGTH_SHORT).show();
                    }
                },2022,4,20);
                datePickerDialog.show();
                break;

            case R.id.b9://时间对话框
                TimePickerDialog timePickerDialog = new TimePickerDialog(NineActivity.this, new TimePickerDialog.OnTimeSetListener() {
                    @Override
                    public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
                        Toast.makeText(NineActivity.this, hourOfDay + "时" + minute + "分钟", Toast.LENGTH_SHORT).show();
                    }
                }, 9, 56, true);
                timePickerDialog.show();
                break;

            case R.id.b10://Adapter的对话框
                AlertDialog.Builder builder10 = new AlertDialog.Builder(NineActivity.this);
                builder10.setTitle("带Adapter的对话框");
                builder10.setIcon(R.mipmap.ic_launcher);

                /*List<Map<String,Object>> list = new ArrayList<Map<String,Object>>();
                for (int i = 0 ; i < 20 ; i++){
                    Map<String, Object> map = new HashMap<String, Object>();
                    map.put("img", R.mipmap.ic_launcher);
                    map.put("title", "title:"+i);
                    list.add(map);
                }*/
                 ArrayList<Fruit> list = new ArrayList<Fruit>();
                for (int i = 0;i < 5;i++) {
                    list.add(new Fruit(R.drawable.dog1, "Apple", "3.65元/千克"));
                    list.add(new Fruit(R.drawable.dog2, "Banana", "3.78元/千克"));
                    list.add(new Fruit(R.drawable.dog1, "Orange", "6.98元/千克"));
                    list.add(new Fruit(R.drawable.dog2, "WaterMelon", "1.26元/千克"));
                    list.add(new Fruit(R.drawable.dog1, "Pear", "2.85元/千克"));
                    list.add(new Fruit(R.drawable.dog2, "Grape", "5.69元/千克"));
                }

               /* for (int i  = 0;i<arrImgID.length;i++){
                    Map<String,Object> map = new HashMap<String, Object>();
                    map.put("img",arrImgID[i]);
                    map.put("title","title"+i);
                    list.add(map);
                }*/
//                SimpleAdapter adapter = new SimpleAdapter(NineActivity.this, list, R.layout.fruit_item, new String[]{"img", "title"}, new int[]{R.id.imageView, R.id.textView});
                FruitAdapter adapter = new FruitAdapter(NineActivity.this, R.layout.fruit_item, list);
                builder10.setAdapter(adapter, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
//                        Map<String, Object> stringObjectMap = list.get(which);
//                        String img = stringObjectMap.get("img").toString();
//                        Toast.makeText(NineActivity.this, "你选择了" + list.get(which).get("title").toString().trim(), Toast.LENGTH_SHORT).show();
                        Toast.makeText(NineActivity.this, "你选择了"+list.get(which).getName(),Toast.LENGTH_SHORT).show();
                    }
                });
                builder10.create().show();
                break;

            case R.id.popWindowButton:
                View view2 = LayoutInflater.from(NineActivity.this).inflate(R.layout.pop_window_alert, null);
                Button innerPop = view2.findViewById(R.id.innerPop);
                innerPop.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(NineActivity.this,
                                "点击了PopWindow按钮", Toast.LENGTH_SHORT).show();
                    }
                });
                PopupWindow popupWindow = new PopupWindow(view2, ActionBar.LayoutParams.WRAP_CONTENT,
                        ActionBar.LayoutParams.WRAP_CONTENT,true);
                popupWindow.setTouchable(true);
                popupWindow.setTouchInterceptor(new View.OnTouchListener() {
                    @Override
                    public boolean onTouch(View v, MotionEvent event) {
                        Log.i("NineActivity", "点击了onTouch");
                        return false;//false表示事件不被拦截,点击外部区域可以关闭popupWindow
                    }
                });
                popupWindow.setBackgroundDrawable(getResources().getDrawable(R.drawable.shape));
                popupWindow.showAsDropDown(view);
                break;
            case R.id.activity_button:
                Intent intent = new Intent(NineActivity.this,ActivityDialog.class);
                startActivity(intent);
                break;

            default:
                break;

        }
    }

至于确定,取消,暂停,适配器,单选列表,多选列表等样式功能,他们的使用都是建立在创立了builder之后,通过builder来设置

注意setmessage()和setItem()这两个方法是冲突的,设置了message的时候是不会显示item里面的内容的,也就是说message的优先级高于item

2、自定义Dialog

dialog样式文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00FFFFFF">

<LinearLayout
    android:layout_width="260dp"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:background="@drawable/shape"
    android:orientation="vertical">

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="15dp"
        android:gravity="center"
        android:text="消息提示"
        android:textColor="#38ADFF"
        android:textSize="18sp" />

    <View
        android:layout_width="match_parent"
        android:layout_height="3px"
        android:background="#00FF5D" />

    <TextView
        android:id="@+id/message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginTop="10dp"
        android:layout_gravity="center"
        android:textColor="#000"
        android:textSize="16sp"
        android:text="提示消息" />

    <View
        android:layout_width="match_parent"
        android:layout_height="3px"
        android:layout_marginTop="15dp"
        android:background="#00FF5D" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="45dp"
        android:orientation="horizontal">

        <Button
            android:id="@+id/no"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_marginStart="10dp"
            android:layout_weight="1"
            android:background="@null"
            android:gravity="center"
            android:singleLine="true"
            android:text="取消"
            android:textColor="#7D7D7D"
            android:textSize="17sp" />

        <View
            android:layout_width="1px"
            android:layout_height="match_parent"
            android:background="#E4E4E4" />

        <Button
            android:id="@+id/yes"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_marginEnd="10dp"
            android:layout_weight="1"
            android:background="@null"
            android:gravity="center"
            android:singleLine="true"
            android:text="确定"
            android:textColor="#38ADFF"
            android:textSize="17sp" />
    </LinearLayout>
</LinearLayout>

</RelativeLayout>

自定义一个Dialog类

package com.example.dialog;

import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;


import androidx.annotation.NonNull;

import com.example.applicationtest1.R;

public class MyDialog extends Dialog {
    public MyDialog(@NonNull Context context, int themeResId) {
        super(context, themeResId);
    }
    private TextView titleView;
    private TextView messageView;
    private String title;
    private String message;
    private Button no;
    private Button yes;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.dialog_layout);
        initView();
        initData();
        initEvent();
    }

    //初始化页面 得到控件
    private void initView(){
         this.titleView = findViewById(R.id.title);
         this.messageView = findViewById(R.id.message);
         this.no = findViewById(R.id.no);
         this.yes = findViewById(R.id.yes);
    }
    //加载数据
    private void initData(){
       titleView.setText(title);
       messageView.setText(message);
    }
    //加载监听事件
    private void initEvent(){
        no.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                noOnclickListener.onNoOnclick();
            }
        });
        yes.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                yesOnclickListener.onYesOnclick();
            }
        });
    }
    private onYesOnclickListener yesOnclickListener;//确定按钮被点击了的监听器
    private onNoOnclickListener noOnclickListener;//取消按钮被点击了的监听器
    public interface onNoOnclickListener {
        public void onNoOnclick();
    }
    public interface onYesOnclickListener {
        public void onYesOnclick();
    }
    // 设置确定按钮监听
    public void setYesOnclickListener(onYesOnclickListener yesOnclickListener) {
        this.yesOnclickListener = yesOnclickListener;
    }
    // 设置取消按钮监听
    public void setNoOnclickListener(onNoOnclickListener noOnclickListener) {
        this.noOnclickListener = noOnclickListener;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public void setMessage(String message) {
        this.message = message;
    }
}

PopupWindow的学习以及使用

PopupWindow是属于子窗口的,他是依赖于应用窗口或者系统窗口的,他可以显示在activity的任意位置,并且在activity的最上层,显示的时候会阻塞UI线程

Popupwindow中的触摸事件最先是会传递到弹窗的,如果这个事件在弹窗中被消耗掉的话(touchable设置成true),那么当前的activity是不会收到这个触摸事件的

通过 PopupWindow popupWindow = new PopupWindow(view2,

ActionBar.LayoutParams.WRAP_CONTENT, ActionBar.LayoutParams.WRAP_CONTENT,true);这个构造方法来获取一个PopupWindow实例,这个方法中传四个参数,第一个是本popupwindow的视图(注意不是外面那个activity的,而是这个小的popupwindow的布局视图),第二三设置的宽高,最后一个设置能否得到焦点

然后得到这个实例后,通过这个实例popupWindow来设置能否被触摸,触摸的逻辑处理,背景颜色,以及将其展示出来

PopupWindow默认是可以通过点击它以外的区域来关闭的,我们可以在setTouchInterceptor()这个方法里面,将返回值返回为true,就可以把触摸事件拦截下来了,就无法通过点击外部区域关闭popupWindow了

十五、Handler + AsyncTask学习

1、了解Handler在Android中的影响

Handler是Android中一套消息传递机制,主要用于线程间的通信,其中有几个相关的概念值得注意。Message、Handler、MessageQueue、Looper。

Message用来装载消息,

Handler用来发送和处理消息,

MessageQueue是消息队列,用来顺序的存放接收的消息,然后被取出,

Looper循环的从MessageQueue中取出消息

简单来说就是当子线程要进行UI操作,或者要与其他线程进行通信的时候,那么这个时候就要使用到这套异步消息处理机制,Handler将要处理UI线程的请求通过SendMessage()发送出去,然后被MessageQueue暂存起来,接着Looper会不停的从MessageQueue中取消息,等取到这条消息之后,然后将其传递到Handler的handleMessage()中

每个线程中只有一个MessageQueue对象,一个Looper对象

Hanlder允许我们发送延时消息,但是如果在延时期间关闭了activity,这个activity就会泄露,这是因为 Message 会持有 Handler,而又因为 Java 的特性,内部类会持有外部类,使得 Activity 会被 Handler 持有,这样最终就导致 Activity 泄露,解决办法可以采用弱引用

2、Handler的使用

activity布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context="com.example.handler.HandleActivity">

    <Button
        android:id="@+id/UIButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="UI操作"
        android:textAllCaps="false"
         />
    <EditText
        android:id="@+id/timeEdit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="请输入总时间"/>

    <TextView
        android:id="@+id/handleTextView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center_vertical"
        android:text="这里是UI线程"
        android:textSize="50dp"
        android:textAllCaps="false"
        android:layout_gravity="center"/>
    <Button
        android:id="@+id/timeButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:background="@drawable/shape"
        android:text="开始计时"/>


</LinearLayout>

activity代码

package com.example.handler;

import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import com.example.applicationtest1.R;
import java.lang.ref.WeakReference;

public class HandleActivity extends AppCompatActivity {

    private Button bt_handler_send;
    private EditText timeEdit;
    private TextView UITextView ;
    private Button timeButton;

/*    private Handler handler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(@NonNull Message msg) {
            if (msg.what == 0){
                UITextView.setText("UI被改变了");
                Toast.makeText(HandleActivity.this,
                        "handleMessage",Toast.LENGTH_SHORT).show();
            }
            super.handleMessage(msg);
        }
    };*/

    //创建一个全局的MyHandler实例,能在onCreate回调方法中调用
    private MyHandler handler = new MyHandler(HandleActivity.this);
    //自定义一个内部类去继承Handler,然后重写handleMessage方法,
    // 由于这个内部类是在主线程当中,所以handleMessage()方法中执行的业务代码也是在主线程当中的
    private static class MyHandler extends Handler{
        //使用弱引用的方式得到activity
        private WeakReference<HandleActivity> weakReference;

        public MyHandler(HandleActivity activity ) {
            this.weakReference = new WeakReference(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            HandleActivity activity = weakReference.get();
            if (msg.what == 1){
                activity.UITextView.setText("UI被改变了");
                Toast.makeText(activity,"handleMessage",Toast.LENGTH_SHORT).show();
            }
        }
    }

    //使用AsyncTask来实现异步消息处理机制
    private class MyAsyncTask extends AsyncTask<Integer,Integer,String>{
        @Override
        protected String doInBackground(Integer... integers) {
            for (int i = integers[0]; i >= 0; i--) {
                try {
                    Thread.sleep(1000);
                    publishProgress(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return "倒计时结束";
        }
        //在这个方法中可以对UI线程进行操作
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
            UITextView.setText(values[0] + "");
        }
        //当后台任务执行完毕并通过return语句返回时,这个方法就会被调用
        // 返回的数据会作为参数传进这个方法中,这个demo中的数据就是上面的“倒计时结束”
        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            timeButton.setText(s);
        }
    }

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

        bt_handler_send = findViewById(R.id.UIButton);
        timeButton = findViewById(R.id.timeButton);
        timeEdit = findViewById(R.id.timeEdit);
        UITextView = findViewById(R.id.handleTextView);

        bt_handler_send.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //使用 handler 发送message = 1的消息
                        handler.sendEmptyMessage(1);
                    }
                }).start();
            }
        });
        timeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int time = Integer.parseInt(timeEdit.getText().toString());
                timeButton.setText("正在倒计时");
                new MyAsyncTask().execute(time);
            }
        });
    }
}
3、AsyncTask的概念

AsyncTask是一个抽象类,它是Android提供的一种实现线程间消息传递的工具,其实底层封装的也还是异步消息处理机制。我们要想使用他就必须去继承它MyASyncTask extends AsyncTask。

一般来说我们继承它需要重写几个方法,分别是onPreExecute()、doInBackground()、onProgressUpdate()、onPostExecute(),这几个方法的作用分别是

onPreExecute():它会在后台任务逻辑代码执行前调用,用于界面的一些初始化操作,比如显示进度条等等

doInBackground():这个方法中的所有代码都会在子线程运行,我们可以将一些耗时的业务代码放在这个方法里面,比如下载一个东西等等。*注意:在这个方法里面是不能进行UI操作的(这里是子线程)*,我们可以调用publishProgress()方法

onProgressUpdate():当在doInBackground方法中调用了publishProgress方法后,就会执行这个方法,他的参数也是publishProgress中传过来的,我们可以在这个方法里对UI线程进行更改的操作,因为此时相当于就是在主线程当中了

onPostExecute():当后台任务执行完毕后也就是doInBackground方法执行完毕后,这个方法就会执行,doInBackground方法的返回值会传进我们这个方法中,我们可以根据返回值来做业务逻辑判断

最后如何启动的问题:

直接创建一个自定义MyASyncTask的实例,然后调用execute()方法

例如 new MyAsyncTask().execute(time);

注意这个方法里面,我们可以传进任意个参数,然后这些参数会传递到doInBackground()方法中

4、AsyncTask的使用

见上述代码

十六、Fragment的使用

Fragment相当于一种可以嵌入在Activity中的UI片段,当我们使用事务实例去调用replace()方法的时候,作为参数传进去的Fragment实例,相当于覆盖在原有的控件之上。

1、学习Fragment的生命周期

状态:Fragment跟Activity一样,也拥有四种状态,即运行状态,暂停状态,停止状态,销毁状态,Fragment总是依附于Activity存在的,但Activity状态变化的时候Fragment的状态也会变化。

不过值得注意的一点是:

如果在提交事务之前,调用了addToBackStack()方法,当Fragment被调用replace()或者reMove()方法后,fragment只会进入停止状态而不是销毁状态,此时有可能但不一定立即被系统回收

如果在提交事务之前,没有调用addToBackStack(),当Fragment被调用replace()或者reMove()方法后,fragment会进入销毁状态,这个时候就会被系统回收了

生命周期:伴随着Fragment的生命周期有几个回调方法分别是:onAttach(),onCreate(),onCreateView(),onActivityCreated(),onStart(),onResume(),到这里Fragment就已经被激活了,接着onPause(),onStop(),onDestroyView(),onDestroy(),onDetach()

onAttach():当fragment与activity建立关联的时候调用

onCreate():当调用过addToBackStak()方法后,从下一个界面回到上一个fragment,这个方法不会被调用,因为此时fragment没有被销毁,所以换句话说他只在fragment的第一次创建的时候才会被调用

onCreateView():为fragment创建视图(加载布局)的时候调用

OnActivityCreate():确保与fragment相关联的Activity已经创建完毕的时候调用

onStart():当fragment由不可见变为可见的时候调用

OnResume():当fragment准备与用户交互的时候调用,此时fragment就已经被激活了

onPause():当我们去调用其他的activity或者fragment的时候这个方法就会被调用,相当于暂停本fragment

onStop():当fragment变为完全不可见的时候这个方法就会调用,然后fragment的状态就变成了停止状态

onDestroyView():当与本fragment关联的视图被移除的时候会被调用

onDestroy():当fragment要被销毁的时候会调用,执行这个方法之后fragment并不是立即就销毁了,他还要与activity解除绑定等操作

onDetach():当fragment与activity解除关联的时候调用

2、 使用Fragment进行切换

自定义一个MyFragment类去继承Android提供的Fragment类,在这个类里面重写onCreateView()方法,在这个方法里面加载一个布局(这个布局可以是自定义的,也可以是其他的)

然后我们在activity里面得到一个fragmentMananger的实例,然后通过这个实例去开启一个事务,再通过这个事务去调用replace()方法,然后在这个方法中我们传入刚刚定义的MyFragment实例,当然也可以指定我们这个fragment实例去替换(覆盖)某个布局或者控件,然后再提交事务

如果我们想要调用了一个fragment之后按返回键不退出当前activity的话,我们可以在提交事务之前调用addToBackStack()方法,将这个fragment实例push进栈

3、了解FragmentManager

有两种方式得到FragmentManager

如果在activity中,我们可以通过getSupportFragmentManager()这个方法来获得一个fragmentManager

如果是在fragment中,我们可以通过getChildFragmentManager()这个方法来获得一个fragmentManager

4、 学习Fragment与Activity之间的通信

Fragment与Activity之间的通信问题,涉及到在Activity中获得Fragment的实例,以及在Fragment中得到与之关联的Activity

在Activity中获得Fragment的实例,我们可以通过从布局文件中获取相对应的Fragment的实例,使用findFragmentById()这个方法,来通过布局文件中的id去获得相对应的fragment,

具体业务代码如下所示:

LeftFragment fragment = (LeftFragment)getSupportFragmentManager().findFragmentById(R.id.leftFragmentButton);

注意这里:如果我们单纯的使用getSupportFragmentManager().findFragmentById()这个方法的话,我们单纯得到的是Fragment的实例,而不是我们自定义的LeftFragment,所以这里我们需要强制类型转换(有继承关系)

在Fragment中获得Activity的实例,在每个Fragment中我们都可以通过调用getActivity()方法来得到与当前Fragment相关联的Activity的实例

十七、SQLite的使用

1、 SQLite的相关概念

SQLite是Android系统内置的一款轻量级的关系型数据库,通常情况下只有几百KB的内存,而且还支持ACID原则

2、学习Android中SQL表的创建,增删改查

1、数据库帮助类SQLiteOpenHelper,我们继承他的时候,必须重写两个方法,onCreate(),onUpgrade(),一个是用来创建数据库及表的,一个是用来更新数据库的

2、getReadableDatabase(),getWriteableDatabase(),这两个方法可以用来打开现有的数据库(如果没有则创建),两者有点不同的是第一个当数据库满的时候第一个方法以只读的方式打开数据库,第二个方法会报错

3、关于对数据库执行操作,首先我们得实例化自定义的数据库帮助类,得到一个对象,再通过这个对象去调用getReadableDatabase()方法或者getWriteableDatabase()方法,然后得到一个SQLiteDatabase 实例,然后通过这个实例去做一些操作,比如执行sql语句使用execSQL()方法,CRUD对应的几个方法分别是insert(),query(),update(),delete().值得注意的是使用查询方法会使用到游标cursor来遍历数据,用完之后记得关闭游标

3、学习Android中SQL更新表的操作

关于数据库更新表,以及行的列的操作,我们可以在自定义的数据库帮助类中的onUpgrade()方法中来通过oldVersion参数判断数据库当前的版本号,然后再执行相应的SQL语句,如果是新加表的话,这个SQL语句还要在onCreate()方法中执行

4、代码

SQLite

package com.example.dataBase;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.Nullable;

public class MyDatabaseHelper extends SQLiteOpenHelper {
    private static final String MyDatabaseHelper = "MyDatabaseHelper";
    private Context context;
    private String name;
    private SQLiteDatabase.CursorFactory factory;
    private int version;

    private String createBook = "create table Book(" +
            "id integer primary key autoincrement," +
            "author text," +
            "price real," +
            "pages integer," +
            "name text)";

    private String createCategory = "  create table Category (" +
            "id integer primary key autoincrement," +
            "category_name text," +
            "category_code integer" +
            " )";

    public MyDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        this.context = context;
        this.name = name;
        this.factory = factory;
        this.version = version;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(createBook);
        Log.d(MyDatabaseHelper, "createBook表创建成功");
        Toast.makeText(context,"数据库创建成功,createBook表创建成功", Toast.LENGTH_SHORT).show();
        db.execSQL(createCategory);
        Log.d(MyDatabaseHelper, "createCategory表创建成功");
        Toast.makeText(context,"createCategory表创建成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//        db.execSQL("drop table if exists Book");
//        db.execSQL("drop table if exists Category");
        if(oldVersion <= 1){
            db.execSQL(createCategory);
            Log.i("MyDatabaseHelper", "createCategory");
        }
    }
}

activity中使用

package com.example.dataBase;

import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.example.applicationtest1.R;

public class DatabaseActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String databaseActivity = "DatabaseActivity";
    private Button createDatabase;

    private Button createData;
    private Button deleteData;
    private Button requireData;
    private Button updateData;
    private MyDatabaseHelper myDatabaseHelper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.database_activity);

        myDatabaseHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);
        //创建数据库
        createDatabase = findViewById(R.id.createDatabase);
        createDatabase.setOnClickListener(DatabaseActivity.this);
        //添加数据
        createData = findViewById(R.id.createData);
        createData.setOnClickListener(DatabaseActivity.this);
        //删除数据
        deleteData = findViewById(R.id.deleteData);
        deleteData.setOnClickListener(DatabaseActivity.this);
        //查询数据
        requireData = findViewById(R.id.requireData);
        requireData.setOnClickListener(DatabaseActivity.this);
        //更新数据
        updateData = findViewById(R.id.updateData);
        updateData.setOnClickListener(DatabaseActivity.this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.createDatabase:
                SQLiteDatabase writableDatabase = myDatabaseHelper.getWritableDatabase();
                break;

            case R.id.createData:
                SQLiteDatabase db1 = myDatabaseHelper.getWritableDatabase();

                ContentValues createValues1 = new ContentValues();
                createValues1.put("name","The Da Vinci Code");
                createValues1.put("author", "Dan Brown");
                createValues1.put("pages", "454");
                createValues1.put("price", "16.96");
                db1.insert("Book", null,createValues1);
                Log.i(databaseActivity, "values1:"+createValues1.get("name"));
                Toast.makeText(DatabaseActivity.this, "values1插入成功!",Toast.LENGTH_SHORT).show();

                ContentValues values2 = new ContentValues();
                values2.put("name", "The Lost Symbol");
                values2.put("author", "Dan Brown");
                values2.put("pages", "510");
                values2.put("price", "19.95");
                db1.insert("Book", null, values2);
                Log.i(databaseActivity, "values2:"+values2.get("name"));
                Toast.makeText(DatabaseActivity.this, "values2插入成功!",Toast.LENGTH_SHORT).show();
                break;

            case R.id.deleteData:
                SQLiteDatabase db2 = myDatabaseHelper.getWritableDatabase();
                db2.delete("Book", "pages > ?",new String[]{"500"});
                Log.d(databaseActivity, "删除操作执行成功");
                break;
            case R.id.requireData:
                SQLiteDatabase db3 = myDatabaseHelper.getWritableDatabase();
                Cursor cursor = db3.query("Book", null, null,
                        null, null, null, null);
                if(cursor.moveToFirst()){
                    do{
                        @SuppressLint("Range") String name
                                = cursor.getString(cursor.getColumnIndex("name"));
                        @SuppressLint("Range") String author
                                = cursor.getString(cursor.getColumnIndex("author"));
                        @SuppressLint("Range") int pages
                                = cursor.getInt(cursor.getColumnIndex("pages"));
                        @SuppressLint("Range") double price
                                = cursor.getDouble(cursor.getColumnIndex("price"));
                        Log.d(databaseActivity, "name:"+name+" "+"author:"+
                                author+" "+"pages:"+pages+" "+"price:"+price);
                    }while (cursor.moveToNext());
                }
                cursor.close();
                break;
            case R.id.updateData:
                SQLiteDatabase db4 = myDatabaseHelper.getWritableDatabase();
                ContentValues updateValues = new ContentValues();
                updateValues.put("price", "100");
                db4.update("Book", updateValues, "name = ?", new String[]{"The Da Vinci Code"});
                Log.d(databaseActivity, "数据更新成功");
                break ;
            default:
                break;
        }
    }

}

十八、Android动画学习

1、了解Android动画的种类以及实现

Android动画一共有两大类,三小种,两大类分别是视图动画,属性动画,其中视图动画又分成补间动画和逐帧动画。

补间动画:

通过确定开始的视图样式和结束的视图样式、中间动画变化过程由系统补全来确定一个动画,

类型分为平移动画,缩放动画,旋转动画,透明度动画,

作用对象:视图控件

逐帧动画:

将动画拆分成帧的形式,且一帧等于一张图片,按顺序播放一组事先定义好的图片

优点:使用方便,简单

缺点:容易引起OOM,因为会用大量且尺寸较大的图片资源

作用对象:视图控件

视图动画改变的是view的视觉效果,不改变view的属性,比如将屏幕左上角的按钮通过补间动画将其移动到右下角,这时候点击右下角是没有响应效果的(因为按钮实际还是在左上角)

属性动画:在一定时间间隔内通过对对象的属性值不断进行赋值,改变它的属性值,从而实现该对象在其属性上的动画效果

作用对象:不仅仅是view控件对象,可以是任意的java对象

2、动画的使用

1.补间动画:首先定义动画的具体参数的样式文件,比如alpha渐变样式,traslate位移等等,然后在activity中使用动画工具去加载一个动画,这个动画方法中传一个视图参数,和一个动画的样式,然后得到一个Animation实例(动画实例),如果我们要改变哪个控件的动画,就可以使用那个控件的实例去调用startAnimation()方法,方法中传入那个动画实例,具体代码如下:

animation = AnimationUtils.loadAnimation(TweenActivity.this, R.anim.tween_scale);

imageView.startAnimation(animation);

布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context="com.example.animation.TweenActivity">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onAlpha"
        android:text="透明度渐变AlphaAnimation"
        android:textAllCaps="false"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:onClick="onScale"
        android:text="缩放动画ScaleAnimation"
        android:textAllCaps="false"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:onClick="onTranslate"
        android:text="位移动画TranslateAnimation"
        android:textAllCaps="false"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:onClick="onRotate"
        android:text="旋转动画RotateAnimation"
        android:textAllCaps="false"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:onClick="onSet"
        android:text="组合动画SetAnimation"
        android:textAllCaps="false"/>

    <ImageView
        android:id="@+id/tween_img"
        android:layout_gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/dog2"/>
    <Button
        android:id="@+id/test"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="测试"/>

</LinearLayout>

activity中使用

package com.example.animation;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;

import com.example.applicationtest1.R;

public class TweenActivity extends AppCompatActivity {
    private ImageView imageView;
    private Animation animation;

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

        imageView = findViewById(R.id.tween_img);
    }

    //透明度渐变
    public void onAlpha(View view){
        animation = AnimationUtils.loadAnimation(TweenActivity.this, R.anim.tween_alpha);
        imageView.startAnimation(animation);
//        findViewById(R.id.test).startAnimation(animation);//按钮也可以渐变
    }
    //缩放渐变
    public void onScale(View view){
        animation = AnimationUtils.loadAnimation(TweenActivity.this, R.anim.tween_scale);
        imageView.startAnimation(animation);
    }

    //位移动画
    public void onTranslate(View view){
        animation = AnimationUtils.loadAnimation(TweenActivity.this, R.anim.tween_translate);
        imageView.startAnimation(animation);
    }
    //旋转动画
    public void onRotate(View view){
        animation = AnimationUtils.loadAnimation(TweenActivity.this,R.anim.tween_rotate);
        imageView.startAnimation(animation);
    }
    //组合动画
    public void onSet(View view){
        animation = AnimationUtils.loadAnimation(TweenActivity.this, R.anim.tween_set);
        imageView.startAnimation(animation);
    }

}

2.帧动画:首先定义一个animation-list类型的样式文件,在这个文件中的item标签中传入一张张图片,然后设置每张图片的展示时间,最后在activity中使用getResources().getDrawable()方法,在这个getDrawable()方法中传入刚刚定义的样式文件,然后这个方法会返回一个Drawable类型的实例,我们需要强制类型转换一下,将其转换成AnimationDrawable类型,得到一个AnimationDrawable类型的实例对象,然后我们需要改变哪个控件的动画,就可以将这个动画设置成他的背景,最后在按钮的点击事件中通过这个动画的实例调用start()和stop()方法来开启

AnimationDrawable animationDrawable = (AnimationDrawable)getResources().getDrawable(R.drawable.frame_animation);

imageView.setBackground(animationDrawable);

布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context="com.example.animation.FrameActivity">
    <Button
        android:id="@+id/startButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="开始"/>
    <Button
        android:id="@+id/endButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="停止"/>
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</LinearLayout>

activity中使用

package com.example.animation;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

import com.example.applicationtest1.R;

public class FrameActivity extends AppCompatActivity implements View.OnClickListener {
    Button startButton;
    Button endButton;
    ImageView imageView;

    private AnimationDrawable animationDrawable;

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

    private void initView() {
        startButton = findViewById(R.id.startButton);
        endButton = findViewById(R.id.endButton);
        imageView = findViewById(R.id.imageView);

        startButton.setOnClickListener(FrameActivity.this);
        endButton.setOnClickListener(FrameActivity.this);

         animationDrawable = (AnimationDrawable)getResources().getDrawable(R.drawable.frame_animation);
        // 把AnimationDrawable设置为ImageView的背景
//        imageView.setBackgroundDrawable(animationDrawable);//这个方法已经被废弃了
        imageView.setBackground(animationDrawable);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.startButton:
                if (animationDrawable != null && !animationDrawable.isRunning()) {//判断
                    animationDrawable.start();
                }
                break;
            case R.id.endButton:
                if (animationDrawable != null && animationDrawable.isRunning()) {
                    animationDrawable.stop();
                }
                break;
        }
    }

}

3.属性动画:

直接调用ObjectAnimator类里面的ofFloat(),ofInt()等等方法,然后得到一个ObjectAnimator类型的实例对象,通过这个动画对象去设置动画的时间,开启等等操作代码如下:

objectAnimator = ObjectAnimator.ofFloat(attr_show, “alpha”, 1f, 0.8f, 0.7f, 0.2f, 0.1f,1f);//布局,类型,渐变程度

objectAnimator.setDuration(4000);

objectAnimator.start();

以ofFloat()方法为例,需要传入三组参数,第一个参数是我们改变的控件的实例,第二个参数是我们要改变的控件的属性,这个属性是我们并没有去定义他这是Android给我们定义好的,我们传进去Alpha这个属性,他会去匹配View中setAlpha()方法,然后将我们参入的第三组参数传进这个setAlpha()方法中,我们传进去的第三组参数可以是任意的数量(前提是在参数所允许的范围内,比如Alpha的范围是0L—1L)

布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:id="@+id/attr_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.animation.AttributeActivity">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onAlpha"
        android:text="透明度渐变" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onScale"
        android:text="缩放" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onTranslate"
        android:text="位移" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onRotate"
        android:text="旋转" />

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="onSet"
        android:text="组合动画" />

    <Button
        android:id="@+id/attr_show"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="属性动画的效果显示" />

</LinearLayout>

activity中使用

package com.example.animation;

import androidx.appcompat.app.AppCompatActivity;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

import com.example.applicationtest1.R;

public class AttributeActivity extends AppCompatActivity {
    private Button attr_show;
    private LinearLayout attr_root;
    private ObjectAnimator objectAnimator;

    private float rotateDu = 0;

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

        attr_show = findViewById(R.id.attr_show);
        attr_root = findViewById(R.id.attr_root);

    }


    //渐变
    public void onAlpha(View view){
        objectAnimator = ObjectAnimator.ofFloat(attr_show, "alpha", 1f, 0.8f, 0.7f, 0.2f, 0.1f,1f);//布局,类型,渐变程度
        objectAnimator.setDuration(4000);
        objectAnimator.start();

    }
    //缩放
    public void onScale(View view){
       objectAnimator = ObjectAnimator.ofFloat(attr_show,"scaleY",1f, 2f, 3f, 4f, 3f, 2f, 1f);
       objectAnimator.setDuration(400);
       objectAnimator.start();
    }
    //位移
    public void onTranslate(View view){
        int width = attr_root.getWidth();

        objectAnimator = ObjectAnimator.ofFloat(attr_show, "translationX", width / 10, width / 9, width / 4, width / 3, width / 2, width,width/10);
        objectAnimator.setDuration(4000);
        objectAnimator.start();
    }
    //旋转
    public void onRotate(View view){
        objectAnimator = ObjectAnimator.ofFloat(attr_show, "rotation", rotateDu, 360);
        objectAnimator.setDuration(2000);
        objectAnimator.setRepeatCount(3);//设置动画重复次数
        objectAnimator.setRepeatMode(ValueAnimator.RESTART);//动画重复模式
        objectAnimator.start();
    }
    //组合
    public void onSet(View view){
        float height = attr_root.getHeight();
        ObjectAnimator objectAnimatorRotate = ObjectAnimator.ofFloat(attr_show, "rotation", rotateDu, 360);
        ObjectAnimator objectAnimatorTr = ObjectAnimator.ofFloat(attr_show, "translationY", height, height / 2, height / 3, height / 4, height / 5, height / 6);
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(4000);
        animatorSet.play(objectAnimatorRotate).with(objectAnimatorTr);
        animatorSet.start();

    }

}

十九、AIDL的学习使用

1、AIDL的几种基本数据类型

Java中的八种基本数据类型,String类型,CharSequence类型,List类型,Map类型,

2、定向tag

tag in:服务端修改不同步到客户端对象,可以看作不是同一个对象,

tag out:客户端传递对象无效,实际传值为新创建对象到服务端,可以看作返回一个这个新创建对象,

tag inout:服务端修改数据同步到客户端对象,可以看作对同一个对象进行修改。

Java 中的基本类型和 String ,CharSequence 的定向 tag *默认且只能是 in*

其他数据类型的定向tag必须指定,可以是in,out,inout(占资源 慎用)

二十、Android ANR的出现以及解决

1、了解Android ANR产生原因以及解决

常见原因:

①主线程阻塞或主线程数据读取

解决:解决办法:避免死锁的出现,使用子线程来处理耗时操作或阻塞任务。尽量避免在主线程query provider、不要滥用SharePreference

②CPU满负荷,I/O阻塞

解决:文件读写或数据库操作放在子线程异步操作。

③内存不足

解决:AndroidManifest.xml文件中可以设置 android:largeHeap=“true”,以此增大App使用内存。不过不建议使用此法,从根本上防止内存泄漏,优化内存使用才是正道。

④各大组件ANR

解决:各大组件生命周期中也应避免耗时操作,注意BroadcastReciever的onRecieve()、后台Service和ContentProvider也不要执行太长时间的任务。

2、学习了解Android ANR分析

二十一、Android OOM的出现以及解决

1、OOM产生的原因

根本原因:内存溢出

因为android系统的app的每个进程或者每个虚拟机有个最大内存限制,如果申请的内存资源超过这个限制,系统就会抛出OOM错误。跟整个设备的剩余内存没太大关系。比如比较早的android系统的一个虚拟机最多16M内存,当一个app启动后,虚拟机不停的申请内存资源来装载图片,当超过内存上限时就出现OOM

可能实际原因:

①请求创建一个超大对象,通常是一个大数组。(所以尽量根据自己的实际需要去初始化数组大小)

②超出预期的访问量/数据量,通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值。当流程突然很高是,由于提前没有对堆的内存空间做合理的准备,所以短时间内线程会创建大量的对象,这些对象可能会短时间内迅速的占满堆内存。

③用终结器(Finalizer),该对象没有立即被 GC。

④内存泄漏,大量的对象引用没有释放,GC没有办法对这些内存空间进行回收导致了内存泄漏的问题。

2、Android的内存组成

APP内存由 dalvik内存 和 native内存 2部分组成,dalvik也就是java堆,创建的对象就是就是在这里分配的,而native是通过c/c++方式申请的内存,Bitmap就是以这种方式分配的。(android3.0以后,系统都默认通过dalvik分配的,native作为堆来管理)

3、解决方案

1、图像缓存 ,在listview或Gallery等控件中一次性加载大量图片时,只加载屏幕显示的资源,尚未显示的不加载,移出屏幕的资源及时释放,采用强引用+软引用2级缓存,提高加载性能

2、采用低内存占用量的编码方式,比如Bitmap.Config.ARGB_4444Bitmap.Config.

ARGB_8888更省内存。

3、及时回收图像 。如果引用了大量的Bitmap对象,而应用又不需要同时显示所有图片。可以将暂时不用到的Bitmap对象及时回收掉。对于一些明确直到图片使用情况的场景可以主动recycle回收

4、不要在循环中创建过多的本地变量 。慎用static,用static来修饰成员变量时,该变量就属于该类,而不是该类实例,它的生命周期是很长的。如果用它来引用一些内存占用太多的实例,这时候就要谨慎对待了。

5、自定义堆内存分配大小 。优化Dalvik虚拟机的堆内存分配,申请超过内存限制的内存分配方式:从Native C分配内存,使用OpenGL的纹理

二十二、Android界面启动优化

1、启动优化分为三类:

(1) 冷启动

(2) 温启动

(3) 热启动

2、了解加快界面绘制方法

1.启动窗口启优化

2.布局优化

​ 2.1减少冗余或嵌套布局

​ 2.2 使用 ViewStub 替代在启动过程中不需要显示的 UI 控件

​ 2.3 使用merge标签减少布局的嵌套层次

​ 2.4 布局复用,可以使用include标签去导入重复的布局

​ 2.5 尽量不要去设置重复背景,这里指的是主题背景(theme), theme 默认会是一个纯色背景,如果我们自定义了界面的背景,那么主题的背景我们来说是无用的。但是由于主题背景是设置在 DecorView 中,所以这里会带来重复绘制,也会带来绘制性能损耗。

3.线程优化

​ 3.1异步初始化

​ 3.2延时初始化

​ 3.3通过线程池管理线程

​ 3.4设置子线程优先级

4.GC优化
5.IO优化
6系统优化

​ 6.1PreFor

​ 6.2 启动加速

​ 6.3 主线程/渲染线程加速

​ 6.4 iowrap

​ 6.5 启动加速

​ 6.6 主线程/渲染线程加速

​ 6.7 iowrap

3.了解ViewStub等方式加快页面启动

ViewStub是一个轻量级的View,它是一个看不见的,并且不占布局位置,占用资源非常小的视图对象。可以为ViewStub指定一个布局,加载布局时,只有ViewStub会被初始化,然后当ViewStub被设置为可见时,或是调用了ViewStub.inflate()时,ViewStub所指向的布局会被加载和实例化,然后ViewStub的布局属性都会传给它指向的布局。这样,就可以使用ViewStub来设置是否显示某个布局。

*使用ViewStub的注意点*

ViewStub只能加载一次,之后ViewStub对象会被置为空。换句话说,某个被ViewStub指定的布局被加载后,就不能再通过ViewStub来控制它了。所以它不适用于需要按需显示隐藏的情况。

ViewStub只能用来加载一个布局文件,而不是某个具体的View,当然也可以把View写在某个布局文件中。如果想操作一个具体的View,还是使用visibility属性。

ViewStub所要替代的layout文件中不能有标签

二十三、自定义View

1、了解自定义View流程

自定义一个类去继承View类,至少重写两个构造方法,

在activity中使用我们自定义View的时候,使用包名+类名来引用,其实跟其他Android自带的控件差不多也是一个控件标签,

然后这个标签中的一些属性,我们可以在attrs.xml中文件进行声明,以键值对的形式(属性名+格式),注意这里的格式是Android预定义好的,

然后我们在有attrs参数的构造方法中,先通过context.obtainStyledAttributes这个方法去获取一个类型数组对象,

然后通过这个对象.getString()等等方法去获取到我们在标签中存进去的值,

得到这个值后,我们就可以在measure,layout,draw等方法中去进行使用了

2、学习View的绘制流程

在onMeasure()方法中测量出view的大小,以及测量模式

在layout()方法中得到确定控件的大小

在draw()方法中绘画出相应的图案

3、 使用自定义View绘制各种形状(圆形,矩形,曲线)等

Circle,drawRect,quadTo(贝塞尔曲线)

二十四、Android的事件分发机制

1、了解Android的事件层级

(1)事件分发的大致流程

Activity -> PhoneWindow ->DecorView(DecorView其实就是一种ViewGroup) ->View

(2)事件分发对象:

MotionEvent.ACTION_DOWN 一根手指在屏幕按下时 1次

MotionEvent.ACTION_Point_DOWN 两根手指在屏幕上按下 1次

MotionEvent.ACTION_MOVE 在屏幕上滑动时 0次或多次

MotionEvent.ACTION_UP 在屏幕抬起时 0次或1次

MotionEvent.ACTION_CANCLE 滑动超出控件边界时 0次或1次

(3)事件分发需要的三个重要方法来共同完成:

public boolean dispatchTouchEvent(event):用于进行点击事件的分发

public boolean onInterceptTouchEvent(event):用于进行点击事件的拦截

public boolean onTouchEvent(event):用于处理点击事件

三个函数的参数均为even,即上面所说的4种类型的输入事件,返回值均为boolean 类型

\2. 学习Activity的触摸事件是如何透传下来的

事件传递过程涉及的几个API如下:

dispatchTouchEvent : 分发事件。如果返回true,表示事件分发下去后被处理了;返回false,则表示分发下去后没有被任何view处理。

onInterceptTouchEvent :拦截事件。如果返回true,则表示拦截事件。如果返回false,则表示不拦截。这里拦截的是本来要传给子View的事件,所以这个方法是ViewGroup独有的。

onTouchEvent : 处理事件。如果返回true,则表示处理事件,如果返回false 则表示不处理事件。

事件传递首先从父容器ViewGroup开始,父容器调用dispatchTouchEvent 分发事件,在该方法内部会调用onInterceptTouchEvent 判断是否为拦截事件,如果返回为true,表示对事件进行拦截,事件会直接传递给VIewGroup的onTouchEvent方法。如果返回为false(默认就是false),那么事件就会传递给View的dispatchTouchEvent 方法,View中没有onInterceptTouchEvent 方法,会直接调用onTouchEvent的方法处理事件。 如果View的onTouchEvent 的方法返回true,那么表示事件被处理,事件传递结束。

如果返回false 表示不处理,事件又会传递给父容器ViewGroup的onTouchEvent 方法处理。

\3. 在自定义View基础上拦截触摸事件分发

二十五、MVC, MVP, MVVM

1、MVC

2、MVP

3、MVVM

二十六、EventBus,Okhttp,Gson了解以及使用

1、EventBus

(1)EventBus是一个基于发布者/订阅者模式的事件总线框架。

(2)优缺点:

解耦和简化Activities, Fragments等组件以及后台线程之间的通信,分离事件发送方和接收方

使得代码更简洁,避免出现复杂的依赖性和生命周期问题

体积小(大概只有50k 的 jar包)

(3)三个要素:

Event 事件。它可以是任意的Object类型,你可以自定义一个Class类。

Subscriber 事件订阅者。在EventBus3.0之前我们必须定义以onEvent开头的几个方法,分别是onEvent、onEventMainThread、onEventBackgroundThread和onEventAsync,而在3.0之后事件处理的方法名可以随意取,不过需要加上注解@subscribe(),并且指定线程模型,默认是POSTING。

Publisher 事件的发布者。我们可以在任意线程里发布事件,一般情况下,使用EventBus.getDefault()就可以得到一个EventBus对象,然后再调用post(Object)方法即可。

EventBus的使用基本上分为简单的3个步骤:定义事件、注册事件订阅者、发布事件。

(4)EventBus线程模式:五种

ThreadMode.POSTING 订阅者方法将在发布事件所在的线程中被调用。事件的传递是同步的,一旦发布事件,所有该模式的订阅者方法都将被调用。

ThreadMode.MAIN 订阅者方法将在主线程(UI线程)中被调用。

ThreadMode.MAIN_ORDERED 订阅者方法将在主线程(UI线程)中被调用。

ThreadMode.BACKGROUND 订阅者方法将在后台线程中被调用。

ThreadMode.ASYNC 订阅者方法将在一个单独的线程中被调用。

(5)EventBus粘性事件

接收粘性事件的订阅者方法注解必须添加sticky = true属性:

发布粘性事件使用postSticky():EventBus.getDefault().postSticky(new MessageEvent(“Hello EventBus!”));

发布一个粘性事件之后,EventBus将一直缓存该粘性事件。如果想要移除粘性事件,那么可以使用如下方法:

// 移除指定的粘性事件

removeStickyEvent(Object event);

// 移除指定类型的粘性事件

removeStickyEvent(Class eventType);

// 移除所有的粘性事件

removeAllStickyEvents();

(6)EventBus事件优先级:@Subscribe注解中设置属性priority,值越大优先级越高

注意:优先级只有在相同的线程模式下才有效。如果相同的线程模式下的订阅方法都没有指定优先级(默认优先级为 0),则接收事件的方法顺序会按照方法名的字母排序。

高优先级的订阅者方法接收到事件之后取消事件的传递。此时,低优先级的订阅者方法将不会接收到该事件。注意: 订阅者方法只有在线程模式为ThreadMode.POSTING时,才可以取消一个事件的传递。

取消事件的传递使用cancelEventDelivery()方法:

(7)EventBus优化

EventBus订阅者索引:默认情况下,EventBus在查找订阅者方法时采用的是反射。订阅者索引是EventBus 3.0 的一个新特性。它可以加速订阅者的注册,是一个可选的优化。订阅者索引的原理是:使用EventBus的注解处理器在编译期间创建订阅者索引类,该类包含了订阅者和订阅者方法的相关信息。EventBus官方推荐在Android中使用订阅者索引以获得最佳的性能。

要开启订阅者索引的生成,你需要在gradle中使用annotationProcessor属性添加EventBus注解处理器,还要设置一个eventBusIndex参数来指定要生成的订阅者索引的完全限定类名。

2、Okhttp

 private void sendRequestWithOkHttp() {
        client = new OkHttpClient();
        request = new Request.Builder().url("https://www.baidu.com").build();
        Call call = client.newCall(request);
        call.enqueue(new Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {

            }

            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                String respondData = response.body().string();
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText(respondData);
                    }
                });
            }
        });

    }

3、Gson

private void getJsonData() {
        OkHttpClient okHttpClient = new OkHttpClient();
        Request request = new Request.Builder().url("http://www.weather.com.cn/data/sk/101010100.html").build();
        okHttpClient.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(@NonNull Call call, @NonNull IOException e) {

            }

            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                String respondData = response.body().string();
                parseJsonData(respondData);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText(respondData);
                    }
                });

            }
        });
    }

    private void parseJsonData(String data) {
        Log.d(TAG, "这里进入了ParseJsonData方法");
        Gson gson = new Gson();
        JsonObject jsonObject = gson.fromJson(data, JsonObject.class);
        Log.d(TAG, "这里输出---------------->"+jsonObject);

    }

二十七、设计模式的六大原则

1、开闭原则(Open Close Principle)

​ 开闭原则的意思是:对扩展开放,对修改关闭。

2、里氏代换原则(Liskov Substitution Principle)

​ 里氏代换原则是面向对象设计的基本原则之一。里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

3、依赖倒转原则(Dependence Inversion Principle)

​ 这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则(Interface Segregation Principle)

​ 这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

5、迪米特法则,又称最少知道原则(Demeter Principle)

​ 最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立

6、单一职责原则

降低类的复杂度,一个类只负责一项职责,提高类的可读性,可维护性,降低变更引起的风险。

二十八、23种设计模式

总体来说设计模式分为三大类:

创建型模式,共五种:

工厂模式(Factory Pattern)

定义一个用于创建产品的接口,由子类决定生产什么产品。

抽象工厂模式(Abstract Factory Pattern)

提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。

单例模式(Singleton Pattern)

某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。

懒汉式,饿汉式,双检锁/双重校验锁,登记式/静态内部类

建造者模式(Builder Pattern)

将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。

原型模式(Prototype Pattern)

将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。

结构型模式,共七种:

适配器模式(Adapter Pattern)

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

桥接模式(Bridge Pattern)

将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。

过滤器模式(Filter、Criteria Pattern)

组合模式(Composite Pattern)

将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

装饰器模式(Decorator Pattern)

动态的给对象增加一些职责,即增加其额外的功能。

外观模式(Facade Pattern)

为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。

享元模式(Flyweight Pattern)

运用共享技术来有效地支持大量细粒度对象的复用。

代理模式(Proxy Pattern)

为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。

行为型模式,共十一种:

责任链模式(Chain of Responsibility Pattern)

避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

命令模式(Command Pattern)

将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。

解释器模式(Interpreter Pattern)

给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。对于一些固定文法构建一个解释句子的解释器。

迭代器模式(Iterator Pattern)

提供一种方法顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。不同的方式来遍历整个整合对象。

中介者模式(Mediator Pattern)

用一个中介对象来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。

备忘录模式(Memento Pattern)

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。所谓备忘录模式就是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。

观察者模式(Observer Pattern)

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

状态模式(State Pattern)

允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。

空对象模式(Null Object Pattern)

策略模式(Strategy Pattern)

定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。在有多种算法相似的情况下,使用 if…else 所带来的复杂和难以维护。

模板模式(Template Pattern)

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。一些方法通用,却在每一个子类都重新写了这一方法。

访问者模式(Visitor Pattern)

主要将数据结构与数据操作分离。稳定的数据结构和易变的操作耦合问题。

二十九、git常用命令

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值