关于AS的一些小问题,如果如下报错
Error:Execution failed for task ':app:preDebugAndroidTestBuild'.
> Conflict with dependency 'com.android.support:support-annotations' in project ':app'. Resolved versions for app (26.1.0) and test app (27.1.1) differ. See https://d.android.com/r/tools/test-apk-dependency-conflicts.html for details.
在build.gradleModule:app
文件中的dependencies{…}
中添加
androidTestCompile('com.android.support:support-annotations:26.1.0') {
force = true
}
即可,如果版本是其他,按照版本修改相应数值即可。
AS中是用gradle构建项目的,gradle是一个先进的项目构建工具。使用基于Groovy的领域特定语言(DSL)声明项目配置,摒弃了基于XML的繁琐配置。
项目中有两个build.gradle文件,一个是外层的,一个是内层app目录下的。
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.0.0'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
repositories中声明了google()和jcenter(),jcenter()是一个代码托管仓库,里面有很多优秀的安卓开源项目,声明后可以轻松引用仓库中的开源项目。dependencies中声明了一个gradle插件,其实gradle不是专门为Android项目开发的,Java和C++中也可以使用,因此需要使用专门的插件。
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.example.k.androidpractie"
minSdkVersion 15
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestCompile('com.android.support:support-annotations:26.1.0') {
force = true
}
}
versionCode表示项目版本号,versionName表示项目的版本名,在后面会说到。
Buildype包用于指定生成安装文件的相关配置,通常包括debug和release,debug是测试版安装文件,release则为正式版安装文件,debug可以忽略不写的。
minifyEnabled指定是否混淆,proguardFiles则用于指定混淆规则,后面有两个文件,一个是SDK目录下的,有所有项目的混淆规则,第二个文件是当前项目根目录下的,可以编写当前项目特有的混淆规则。
dependencies可以指定当前项目所有的依赖关系。通常一个AS项目一共有3种依赖方式:本地依赖、库依赖和远程依赖。本地依赖是对本地Jar包或目录添加依赖,库依赖是对库模块添加依赖,远程依赖则可以对jcenter等仓库中的项目添加依赖。
Android中的日志工具类是Log(android.util.Log),提供了五个相关方法:
测试一下,在MainAvtivity.java中的onCreate()方法中添加一行
Log.d("MainActivity","this is a dubug message");
就可以输入debug信息了,如下图所示,其中第一个参数是tag,一般写当前类名,便于过滤,第二个参数就是需要输出的信息。
为了方便使用日志工具,可以在每个类开头设置一个变量
private static final String TAG="...";
之后Log中的第一个参数直接TAG即可。
logcat可以使用过滤功能,AS默认提供了两个过滤器,也可以自定义。点击Edit Fliter Configuration
,写好tag就行,点击OK,可以看到就只显示我们需要的log了。
等等,日志大概常用的就是这些,剩下的自己捉摸吧。
在此手动创建一个活动。
新建一个项目,不要选择Empty Activity
,选择Add No Activity
。
进入后,右键app-java-com.example.k.androidpractice_1 -> New -> Activity -> Empty Activity
,同时取消Generate Layout File(自动创建布局文件)和Launcher Activity(设置为当前项目主活动)。
之后新建一个布局文件
右键app/res -> New -> Dictory
,新建一个名为layout的文件夹,然后右键layout文件夹,New -> Layout resource file
,xml名为first_layout
,默认为LinearLayout。
在xml中添加一个Button,修改代码如下
<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_1"
android:text="this is a button"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
LinearLayout>
切换到design
之后需要在Activity中加载布局,在FirstActivity中添加如下一行
setContentView(R.layout.first_layout);
设置布局,传入布局id即可。
在AndroidManifest.xml中注册Activity,其实AS已经帮我们注册了FirstActivity这个活动,代码如下
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.k.androidpractice_1">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".FirstActivity">activity>
application>
manifest>
其中android:name中应为com.example.k.androidpractice_1.FirstActivity
,因为外部有package,这里就简写了。
但是还是需要设置主活动,否则程序无法知道首先启动哪个Activity。
将Activity标签修改如下
<activity android:name=".FirstActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
intent-filter>
activity>
注意:如果没有指定一个Activity为主活动,程序还是可以运行的,只不过无法看到图标,一般是作为第三方服务提供的,比如支付宝的支付服务。
之后运行可以看到程序正常运行了。
Toast是一种很友好的提示方式,可以以短小的信息提示用户某些信息,在此对按钮进行监听以显示Toast。
修改MainActivity代码如下
Button Button=findViewById(R.id.Button_1);
Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FirstActivity.this,"this is a message",Toast.LENGTH_SHORT).show();
}
});
设置菜单大概需要三步:
在res文件夹下新建文件夹menu,右键menu文件夹,New -> Menuresourcefile,输入main点ok。在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>
这里创建了两个菜单选项Add和Remove指定其id和显示内容。
需要重写onCreateOptionsMenu()
方法,按ctrl+O
重写方法,找到我们需要的,点击OK。
修改如下
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main,menu);
return true;
}
其中getMenuInflater()方法获得MenuInflater对象,调用inflater()方法创建菜单。
之后需要定义菜单的响应事件,即监听菜单,需要重写onOptionsItemSelected()方法
public boolean onOptionsItemSelected(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;
}
如果想结束一个Activity,在代码中可以直接通过finish()方法实现,即按返回键的功能
在程序启动之后指挥进入到主活动,如何跳转到其他活动呢?需要使用intent。
创建一个Activity,叫SecondActivity,勾选generate不选launcher。
在activity_second.xml中添加Button控件
<Button
android:id="@+id/Button2"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="这是second activity"
/>
修改FirstActivity中对Button的监听,Intent一个重载是第一个参数为启动活动的上下文,第二个参数为需要启动的目标活动
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.first_layout);
Button Button=findViewById(R.id.Button_1);
Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
startActivity(intent);
}
});
}
即为不明确启动某个Activity,而是指定一系列action或者category等信息,然后由系统找到合适的Activity启动。
修改AndroidManifest.xml中的SecondActivity标签
<activity android:name=".SecondActivity">
<intent-filter>
<action android:name="con.example.k.androidpractice_1.ACTION_START"/>
<category android:name="android.intent.category.DEFAULT"/>
intent-filter>
activity>
其中action指明了该活动可以响应的action,category具体包含了一些附加信息。
修改FirstActivity中的按钮监听
Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent("con.example.k.androidpractice_1.ACTION_START");
startActivity(intent);
}
});
只有当action和category同时匹配的时候才能正确响应活动,由于此时的category是DEFAULT,因此不写的话会默认为DEFAULT。
每个Intent只可以指定一个action,但是可以指定多个category。
然后一个例子,启动浏览器,代码如下
Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://www.baidu.com"));
startActivity(intent);
}
});
如下所示,可以打开网页了,要注意网址一定要正确,比如必须要加http://,否则报错。其中INTENT.ACTION_VIEW是系统中的常量,值为android.intent.action.VIEW。
通过setData()传入数据,标签中可以添加标签,更加精确配置响应什么类型的数据,主要可以配置如下内容
只有标签中指定的内容和Intent中携带的数据一致的时候才能正确响应。
一个简单示例,以响应http协议。新建Third_Activity,添加一个Button3,修改AndroidManifest.xml
<activity android:name=".ThirdActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http"/>
intent-filter>
activity>
之后启动程序,点击按钮,可以看到已经识别到我们自定义的Activity了。
大致思想就是先将数据存储到Intent中,再目标活动中取出数据即可。主要用到了putExtra()和getStringExtra()
方法。
filename:FirstActivity.java
Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String data="this is a data string";
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("extra_data",data);
startActivity(intent);
}
});
filename:SecondActivity.java
setContentView(R.layout.activity_second);
Intent intent=getIntent();
String data=intent.getStringExtra("extra_data");
Toast.makeText(SecondActivity.this,data,Toast.LENGTH_SHORT);
大致步骤为三步:
需要使用到这么一个方法,startActivityForResult(),这个方法在销毁活动的时候会返回一个结果给上一级,有两个参数,第一个参数为Intent,第二个参数为一个请求码,保证其唯一即可。
在待销毁活动中创建一个Intent,不需要有参数,只是用来传递数据,通过putExtra把数据传入Intent中,调用setResult()返回Intent,有俩各个参数,第一个一般为RESULT_OK或者RESULT_CANCELED,之后调用finish()销毁。同时需要重写上一级活动中的onActivityResult()方法以得到数据,因为活动销毁后会调用上一级活动的这个方法。
修改代码
filename:FirstActivity.java
...
Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String data="this is a data string";
Intent intent=new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent,1);
}
});
...
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode){
case 1:
if (resultCode==RESULT_OK){
String string=data.getStringExtra("data_return");
Toast.makeText(FirstActivity.this, string, Toast.LENGTH_SHORT).show();
}
break;
}
}
filename:SecondActivity.java
...
Button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent();
intent.putExtra("data_return","hello,firstactivity");
setResult(RESULT_OK,intent);
finish();
}
});
可以看到成功获取到了数据。
由于可能会有多个Activity返回到同一个活动,因此需要在onActivityResult()中首先判断requestCode的值确定其来自于哪个活动(即为最开始的请求码),然后根据resultCode判断是否成功,之后从data中获取数据。
==============
如果是通过返回键返回的呢,重写待销毁活动的onBackPressed()方法即可。
安卓中是通过任务来管理活动的,一个任务是一组存放在栈里的活动的集合,即返回栈。每当启动一个新的活动的时候,活动入栈处于栈顶位置,按下返回键或者调用finish()方法销毁一个方法的时候,栈顶的活动会出栈。系统总是显示栈顶的活动给用户。
每个活动在生命周期最多有四个可能的活动状态
Activity类中定义了七个回调方法,覆盖了生命周期中的每一个环节
还可以分为三种生存期
当一个活动进入停止状态的时候,可能会被回收。如在活动A上启动活动B,此时A进入停止状态,但是由于内存不足,A被回收,从活动B返回之后,仍会正常显示活动A,只不过不会调用onRestart()方法,而是会调用onCreate()方法。比如正在文本框中输入了文字,然后启动了另一个活动,返回之后,打的字没了,就是说这个活动被重新创建了。
Activity类中提供一个onSaveInstanceState()回调方法,可以保证在活动被回收之前一定会被调用
可以在此时临时保存数据避免数据消失的问题。重写该方法,参数是一个Bundle,Bundle提供了一系列方法用于保存数据,如putString(),putInt()等,这些方法都有两个参数,第一个是键值,第二个是保存的内容值。
protected void onSaveInstanceState(Bundle outState){
super.onSaveInstanceState(outState);
String tempData="this is s temp data";
outState.putString("data_key",tempData);
}
如何取出数据呢?onCreate()的参数也为Bundle,在该方法中从Bundle中取出数据
protected void onCreate(Bundle savedInstancedState){
super.onCreate(saveInstanceState);
Log.d(TAG,"onCreate);
if (savedInstancedState != null){
String tempData=savedInstancedState.getString("data_key");
Log.d(TAG,tempData);
}
}
Intent和Bundle有些类似,当然Intent可以和Bundle结合使用,把数据先保存到Bundle中,在把Bundle传入Intent中。
在实际项目中需要根据需求合理设定不同的启动模式,一共有四种standard、singleTop、singleTask和singleInstance。
通过AndroidManifest.xml中中的android:launchMode属性进行设置。
在实际中可能并不确定哪个界面对应的是哪个Activity。这时候需要进行一点修改就好了。
新建一个名为BaseActivity的普通java class,不需要注册为Activity,编写代码如下
public class BaseActivity extends AppCompatActivity{
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
}
}
之后将项目中Activity类中继承的AppCompatActivity改为继承BaseActivity,此时项目中的Activity类依然继承AppCompatActivity,因为BaseActivity继承于AppCompatActivity。
之后再运行程序,每当启动一个活动日志就会打印出该活动对应的类名。
当启动多个程序的时候需要直接退出程序,直接按返回键的话可能需要多次,要想一次退出的话可以使用一个集合作为Activity管理器来保存当前所有活动进行处理。
public class ActivityCollector{
public static List<Activity> acticities=new ArrayList<>();
public static void addActivity(Activity activity){
acticities.add(activity);
}
public static void removeActivity(Activity activity){
activities.remove(activity);
}
public static void finishAll(){
for (Activity activity:activities){
if (!activity.isFinishing()){
activity.finish();
}
}
activities.clear();
}
}
之后需要修改一下上面的BaseActivity代码
public class BaseActivity extends AppCompatActivity{
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Log.d("BaseActivity",getClass().getSimpleName());
ActivityCollector.addActivity(this);
}
protected void onDestroy(){
super.onDestroy();
ActivityCollector.removeActivity(this);
}
}
之后如果需要一次退出程序的话只需要调用ActivityCollector.finishAll()方法即可。
在启动另一个活动的时候,需要传递参数的话,如果不是我们写的活动,不太容易知道都需要什么参数之类的,因此修改一下代码,结构就会很清晰明了。
filename:SecondActivity.java
public class SecondActivity extends BaseAvtivity{
public static void actionStart(Context context,String Data1,String Data2){
Intent intent=new Intent(context,SecondActivity.class);
intent.putExtra("param1",Data1);
intent.putExtra("param2",Data2);
context.startActivity(intent);
}
...
}
然后
filename:FirstActivity.java
public void onClick(View v){
SecondActivity.actionActivity(FirstActivity.this,"123","456");
}
这样就很清楚了。
安卓只写过几个小程序,虽然现在只把活动看完了,但是感觉真的认识了不少东西和方法,比如生命周期生存期,之前只是知道现象但是具体不知道是什么,现在有了一个系统的总结。几句这样,明天继续。