复习第一行代码从Intent开始,仅限使用层面
Intent篇
一intent分为显示和隐式
1显示
intent直接指定了要启动的活动目标(已经很明确要哪个activity响应),
Intent intent = new Intent(FirstActivity.this, SecondActivity.class); startActivity(intent);
直接构造一个intent,参数一为上下文(activity的超父类就是一个context上下文),参数二为要目标活动。
2隐式
隐式不需要指定响应的活动,但是要定义一个action和category等信息,这样其他的活动根据action和category等信息可以判定需不需要响应,类似于两者做约定,你满足我的相应条件才可以响应我。
例如现在分享,常见的举例。有个网址,点击打开的时候,弹出系统自带的浏览器,以及其他你安装的浏览器。让你选择用哪一个打开,A为你的网址点击活动,
A此时定义自己的Action(打开网址实际上不止需要定义一个action。这里是错误举例,下面会纠正)
Intent intent = new Intent(“com.example.activitytest.ACTION_START”); startActivity(intent);
A定义自己的Action为com.example.activitytest.ACTION_START,那么如果要响应这个Activity,你的要拿来响应这个Activity的就得定义好响应的Action和category等例如拿来响应的第二个Activity
<activity android:name=".SecondActivity" >
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START" />
</intent-filter>
</activity>
如果只是这样子,定义好响应的活动的action会发现有问题报错
No Activity found to handle Intent {act=com.example.activitytest.ACTION_START }
原因就是Inten自带一个隐藏的默认的category。
所以响应的活动中还需添加category。
<activity android:name=".SecondActivity" >
<intent-filter>
<action android:name="com.example.activitytest.ACTION_START" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
当然你也可以自己定义其他的category
intent.addCategory(“com.example.activitytest.MY_CATEGORY”);
相应的响应的活动中也需要添加category,否则又会报错No Activity found to handle Intent
<category android:name="com.example.activitytest.MY_CATEGORY" />
特殊例子
调用系统自带的应用(后期注意调用相机)此处调用浏览器
Intent intent = new Intent(Intent.ACTION_VIEW);
//对应的action常量为android.intent.action.VIEW
intent.setData(Uri.parse("http://www.baidu.com"));
Intent的action是Intent.ACTION_VIEW,这是一个Android系统内置的动作,其常量值为android.intent.action.VIEW。然后通过Uri.parse()方法,将一个网址字符串解析成一个Uri对象,再调用Intent的setData()方法将这个Uri对象传递进去。(自定义的活动想要响应这个intent,fliter中添加即可)
调用系统自带的拨号
Intent intent = new Intent(Intent.ACTION_DIAL); //android6.0以后不使用因为涉及到动态权限问题
intent.setData(Uri.parse(“tel:10086”));
主要注意parse中协议切换,如果是6.0以后Action换成Intent.ACTION_DIAL即可,同时注意动态权限问题
3可返回的intent
1.2中提到的都是单方面响应,无数据返回。
给定一个场景A,B activity各有一个TextView和Button,TextView已经都有默认值A跟B 。现点击A的按钮携带数据A到B活动,且将B活动TextView修改为A,从B中点击按钮返回A可以将A中TextView修改为B(也可不修改)
第一步依然需要在A中指定响应的活动,同时传递数据
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
intent.putExtra("TEST","A");
startActivityForResult(intent,1);
putExtra方法参数类似于key,value键值对。对方要取用这个数据需要传这个key才行。这里需要返回数据所以使用startActivityForResult,第二个参数一代表返回码,到时候需要返回的时候根据这个返回码知道是返回给我的。此处如果不需要返回正常携带数据给第二个活动直接startActivity即可。
在B activity中接受数据根据键值对获取,先获取intent ,直接
Intent intent = getIntent();
String rData = intent.getStringExtra(“TEST”);取到数据即可使用TextView的setText方法设置TextView的值。同时要设置返回携带数据的时候一样构建intent,设置携带的数据,区别在于不要使用
startActivity去重新启动Aactivity并携带数据回去。而要使用
setResult(1,intent1);第一个参数返回码1
finish();//结束B activity。
同时A中要重写onActivityResult(int requestCode, int resultCode, Intent data)方法
接受回传的数据,要判断下返回码是否符合
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1) {
String rsData = data.getStringExtra("RTEST");
main_text.setText(rsData);
}
}
4注意事项用户不一定走按钮返回,现在都有返回键本身,如果通过返回键本身返回想携带数据,那B中重写onBackPressed()方法即可
生命周期
一生命周期的七个方法
1onCreate() 活动第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如说加载布局、绑定事件等
2onStart() 在活动由不可见变为可见的时候调用。
3onResume()。这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。
4onPause()。这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗CPU的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。
5onStop()。这个方法在活动完全不可见的时候调用。它和onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()方法并不会执行。
6onDestroy()。这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。
7onRestart()。这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了
如图
活动的四个模式
standard
是活动默认的启动模式,在不进行显式指定的情况下,所有活动都会自动使用这种启动模式。因此,到目前为止我们写过的所有活动都是使用的standard模式。经过上一节的学习,你已经知道了Android是使用返回栈来管理活动的,在standard模式(即默认情况)下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
singleTop
当活动的启动模式指定为singleTop,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
singleTask
当活动的启动模式指定为singleTask,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
singleInstance
同于以上3种启动模式,指定为singleInstance模式的活动会启用一个新的返回栈来管理这个活动(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈)。那么这样做有什么意义呢?想象以下场景,假设我们的程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实例,应该如何实现呢?使用前面3种启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个活动在不同的返回栈中入栈时必然是创建了新的实例。而使用singleInstance模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题
创建一个Activity管理器以及让Activity打印当前所处活动
1创建一个BaseActivity继承于AppCompatActivity,同时不需要再mainfests中注册,后面的Activity继承这个BaseActivity即可,重写oncreate()方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
}
在每一个Activity创建时打印当前活动名
getClass() 返回此 Object 的运行时类。即这个Activity类名。
getSimpleName()得到类的简写名称
2activity回收
新建一个ActivityCollector类作为活动管理器
public class ActivityCollector {
//创建集合承载Activity
public static List<Activity> activities = new ArrayList<>();
//此方法放在oncreate中,创建时就添加到集合中来
public static void addActivity(Activity activity) {
activities.add(activity);
}
//放在onDestory方法中,销毁会受到的Activity就移除
public static void removeActivity(Activity activity) {
activities.remove(activity);
}
//遍历执行每个还在集合中未被销毁回收的Activity,进行finsh()销毁回收
public static void finishAll() {
for (Activity activity : activities) {
if (!activity.isFinishing()) {
activity.finish();
}
}
//遍历完销毁完集合中元素应该清空
activities.clear();
}
}
重写BaseActivity方法,后续的Activity都继承于此BaseActivity
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("BaseActivity", getClass().getSimpleName());
//创建时添加到集合取去
ActivityCollector.addActivity(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
//销毁时从集合中移除
ActivityCollector.removeActivity(this);
}
}
现在不论在哪个地方 调用ActivityCollector.finishAll();即可实现销毁回收Activity
UI
android:layout_width和android:layout_height指定了控件的宽度和高度。Android中所有的控件都具有这两个属性,可选值有3种:match_parent、fill_parent和wrap_content。其中match_parent和fill_parent的意义相同,现在官方更加推荐使用match_parent。match_parent表示让当前控件的大小和父布局的大小一样,也就是由父布局来决定当前控件的大小。wrap_content表示让当前控件的大小能够刚好包含住里面的内容,也就是由控件内容决定当前控件的大小
android:gravity来指定文字的对齐方式,可选值有top、bottom、left、right、center等,可以用“|”来同时指定多个值,这里我们指定的center,效果等同于center_vertical|center_horizontal
TextView
android:textSize属性可以指定文字的大小
通过android:textColor属性可以指定文字的颜色
ProgressBar
ProgressBar用于在界面上显示一个进度条,表示我们的程序正在加载一些数据
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
可以给ProgressBar指定不同的样式,刚刚是圆形进度条,通过style属性可以将它指定成水平进度条
style="?android:attr/progressBarStyleHorizontal"
progressBar.getProgress()获取进度
还可以通过android:max属性给进度条设置一个最大值android:max=“100”
Android控件的可见属性。所有的Android控件都具有这个属性,可以通过android:visibility进行指定,可选值有3种:visible、invisible和gone。visible表示控件是可见的,这个值是默认值,不指定android:visibility时,控件都是可见的。invisible表示控件不可见,但是它仍然占据着原来的位置和大小,可以理解成控件变成透明状态了。gone则表示控件不仅不可见,而且不再占用任何屏幕空间。我们还可以通过代码来设置控件的可见性,使用的是setVisibility()方法,可以传入View.VISIBLE、View.INVISIBLE和View.GONE这3种值。
AlertDialog
AlertDialog可以在当前的界面弹出一个对话框,这个对话框是置顶于所有界面元素之上的,能够屏蔽掉其他控件的交互能力,因此AlertDialog一般都是用于提示一些非常重要的内容或者警告信息。比如为了防止用户误删重要内容,在删除前弹出一个确认对话框。
AlertDialog.Builder dialog = new AlertDialog.Builder (MainActivity.this);
dialog.setTitle("This is Dialog"); //弹出框的标题
dialog.setMessage("Something important."); //弹出的提示
dialog.setCancelable(false); //设置不能用bcak键关闭
dialog.setPositiveButton("OK", new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface dialog, int which) {
//确定的逻辑
}
});
dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//否定的逻辑
}
});
dialog.show();
首先通过AlertDialog.Builder创建一个AlertDialog的实例,然后可以为这个对话框设置标题、内容、可否用Back键关闭对话框等属性,接下来调用setPositiveButton()方法为对话框设置确定按钮的点击事件,调用setNegativeButton()方法设置取消按钮的点击事件,最后调用show()方法将对话框显示出来
ProgressDialog
ProgressDialog和AlertDialog有点类似,都可以在界面上弹出一个对话框,都能够屏蔽掉其他控件的交互能力。不同的是,ProgressDialog会在对话框中显示一个进度条,一般用于表示当前操作比较耗时,让用户耐心地等待。它的用法和AlertDialog也比较相似
ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setTitle("This is ProgressDialog");
progressDialog.setMessage("Loading...");
progressDialog.setCancelable(true);
progressDialog.show();
先构建出一个ProgressDialog对象,然后同样可以设置标题、内容、可否取消等属性,最后也是通过调用show()方法将ProgressDialog显示出来
注意,如果在setCancelable()中传入了false,表示ProgressDialog是不能通过Back键取消掉的,这时你就一定要在代码中做好控制,当数据加载完成后必须要调用ProgressDialog的dismiss()方法来关闭对话框,否则ProgressDialog将会一直存在。
布局
1线性布局属性 LinearLayout
android:orientation属性指定了排列方向是vertical,如果指定的是horizontal,控件就会在水平方向上排列了。
android:gravity用于指定文字在控件中的对齐方式,而android:layout_gravity用于指定控件在布局中的对齐方式。android:layout_gravity的可选值和android:gravity差不多,但是需要注意,当LinearLayout的排列方向是horizontal时,只有垂直方向上的对齐方式才会生效,因为此时水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变,因而无法指定该方向上的对齐方式。同样的道理,当LinearLayout的排列方向是vertical时,只有水平方向上的对齐方式才会生效
android:layout_weight按比例指定控件大小
2RelativeLayout相对布局
相对父布局
android:layout_alignParentLeft和父布局的左对齐,
android:layout_alignParentTop和父布局的上端对齐,
android:layout_alignParentRight和父布局右对齐
android:layout_centerInParent居中显示,
android:layout_alignParentBottom和父布局的下端对齐
如果要设置左上即设置
android:layout_alignParentLeft和父布局的左对齐,
android:layout_alignParentTop和父布局的上端对齐 即可。
相对控件布局
android: layout_below=“B控件的id”表示让一个控件位于另一个控件B的下方,android:layout_toLeftOf表示让一个控件位于另一个控件的左侧,android:layout_toRightOf表示让一个控件位于另一个控件的右侧。
android:layout_alignLeft表示让一个控件的左边缘和另一个控件的左边缘对齐,android:layout_alignRight表示让一个控件的右边缘和另一个控件的右边缘对齐。此外,还有android:layout_alignTop和android:layout_alignBottom,道理都是一样的注意,当一个控件去引用另一个控件的id时,该控件一定要定义在引用控件的后面,不然会出现找不到id的情况
自定义布局跟自定义控件
所有控件都是直接或间接继承自View的,所用的所有布局都是直接或间接继承自ViewGroup的。View是Android中最基本的一种UI组件,它可以在屏幕上绘制一块矩形区域,并能响应这块区域的各种事件,因此,我们使用的各种控件其实就是在View的基础之上又添加了各自特有的功能。而ViewGroup则是一种特殊的View,它可以包含很多子View和子ViewGroup,是一个用于放置控件和布局的容器
场景,将安卓的标题栏修改成iphone的标题栏,即带返回,且可以复用即不是单纯修改一个布局文件来实现。而是自定义一个布局文件,给其他布局文件引用达到可复用。
新建布局文件title.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_bg">
<Button
android:id="@+id/title_back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:background="@drawable/back_bg"
android:text="Back"
android:textColor="#fff" />
<TextView
android:id="@+id/title_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:gravity="center"
android:text="Title Text"
android:textColor="#fff"
android:textSize="24sp" />
<Button
android:id="@+id/title_edit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="5dp"
android:background="@drawable/edit_bg"
android:text="Edit"
android:textColor="#fff" />
</LinearLayout>
然后在main_activity中引用
<LinearLayout xmlns:android="http://schemas.android.com/apk/re/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<include layout="@layout/title" />
</LinearLayout>
其他想用这个界面的都可以引用布局文件即可
同时还需要MainActivity中将自带的标题栏隐藏掉
ActionBar actionbar = getSupportActionBar();
if (actionbar != null) {
actionbar.hide();
}
但是这样仍然有一个问题存在,意味着每个引用了这个布局的activity都要获取返回跟编辑键的button实例,然后给button绑定事件操作。所以需要构建自定义控件
新建TitleLayout继承自LinearLayout,让它成为我们自定义的标题栏控
件
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
LayoutInflater.from(context).inflate(R.layout.title, this);
}
}
重写了LinearLayout中带有两个参数的构造函数,在布局中引入TitleLayout控件就会调用这个构造函数。然后在构造函数中需要对标题栏布局进行动态加载,这就要借助LayoutInflater来实现了。通过LayoutInflater的from()方法可以构建出一个LayoutInflater对象,然后调用inflate()方法就可以动态加载一个布局文件,inflate()方法接收两个参数,第一个参数是要加载的布局文件的id,这里我们传入R.layout.title,第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为TitleLayout,于是直接传入this
修改activity_main.xml中的代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.uicustomviews.TitleLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
添加自定义控件和添加普通控件的方式基本是一样的,只不过在添加自定义控件的时候,我们需要指明控件的完整类名
修改TitleLayout代码
public class TitleLayout extends LinearLayout {
public TitleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
## //布局文件加载过来,然后给里面的控件书写逻辑,
//最后在main——activity中引用控件,
//实际上此时引用的是一个编辑跟返回组合而成的控件,
//布局也存在。所以相当于从main_activity中国引用布局文件
LayoutInflater.from(context).inflate(R.layout.title, this);
Button titleBack = (Button) findViewById(R.id.title_back);
Button titleEdit = (Button) findViewById(R.id.title_edit);
titleBack.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((Activity) getContext()).finish();
}
});
titleEdit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getContext(), "You clicked Edit button", Toast.LENGTH_SHORT).show();
}
});
}
}
此处一定要理解清楚,可以自定义布局,然后活动的布局文件引用布局,然后活动中获取控件实例,进行其他逻辑书写,。这是方法一
方法二。自定义一个控件,自定义控件中加载布局文件,意味着你要制作一个自定义控件,先给出一个自定义的布局,然后自定义控件中加载该布局(毕竟控件本身也是需要布局的),同时去写控件的事件逻辑。最后在其他活动布局中引用控件,相当于布局文件也引用了。
ListView使用与Adapter
在布局文件中引入ListView跟添加控件一样,给一个ID记得
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
在活动中构建数据,加载ListView,最后ListView设置Adapter如下
public class MainActivity extends AppCompatActivity {
private String[] data = {"Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape", "Pineapple", "Strawberry",
"Cherry", "Mango", "Apple", "Banana", "Orange", "Watermelon", "Pear", "Grape", "Pineapple", "Strawberry",
"Cherry", "Mango"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, data);
ListView listView = (ListView) findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
}
其中Adapter要设置布局文件,此处用的是系统自带的一个TextView的布局android.R.layout.simple_list_item_1
LIstView的使用紧记三步走方法即可。
ArrayAdapter
自定义一个布局文件,包含一个ImageView和一个TextView显示水果的图片和名称id分别为fruit_image 以及fruit_name,自定义一个Fruit类,包含水果名称name和imageId属性(图片资源对应的ID),分别给予set,get方法
自定义FruitAdapter继承自ArrayAdapter
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects) {
super(context, textViewResourceId, objects);
resourceId = textViewResourceId;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Fruit fruit = getItem(position); // 获取当前项的Fruit实例
View view = LayoutInflater.from(getContext()).inflate(resourceId, parent, false);
ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return view;
}
}
FruitAdapter重写了父类的一组构造函数,用于将上下文、ListView子项布局的id和数据都传递进来。另外又重写了getView()方法,这个方法在每个子项被滚动到屏幕内的时候会被调用。在getView()方法中,首先通过getItem()方法得到当前项的Fruit实例,然后使用LayoutInflater来为这个子项加载我们传入的布局。这里LayoutInflater的inflate()方法接收3个参数,前两个参数我们已经知道是什么意思了,第三个参数指定成false,表示只让我们在父布局中声明的layout属性生效,但不会为这个View添加父布局,因为一旦View有了父布局之后,它就不能再添加到ListView中了。
public class MainActivity extends AppCompatActivity {
private List<Fruit> fruitList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits(); // 初始化水果数据
FruitAdapter adapter = new FruitAdapter(MainActivity.this, R.layout.fruit_item, fruitList);
ListView listView = (ListView) findViewById(R.id.list_view);
listView.setAdapter(adapter);
}
private void initFruits() {
for (int i = 0; i < 2; i++) {
Fruit apple = new Fruit("Apple", R.drawable.apple_pic);
fruitList.add(apple);
Fruit banana = new Fruit("Banana", R.drawable.banana_pic);
fruitList.add(banana);
Fruit orange = new Fruit("Orange", R.drawable.orange_pic);
fruitList.add(orange);
Fruit watermelon = new Fruit("Watermelon", R.drawable.watermelon_pic);
fruitList.add(watermelon);
Fruit pear = new Fruit("Pear", R.drawable.pear_pic);
fruitList.add(pear);
Fruit grape = new Fruit("Grape", R.drawable.grape_pic);
fruitList.add(grape);
Fruit pineapple = new Fruit("Pineapple", R.drawable.pineapple_pic);
fruitList.add(pineapple);
Fruit strawberry = new Fruit("Strawberry", R.drawable.strawberry_pic);
fruitList.add(strawberry);
Fruit cherry = new Fruit("Cherry", R.drawable.cherry_pic);
fruitList.add(cherry);
Fruit mango = new Fruit("Mango", R.drawable.mango_pic);
fruitList.add(mango);
}
}
}
三步走构建数据,Adapter加载布局,加载ListView,ListView添加Adapter
给ListView每一项添加点击事件
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Fruit fruit = fruitList.get(position);
Toast.makeText(MainActivity.this,fruit.getName(),Toast.LENGTH_SHORT).show();
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
}
});
例如此处给每一项添加点击事件,弹出一个toast以及打开默认浏览器响应。在MainActivity中添加即可,例如此处做一个点击之后跳转到详情的页面,那么意味着就需要带参数过去。
RecyclerView(注意学习二级列表实现,很常用)
首先导包,第三方架包导入在build.gradle(Moudule:app中)
implementation ‘com.android.support:recyclerview-v7:28.0.0’
1在MainActivity中引入RecyclerView注意哦此处不能照搬书里的,由于版本迭代,所属的包有了些许更改,如果按照书中原添加方式
<android.support.v7.widget.RecyclerView android:id="@+id/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" />
这样最后会出现报错android.view.InflateException: Binary XML file line #9: Binary XML file line 仔细观察你的类中导入的recyclerView的包全类名是
androidx.recyclerview.widget.RecyclerView
所以此处MainActivity中需要修改为(跟类中保持一致)
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
然后添加FruitAdapterR继承于RecyclerView.Adapter且泛型指定为FruitAdapter.ViewHolder
代码如下
public class FruitAdapterR extends RecyclerView.Adapter<FruitAdapterR.ViewHolder> {
private List<Fruit> mFruitList;
//内部类,绑定控件
static class ViewHolder extends RecyclerView.ViewHolder {
ImageView fruitImage;
TextView fruitName;
public ViewHolder(View view) {
super(view);
fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
fruitName = (TextView) view.findViewById(R.id.fruit_name);
}
}
public FruitAdapterR(List<Fruit> fruitList){
mFruitList = fruitList;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.fruit_item,parent,false);
ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Fruit fruit = mFruitList.get(position);
holder.fruitImage.setImageResource(fruit.getImageId());
holder.fruitName.setText(fruit.getName());
}
@Override
public int getItemCount() {
return mFruitList.size();
}
}
ViewHolder的构造函数中要传入一个View参数,这个参数通常就是RecyclerView子项的最外层布局,那么我们就可以通过findViewById()方法来获取到布局中的ImageView和TextView的实例了
FruitAdapterR的构造函数,这个方法用于把要展示的数据源传进来,并赋值给一个全局变量mFruitList,我们后续的操作都将在这个数据源的基础上进行
FruitAdapter是继承自RecyclerView.Adapter的,那么就必须重写onCreateViewHolder()、onBindViewHolder()和getItemCount()
onCreateViewHolder()方法是用于创建ViewHolder实例的,我们在这个方法中将fruit_item布局加载进来,然后创建一个ViewHolder实例,并把加载出来的布局传入到构造函数当中,最后将ViewHolder的实例返回。onBindViewHolder()方法是用于对RecyclerView子项的数据进行赋值的,会在每个子项被滚动到屏幕内的时候执行,这里我们通过position参数得到当前项的Fruit实例,然后再将数据设置到ViewHolder的ImageView和TextView当中即可。getItemCount()方法就非常简单了,它用于告诉RecyclerView一共有多少子项,直接返回数据源的长度就可以了
最后在MainActivity中
FruitAdapter fruitAdapter = new FruitAdapter(MainActivity.this,R.layout.fruit_item,fruitList);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
FruitAdapterR adapterR = new FruitAdapterR(fruitList);
recyclerView.setAdapter(adapterR);
先获取到RecyclerView的实例,然后创建了一个LinearLayoutManager对象,并将它设置到RecyclerView当中。LayoutManager用于指定RecyclerView的布局方式,这里使用的LinearLayoutManager是线性布局的意思,可以实现和ListView类似的效果。接下来我们创建了FruitAdapter的实例,并将
水果数据传入到FruitAdapter的构造函数中,最后调用RecyclerView的setAdapter()方法来完成适配器设置,这样RecyclerView和数据之间的关联就建立完成了。
RecyclerView的横向滚动与瀑布流布局
首先要对fruit_item布局进行修改,因为目前这个布局里面的元素是水平排列的,适用于纵向滚动的场景,而如果我们要实现横向滚动的话,应该把fruit_item里的元素改成垂直排列
android:orientation=“vertical”
控件添加
android:layout_gravity=“center_horizontal”
MainActivity中只加入了一行代码,调用LinearLayoutManager的setOrientation()方法来设置布局的排列方向,默认是纵向排列的,我们传入LinearLayoutManager.HORIZONTAL表示让布局横行排列
碎片学习
个人理解碎片类似于大的自定义控件,碎片有自己的布局文件,例如此处定义两个fragment一左一右left-fragment.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="按钮"/>
</LinearLayout>
right_fragment.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#00ff00"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="这是右边的碎片"/>
</LinearLayout>
添加到main——activity.xml中去,与控件添加方法一致,只不过要指明清楚命名空间android:name=“全类名”,增加这个属性即可。
同时创建LeftFragment继承Fragment重写oncreate方法,方法中加载布局文件
public class LeftFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.left_fragment,container,false);
return view ;
}
}
动态添加fragment
但是这样就没实现动态添加,这相当于往固定好的布局里面再塞了两个控件,动态添加相当于此处我提供好框子,到时候可以动态的往里面放fragment就行。
新添加一个fragmnet。another_right_fragment.xml.里面放一个TextView
同样声明一个类继承Fragment。
修改main_activity.xml文件。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<fragment
android:id="@+id/left_fragment"
android:name="com.test.androidstudy.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<FrameLayout
android:id="@+id/right_layout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"></FrameLayout>
</LinearLayout>
FrameLayout中,还记得这个布局吗?在上一章中我们学过,这是Android中最简单的一种布局,所有的控件默认都会摆放在布局的左上角。由于这里仅需要在布局里放入一个碎片,不需要任何定位,因此非常适合使用FrameLayout
意味着在此处放置了一个布局,然后再往布局中动态添加碎片即可。
然后修改MainActivity。实现效果就是左边显示不变。右边先显示right_fragment。点击左边的fragmnet的按钮时,右边切换为another_fragment
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(this);
replaceFragment(new RightFragment());
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
replaceFragment(new AnotherRightFragment());
break;
}
}
private void replaceFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout, fragment);
**transaction.addToBackStack(null)**;//添加了这个点击返回键时不会直接退出,会像activty一样返回到上一个活动一样
transaction.commit();
}
动态添加五步走
动态添加碎片主要分为5步。
(1) 创建待添加的碎片实例。(2) 获取FragmentManager,在活动中可以直接通过调用getSupportFragmentManager()方法得到。(3) 开启一个事务,通过调用beginTransaction()方法开启。(4) 向容器内添加或替换碎片,一般使用replace()方法实现,需要传入容器的id和待添加的碎片实例。(5) 提交事务,调用commit()方法来完成
事务提交之前调用了FragmentTransaction的addToBackStack()方法,它可以接收一个名字用于描述返回栈的状态,一般传入null即可。现在重新运行程序,并点击按钮将AnotherRightFragment添加到活动中,然后按下Back键,你会发现程序并没有退出,而是回到了RightFragment界面,继续按下Back键,RightFragment界面也会消失,再次按下Back键,程序才会退出
碎片与活动互相通信
FragmentManager提供了一个类似于findViewById()的方法,专门用于从布局文件中获取碎片的实例。调用FragmentManager的findFragmentById()方法,可以在活动中得到相应碎片的实例,然后就能轻松地调用碎片里的方法了。
RightFragment rightFragment = (RightFragment)
getSupportFragmentManager() .findFragmentById(R.id.right_fragment);
碎片中获取Activity
在每个碎片中都可以通过调用getActivity()方法来得到和当前碎片相关联的活动实例,另外当碎片中需要使用Context对象时,也可以使用getActivity()方法,因为获取到的活动本身就是一个Context对象
MainActivity activity = (MainActivity) getActivity();
如果需要实现碎片与碎片通信,那就一个碎片获取活动,再从获取的活动获取碎片即可实现
碎片的生命周期
1运行状态
当一个碎片是可见的,并且它所关联的活动正处于运行状态时,该碎片也处于运行状态
2暂停状态
当一个活动进入暂停状态时(由于另一个未占满屏幕的活动被添加到了栈顶),与它相关联的可见碎片就会进入到暂停状态
3停止状态
当一个活动进入停止状态时,与它相关联的碎片就会进入到停止状态,或者通过调用FragmentTransaction的remove()、replace()方法将碎片从活动中移除,但如果在事务提交之前调用addToBackStack()方法,这时的碎片也会进入到停止状态。总的来说,进入停止状态的碎片对用户来说是完全不可见的,有可能会被系统回收
4销毁状态
碎片总是依附于活动而存在的,因此当活动被销毁时,与它相关联的碎片就会进入到销毁状态。或者通过调用FragmentTransaction的remove()、replace()方法将碎片从活动中移除,但在事务提交之前并没有调用addToBackStack()方法,这时的碎片也会进入到销毁状态
所以活动中也会有一些回调方法
onAttach()。当碎片和活动建立关联的时候调用
onCreateView()。为碎片创建视图(加载布局)时调用
onActivityCreated()。确保与碎片相关联的活动一定已经创建完毕的时候调用
onDestroyView()。当与碎片关联的视图被移除的时候调用
onDetach()。当碎片和活动解除关联的时候调用
但是
这种动态添加还只是实现了事件点击,如何达到根据分辨率自己去切换?
例如微博热搜在平板模式下就是左边显示热搜列表,右边显示详情。换成android手机就不是这样显示了。
所以怎样才能在运行时判断程序应该是使用双页模式还是单页模式呢?这就需要借助限定符(Qualifiers)来实现了
在main_activity中只添加一个fragment。
在res目录下新建layout-large文件夹,在这个文件夹下新建一个布局,也叫作activity_main.xml
添加两个fragment,left和right,MainActivity中什么也不添加,正常启动如果是小屏幕手机,那么就加载layout中的main——activity布局文件,只显示一个fragment。如果是大屏幕,那么久加载layout-large布局文件,左边显示一个fragment,右边显示一个fragment。
此处限定符就是large,只要是屏幕被判定为large的就会加载两个fragment的布局,那么判断标准是啥。或者怎么限定?所以常见限定符如下
例如res目录下新建layout-sw600dp文件夹,然后在这个文件夹下新建activity_main.xml布局,就代表如果是大于600dp的会加载这里面的布局
广播(广播接收器中是不允许开启线程的)
意味着有发动广播,还可以接受广播。所以就有了接受器BroadcastReceiver。
广播又分为标准广播和有序广播
标准广播:是一种完全异步执行的广播,在广播发出之后,所有的广播接收器几乎都会在同一时刻接收到这条广播消息,因此它们之间没有任何先后顺序可言。这种广播的效率会比较高,但同时也意味着它是无法被截断的
有序广播:是一种同步执行的广播,在广播发出之后,同一时刻只会有一个广播接收器能够收到这条广播消息,当这个广播接收器中的逻辑执行完毕后,广播才会继续传递。所以此时的广播接收器是有先后顺序的,优先级高的广播接收器就可以先收到广播消息,并且前面的广播接收器还可以截断正在传递的广播,这样后面的广播接收器就无法收到广播消息了。
1接受广播。系统本身就有很多广播,我们先学习接收器,接收广播
要接收广播,就得对自己感兴趣的广播注册,有点类似于隐式意图那种,你得符合广播的action等条件才能接收
注册广播的方式一般有两种,在代码中注册和在AndroidManifest.xml中注册,其中前者也被称为动态注册,后者也被称为静态注册
新建一个广播接收器NetworkChangeReceiver继承于BroadcastReceiver,广播接收器都是继承于BroadcastReceiver,在接收器中进行收到广播后的逻辑处理。
重写onReceive方法
public class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "network changes", Toast.LENGTH_SHORT).show();
}
}
这里是对网络状态发生变化进行一个监听。
在MainActivity中动态注册广播
public class MainActivity extends AppCompatActivity {
private IntentFilter intentFilter;
private NetworkChangeReceiver networkChangeReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intentFilter = new IntentFilter();
intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
networkChangeReceiver = new NetworkChangeReceiver();
registerReceiver(networkChangeReceiver, intentFilter);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(networkChangeReceiver);
}
}
其实就是添加Filter。添加action,对应相应的广播的action即可,所以如果你是自定义广播意味着你定义好action,你要接受或者别人要接受都得符合你的action,最后注册广播即可。最后动态注册的都要取消注册。在onDersory中取消注册即可
静态注册
在mainfests中注册即可,程序不需要启动就能监听。动态注册还需要启动程序才行,因为是在代码中动态注册的。
在mainfests中添加,跟activity平级,添加到application中,不要添加到activity中去了。同样是用filter添加注册
<receiver
android:name=".NetWorkChangeReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
安卓7.0以后网络状态变化监听不能用静态注册了。所以上述尝试注意
静态注册开机启动会后弹出一个toast
自定义广播
发送一个标准广播
静态注册,接受器中仍然是Toast弹出一个提示
mainfests中添加filter
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.broadcasttest.MY_BROADCAST" />
</intent-filter>
</receiver>
在MainActivity中添加一个按钮,给一个点击事件,发送广播
Intent intent = new
Intent("com.example.broadcasttest.MY_BROADCAST");
sendBroadcast(intent);
这就是自定义广播,此时广播可被其他应用接受。
此处采用静态注册,需要动态注册也可以
myReceiver = new MyReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.example.broadcasttest.MY_BROADCAST");
registerReceiver(myReceiver,intentFilter);
当然最后不要忘记在onDestory中取消注册。同样可以再起一个应用,两个应用都可以接收到
刚刚的广播大家都可以接受到,没有人可以中断,只要filter的action符合即可接收到。
下面发送有序广播,并设置优先级
同样MainActivity给一个点击事件,事件内容,发送有序广播
Intent intent = new Intent("com.example.broadcasttest.MY_BROADCAST"); sendOrderedBroadcast(intent, null);
定义两个应用,两个广播接收器,动态静态都行,这里第一参数是intent,第二个是权限一般是null。
此处使用静态且一个设置优先级为100,另一个低于100即可
<receiver
android:enabled="true"
android:exported="true"
android:name=".MyBroadcastReceiver">
<intent-filter android:priority="100">
<action android:name="com.example.broadcasttest.MY_BROADCAST" />
</intent-filter>
</receiver>
另一个priority设置为小于100即可。
在100的广播接收器中Receiver(类中)添加
abortBroadcast();
这时候你会发现。100的处理器接收到广播后,另一处理器接收不到广播。
如果取消abortBroadcast();,两个处理器都能接收到广播。
**
本地广播
**
之前的广播只要大家的action,category等对的上,那么大家就能接收,如果只想让本应用接收那么就可以采用本地广播
动态注册即代码中注册
添加一个LocalReceiver处理器
创建IntenFilter添加过滤IntentFilter intentFilter = new IntentFilter()
添加actionintentFilter.addAction(“com.example.broadcasttest.LOCAL_BROADCAST”)
如果是前面的普通动态广播,那么此处就直接注册广播了
registerReceiver(LocalReceiver的实例, intentFilter);
本地广播就多一个步骤
先创建一个localBroadcastManager
LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(this)
再通过localBroadcastManager .registerReceiver(LocalReceiver的实例, intentFilter)
发送广播也要用localBroadcastManager .sendBroadcast(intent)
数据持久化方案(注意SharedPreferences以及okhttp还有没提到的retrofit框架)
一直接通过文件流操作
Context类中提供了一个openFileOutput()方法,可以用于将数据存储到指定的文件中。这个方法接收两个参数,第一个参数是文件名,在文件创建的时候使用的就是这个名称,注意这里指定的文件名不可以包含路径,因为所有的文件都是默认存储到/data/data//files/目录下的。第二个参数是文件的操作模式,主要有两种模式可选,MODE_PRIVATE和MODE_APPEND。其中MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容,而MODE_APPEND则表示如果该文件已存在,就往文件里面追加内容,不存在就创建新文件。返回的是一个FileOutputStream对象
Context类中还提供了一个openFileInput()方法,用于从文件中读取数据。这个方法要比openFileOutput()简单一些,它只接收一个参数,即要读取的文件名,然后系统会自动到/data/data//files/目录下去加载这个文件,并返回一个FileInputStream对象
public void write(String data) throws IOException {
FileOutputStream fileOutputStream = null;
BufferedWriter writer = null;
try {
fileOutputStream = openFileOutput("datawrite22", Context.MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(fileOutputStream));
writer.write(data);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
writer.close();
}
}
public void read(String data1){
FileInputStream fileInputStream = null;
BufferedReader bufferedReader = null ;
try {
fileInputStream =openFileInput(data1);
bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
String read = "";
StringBuilder line = new StringBuilder();
while((read=bufferedReader.readLine())!=null){
line.append(read);
}
String data = line+"" ;
write(data);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
SharedPreferences(特别注意。用来做本地缓存的记住账号密码操作,特别提醒记得使用MD5加密最好)SharedPreferences文件都是存放在/data/data//shared_prefs/目录下的。
三步走战略
1获取SharedPreferences
2通过SharedPreferences对象调用edit()方法获取一个SharedPreferences.Editor对象
3调用editor的方法存储比如添加一个布尔型数据就使用putBoolean()方法,添加一个字符串则使用putString()方法,以此类推
最后调用editor的apply()或者commit()方法将添加的数据提交。
二得到SharedPreferences有三种方式
1Context类中的getSharedPreferences(),方法接收两个参数,第一个参数用于指定SharedPreferences文件的名称,如果指定的文件不存在则会创建一个,第二个参数用于指定操作模式,目前只有MODE_PRIVATE这一种模式可选,它是默认的操作模式,和直接传入0效果是相同的
2Activity类中的getPreferences()方法,和Context中的getSharedPreferences()方法很相似,不过它只接收一个操作模式参数,因为使用这个方法时会自动将当前活动的类名作为SharedPreferences的文件名
3PreferenceManager类中的getDefaultSharedPreferences()方法这是一个静态方法,它接收一个Context参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreferences文件。
//使用Context类中的getSharedPreferences()
public void save1(String account,String passWord){
SharedPreferences sharedPreferences = getSharedPreferences("userInfo",MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("account",account);
editor.putString("passWord",passWord);
editor.apply();
}
//使用当前context的getPreferences
public void save2(String account,String passWord){
SharedPreferences sharedPreferences = this.getPreferences(MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("account",account);
editor.putString("passWord",passWord);
editor.commit();
}
public void save3(String account,String passWord){
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("account",account);
editor.putString("passWord",passWord);
editor.apply();
}
二从SharedPreferences中获取数据
1先获取SharedPreferences对象,用getSharedPreferences传入文件名
2调用SharedPreferences对象的各种get方法,例如getString()、getInt()和getBoolean()方法,参数之前存入的key
public String read(String fileName){
SharedPreferences sharedPreferences = getSharedPreferences("userInfo",MODE_PRIVATE);
return sharedPreferences.getString("account","默认值");
}
默认值意思是当去文件中找不到这个key时,返回你传入的默认值
三数据库技术SQLITE
通过SQLiteOpenHelper抽象类来实现去继承它。SQLiteOpenHelper中有两个抽象方法,分别是onCreate()和onUpgrade(),我们必须在自己的实现类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
SQLiteOpenHelper中还有两个非常重要的实例方法:getReadableDatabase()和getWritableDatabase()方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法则将出现异常
这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法则将出现异常。SQLiteOpenHelper中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。这个构造方法中接收4个参数,第一个参数是Context,这个没什么好说的,必须要有它才能对数据库进行操作。第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。第三个参数允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入null。第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。构建出SQLiteOpenHelper的实例之后,再调用它的getReadableDatabase()或getWritableDatabase()方法就能够创建数据库了,数据库文件会存放在/data/data//databases/目录下。此时,重写的onCreate()方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。
public class DataBaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table book(id integer primary key autoincrement," +
"author text," +
"price real," +
"pages integer," +
"name text)";
public DataBaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(CREATE_BOOK);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
}
public class MainActivity extends AppCompatActivity {
private DataBaseHelper dataBaseHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dataBaseHelper = new DataBaseHelper(this,"BookTable.db",null,1);
dataBaseHelper.getWritableDatabase();
}
}
android studio3.63版本最上面VIEW窗口选择TOOL WINDOWS 点击选择DEVICE FILE EXPLOR 然后在/data/data/你的包名/databases下面会生成你创建的数据库文件,名字BookTable.db
此处是重写onCreate方法实现,而onUpgrade就是升级用。
往建表语句中添加一个字段 year 类型text。
如果只修改建表语句,不重写onUpgrade方法,此时由于库已经存在,所以不会执行onCreate方法。所以不会建表,重写onUpgrade方法
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
sqLiteDatabase.execSQL("drop table if exists Book");
onCreate(sqLiteDatabase);
}
并且修改构造器的版本号。之前版本是1现在由于做了更改,大于1即可
其他不变。运行程序再去查看即可。
进行增删盖查CRUD
SQLiteOpenHelper的getReadableDatabase()或getWritableDatabase()方法是可以用于创建和升级数据库的,不仅如此,这两个方法还都会返回一个SQLiteDatabase对象,借助这个对象就可以对数据进行CRUD操作了
insert调用SQLiteDatabase对象的insert()方法,传入三个参数,表名,第二个参数用于在未指定
添加数据的情况下给某些可为空的列自动赋值NULL,第三个参数是一个ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可
ContentValues values = new ContentValues();
values.put("name", "The Da Vinci Code");
values.put("author", "Dan Brown");
values.put("pages", 454);
values.put("price", 16.96);
db.insert("Book", null, values); // 插入第一条数据
**values.clear();** // 开始组装第二条数据
values.put("name", "The Lost Symbol");
values.put("author", "Dan Brown");
values.put("pages", 510);
values.put("price", 19.95);
db.insert("Book", null, values);
注意values如果有后续添加记得clear一下
update方法更新
这个方法接收4个参数,第一个参数和insert()方法一样,也是表名,在这里指定去更新哪张表里的数据。第二个参数是ContentValues对象,要把更新数据在这里组装进去。第三、第四个参数用于约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。第三个参数相当于where,例如可以传入,第四个是需要修改where条件的值
db.update("Book", values, "name =?", new String[] { "The Da VinciCode" }); }
删除delete
这个方法接收3个参数,第一个参数仍然是表名,这个已经没什么好说的了,第二、第三个参数又是用于约束删除某一行或某几行的数据,不指定的话默认就是删除所有行。
query查询方法
最短的一个方法重载也需要传入7个参数。那我们就先来看一下这7个参数各自的含义吧。第一个参数不用说,当然还是表名,表示我们希望从哪张表中查询数据。第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。第三、第四个参数用于约束查询某一行或某几行的数据,不指定则默认查询所有行的数据。第五个参数用于指定需要去group by的列,不指定则表示不对查询结果进行group by操作。第六个参数用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式同时调用query()方法后会返回一个Cursor对象,查询到的所有数据都将从这个对象中取出接着我们调用它的moveToFirst()方法将数据的指针移动到第一行的位置,然后进入了一个循环当中,去遍历查询到的每一行数据。在这个循环中可以通过Cursor的getColumnIndex()方法获取到某一列在表中对应的位置索引,然后将这个索引传入到相应的取值方法中,就可以得到从数据库中读取到的数据了(跟JDBC中hasnext差不多)。
最后调用close()方法来关闭Cursor
同时获取到SQLiteDatabase对象后还可以通过SQL直接操作数据库。
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", new String[] { "The Da Vinci Code", "Dan Brown", "454", "16.96" });
update跟删除传参跟上面差不多
查询
db.rawQuery("select * from Book", null);
使用第三方开源库来操作LitePal
1在build.gradle(Module:app)中添加依赖,引入架包,记得sync(右上角或者自己手动sync)
compile 'org.litepal.android:core:1.4.1'
compile可能会让你换成implementation。
2切换为Project视图,在app/src/main下创建assets目录。assets目录下新建litepal.xml文件。添加内容如下
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="BookStore"></dbname>
<version value="1"></version>
<list></list>
</litepal>
标签用于指定数据库名,标签用于指定数据库版本号,标签用于指定所有的映射模型,待会指定
3修改AndroidManifest.xml中的代码。application添加android:name="org.litepal.LitePalApplication
将项目的application配置为org.litepal.LitePalApplication,这样才能让LitePal的所有功能都可以正常工作。此处配置原因后续讲解。
4创建映射的对象(一般一张表映射为一个对象,属性对应表的字段)添加一个Bean对象Book,添加字段id,author,price,pages,name添加get,set方法。
5在litepal.xml添加list 包名得是你的Book类的全类名
6调用LitePal.getDatabase()方法即可创建成功
进行增删盖查操作。
例如往表添加一个字段,添加后添加get,set方法即可。添加表的话就添加对应的Bean对象然后在list中添加
1进行增删盖查操作,让相应的实体类(Bean)继承DataSupport。
2例如新增
Book book = new Book()。
调用set方法设置属性,最后调用
book.save()方法即可新增。
修改,如果对象已经存在,调用save方法就会更改。例如
book.setPrice("200");
book.save();
book.setPrice(10.99);
book.save()
第一次save了一个价格,再次save时由于对象已经存在,则进行修改。不会新添加一条数据。
使用update修改
LitePal.getDatabase();
Book book = new Book();
book.setPrice(14.95);
book.setPress("Anchor");
book.updateAll("name = ? and author = ?", "The Lost Symbol", "DanBrown");
还可以调用book.setToDefault(“pages”);代表将该字段设置为对应类型的默认值。
删除操作
DataSupport.deleteAll(Book.class, "price < ?", "15");
查询所有
List<Book> books =DataSupport.findAll(Book.class)
查询第一条
Book firstBook = DataSupport.findFirst(Book.class);
查整表但是只需要两列
List<Book> books = DataSupport.select("name", "author").find(Book.class);
带条件查整表
List<Book> books = DataSupport.where("pages > ?", "400").find(Book.class);
综合查询
List<Book> books = DataSupport.select("name", "author", "pages").where("pages > ?", "400").order("pages").limit(10).offset(1).find(Book.class);
原生sql查询
Cursor c = DataSupport.findBySQL("select * from Book where pages > ? and price < ?", "400", "20");
内容提供器(四大组件之一),暂时先了解即可熟练的获取系统自带的内容即可,别的APP一般不会让你能跨程序访问数据。
系统本身已经自带了一些内容提供器,先访问系统自带的内容提供器,例如经常会用到的获取系统联系人
要访问其他应用的数据,需要借助ContentResolver类,通过Context的getContentResolver方法获取该实例,然后通过ContentResolver类提供的insert等方法进行增删改查操作,其中方法参数中表名要做处理,不能直接传入表名而需要传入URI对象,URI由两部分组成,authority和path,一般authority用全类名用来区分不同程序。path对应表名。一个URI组成如下
com.example.app.provider/table1和com.example.app.provider/table2分别代表1跟表2。同时URI需要带上协议声明(像之前的tel,http一样)。
content://com.example.app.provider/table1
同理调用Uri。parse转化为Uri对象
Uri uri = Uri.parse("content://com.example.app.provider/table1")
例如执行查询操作
Cursor cursor = getContentResolver().query(uri,
projection,//指定查询的列名
selection,//指定where约束条件,占位符?
selectionArgs,//指定为where中占位符提供具体的值,数组提供
sortOrder);//指定排序方式
查询返回的仍然是一个Cursor对象,使用moveToNext获取即可,最后记得cursor。close()
添加跟更新也是使用ContentValues调用put方法放数据。
ContentValues values = new ContentValues();values.put("column1", "text");values.put("column2", 1);getContentResolver().insert(uri, values);
ContentValues values = new ContentValues();
values.put("column1", "");
getContentResolver().update(uri, values, "column1 = ? and column2 = ?", newString[]{
"text", "1"
});
读取系统联系人
使用ListView展示,在MainActivity中添加一个ListView。
1获取ListView对象
ListView contactsView = (ListView) findViewById(R.id.contacts_view);
构造adapter
adapter = new ArrayAdapter(this, android.R.layout. simple_list_item_1, contactsList);参数分别为上下文,展现的数据的布局文件,此处为系统自带的一个Text文本的布局,可以自己新建一个布局文件里面放一个TextView(此处展示的数据会被拼接成一个字符串,添加换行字符并且),最后一个参数是要展示的数据。
list的对象添加adapter
ontactsView.setAdapter(adapter);
另外android6.0以后需要动态申请获取联系*限。此处不做记录,待会后面再专门记录关于权限。
书写读取联系人的方法。
public void readContacts(){
Cursor cursor = null;
try {
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null, null, null, null);
if (cursor!=null){
while(cursor.moveToNext()){
String disPlayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactsList.add(disPlayName +"\n" +number);
}
**adapter.notifyDataSetChanged();//刷新**
}
}catch (Exception e){
e.printStackTrace();
}finally {
if (cursor!=null){
cursor.close();
}
}
}
特别提醒记得刷新adapter
。构建自己的ContentProvider
//to do暂时状态不好,不细看此处问题先暂挂后续再回来更改
使用通知Notification重点结合服务和广播来实现
1首先获取一个NotificationManager来管理通知可以调用Context的getSystemService()方法来获取,这个方法参数接受一个字符串用于确定获取系统的哪个服务,此处传入Context.NOTIFICATION_SERVICE
2使用Builder构造器来创建Notification对象,此处由于8.0后android每个通知需要有一个channelid所以书中的
NotificationCompat.Builder(上下文).build()不再适用。参数中现在还需要添加一个channelId所以
Notification notification = new NotificationCompat.Builder(this,"1").build();
这是构建了一个通知,但是真正的通知包含很多信息,所以给通知设置如下信息
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(MainActivity.this,"1")
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(action);
Notification notification = notificationBuilder.build();
NotificationManager的notify()方法就可以让通知显示出来。这个方法两个参数,第一个参数是id,要保证为每个通知所指定的id都是不同的。第二个参数则是Notification对象,这里直接将我们刚刚创建好的Notification。
同时如果是系统大于8.0版本,那么在创建通知前还要创建通道。此处为保证兼容性,做判断处理
//安卓8.0之后
if(Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O){
//只在Android O之上需要渠道,这里的第一个参数要和下面的channelId一样
NotificationChannel notificationChannel = new NotificationChannel("1","name",NotificationManager.IMPORTANCE_HIGH);
//如果这里用IMPORTANCE_NOENE就需要在系统的设置里面开启渠道,通知才能正常弹出
manager.createNotificationChannel(notificationChannel);
}
保证此处的channelId跟通知的channelId一致即可。但是此时的通知点击无用,一般我们后台收到通知就可打开某app进入到指定页面。所以使用PendingIntent。
1首先获取PendingIntent的实例
根据需求来选择是使用getActivity()方法、getBroadcast()方法,还是getService()方法。这几个方法所接收的参数都是相同的,第一个参数依旧是Context,不用多做解释。第二个参数一般用不到,通常都是传入0即可。第三个参数是一个Intent对象,我们可以通过这个对象构建出PendingIntent的“意图”。第四个参数用于确定PendingIntent的行为,有FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT这4种值可选
在构建通知的时候NotificationCompat.Builder的setContentIntent()参数就是设置一个PendingIntent对象。
添加第二个activity。只添加一个TextView显示一行文字。
构建一个显示的意图
Intent intent = new Intent(this, SecondActivity.class);
创建PendingIntent对象
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
创建notification时.setContentIntent(pi)即可。但是这样构建的通知有一个问题就是通知出现在通知栏没问题,点击 跳转也没问题,但是正常的应该是点击进入后这个通知应该消失的。所以还需要设置。setAutoCancel()参数设置为true就表示点击时通知取消。或者调用NotificationManager的cancel(),参数传入前面设置的通知的id即可。例如前面传入的是1.不是channelid哦,注意区分。
动态获取权限
android6.0以前获取权限只需要在mainfests文件中加上你要的权限,这样安装APP时用户能看到你申请了哪些权限,注意,仅限于能看到。。但是没法取消某个权限,这就导致了,可能APP不管需不需要都申请了再说,你还没法拒绝。
例如添加相机权限
声明在mainfests中就行了。
但android6.0以后对于一些危险权限(涉及隐私)就需要动态申请,除了mainfests中声明,代码中还需要动态让用户选择授权或者拒绝。
第一步先判断用户是不是给了这个权限。
ContextCompat.checkSelfPermission()方法。checkSelfPermission()方法接收两个参数,第一个参数是Context,这个没什么好说的,第二个参数是具体的权限名,比如打电话的权限名就是Manifest.permission.CALL_PHONE,然后我们使用方法的返回值和PackageManager.PERMISSION_GRANTED做比较,相等就说明用户已经授权,不等就表示用户没有授权,没有授权的话
ActivityCompat.requestPermissions()方法来向用户申请授权,requestPermissions()方法接收3个参数,第一个参数要求是Activity的实例,第二个参数是一个String数组,我们把要申请的权限名放在数组中即可,第三个参数是请求码,只要是唯一值就可以了,这里传入了1
if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{ Manifest.permission.CALL_PHONE }, 1);
}
调用完了requestPermissions()方法之后,系统会弹出一个权限申请的对话框,然后用户可以选择同意或拒绝我们的权限申请,不论是哪种结果,最终都会回调到onRequestPermissionsResult()方法中,而授权的结果则会封装在grantResults参数当中。这里我们只需要判断一下最后的授权结果,如果用户同意的话就调用call()方法来拨打电话,如果用户拒绝的话我们只能放弃操作,并且弹出一条失败提示
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
call();
} else {
Toast.makeText(this, "You denied the permission", Toast.LENGTH_ SHORT).show();
} break;
default:
}
}
同时还可以进行多个权限一次申请,避免一次申请。
先创建一个集合用于存放要授的权限
List permissionList = new ArrayList<>();
然后对每个权限判断,。如果没授权就添加到集合,最后对集合进行判断,如果 集合非空(说明有未授予的权限)。那么就调用
ActivityCompat.requestPermissions();方法,将集合转成数组。作为第二个参数传入String[] permissions = permissionList.toArray(new String[permissionList.size()]);
最后回调的方法中处理,可以设置判断必须同意所有权限才能使用否则finish()或者可以判断某个权限是否有授予。
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case 1:
if (grantResults.length > 0) {
for (int result : grantResults) {
if (result != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "必须同意所有权限才能使用本程序", Toast.LENGTH_SHORT).show();
finish();
return;
}
}
requestLocation();
} else {
Toast.makeText(this, "发生未知错误", Toast.LENGTH_SHORT).show();
finish();
}
break;
default:
}
}
当然mainfests中千万不要忘记添加你申请的所有权限。
调用系统相机以及相册
//TODO
播放音频以及视频
//TODO
//使用HTTP访问网络(重点在OKHTTP以及retrofit框架学习)
特别提醒,注意网络使用需要声明权限。不声明权限的话,报错的提示是找不到这个地址(www.baidu.com)就会引导你往错误方向排查问题
特别提醒2网络请求属于耗时操作。不要放在主线程中操作,否则会引起崩溃等问题
此处使用HttpConnectin就不再做记录了,因为项目中基本不会使用了,但是了解还是有必要的
另外在网络交互中一般发送或者接受都是对json的处理。所以注意区分json格式的字符串以及json对象的区别。同时关注GSON的使用。
1添加okhttp的依赖
compile ‘com.squareup.okhttp3:okhttp:3.4.1’
2创建okhttp实例
OkHttpClient client = new OkHttpClient();
3创建Request对象(针对get请求)
Request request = new Request.Builder().build();
4设置请求地址等其他参数。在build之前
Request request = new Request.Builder().url("http://www.baidu.com").build();
5调用OkHttpClient的newCall()方法来创建一个Call对象,并调用它的execute()方法来发送请求并获取服务器返回的数据。
Response response = client.newCall(request).execute();
6获取返回数据
String responseData = response.body().string();
二如果是post请求
1先得构建一个对象存取要提交的参数
RequestBody requestBody = new FormBody.Builder().add("username", "admin").add("password", "123456").build();
2然后在Request.Builder中调用一下post()方法,并将RequestBody对象传入:
Request request = new Request.Builder().url("http://www.baidu.com").post(requestBody).build();
3接下来的操作就和GET请求一样了,调用execute()方法来发送请求并获取服务器返回的数据即可
总体事例如下
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//request();
requestPost();
}
public void request() {
new Thread(new Runnable() {
@Override
public void run() {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url("https://www.baidu.com/").build();
try {
Response response = okHttpClient.newCall(request).execute();
String responseData = response.body().string();
Log.d("resdata", responseData);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
public void requestPost() {
new Thread(new Runnable() {
@Override
public void run() {
OkHttpClient okHttpClient = new OkHttpClient();
RequestBody requestBody = new FormBody.Builder().add("username", "admin").add("password", "123456").build();
Request request = new Request.Builder().url("http://www.baidu.com").post(requestBody).build();
try {
Response response = okHttpClient.newCall(request).execute();
String responseData = response.body().string();
Log.d("postdata", responseData);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
JSON格式解析。XML解析就不提了。
JSON格式是键值对形式。
JSON类型分为数组和对象。两者内部可彼此包含
json对象大括号包含
{“name”:“学习” , “url”:“www.xuexi.com” }。
那么调用的时候就直接json对象的get方法即可
可以自己去下载一个apache服务器(或者自己手搭建一套web服务建议还是复习下web后端)。如何安装
https://blog.csdn.net/vagabond_/article/details/90436858
,编辑网页内容作为请求的地址拿来测试比较合适。
安装完成后,注意,如果在虚拟机测试IP地址不要填写127.0.0.1.要填写外部PC的地址。
解析第一种最普通的格式创建data.json文件在安装目录中创建(D:\Apache\Apache24\htdocs).添加内容,注意引号。不使用引号,或者单引号也能取到值,但是会报错。暂时不清楚会带来什么影响。
{"name":"steven","age":"18","sex":"man"}
这种就直接OKHTTP创建客户端。创建request或者REquestBody。返回response转换成String类型。再转成JSONObject。然后get方法获得即可。
加粗样式二json对象中有json数组
{name:steven,score:[100,150,120]}
先第一步切分,最外层是json对象有俩个元素,name跟score
所以上来依然转为JSON对象
JSONObject jsonObject = new JSONObject(data);
data为网络请求返回的json并且变为String类型、然后获取第二个对象
JSONArray jsonArray = new JSONArray(jsonObject.get("score").toString());
千万不要忘记使用toString或者使用+""拼接将其转成String类型后再作为参数使用JSONArray变为JSON数组对象
最后正常的for循环调用即可
for (int i =0;i<jsonArray.length();i++){
Log.d("JSON解析",""+jsonArray.get(i));
}
三JSON对象中有数组,数组中有json对象
{"name":"steven","score":[{"math":"100","english":"150"},{"math":"120","english":"150"}]}
2前面都是正常获取,到了数组通过get(i)获取到的就是JSON对象了,记得使用toString或者拼接+“”变成String类型作为参数传入JSONObject再通过get方法获取即可`
Log.d("06283",""+jsonArray.get(i));
JSONObject jsonObject1 = new JSONObject(jsonArray.get(i).toString());
Log.d("06284",jsonObject1.get("math").toString());
总之就是获取到JSON对象就使用JSONObject并且参数要是String类型,数组就用JSONArray,同样参数也是String类型,所以可以使用toString或者拼接+“”构造
使用GSON当然你可以使用ali的fastjson等等。但是安卓一般用GSON
添加依赖
compile 'com.google.code.gson:gson:2.7'
1最基础的json格式
{"name":"steven","age":"18","sex":"man"}
1创建一个实体类
对应相应的属性。
2使用OKHTTP正常获取返回体并转为String。
3调用GSON
Gson gson = new Gson();
People people = gson.fromJson(data,People.class);
返回的为你创建的对应的实体类。
类型跟你设置的类型一致,例如我这里age是int,其他都是String。
二解析的是一个数组
[{"name":"steven","age":"18","sex":"man"},{"name":"steven1","age":"19","sex":"man"},{"name":"steven2","age":"20","sex":"man"}]
里面包含三个对象。
调用new TypeToken方法返回一个集合,
List<People> people = gson.fromJson(data, new TypeToken<List<People>>() {}.getType());
然后使用增强for循环或者普通for循环都可以取到你要的值。
for(People people1:people){
Log.d("0628",people1.getName());
}
三解析复杂的使用GSON
学习博客
https://www.jianshu.com/p/3b8ef7162e69
例如
{"name":"steven","age":"18","sex":"man","student":[{"score":"100","math":"200"},{"score":"1001","math":"2001"}]}
那么在实体类构造时有[]就需要添加List以及内部类
public class People {
private String name;
private int age;
private String sex;
private List<Student> student;
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", student=" + student +
'}';
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getSex() {
return sex;
}
public List<Student> getStudent() {
return student;
}
public void setStudent(List<Student> student) {
this.student = student;
}
public static class Student{
private String score;
private String math;
public void setScore(String score) {
this.score = score;
}
public void setMath(String math) {
this.math = math;
}
public String getScore() {
return score;
}
public String getMath() {
return math;
}
}
}
注意字段名一定要跟json中一致才行。
解析的时候正常解析
People people = gson.fromJson(data,People.class);
获取Student的实例会返回数组。取第一个并取他的数学成绩
people.getStudent().get(0).getMath()
同时注意,你的实体类定义有10个字段,JSON对象并不一定给你十个字段,其中JSON对象中有些字段不存在,那么解析出来的对象相应的值就为null。
网络中的回调机制
由于网络请求是耗时操作,所以上面的代码是加在子线程中操作的,但是上面的所有操作包括接收返回值,处理等都是在子线程,所以是没问题的,正常情况下我们封装了一个网络请求的方法,在主线程中接受返回值再操作,有可能会出现接收到返回值,因为主线程调用这个方法时不会一直等待子线程网络请求执行完。所以上述操作模式会出现一定的问题。这个时候就需要使用回调机制,就是在成功或失败的时候回调操作。
添加接口
public interface HttpCallbackListener {
void onFinish(String response);
void onError(Exception e);
}
添加抽象方法成功获取返回体的方法onFinish,以及错误的时候onError。
网络请求创建客户端,创建BUilder,等操作都是固定重复操作。所以可以封装成一个工具类
public static void sendHttpRequest(final String address, final HttpCallbackListener listener) {
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection = null;
try {
URL url = new URL(address);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(8000);
connection.setReadTimeout(8000);
connection.setDoInput(true);
connection.setDoOutput(true);
InputStream in = connection.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader
(in));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
if (listener != null) { // 回调onFinish()方法
listener.onFinish(response.toString());
}
} catch (Exception e) {
if (listener != null) { // 回调onError()方法
listener.onError(e);
}
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}).start();
}
最后在主线程调用并传入HttpCallbackListener的实例,并实现抽象方法。当服务器成功响应的时候,我们就可以在onFinish()方法里对响应数据进行处理了。类似地,如果出现了异常,就可以在onError()方法里对异常情况进行处理,但是这种借助最原始的HttpConnection的方法还是太过于繁琐
public static void sendOkHttpRequest(String address, okhttp3.Callback callback) {
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(address).build();
client.newCall(request).enqueue(callback);
}
sendOkHttpRequest()方法中有一个okhttp3.Callback参数,这个是OkHttp库中自带的一个回调接口,类似于我们刚才自己编写的HttpCallbackListener。然后在client.newCall()之后没有像之前那样一直调用execute()方法,而是调用了一个enqueue()方法,并把okhttp3.Callback参数传入。相信聪明的你已经猜到了,OkHttp在enqueue()方法的内部已经帮我们开好子线程了,然后会在子线程中去执行HTTP请求,并将最终的请求结果回调到okhttp3.Callback当中
。在调用方法时传入实例。并且实现抽象方法
HttpUtil.sendOkHttpRequest("http://www.baidu.com",new okhttp3.Callback(){
@Override public void onResponse(Call call,Response response)throws IOException{
// 得到服务器返回的具体内容
String responseData=response.body().string();}
@Override public void onFailure(Call call,IOException e){
// 在这里对异常情况进行处理
}});
另外不管是使用HttpURLConnection还是OkHttp,最终的回调接口都还是在子线程中运行的,因此我们不可以在这里执行任何的UI操作,除非借助runOnUiThread()方法来进行线程转换大
服务相当重要,结合通知,多线程,网络等使用。例如一个后台下载案例
1安卓不能在子线程中对UI进行操作,即你不能在子线程中对界面的编辑框等进行修改。
除非使用异步机制。
例如在MainActivity的布局中添加一个TextView,一个Button。给按钮添加一个点击事件,当点击按钮后对TextView的内容进行修改(且是在子线程中进行修改)
public class MainActivity extends AppCompatActivity {
public static final int UPDATE_TEXT = 1;
TextView edit ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit = (TextView) findViewById(R.id.edit_main);
Button btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message); // 将Message对象发送出去
}
}).start();
}
});
}
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT: // 在这里可以进行UI操作
edit.setText("Nice to meet you");
break;
default:
break;
}
}
};
}
此处在子线程中定义一个Handler类,并重写他的方法,该方法接受一个Message参数。类似于监听器当接受到的Message为定义的全局变脸UPDATE_TEXT的时候进行修改。在按钮事件中,添加子线程进行MESSAGE的发送。但是对于UI的修改还是在主线程中进行,Handler还是在主线程进行的。
MESSAGE
Message是在线程之间传递的消息,它可以在内部携带少量的信息,用于在不同线程之间交换数据。上一小节中我们使用到了Message的what字段,除此之外还可以使用arg1和arg2字段来携带一些整型数据,使用obj字段携带一个Object对象。
Handler
Handler顾名思义也就是处理者的意思,它主要是用于发送和处理消息的。发送消息一般是使用Handler的sendMessage()方法,而发出的消息经过一系列地辗转处理后,最终会传递到Handler的handleMessage()方法中。
MessageQueue
MessageQueue是消息队列的意思,它主要用于存放所有通过Handler发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程中只会有一个MessageQueue对象。
LooperLooper
LooperLooper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入到一个无限循环当中,然后每当发现MessageQueue中存在一条消息,就会将它取出,并传递到Handler的handleMessage()方法中。每个线程中也只会有一个Looper对象。
使用的runOnUiThread其实就是对上述进行了封装
private void showResponse(final String response) {
runOnUiThread(new Runnable() {
@Override
public void run() { // 在这里进行UI操作,将结果显示到界面上
responseText.setText("修改的内容");
}
});
}
使用AsyncTask
AsyncTask进行了更好的封装
AsyncTask是一个抽象类,创建一个子类去继承它。在继承时我们可以为AsyncTask类指定3个泛型参数
Params。在执行AsyncTask时需要传入的参数,可用于在后台任务中使用。Progress。后台任务执行时,如果需要在界面上显示当前的进度,则使用这里指定的泛型作为进度单位。一般定义为Integer。
Result。当任务执行完毕后,如果需要对结果进行返回,则使用这里指定的泛型作为返回值类型。
有四个方法需要重写
onPreExecute()
在后台任务开始执行之前调用,用于进行一些界面上的初始化操作,比如显示一个进度条对话框等
doInBackground(Params…)
这个方法中的所有代码都会在子线程中运行,我们应该在这里去处理所有的耗时任务。任务一旦完成就可以通过return语句来将任务的执行结果返回,如果AsyncTask的第三个泛型参数指定的是Void,就可以不返回任务执行结果。注意,在这个方法中是不可以进行UI操作的,如果需要更新UI元素,比如说反馈当前任务的执行进度,可以调用**publishProgress (Progress…)**方法来完成。
onProgressUpdate(Progress…)
当在后台任务中调用了publishProgress(Progress…)方法后,onProgressUpdate (Progress…)方法就会很快被调用,该方法中携带的参数就是在后台任务中传递过来的。在这个方法中可以对UI进行操作,利用参数中的数值就可以对界面元素进行相应的更新。
*onPostExecute(Result)
当后台任务执行完毕并通过return语句进行返回时,这个方法就很快会被调用。返回的数据会作为参数传递到此方法中,可以利用返回的数据来进行一些UI操作,比如说提醒任务执行的结果,以及关闭掉进度条对话框等。
使用AsyncTask的诀窍就是,在doInBackground()方法中执行具体的耗时任务,在onProgressUpdate()方法中进行UI操作,在onPostExecute()方法中执行一些任务的收尾工作,我们并不需要去考虑什么异步消息处理机制,也不需要专门使用一个Handler来发送和接收消息,只需要调用一下publishProgress()方法,就可以轻松地从子线程切换到UI线程了。
服务的基本用法
创建服务继承于Service类
一在Mainfests中注册,同时Exported属性表示是否允许除了当前程序之外的其他程序访问这个服务,Enabled属性表示是否启用这个服务。这两个属性开通
二重写一些方法或者实现抽象方法
onBind(Intent intent)是该类唯一的抽象方法,暂时使用不到
重写onCreate()、onStartCommand()和onDestroy()这3个方法,它们是每个服务中最常用到的3个方法了。其中onCreate()方法会在服务创建的时候调用,onStartCommand()方法会在每次服务启动的时候调用,onDestroy()方法会在服务销毁的时候调用。通常情况下,如果我们希望服务一旦启动就立刻去执行某个动作,就可以将逻辑写在onStartCommand()方法里。而当服务销毁时,我们又应该在onDestroy()方法中去回收那些不再使用的资源
在MainActivity中启动停止服务
Intent startIntent = new Intent(this, MyService.class); startService(startIntent); // 启动服务
Intent stopIntent = new Intent(this, MyService.class);
stopService(stopIntent); // 停止服务
停止服务时不一定有按钮事件,想让服务执行到代码某处停止可以使用stopSelf()方法,该方法可以在任意位置让服务停止。
目前这种服务有一个问题就是,当该服务启动之后,完全不可控对于Activity而言。活动的启动是通过Activity触发的但是触发完之后服务内部干了啥,具体实现了什么。完全不可控。所以可以通过Binder对象实现服务与activity的通信,例如执行一个下载任务,在服务当中去执行具体下载的逻辑结合Aysctask执行耗时的网络请求,告诉主线程下载的进度。显示在Activity上,在MyService中,oncreate,onStartCommand方法不变,添加内部类DownloadBinder(l小写区别于自带的) 继承于Binder类
该类中添加下载方法stratDownload以及结束时的endDownload方法。
public class MyService extends Service {
private static final String TAG="MyService";
private DownloadBinder mBinder = new DownloadBinder();
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
return mBinder ;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG,"endsERVICE");
}
class DownloadBinder extends Binder{
public void startDownload(){
Log.d(TAG,"start download");
}
public void endDownload(){ Log.d(TAG,"download s%");
}
}
同时在,MyService中创建这个Binder的实例,并且在onBind方法中返回这个实例.
接在在mainActivity中。绑定这个服务从而去调用Binder当中的方法。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_start;
private Button btn_end ;
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
downloadBinder = (MyService.DownloadBinder) iBinder;
downloadBinder.startDownload();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
downloadBinder.endDownload();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_start = (Button) findViewById(R.id.btn_start);
btn_end = (Button) findViewById(R.id.btn_end);
btn_start.setOnClickListener(this);
btn_end.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.btn_start:
Intent intent = new Intent(this,MyService.class);
bindService(intent,connection,BIND_AUTO_CREATE);
break;
case R.id.btn_end:
unbindService(connection);
break;
default:
break;
}
}
}
创建一个ServiceConnection的匿名类。重写该类的onServiceConnected方法以及onServiceDisconnected方法,该方法分别是活动与服务绑定成功时调用以及活动与服务断开连接时调用,而onServiceDisconnected()方法在连接正常关闭的情况下是不会被调用的, 该方法只在Service 被破坏了或者被杀死的时候调用.
例如, 系统资源不足, 要关闭一些Services, 刚好连接绑定的 Service 是被关闭者之一, 这个时候onServiceDisconnected() 就会被调用。所以案例中虽然在onServiceDisconnected中添加了结束方法,但是该方法正常情况下不会被调用,在MyService中的onDestory方法添加日志,在结束服务时会打印出日志便于观察,在onServiceConnected中向下转型,得到DownloadBinder实例。切记编码中其他地方向下转型要做判断最好,因为男人属于person类,但是person类不一定都是男人。所以向下转型需要用instanceof方法做判断,得到了downloadBinder对象就可以调用downloadBinder的方法了。在MainActivity的按钮点击事件分别进行服务的绑定与解除绑定。其中绑定时调用bindService()方法,该方法接受三个参数意图,ServiceConnection的实例。第三个参数则是一个标志位,这里传入BIND_AUTO_CREATE表示在活动和服务进行绑定后自动创建服务。这会使得MyService中的onCreate()方法得到执行,但onStartCommand()方法不会执行。此时只是绑定服务,从而创建了服务,而不是启动服务。所以onStartCommand()方法没有执行,当你绑定服务之后其实你还是可以在任意地方调用Context的startService方法来启动服务。
服务的生命周期
一旦在项目的任何位置调用了Context的startService()方法,相应的服务就会启动起来,并回调onStartCommand()方法。如果这个服务之前还没有创建过,onCreate()方法会先于onStartCommand()方法执行。服务启动了之后会一直保持运行状态,直到stopService()或stopSelf()方法被调用。注意,虽然每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,服务就会停止下来了。另外,还可以调用Context的bindService()来获取一个服务的持久连接,这时就会回调服务中的onBind()方法。类似地,如果这个服务之前还没有创建过,onCreate()方法会先于onBind()方法执行。之后,调用方可以获取到onBind()方法里返回的IBinder对象的实例,这样就能*地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。当调用了startService()方法后,又去调用stopService()方法,这时服务中的onDestroy()方法就会执行,表示服务已经销毁了。类似地,当调用了bindService()方法后,又去调用unbindService()方法,onDestroy()方法也会执行,这两种情况都很好理解。但是需要注意,我们是完全有可能对一个服务既调用了startService()方法,又调用了bindService()方法的,这种情况下该如何才能让服务销毁掉呢?根据Android系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行
服务一般是在后台默默进行,通过进程管理可见,但用户一般无法感知,所以有了前台服务。让服务像通知一样显示在通知栏让用户肉眼随时可见。知道服务在干什么。
构建前台服务时候在Service的onCreate方法中添加
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, 0);
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle("This is content title")
.setContentText("This is content text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher))
.setContentIntent(pi).build();
startForeground(1, notification);
这就是之前的创建通知的方法。只不过这次在构建出Notification对象后并没有使用NotificationManager来将通知显示出来,而是调用了startForeground()方法。这个方法接收两个参数,第一个参数是通知的id,类似于notify()方法的第一个参数,第二个参数则是构建出的Notification对象。调用startForeground()方法后就会让MyService变成一个前台服务,并在系统状态栏显示出来。
由于服务是在主线程中执行的,所以如果服务中执行耗时操作很可能就会导致程序崩溃。所以最好是在服务的每个方法创建一个子线程去具体执行逻辑,例如
public class MyService extends Service { ...
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 处理具体的逻辑
//逻辑执行完
stopSelf();
}
}).start();
return super.onStartCommand(intent, flags, startId);
}}
并且相关逻辑执行完以后调用stopself方法停止服务,否则该服务会一直处于运行状态。但是这种写法每次都需要自己手动创建多线程或者停止,不是很方便。所以在创建服务时可以创建IntentService类的子类。
省略前端知识
全局context对象
Android提供了一个Application类,每当应用程序启动的时候,系统就会自动将这个类进行初始化。而我们可以定制一个自己的Application类,以便于管理程序内一些全局的状态信息,比如说全局Context。所以可以执行很多通用操作。
自定义一个Application类
public class MyApplication extends Application {
private static Context context;
@Override
public void onCreate() {
context = getApplicationContext();
}
public static Context getContext() {
return context;
}
}
重写了父类的onCreate()方法,并通过调用getApplicationContext()方法得到了一个应用程序级别的Context,然后又提供了一个静态的getContext()方法,在这里将刚才获取到的Context进行返回。接下来我们需要告知系统,当程序启动的时候应该初始化MyApplication类,而不是默认的Application类。这一步也很简单,在AndroidManifest.xml文件的标签下进行指定就可指定为自己的Application类全类名
android:name=“com.example.networktest.MyApplication”
后面的定时任务等类容具体结合实际项目使用。定时任务方法很多。
本文地址:https://blog.csdn.net/jetaimels/article/details/106708359