Android学习笔记,不断更新
Android笔记
IDE:Android Studio
文章目录
新建工程
详解项目中的文件资源
- build
这个目录和外层的build 目录类似,主要也是包含了一些在编译时自动生成的文件,不过它里面的内容会更多更杂,我们不需要过多关心。 - libs
如果你的项目中使用到了第三方jar 包,就需要把这些jar 包都放在libs 目录下,放在这个目录下的jar 包都会被自动添加到构建路径里去。 - androidTest
此处是用来编写Android Test 测试用例的,可以对项目进行一些自动化测试。 - java
毫无疑问, java 目录是放置我们所有Java 代码的地方,展开该目录,你将看到我们刚才创建的HelloWorldActivity 文件就在里面。 - res
这个目录下的内容就有点多了。简单点说就是你在项目中使用到的所有图片、布局、字符串等资源都要存放在这个目录下。当然这个目录下还有很多子目录,图片放在drawable 目录下,布局放在layout 目录下,字符串放在values 目录下,所以你不用担心会把整个m 目录弄得乱糟糟的。 - AndroidManifest.xml
这是你整个Android 项目的配置文件,你在程序中定义的所有四大组件都需要在这个文件里注册,另外还可以在这个文件中给应用程序添加权限声明。由于这个文件以后会经常用到,我们用到的时候再做详细说明。 - test
此处是用来编写Unit Test 测试用例的,是对项目进行自动化测试的另一种方式。 - gitignore
这个文件用于将叩p 模块内的指定的目录或文件排除在版本控制之外,作用和外层的.gitignore 文件类似。 - app.iml
IntelliJ IDEA 项目自动生成的文件,我们不需要关心或修改这个文件中的内容。 - build.gradle
这是app 模块的gradle 构建脚本,这个文件中会指定很多项目构建相关的配置,我们稍后将会详细分析gradle 构建脚本中的具体内容。 - proguard-rules.pro
这个文件用于指定项目代码的混淆规则,当代码开发完成后打成安装包文件,如果不希望代码被别人**,通常会将代码进行混淆,从而让**者难以阅读。
详解res目录文件资源
所有以drawable开头的文件夹都是用来放图片的;
所有以mipmap开头的文件夹都是用来放应用图标的
所有以values 开头的文件夹都是用来放字符串、样式、颜色等配置的;
layout 文件夹是用来放布局文件的;
之所以有这么多mipmap开头的文件夹,其实主要是为了让程序能够更好地兼容各种设备。drawable 文件夹也是相同的道理,应该创建drawable-hdpi 、drawable-xhdpi 、drawable-xxhdpi 等文件夹。在制作程序的时候最好能够给同一张图片提供几个不同分辨率的版本,分别放在这些文件夹下,然后当程序运行的时候,会自动根据当前运行设备分辨率的高低选择加载哪个文件夹下的图片。当然这只是理想情况,更多的时候美工只会提供给我们一份图片,这时你就把所有图片都放在drawable-xxhdpi 文件夹下就好了。
引用res目录下的资源
在代码中通过R.string.hello_world 可以获得该宇符串的引用。
在XML 中通过@string/hello_world 可以获得该字符串的引用。
其中string 部分是可以替换的,如果是引用的图片资源就可以替换成drawable ,如果是引用的应用图标就可以替换成mipmap ,如果是引用的布局文件
就可以替换成layout ,以此类推。
在活动中使用toast
通过findViewByid ()方法获取到在布局文件中定义的元素,返回一个View 对象,需要向下转型将它转成Button 对象。
通过调用setOnClicklistener()方法为按钮注册一个监听器,点击按钮时就会执行监听器中的onClick()方法。
通过静态方法mnakeText()创建出一个Toast 对象,然后调用show()将Toast 显示出来就可以了。
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Toast test", Toast.LENGTH_SHORT).show();
}
});
#### 在活动中使用menu
首先在res 目录下新建→个menu 文件夹,右击res 目录→New→Directory ,输入文件夹名menu ,点击OK。接着在这个文件夹下再新建一个名叫main 的菜单文件,右击menu 文件夹→New→Menu resource file。
在main.xml 中添加如下代码,其中标签用来创建具体的一个菜单项:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/add_item"
android:title="Add"/>
<item
android:id="@+id/remove_item"
android:title="Remove"/>
</menu>
接着重新回到FirstActivity 中来重写onCreateOptionsMenu()、onOptionsItemSelected()方法,重写方法可以使用Ctrl+O 快捷键
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()){
case R.id.add_item:
Toast.makeText(this, "ADD", Toast.LENGTH_SHORT).show();
break;
case R.id.remove_item:
Toast.makeText(this, "REMOVE", Toast.LENGTH_SHORT).show();
break;
default:
break;
}
return true;
}
销毁当前活动
按一下Back()键,或使用:
finish();
Java中的Context上下文
简单理解Context
“环境”、“容器”,承上启下。当前对象在程序中所处的一个环境,一个与系统交互的过程。比如微信聊天,此时的“环境”是指聊天的界面以及相关的数据请求与传输,Context在加载资源、启动Activity、获取系统服务、创建View等操作都要参与。
在编程术语中,它是对当前工作单元的行为有任何影响的较大的周边部分。例如所使用的运行环境、环境变量、实例变量、局部变量、其他类的状态、当前环境的状态等等。
在一些API中,你会在接口/类中看到这个名称,例如Servlet的ServletContext、JSF的FacesContext、Spring的ApplicationContext、Android的Context、JNDI的InitialContext等等。它们通常都遵循Facade模式,该模式让最终用户不需要了解环境细节抽象到单个接口/类中。
源码中的Context
Context提供了关于应用环境全局信息的接口。它是一个抽象类,它的执行被Android系统所提供。它允许获取以应用为特征的资源和类型,是一个统领一些资源(应用程序环境变量等)的上下文。就是说,它描述一个应用程序环境的信息(即上下文);是一个抽象类,Android提供了该抽象类的具体实现类;通过它我们可以获取应用程序的资源和类(包括应用级别操作,如启动Activity,发广播,接受Intent等)。既然上面Context是一个抽象类,那么肯定有他的实现类,我们在Context的源码中通过IDE可以查看到他的子类最终可以得到如下关系图:
Context能干什么
Context到底可以实现哪些功能呢?这个就实在是太多了,弹出Toast、启动Activity、启动Service、发送广播、操作数据库等等都需要用到Context。
Context作用域
Intent的使用
使用显示Intent
首先构建出了一个Intent ,传人FirstActivity.this 作为上下文,传入SecondActivity.class 作为目标活动,这样我们的“意图”就非常明显了,即在FirstActivity 这个活动的基础上打开SecondActivity 这个活动。然后通过startActivity()方法来执行这个Intent。
@Override
public void onClick(View v) {
Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
startActivity(intent);
}
使用隐式Intent
通过在标签下配置的内容,可以指定当前活动能够响应的action 和category ,打开AndroidManifest.xml ,添加如下代码:
<activity android:name=".Main2Activity">
<intent-filter>
<action android:name="com.sxf.activitytest.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
在标签中我们指明了当前活动可以响应com.sxf.activitytest.ACTION_START 这个action ,而标签则包含了一些附加信息,更精确地指明了当前的活动能够响应的Intent 中还可能带有的category 。只有和中的内容同时能够匹配上Intent 中指定的action 和category 时,这个活动才能响应该Intent 。
修改 FirstActivity 中按钮的点击事件,代码如下所示:
@Override
public void onClick(View v) {
Intent intent = new Intent("com.sxf.activitytest.ACTION_START");
startActivity(intent);
}
android.intent.category.DEFAULT 是-种默认的 category ,在调用 startActivity ()方法的时候会自动将这个 category 添加到 Intent 中。
每个 Intent 中只能指定一个 action ,但却能指定多个category:
intent.addCategory("com.sxf.activitytest.MY_CATEGORY1");
intent.addCategory("com.sxf.activitytest.MY_CATEGORY2");
更多隐式Intent
使用隐式Intent ,不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得Android 多个应用程序之间的功能共享成为了可能。
调用浏览器打开网页
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
}
Intent.ACTION VIEW ,这是一个Android 系统内置的动作,其常量值为android.intent.action.VIEW。然后通过Uri.parse ()方法,将一个网址字符串解析成一个Uri 对象,再调用Intent 的setData()方法将这个Uri 对象传递进去。
setData()接收一个Uri 对象,主要用于指定当前Intent 正在操作的数据,而这些数据通常都是以字符串的形式传入到Uri.parse ()方法中解析产生的。
还可以在标签中再配置一个标签,用于更精确地
指定当前活动能够响应什么类型的数据。标签中主要可以配置以下内容:
- android:scheme。用于指定数据的协议部分,如http、geo、tel
- android:host。用于指定数据的主机名部分,如www.baidu.com 部分
- android:port。 用于指定数据的端口部分,一般紧随在主机名之后
- android: path。用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容
- android:mimeType。用于指定可以处理的数据类型,允许使用通配符的方式进行指定
只有标签中指定的内容和Intent 中携带的Data 完全一致时,当前活动才能够响应该Intent。
拨打电话
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
}
其他常用Intent
1 ,web浏览器
Uri uri = Uri. parse ( "http://www.baidu.com" );
returnIt = new Intent (Intent . ACTION_VIEW , uri );
2,地图
Uri mapUri = Uri. parse ( "geo:38.899533,-77.036476" );
returnIt = new Intent (Intent . ACTION_VIEW , mapUri);
3,调拨打电话界面
Uri telUri = Uri. parse ( "tel:100861" );
returnIt = new Intent (Intent . ACTION_DIAL , telUri);
4,直接拨打电话
Uri callUri = Uri. parse ( "tel:100861" );
returnIt = new Intent (Intent . ACTION_CALL , callUri);
5,卸载
Uri uninstallUri = Uri. fromParts ( "package" , " xxx " , null );
returnIt = new Intent (Intent . ACTION_DELETE , uninstallUri);
6,安装
Uri installUri = Uri. fromParts ( "package" , " xxx " , null );
returnIt = new Intent (Intent . ACTION_PACKAGE_ADDED , installUri);
7,播放
Uri playUri = Uri. parse ( "file:///sdcard/download/everything.mp3" );
returnIt = new Intent (Intent . ACTION_VIEW , playUri);
8,调用发邮件
Uri emailUri = Uri. parse ( "mailto:aaa@qq.com" );
returnIt = new Intent (Intent . ACTION_SENDTO , emailUri);
9,发邮件
returnIt = new Intent (Intent . ACTION_SEND );
String[] tos = { “aaa@qq.com" };
String[] ccs = { “aaa@qq.com" };
returnIt .putExtra(Intent . EXTRA_EMAIL , tos);
returnIt .putExtra(Intent . EXTRA_CC , ccs);
returnIt .putExtra(Intent . EXTRA_TEXT , "body" );
returnIt .putExtra(Intent . EXTRA_SUBJECT , "subject" );
returnIt .setType( "message/rfc882" );
Intent . createChooser ( returnIt , "Choose Email Client" );
10,发短信
Uri smsUri = Uri. parse ( "tel:100861" );
returnIt = new Intent (Intent . ACTION_VIEW , smsUri);
returnIt.putExtra( "sms_body" , "shenrenkui" );
returnIt.setType( "vnd.android -dir/mms-sms" );
11,直接发邮件
Uri smsToUri = Uri. parse ( "smsto://100861" );
returnIt = new Intent (Intent . ACTION_SENDTO , smsToUri);
returnIt.putExtra( "sms_body" , “test" );
12,发彩信
Ur i mmsUri = Uri. parse ( "content://media/external/images/media/23" );
returnIt = new Intent (Intent . ACTION_SEND );
returnIt.putExtra( "sms_body" , “test" );
returnIt.putExtra(Intent . EXTRA_STREAM , mmsUri);
returnIt.setType( "image/png" );
向下一个活动传递数据
Intent 中提供了一系列putExtra()方法的重载,可以把我们想要传递的数据暂存在Intent 中,启动了另一个活动后,只需要把这些数据再从Intent中取出即可。
MainActivity.java:
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, Main2Activity.class);
intent.putExtra("extradata", "this is extra data");
startActivity(intent);
}
Main2Activity.java:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Intent intent = getIntent();
String data = intent.getStringExtra("extradata");
System.out.println(data);
}
通过getIntent()方法获取到用于启动SecondActivity 的Intent ,然后调用
getStringExtra()方法,传入相应的键值,就可以得到传递的数据。如果传递的是整型数据,则使用getintExtra方法;如果传递的是布尔型数据,则使用getBooleanExtra()方法,以此类推。
向上一个活动传递数据
Activity 中还有一个startActivityForResult()方法也是用于启动活动的,但这个方法期望在活动销毁的时候能够返回一个结果给上一个活动。方法接收两个参数,第一个参数还是Intent ,第二个参数是请求码(只要是一个唯一值就可以),用于在之后的回调中判断数据的来源。
MainActivity.java:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mainlayout);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, Main2Activity.class);
startActivityForResult(intent, 1);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String resdata = data.getStringExtra("data_return");
System.out.println(resdata);
}
break;
default:
break;
}
}
Main2Activity.java:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Button button = (Button) findViewById(R.id.button2);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("data_return", "this is return data test");
setResult(RESULT_OK, intent);
finish();
}
});
}
通过按下Back键向上传递
重写onBackPressed ()方法来解决这个问题,当用户按下Back 键,就会去执行onBackPressed ()方法中的代码:
Main2Activity.java:
@Override
protected void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return", "this is return data test");
setResult(RESULT_OK, intent);
finish();
}
活动的生存期
Activity 类中定义了7 个回调方法,覆盖了活动生命周期的每一个环节。以下7 个方法中除了onRestart()方法,其他都是两两相对的。
onCreate()。这每个活动中我们都重写了这个方法,它会在活动第一次被创建的时候调用。你应该在这个方法中完成活动的初始化操作,比如说加载布局、绑定事件等。
onStart()。这个方法在活动由不可见变为可见的时候调用,对资源进行加载。
onResume()。这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。
onPause()。这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗CPU 的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。
onStop()。这个方法在活动完全不可见的时候调用,对资源进行释放。它和onPause()方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么onPause()方法会得到执行,而onStop()方法并不会执行。
onDestroy()。这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。
onRestart()。这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。
活动被回收前的数据保存与恢复
**onSaveInstanceState()**回调方法可以保证在活动被回收之前一定会被调用,因此我们可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("key", "value");
}
使用的onCreate()方法其实也有一个Bundle 类型的参数。这个参数在一般情况下都是null,但是如果在活动被系统回收之前有通过onSaveinstanceState()方法来保存数据的话,这个参数就会带有之前所保存的全部数据。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null){
String value = savedInstanceState.getString("key");
}
}
创建自定义控件
1、新建xxx.xml文件,完成布局,这就是自定义的一个控件了;
2、在需要调用的yyy.xml文件中,使用以下语句引入进来:
<include layout="@layout/xxx" />
(3、在MainActivity.java中将系统自带的标题栏隐藏:)
ActionBar actionbar = getSupportActionBar();
if (actionbar != null) {
actionbar.hide();
}
4、新建一个类,用于注册事件响应:
public class Title extends LinearLayout {
public Title(Context context, AttributeSet attributeSet){
super(context, attributeSet);
LayoutInflater.from(context).inflate(R.layout.title, this);
Button buttonback = findViewById(R.id.buttonBack);
buttonback.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
((Activity)getContext()).finish();
}
});
}
}
首先重写了LinearLayout 中带有两个参数的构造函数,在布局中引人TitleLayout 控件就会调用这个构造函数。然后在构造函数中需要对标题栏布局进行动态加载,这就要借助LayoutInflater来实现了。通过Layoutinflater的from( )方法可以构建出一个LayoutInflater对象,然后调用inflate()方法就可以动态加载一个布 局文件,inflate()方法接收两个参数,第一个参数是要加载的布局文件的id,这里我们传入R.layoutitle, 第二个参数是给加载好的布局再添加一个父布局,这里我们想要指定为TitleLayout,于是直接传人this。
5、在需要调用的yyy.xml文件中,使用以下语句引入进来:
<com.sxf.myapplication.Title
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
报错整理
1、General error during semantic analysis: Unsupported class file major version 57
可以去File->Project Structuer中查看当前导入的项目中使用的SDK版本是否与之前项目匹配的相同,如果不相同修改为相同版本即可
2、NoClassDefFoundError: Failed resolution of: Landroidx/localbroadcastmanager/content/LocalBroadcastManager
在app目录下的build.gradle中添加:implementation ‘com.android.support:localbroadcastmanager:29.0.0’