android合集

android多线程

runOnUiThread

这个可以直接在子线程中调用,并且修改ui

1
2
3
4
5
6
7
8
public void change(final String a){
runOnUiThread(new Runnable() {
@Override
public void run() {
tv.setText(a);
}
});
}

多线程调用

1
2
3
4
5
6
7
8
9
10
11
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
change("aaaaaaaaaaaa");
}
}).start();

实现了五秒后更新

Handler

现在主线程中创建一个handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
final Handler handler = new Handler(){
public void handleMessage(Message message){
tv.setText(""+message.what);
}
};
然后在子线程中调用handler.sendEmptyMessage(1);就能调用上面的handlemessage方法,传入的1是传入message的what属性,handler.sendMessage(message)方法发送这个message到主线程中的那个回调

new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(1);
}
}).start();

android retrofit2的基本使用,搭配rxjava

依赖

1
2
3
4
5
6
7
8
9
10
implementation 'com.google.code.gson:gson:2.6.1'
implementation 'com.orhanobut:logger:2.1.0'
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.3'
implementation 'com.squareup.retrofit2:adapter-rxjava:2.4.0'
implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'

最简单的使用:

先创建接口:

1
2
3
4
5
6
7
8
public interface WeatherService {

@GET("s6/weather/now")
Observable<WeatherEntity> getWeather(@Query("key") String key, @Query("location") String location);

@GET("/")
Call<ResponseBody> baidu();
}

两个get

使用异步获取返回的原生字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
OkHttpClient client = new OkHttpClient.Builder().
connectTimeout(1, TimeUnit.SECONDS).
readTimeout(60, TimeUnit.SECONDS).
writeTimeout(60, TimeUnit.SECONDS).build();
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://badu.com")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();

Call<ResponseBody> stringvalue = retrofit.create(WeatherService.class).baidu();
stringvalue.enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, retrofit2.Response<ResponseBody> response) {
//访问成功时调用,调用方法是response.body.string()
try {
Log.d(TAG, "onResponse: "+response.body().string());
} catch (IOException e) {
e.printStackTrace();
}
}

@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
//如果访问失败的调用
Log.d(TAG, "onFailure: "+"失败了");
}
});

使用rxjava

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//创建一个连接对象,设置连接超时时间,读超时时间,写超时
OkHttpClient.Builder client = new OkHttpClient.Builder().
connectTimeout(1, TimeUnit.SECONDS).
readTimeout(60, TimeUnit.SECONDS).
writeTimeout(60, TimeUnit.SECONDS);
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://free-api.heweather.com")
.client(client.build())
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
WeatherService weatherService = retrofit.create(WeatherService.class);
weatherService.getWeather("xxxxx", "郑州")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<WeatherEntity>() {
@Override
public void onSubscribe(Disposable d) {

}

@Override
public void onNext(WeatherEntity weatherEntity) {
//响应解析的序列一个个进来
weatherEntities.add(weatherEntity);
}

@Override
public void onError(Throwable e) {
//出错了时候
mView.dataError(e);
}

@Override
public void onComplete() {
//响应解析的所有东西都完成了调用
mView.setData(weatherEntities);
}
});

打印log

1
2
3
4
5
6
7
8
HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor();
if(BuildConfig.DEBUG){
//显示日志
logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
}else {
logInterceptor.setLevel(HttpLoggingInterceptor.Level.NONE);
}
client.addInterceptor(logInterceptor);

让一个okhttp.builder对象client添加以下这个log解析器就ok

自定义结果解析器

自己解析返回的结果GsonConverFactory.create,点进去这个方法,主要看responseBodyConverter方法,这个是处理返回请求的,他是调用了GsonResponseBodyConverter来处理请求,点进去这个类,public T convert(ResponseBody value)这个方法就是返回要构造的对象,参数value就是返回的response的body值,可以通过value.string();得到具体的字符串,在这个方法里解析可以通过throw的方式调用观察者的onError方法

所以重写结果解析器需要复制出来GsonConverFactory这个类,并且复制GsonResponseBodyConverter和GsonRequestBodyConverter这两个类,并且重新写一下GsonResponseBodyConverter这个类的convert方法对结果进行解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public T convert(ResponseBody value) throws IOException {
String response = value.string();
Log.d("tag", "convert: " + response);
//抛出异常,通过观察者的onError回掉接收结果
if (1 == 1)
throw new JsonIOException("抛出异常");
JsonReader jsonReader = gson.newJsonReader(value.charStream());
try {
T result = adapter.read(jsonReader);
if (jsonReader.peek() != JsonToken.END_DOCUMENT) {
throw new JsonIOException("JSON document was not fully consumed.");
}
return result;
} finally {
value.close();
}
}

rxjava和rxAndroid

rxJava

RxJava 有四个基本概念:Observable (可观察者,即被观察者)、 Observer (观察者)、 subscribe (订阅)、事件。Observable 和 Observer 通过 subscribe() 方法实现订阅关系,从而 Observable 可以在需要的时候发出事件来通知 Observer。

基本创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Observable<String> oble = Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(@NonNull ObservableEmitter<String> e) throws Exception {
e.onNext("hello");
e.onComplete();
e.onNext("hello2");

}
});

Observer<String> oser = new Observer<String>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
Log.w("","onSubscribe");
} @Override
public void onNext(@NonNull String s) {
Log.w("","onNext = "+s);
} @Override
public void onError(@NonNull Throwable e) {
Log.w("","onError" + e);
} @Override
public void onComplete() {
Log.w("","onComplete");
}
};

Log.w("","subscribe");
oble.subscribe(oser);

subscribe
onSubscribe
onNext = hello
onComplete

其中oble是一个被观察者,oser是一个观察者,被观察者可以调用onNext向观察者发送内容,此时观察者就能通过重写的onNext获取到数据,执行相应的操作

另外观察者还有oncomplete和onerror,如果执行了onComplete方法,那么就会断开联系,所以hello2没有显示出来,如果发生了错误会调用了onerror也会立马断开联系。

另一些使用方法

简写被观察者

上面的例子是create一个最基本的被观察者,当如果被观察者只有一个动作的时候就不需要那么复杂的操作,可以用一个just

1
Observable<String> observable = Observable.just("hello");

这样就是只执行一个onNext(’hello’);

简写观察者

当然对于观察者也是一样,如果不用考虑oncomplete和onerror也可以简写,创建一个consumer对象,重写accept方法就行,然后通过被观察者.subscribe(观察者)来建立联系

1
2
3
4
5
6
7
8
Observable<String> observable = Observable.just("hello");
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
System.out.println(s);
}
};
observable.subscribe(consumer);

创建完成或者错误的另一些方法

可以创建一个action对象来处理oncomplete的事件,用一个consumer来处理onnext和onerror事件,最后重载subscribe的一些方法达到建立关系的目的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Observable<String> observable = Observable.just("hello");
Action onCompleteAction = new Action() {
@Override
public void run() throws Exception {
Log.i("kaelpu", "complete");
}
};
Consumer<String> onNextConsumer = new Consumer<String>() {
@Override
public void accept(String s) throws Exception {
Log.i("kaelpu", s);
}
};
Consumer<Throwable> onErrorConsumer = new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
Log.i("kaelpu", "error");
}
};
observable.subscribe(onNextConsumer, onErrorConsumer, onCompleteAction);

public final Disposable subscribe() {}
public final Disposable subscribe(Consumer<? super T> onNext) {}
public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError) {}
public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError, Action onComplete) {}
public final Disposable subscribe(Consumer<? super T> onNext, Consumer<? super Throwable> onError, Action onComplete, Consumer<? super Disposable> onSubscribe) {}
public final void subscribe(Observer<? super T> observer) {}

上面是subscribe一些重载方法

线程调度

rxJava最大的好处就是能够在多线程的情况下去实现,主要能应用在Android更新UI上。。

在建立关系subscribe的时候会有一些方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Observable<Integer> observable = Observable.create(new ObservableOnSubscribe<Integer>() {
@Override
public void subscribe(ObservableEmitter<Integer> emitter) throws Exception {
Log.d("kaelpu", "Observable thread is : " + Thread.currentThread().getName());
Log.d("kaelpu", "emitter 1");
emitter.onNext(1);
}
});

Consumer<Integer> consumer = new Consumer<Integer>() { @Override
public void accept(Integer integer) throws Exception {
Log.d("kaelpu", "Observer thread is :" + Thread.currentThread().getName());
Log.d("kaelpu", "onNext: " + integer);
}
};

observable.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(consumer);

最后那个建立关系的意思是让被监听者在Schedulers.newThread()这个新线程上,然后让观察者在AndroidSchedulers.mainThread()这个主线程上,就实现了主线程更新UI的操作,主要就是subscribeOn是让被观察者运行的线程,observeOn是观察者运行的线程

操作符的使用和Android的一些扩展可以看原文

作者:蒲文辉
链接:https://www.jianshu.com/p/7eb5ccf5ab1e
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

android中webview的交互

webview中js调用Java代码

大概思路是写一个Java类,然后通过webview的addJavascriptInterface方法把那个类传到页面中,然后页面就能直接通过指定的名字调用方法

布局就是上面一个webview下面一个textView

  • mainactivity.Java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class MainActivity extends Activity implements JsBridje{

    private WebView mWebView;
    private TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mWebView = (WebView)findViewById(R.id.wv_webview);
    mTextView = (TextView)findViewById(R.id.tv_result);

    //允许webview加载js
    mWebView.getSettings().setJavaScriptEnabled(true);
    //2.创建一个js接口类,jsinterface
    //3.创建一个就是接口类传递到webview中,第一个参数是Java接口类对象,第二个是传入到js中的名称
    mWebView.addJavascriptInterface(new JsInterface(this),"javaslei");

    //加载要显示的html
    mWebView.loadUrl("file:///android_asset/index.html");
    }

    //重写一个jsbridje接口,让jsinterface类能够调用,改变UI
    @Override
    public void setvalue(String value) {
    mTextView.setText(value);
    }
    }

可以看到调用的addjavascriptinterface方法第一个参数是传入了一个对象,第二个参数是一个字符串,在js中直接调用字符串.方法即可

  • JsInterface.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class JsInterface {
    private static final String TAG = "JsInterface";
    private JsBridje jsBridje;

    public JsInterface(JsBridje jsBridje) {
    this.jsBridje = jsBridje;
    }

    /**
    * 从js中调用的方法,这个方法不在主线程中执行,所以不能在这里改变UI
    * @param value
    */
    @JavascriptInterface
    public void setvalue(String value){
    jsBridje.setvalue(value);
    }
    }

js调用的方法一定要加上@JavascriptInterface

因为要改变页面内容,所以引入了一个什么设计模式实现一jsbridje接口

  • JsBridje.java
    1
    2
    3
    public interface JsBridje {
    void setvalue(String value);
    }

html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webview</title>
<script type="text/javascript">
function oclick(){
var inputEle = document.getElementById('uservalue');
if(window.javaslei){
javaslei.setvalue(inputEle.value);
}else{
alert('没找到Java对象');
}
}
</script>
</head>
<body>
<h2>webview</h2>
<div>
<span>请输入要传递的值</span>
<input type="text" id="uservalue" />
<p onclick="oclick()">提交</p>
</div>
</body>
</html>

可以看到,在html中直接调用javaslei(mainactivity传入的名字).setvalue方法就能调用了jsinterface类中的方法,然后这个方法通过调用jsbridje的接口方法去调用mainactivity.java的setvalue方法改变textview的值

java中调用js代码

其实就一行:mWebView.loadUrl(“javascript:要执行的命令”);

布局文件是webview和一个edittext和一个button

  • mainactivity.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public class MainActivity extends Activity{

    private WebView mWebView;
    private EditText meditview;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mWebView = (WebView)findViewById(R.id.wv_webview);
    meditview = (EditText) findViewById(R.id.tv_value);

    //允许webview加载js
    mWebView.getSettings().setJavaScriptEnabled(true);

    //加载要显示的html
    mWebView.loadUrl("file:///android_asset/index.html");
    }

    public void onclick(View view) {
    String value = meditview.getText().toString();
    mWebView.loadUrl("javascript:if(window.remote){window.remote('"+value+"')}");
    }
    }

可以看到直接调用了js中的remote方法传入了一个字符串参数,注意那个字符串参数前后要加上单引号

html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webview</title>
<script type="text/javascript">
function remote(str){
document.write(str);
}
</script>
</head>
<body>
</body>
</html>

html中就一个remote方法,就是把获取到的值写到界面中

使用chrome调试APP中的网页

先启用调试mWebView.setWebContentsDebuggingEnabled(true);

然后chrome访问chrome://inspect/

然后就能看到相应的inspect,点进去就能调试,不过肯定打不开,因为需要翻墙.或者下载离线包

解决了上面的内容就能够像调试网页的方式一样去调试APP中的html代码

一些常见的错误

当js调用java 代码出现了throw,此时APP并不会崩溃,但是会在html的控制台中抛出一个错误

如果js没有判断是否有相应的方法就去调用会出现找不到方法的错误

参数类型错误,常见的有数组和对象里面的问题,因为js是弱类型语言,而Java是强类型,,所以如果类型有错误会导致程序出错

字符串为空值的时候会传入一个字符串类型的undefined,解决办法就是当要传入的对象为空值的时候,传一个””就行

android调用相机和相册

android调用相机拍照

实现点击按钮开始调用相机拍照,并且返回拍照的照片

  • mainactivity.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    public class MainActivity extends Activity {
    private ImageView imageView;
    private Uri imageuri;
    private static final int TAKE_PHOTO=1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    imageView = (ImageView)findViewById(R.id.picture);
    }

    public void one(View view) {
    File outputImage = new File(getExternalCacheDir(),"output_image.jpg");
    if(outputImage.exists()){
    outputImage.delete();
    }
    try {
    outputImage.createNewFile();
    } catch (IOException e) {
    e.printStackTrace();
    }
    if(Build.VERSION.SDK_INT>=24){
    imageuri = FileProvider.getUriForFile(MainActivity.this,"com.example.cameraalbumtest.fileprovider",outputImage);
    }else{
    imageuri = Uri.fromFile(outputImage);
    }
    Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
    intent.putExtra(MediaStore.EXTRA_OUTPUT,imageuri);
    startActivityForResult(intent,TAKE_PHOTO);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch(requestCode){
    case TAKE_PHOTO:
    if(resultCode==RESULT_OK){
    try{
    Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageuri));
    imageView.setImageBitmap(bitmap);
    }catch(FileNotFoundException e){
    e.printStackTrace();
    }
    }
    }
    }
    }

布局文件activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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=".MainActivity">
<Button
android:text="one"
android:onClick="one"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

在sdk24以后还需要需要一个内容提供器,现在清单文件下注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.xwmdream.myapplication">

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<application
.......
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.exaample.cameraalibumtest.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>

</application>

</manifest>

  • res/xml/file_paths.xml
    1
    2
    3
    4
    <?xml version="1.0" encoding="utf-8"?>
    <pahts xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path=""/>
    </pahts>

android调用相册

  • MainActivity.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    public class MainActivity extends Activity {
    private ImageView imageView;
    private Uri imageuri;
    private static final int CHOOSE_PHOTO = 2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    imageView = (ImageView) findViewById(R.id.picture);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
    case CHOOSE_PHOTO:
    if(resultCode==RESULT_OK){
    if(Build.VERSION.SDK_INT>=19){
    handleImageOnKitKat(data);
    }else{
    handleImageBeforeKitKat(data);
    }
    }
    }
    }

    private void handleImageBeforeKitKat(Intent data) {
    Uri uri = data.getData();
    String imagePath = getImagePath(uri,null);
    displayImage(imagePath);
    }

    private void handleImageOnKitKat(Intent data) {
    String imagePath = null;
    Uri uri = data.getData();
    if(DocumentsContract.isDocumentUri(this,uri)){
    String docId = DocumentsContract.getDocumentId(uri);
    if("com.android.providers.media.documents".equals(uri.getAuthority())){
    String id = docId.split(":")[1];
    String selection = MediaStore.Images.Media._ID+"="+id;
    imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection);
    }else if("com.android.providers.downloads.documents".equals(uri.getAuthority())){
    Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));
    imagePath = getImagePath(contentUri,null);
    }
    }else if("content".equalsIgnoreCase(uri.getScheme())){
    imagePath = getImagePath(uri,null);
    }else if("file".equalsIgnoreCase(uri.getScheme())){
    imagePath = uri.getPath();
    }
    displayImage(imagePath);
    }

    private void displayImage(String imagePath) {
    if(imagePath!=null){
    Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
    imageView.setImageBitmap(bitmap);
    }else{
    Toast.makeText(this,"打开图片错误",Toast.LENGTH_SHORT).show();
    }
    }

    private String getImagePath(Uri uri, String selection) {
    String path = null;
    Cursor cursor = getContentResolver().query(uri,null,selection,null,null);
    if(cursor != null){
    if(cursor.moveToFirst()){
    path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
    }
    cursor.close();
    }
    return path;
    }

    public void two(View view) {
    if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
    ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
    } else {
    openAlbum();
    }
    }

    private void openAlbum() {
    Intent intent = new Intent("android.intent.action.GET_CONTENT");
    intent.setType("image/*");
    startActivityForResult(intent, CHOOSE_PHOTO);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    switch (requestCode) {
    case 1:
    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
    openAlbum();
    } else {
    Toast.makeText(this, "没有权限", Toast.LENGTH_SHORT).show();
    }
    break;
    default:
    break;
    }
    }

    }

布局文件xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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=".MainActivity">
<Button
android:onClick="two"
android:text="two"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/picture"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

其中涉及到针对sdk大于19的各个方法验证

还有动态申请权限等问题

当然如果项目中用到的话还需要对于图片文件压缩,否则会内存泄漏

android通知的使用

普通通知

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//创建通知管理
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//构建一个通知
NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"通知分组");
//点击通知相应的intent
Intent mIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://xwmdream.cn"));
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, mIntent, 0);
builder.setContentIntent(pendingIntent);
//在状态栏中的小图标,只显示alpha
builder.setSmallIcon(R.drawable.ic_launcher_background);
//通知标题
builder.setContentTitle("标题");
//点击了此标题是否自动划掉此通知
builder.setAutoCancel(true);
//通知里是否显示时间
builder.setShowWhen(true);
//什么时候发送通知,系统毫秒数
builder.setWhen(System.currentTimeMillis());
//状态栏划过的摘要
builder.setTicker("状态栏显示的摘要");
//是否进行中,如果是进行中,则没法清除
builder.setOngoing(false);

//添加一些默认值
/*
NotificationCompat.DEFAULT_SOUND 添加默认声音提醒
NotificationCompat.DEFAULT_VIBRATE 添加默认震动提醒
NotificationCompat.DEFAULT_LIGHTS 添加默认呼吸灯提醒
NotificationCompat.DEFAULT_ALL 同时添加以上三种默认提醒
* */
builder.setDefaults(NotificationCompat.DEFAULT_ALL);
//设置震动停止1000,震100,停400,震500,如果设置了默认值,这个就不管用了
builder.setVibrate(new long[]{1000, 100, 400, 500});

/*优先级 描述
NotificationCompat.PRIORITY_MAX 重要而紧急的通知,通知用户这个事件是时间上紧迫的或者需要立即处理的。
NotificationCompat.PRIORITY_HIGH 高优先级用于重要的通信内容,例如短消息或者聊天,这些都是对用户来说比较有兴趣的
NotificationCompat.PRIORITY_DEFAULT 默认优先级用于没有特殊优先级分类的通知
NotificationCompat.PRIORITY_LOW 低优先级可以通知用户但又不是很紧急的事件。只显示状态栏图标
NotificationCompat.PRIORITY_MIN 用于后台消息 (例如天气或者位置信息)。只有用户下拉通知抽屉才能看到内容,不会通知和响铃
*/
builder.setPriority(NotificationCompat.PRIORITY_HIGH);

/*锁屏时显示,好像没用
setVisibility() 方法共有三个选值:
1.VISIBILITY_PRIVATE : 显示基本信息,如通知的图标,但隐藏通知的全部内容;
2.VISIBILITY_PUBLIC : 显示通知的全部内容;
3.VISIBILITY_SECRET : 不显示任何内容,包括图标。*/
builder.setVisibility(NotificationCompat.VISIBILITY_SECRET);


//下面是显示进度条
int max = 100; // 进度最大值
int progress = 50; // 当前进度
boolean indeterminate = false; // 是否是不明确的进度条,为true就是模糊的进度条,就那种花纹一直动的进度条
builder.setProgress(max, progress, indeterminate);


//消息中的内容
builder.setContentText("小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容小内容");

//设置长文本,默认的是一行,多余的是省略号,这个是有多少就显示多少,如果启用这个,就不会显示小内容
builder.setStyle(new NotificationCompat.BigTextStyle().bigText("长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容长内容"));
//显示大图,如果设置这个,会显示一行小内容,然后显示大图片
Bitmap aBigBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher);
builder.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(aBigBitmap));

//构建一个通知实体
Notification notification = builder.build();
//通过消息管理器发送,第一个参数是消息id参数,要不一样,如果一样就视为同一个通知不会重复发送
notificationManager.notify(num++, notification);
//管理器取消的通知,其中的id是上面发送里面的id
//notificationManager.cancel(0);
//清除所有通知
//notificationManager.cancelAll();

自定义通知

先创建布局文件

  • message.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center">
    <ImageView
    android:id="@+id/iv"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@mipmap/ic_launcher"/>
    <TextView
    android:id="@+id/tv"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:gravity="center"
    android:text="仗剑走天涯"/>
    <Button
    android:id="@+id/btn1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="播放"/>
    <Button
    android:id="@+id/btn2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="下一首"/>
    </LinearLayout>

Java代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"通知分组");
builder.setSmallIcon(R.mipmap.ic_launcher);

//加载布局
RemoteViews rv = new RemoteViews(getPackageName(), R.layout.message);

rv.setTextViewText(R.id.tv, "泡沫");//修改自定义View中的歌名

//通过pendingIntent发送广播的方式来设置监听事件
Intent button1I = new Intent("btn1");
PendingIntent button1PI = PendingIntent.getBroadcast(this, 0, button1I, 0);
rv.setOnClickPendingIntent(R.id.btn1,button1PI);

//修改自定义View中的图片(两种方法)
// rv.setImageViewResource(R.id.iv,R.mipmap.ic_launcher);
rv.setImageViewBitmap(R.id.iv, BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));
builder.setContent(rv);
Notification notification = builder.build();
notificationManager.notify(num++, notification);

android内容提供者以及观察者

内容提供者

内容提供器作为Android四大组建之一.感觉没啥太大用途.

感觉就是一个应用程序创建一个可以被别的程序访问的数据库

访问其他程序中的数据

如果想要访问别的内容提供器中的共享数据,就要借助ContentResolver类,可以通过Context中的getContentResolver方法得到,它提供了一些列对数据的crud操作,操作和sqlitedatabase很相似,但是ContentResolver没有库和表,而是用一个uri代替,如’content://com.xxx.xxxx.xxx/aaa’表示访问包名是com.xxx.xxxx.xxx程序的aaa表,然后进行curd操作

查询:
1
2
3
4
5
6
getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder);
//uri:看上面
//projection 指定的列名,相当于select的列名
//selection 查询条件,相当于where语句
//selectionArgs 查询条件中?的参数
//sortOrder 排序条件

返回的是一个cursor结果集,和sqlite用法一样

  • 查询手机上所有的联系人
    1
    2
    3
    4
    5
    6
    7
    cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
    if(cursor!=null){
    while(cursor.moveToNext()){
    Log.d(TAG, "onCreate: "+cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)));
    Log.d(TAG, "onCreate: "+cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)));
    }
    }

其中的参数都是系统预设的

  • 查询手机上电话为10086的联系人的名字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Cursor cursor = null;
    try{
    cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,new String[]{ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME},ContactsContract.CommonDataKinds.Phone.NUMBER+" = ?",new String[]{"10086"},null);
    if(cursor!=null){
    //找到了这个联系人
    while(cursor.moveToNext()){
    Log.d(TAG, "onCreate: "+cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)));
    }
    }else{
    //没找到这个联系人
    }
    }catch (Exception e){
    e.printStackTrace();
    }
  • 插入联系人(参考)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    addContact("zhangphil", "12345678901","asdf@aa.com");
    // 一个添加联系人信息的例子
    public void addContact(String name, String phoneNumber,String email) {
    // 创建一个空的ContentValues
    ContentValues values = new ContentValues();

    // 向RawContacts.CONTENT_URI空值插入,
    // 先获取Android系统返回的rawContactId
    // 后面要基于此id插入值
    Uri rawContactUri = getContentResolver().insert(RawContacts.CONTENT_URI, values);
    long rawContactId = ContentUris.parseId(rawContactUri);
    values.clear();

    values.put(Data.RAW_CONTACT_ID, rawContactId);
    // 内容类型
    values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
    // 联系人名字
    values.put(StructuredName.GIVEN_NAME, name);
    // 向联系人URI添加联系人名字
    getContentResolver().insert(Data.CONTENT_URI, values);
    values.clear();

    values.put(Data.RAW_CONTACT_ID, rawContactId);
    values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
    // 联系人的电话号码
    values.put(Phone.NUMBER, phoneNumber);
    // 电话类型
    values.put(Phone.TYPE, Phone.TYPE_MOBILE);
    // 向联系人电话号码URI添加电话号码
    getContentResolver().insert(Data.CONTENT_URI, values);
    values.clear();

    values.put(Data.RAW_CONTACT_ID, rawContactId);
    values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
    // 联系人的Email地址
    values.put(Email.DATA, email);
    // 电子邮件的类型
    values.put(Email.TYPE, Email.TYPE_HOME);
    // 向联系人Email URI添加Email数据
    getContentResolver().insert(Data.CONTENT_URI, values);
    }

内容观察者

内容观察者就是要观察一个uri,当这个uri下内容发生改变的时候执行的操作,类似于数据库的触发器
内容观察者要继承ContentObserver类,有一个handler参数用于多线程更新UI
主要方法是onChange(boolean self) 这个方法是当观察的uri发生改变的时候调用的方法

注册内容观察者

1
getContentResolver().registerContentObserver(uri,notifyForDescendents,ContentResolver);

uri是要监听的地址uri

  • notifyForDescendents表示是否模糊匹配,例如一个uri为aa.bb.cc/dd,当notifyForDescendents为true的时候即模糊匹配,此时aa.bb.cc/ff发生变化时也会触发此观察者

为false时只有aa.bb.cc/dd发生变化时触发

ContentResolver是内容观察者对象

注销内容观察者

1
getContentResolver().unregisterContentObserver(ContentResolver);

创建内容观察者

1
2
3
4
5
6
7
8
9
10
11
public class MyContentResolver extends ContentObserver {
public MyContentResolver(Handler handler) {
super(handler);
}

@Override
public void onChange(boolean selfChange) {
super.onChange(selfChange);
Log.d("onChange: ","发生了变化");
}
}

android的fragment(碎片)

OCTOBER 1, 2018
个人感觉碎片就是把activity分成一片一片的.和activity一样有生命周期

静态创建碎片
效果图:
图片丢失

整个界面分为上下两部分,是两个fragment

上面的fragment(title_fragment)

先创建布局

  • fragment_title.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="horizontal"
    android:background="@color/colorAccent"
    android:layout_height="wrap_content">
    <TextView
    android:id="@+id/tv_back"
    android:text="返回"
    android:layout_centerVertical="true"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
    <TextView
    android:id="@+id/tv_title"
    android:text="我是标题"
    android:layout_centerInParent="true"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

    </RelativeLayout>

创建titlefragment类

继承v4的Fragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import android.support.v4.app.Fragment;

public class TitleFragment extends Fragment {
/**
* 表示fragment第一次创建绘制用户界面时系统回调的方法
* @param inflater 表示布局填充器或者加载器,将xml文件转化成view对象
* @param container 表示当前fragment出入activity的布局视图对象
* @param savedInstanceState 表示存储上一个fragment的信息
* @return view表示当前加载的fragment视图,如果fragment不 提供视图可以返回null
*/
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_title,container,false);
TextView tv1 = view.findViewById(R.id.tv_back);
tv1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getActivity(),"返回",Toast.LENGTH_SHORT).show();
}
});
TextView tv2 = view.findViewById(R.id.tv_title);
tv2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getActivity(),"标题",Toast.LENGTH_SHORT).show();
}
});
return view;
}
}

重写了oncreateView方法,是第一次绘制用户界面调用的方法,里面使用布局填充器加载了xml布局,得到一个View对象,然后通过view.findviewbyid方法获取到布局上的空间,然后注册点击事件

下面的ContentFragment

xml布局
  • fragment_content.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
    android:id="@+id/textView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:text="我是内容哈哈哈哈" />
    </LinearLayout>

布局很简单,一个tv,一句话

同样创建他的Fragment类
  • ContentFragment.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import android.support.v4.app.Fragment;

    public class ContentFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_content,container,false);
    return view;
    }
    }

activity加载fragment

  • activity_main.xml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?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=".MainActivity">
    <fragment
    android:id="@+id/fragment_title"
    android:name="cn.xwmdream.fragment.TitleFragment"
    android:layout_width="match_parent"
    android:layout_weight="1"
    android:layout_height="0dp"/>

    <fragment
    android:id="@+id/fragment_content"
    android:name="cn.xwmdream.fragment.ContentFragment"
    android:layout_width="match_parent"
    android:layout_height="0dp"
    android:layout_weight="9" />
    </LinearLayout>

线性布局,上下两个fragment,name是要加载fragment的路径,注意,一定要给每一个fragment创建id,否则报错

MainActivity.java还是常规操作,不解释

动态引入fragment

在activity中

  1. 创建fragment的管理器对象
  2. 获取fragment的事务对象并开启(事务具有一致性)
  3. 调用事务的动态方法,动态添加移除替换fragment
  4. 提交事务

修改activity_main.xml

把原来的fragment换成linearlayout容器,这些容器一会动态添加fragment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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=".MainActivity">
<LinearLayout
android:orientation="horizontal"
android:id="@+id/ll_title"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp"></LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:id="@+id/ll_content"
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="0dp"></LinearLayout>
</LinearLayout>

  • MainActivity.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //1.创建fragment管理器对象
    FragmentManager manager = getSupportFragmentManager();

    //2.获取fragment的事务对象并且开启事务
    FragmentTransaction transaction = manager.beginTransaction();

    //3.调用事务操作fragment,
    //add方法,第一个参数是把碎片添加到哪个布局,第二个是碎片对象
    TitleFragment titleFragment = new TitleFragment();
    transaction.add(R.id.ll_title,titleFragment);
    transaction.add(R.id.ll_content,new ContentFragment());

    //remove(Fragment)删除一个fragment对象,
    //transaction.remove(titleFragment);

    //replace(布局id,新的fragment对象) 把某个布局中的fragment替换成新的
    //transaction.replace(R.id.ll_content,new TitleFragment());

    //4.提交事务
    transaction.commit();

注意动态引用的viewgroup里面不能有子元素

引用过add后如果不引用remove是不能再次add

replace相当于remove+add

生命周期

生命周期主要函数

onattach当碎片和活动建立起关联的时候调用

onCreateView()第一次为碎片创建视图(加载布局)的时候调用,控件关联写在这里

onActivityCreated()确保碎片与相关活动一定创建完毕的时候调用

onDestoryView当碎片与关联的试图移除的时候调用

onDetach当碎片和活动解除关联的时候调用

静态生命周期

(f代表fragment,a代表activity)

启动:f.onAttach->f.onCreate->f.onCreateView->a.onCreate->f.onActivityCreated->f.onStart->a.onStart->a.onResume->f.onResume

暂停:f.onPause->a.onPause->f.onStop->a.onStop

重新开始:a.onRestart->f.onStart->a.onStart->a.onResume->f.onResume

销毁:(暂停)->f.onDestroyView->f.onDestroy->f.onDetach->a.onDestroy

动态添加生命周期

在oncreate函数中添加:
a.onCreate>f.onAttach->f.onCreate->f.onCreateView->f.onActivityCreated->f.onStart->a.onStart->a.onResume->f.onResume

在事件中添加:

f.onAttach->f.onCreate->f.onCreateView->f.onActivityCreated->f.onStart->f.onResume

暂停,重新开始,销毁同上

心得:

感觉注册就是静态注册,看在哪个方法中注册,如果在系统回调函数中注册,会先打印activity回掉前的方法,和静态的相互顺序一样

fragment交互中的生命周期:

函数中的生命周期
add:当使用add函数启动另一个fragment,只执行第二个fragment的启动周期

此时的销毁周期f同时表示两个fragment的生命周期,并且老的fragment会先执行

replace:(1.表示原来的fragment,2表示新的fragment):2.onAttach->2.onCreate->1.onPause->1.onStop->1.onDestroyView->1.onDestroy->1.onDetach->2.onCreateView->2.onActivityCreated->2.onStart->2.onResume

remove:销毁中所有fragment的周期

返回栈中的生命周期:

压入栈:2.onAttach->2.onCreate->1.onPause->1.onStop->1.onDestroyView->2.onCreateView->2.onActivityCreated->2.onStart->2.onResume

类似replace,,区别:就是第一个销毁状态只执行到onDestroyView,不执行onDestroy和onDetach

弹出栈:(2表示栈顶对象,1表示底下对象):2.onPause->2.onStop->2.onDestroyView->2.onDestroy->2.onDetach->1.onCreateView->1.onActivityCreated->1.onActivityCreated->1.onResume

传值

activity向fragment传值:

activity通过id获取fragment对象:

1
xFragment xfragment = (xFragment)getFragmentManager().findFragmentById(R.id.xfragment);

  • activity.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //创建管理器
    FragmentManager manager = getSupportFragmentManager();
    //开启事务
    FragmentTransaction transaction = manager.beginTransaction();
    //创建碎片对象
    TitleFragment1 titleFragment = new TitleFragment1();
    //创建Bundle对象用于保存值
    Bundle bundle = new Bundle();
    //保存值
    bundle.putString("hello","world");
    //把值保存到fragment对象中
    titleFragment.setArguments(bundle);
    //添加
    transaction.add(R.id.fl, titleFragment);
    //提交
    transaction.commit();
  • fragment.java

    1
    2
    3
    4
    5
    Bundle bundle = getArguments();
    //不为空获取值
    if(bundle!=null){
    bundle.getString("hello");
    }

fragment向activity传值

获取activity对象:
1
getActivity();

传值思想:

创建一个接口,接口里面实现一个有参数值的方法,让activity实现这个接口,并重写接口那个方法

在fragment的onAttach方法中实例化那个接口,实例化对象就是getActivity

然后再需要传值的时候调用接口的那个方法就能把值传到activity重写的方法里

fragment向fragment传值

1、在AFragment中通过getFragmentManager.findFragmentById(int id)获取BFragment实例,调用BFragment的方法实现传值

2、在AFragment中通过getFragmentManager.findFragmentById(int id).getView().findViewById(int id)获取到BFragment中的view对象,对控件直接进行传值

3、在AFragment中直接getActivity().findViewById(int id)获取属于当前Activity的BFragment中的view对象

自适应(平板和手机的适配)

Android的限定符:

名称 像素密度范围 图片大小
mdpi 120dpi~160dpi 48×48px
hdpi 160dpi~240dpi 72×72px
xhdpi 240dpi~320dpi 96×96px
xxhdpi 320dpi~480dpi 144×144px
xxxhdpi 480dpi~640dpi 192×192px
small 小屏幕
normal 基准屏幕
large 大屏幕
xlarge 超大屏幕
land 横向屏幕
port 纵向屏幕
long 比标准屏幕宽高比明显的高或者宽的这样屏幕
notlong 和标准屏幕配置一样的屏幕宽高比

可以创建不同屏幕的布局进行写一些逻辑.比如layout-xhdpi,表示像素密度在240dpi-320dpi使用的布局文件

在Java代码中通过fandviewbyid!=null判断是否加载某些不同布局的控件,写不同的逻辑

还可以使用最小宽度限定符,layout-sw600dp sw600dp表示small width 600dp,表示最小600dp时候使用的布局,及宽度大于等于600dp调用的布局.这里的宽度指的是长宽比较小的那个值

android滑动列表RecyclerView

简介

RecyclerView是Android官方推荐使用的滚动控件

我认为的recyclerview是通过一个布局实现整体的布局,然后通过一个适配器实现子项布局点击等一些操作

写一个水果列表.

一个水果列表

  • 首先添加依赖

    1
    'com.android.support:recyclerview-v7:28.0.0'
  • 创建一个主activity,并编写布局,就是创建一个recyclerview,然后长宽match_parent
    main_layout.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?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:orientation="vertical"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

    </LinearLayout>
  • 创建一个水果列表的子布局,就是列表中单个元素的布局方式fruit_item.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
    android:id="@+id/fruit_image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
    <TextView
    android:id="@+id/fruit_name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />
    </LinearLayout>
  • 写一个水果列表子布局的类Fruit.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class Fruit {
    private String name;
    private int imageId;

    public Fruit(String name, int imageId) {
    this.name = name;
    this.imageId = imageId;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getImageId() {
    return imageId;
    }

    public void setImageId(int imageId) {
    this.imageId = imageId;
    }
    }
  • 开始写适配器,并创建点击事件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    import android.support.annotation.NonNull;
    import android.support.v7.widget.RecyclerView;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.ImageView;
    import android.widget.TextView;
    import android.widget.Toast;

    import java.util.List;

    public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>{
    private List<Fruit> mFruitList;

    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int type) {
    View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.fruit_item,viewGroup,false);
    final ViewHolder holder = new ViewHolder(view);
    holder.fruitView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    int position = holder.getAdapterPosition();
    Fruit fruit = mFruitList.get(position);
    Toast.makeText(v.getContext(),"you click view "+fruit.getName(),Toast.LENGTH_SHORT ).show();
    }
    });
    return holder;
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
    Fruit fruit = mFruitList.get(i);
    viewHolder.fruitImage.setImageResource(fruit.getImageId());
    viewHolder.fruitName.setText(fruit.getName());
    }

    @Override
    public int getItemCount() {
    return mFruitList.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder{
    View fruitView;
    ImageView fruitImage;
    TextView fruitName;
    public ViewHolder(@NonNull View itemView) {
    super(itemView);
    fruitView = itemView;
    fruitImage = (ImageView)itemView.findViewById(R.id.fruit_image);
    fruitName = (TextView)itemView.findViewById(R.id.fruit_name);
    }
    }
    public FruitAdapter(List<Fruit> fruitList){
    mFruitList = fruitList;
    }
    }
  • 其中适配器实现了一个ViewHolder的内部类,里面是适配器对应的子控件,view是整个子控件,imageview是图片控件,textview是后面的文字控件,可以在适配器的onCreateViewHolder方法中为他们创建对应的点击事件.

mfruitList是对应整个滑动列表的元素列表

  • MainActivity.java,自备图片
    MainActivity.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.support.v7.widget.LinearLayoutManager;
    import android.support.v7.widget.RecyclerView;

    import java.util.ArrayList;
    import java.util.List;

    public class MainActivity extends AppCompatActivity {

    //水果元素列表
    private List<Fruit> fruitList = new ArrayList<Fruit>();

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

    //初始化水果列表
    initFruits();

    //绑定recycleview
    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

    //加载一个布局
    LinearLayoutManager layoutManager = new LinearLayoutManager(this);

    //把布局弄成横向滑动的
    //layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);

    //把布局弄成纵向3列瀑布流形式
    //StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);

    //让recycleview加载创建的布局
    recyclerView.setLayoutManager(layoutManager);

    //创建适配器,传入水果元素列表
    FruitAdapter adapter = new FruitAdapter(fruitList);

    //加载适配器
    recyclerView.setAdapter(adapter);
    }

    //初始化水果列表,传入水果名字和图片id
    private void initFruits() {
    for (int i = 0; i < 2; i++) {
    fruitList.add(new Fruit("菠萝", R.drawable.boluo));
    fruitList.add(new Fruit("草莓", R.drawable.caomei));
    fruitList.add(new Fruit("橙子", R.drawable.chengzi));
    fruitList.add(new Fruit("荔枝", R.drawable.lizhi));
    fruitList.add(new Fruit("龙眼", R.drawable.longyan));
    fruitList.add(new Fruit("芒果", R.drawable.mangguo));
    fruitList.add(new Fruit("猕猴桃", R.drawable.mihoutao));
    fruitList.add(new Fruit("苹果", R.drawable.pingguo));
    fruitList.add(new Fruit("葡萄", R.drawable.puto));
    fruitList.add(new Fruit("圣女果", R.drawable.shengnvguo));
    fruitList.add(new Fruit("水蜜桃", R.drawable.shuimitao));
    fruitList.add(new Fruit("提子", R.drawable.tizi));
    fruitList.add(new Fruit("香蕉", R.drawable.xiangjiao));
    fruitList.add(new Fruit("西瓜", R.drawable.xigua));
    fruitList.add(new Fruit("西红柿", R.drawable.xihongshi));
    fruitList.add(new Fruit("杨桃", R.drawable.yangtao));
    fruitList.add(new Fruit("樱桃", R.drawable.yingtao));
    }
    }
    }
  • 加载的时候要加载一个布局,上面写的是默认纵向列表布局,可以设置成横向,还有瀑布流形式,不过设置的时候一定要注意fruit_item.xml中间的布局变化

  • 如果设置成横向滑动就不要把宽度设置成父类最大

  • 如果设置成纵向瀑布流形式,要把最外层的width设置成父类最宽

更新数据

更新数据的方法是更新fruitList这个数组,然后再执行对应的适配器方法,可以把适配器弄成成员变量

刷新全部可见的item,notifyDataSetChanged()

刷新指定item,notifyItemChanged(int)

从指定位置开始刷新指定个item,notifyItemRangeChanged(int,int)

插入、移动或者删除一个并自动刷新,notifyItemInserted(int)、notifyItemMoved(int)、notifyItemRemoved(int)

局部刷新,notifyItemChanged(int, Object)

列表滚动到制定项

recyclerView.scrollToPosition(int);

会让第指定个项目出现在屏幕上,只是完全出现在屏幕上,不是屏幕第一个

高级用法

RecyclerView实现滑动删除和拖拽功能
总结和分析几种判断RecyclerView到达底部的方法

Android使用SharePreferences存储数据

SEPTEMBER 25, 2018

简介

SharedPreferences存储类用来存储一些键值对,比如用户的设置信息

可以存储五大基本类型String,Float,Long,int,boolean和字符串集合StringSet,其中StringSet是一个字符串的集合,Set

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
Set set = new HashSet<String>();
set.add("one");
set.add("two");

SharedPreferences sp = getSharedPreferences("data",MODE_PRIVATE);
SharedPreferences.Editor editor= sp.edit();
editor.putString("name","xwm");
editor.putInt("int",500);
editor.putBoolean("bool",true);
editor.putLong("long",12l);
editor.putFloat("float", (float) 3.1415926);
editor.putStringSet("StringSet",set);
editor.commit();

先创建一个sp对象,然后获取到编辑器editor,在editor中保存数据,保存完以后要进行commit,否则不能保存;

他会把数据保存到data/data//shared_prefs文件夹中的xml文件里,如以上代码会保存成

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="name">xwm</string>
<float name="float" value="3.1415925" />
<boolean name="bool" value="true" />
<long name="long" value="12" />
<set name="StringSet">
<string>one</string>
<string>two</string>
</set>
<int name="int" value="500" />
</map>

获取数据

1
2
3
4
5
6
7
8
9
SharedPreferences spa = getSharedPreferences("data", MODE_PRIVATE);
//获取五大基本类型
Log.d(TAG, "String: "+spa.getString("name","空"));
Log.d(TAG, "int"+spa.getInt("int",-1));
Log.d(TAG, "bool"+spa.getBoolean("bool",false));
Log.d(TAG, "Long"+spa.getLong("long",0l));
Log.d(TAG, "float"+spa.getFloat("float",(float)0.0));
//获取StringSet对象
Log.d(TAG, "StringSet"+spa.getStringSet("StringSet",null));

String: xwm
int500
booltrue
Long12
float3.1415925
StringSet[two, one]

先创建一个SharePreferences对象,然后可以通过get方法通过key获取到各类型的数据,get方法有两个参数,第一个是要取出值的key,第二个值是如果这个key没有对应的值要返回的默认值

取出方法还有一个getAll方法,是将所有的键值对弄成一个map返回回来

提交方法

sharepreferences有两个提交方法,一个是上面用到的commit,还有一个apply
这两个方法的区别在于:

  1. apply没有返回值而commit返回boolean表明修改是否提交成功
  2. apply是将修改数据原子提交到内存, 而后异步真正提交到硬件磁盘, 而commit是同步的提交到硬件磁盘,因此,在多个并发的提交commit的时候,他们会等待正在处理的commit保存到磁盘后在操作,从而降低了效率。而apply只是原子的提交到内容,后面有调用apply的函数的将会直接覆盖前面的内存数据,这样从一定程度上提高了很多效率。
  3. apply方法不会提示任何失败的提示。
    由于在一个进程中,sharedPreference是单实例,一般不会出现并发冲突,如果对提交的结果不关心的话,建议使用apply,当然需要确保提交成功且有后续操作的话,还是需要用commit的。

参考:https://blog.csdn.net/jake9602/article/details/18414841

android广播

参考:Android四大组件:BroadcastReceiver史上最全面解析
安卓独有的广播机制,就像生活中的广播一样,一个发射广播,很多人能同时接受到广播内容

实现原理

采用的模型

  • Android中的广播使用了设计模式中的观察者模式:基于消息的发布 / 订阅事件模型
  • 因此,Android将广播的发送者 和 接收者 解耦,使得系统方便集成,更易扩展

模型讲解

  • 模型中有3个角色:
  • 消息订阅者(广播接收者)
  • 消息发布者(广播发布者)
  • 消息中心(AMS,即Activity Manager Service)

示意图

广播接收者

静态注册

  • 静态注册是指在清单文件中注册

在as中new一个other选择Broadcast Receiver就能创建一个广播接收者,两个复选框,代表能不能由系统实例化和接收程序之外的广播

as会自动在清单文件注册,打开清单文件可以看到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<receiver
android:enabled=["true" | "false"]
//此broadcastReceiver能否接收其他App的发出的广播
//默认值是由receiver中有无intent-filter决定的:如果有intent-filter,默认值为true,否则为false
android:exported=["true" | "false"]
android:icon="drawable resource"
android:label="string resource"
//继承BroadcastReceiver子类的类名
android:name=".mBroadcastReceiver"
//具有相应权限的广播发送者发送的广播才能被此BroadcastReceiver所接收;
android:permission="string"
//BroadcastReceiver运行所处的进程
//默认为app的进程,可以指定独立的进程
//注:Android四大基本组件都可以通过此属性指定自己的独立进程
android:process="string" >

//用于指定此广播接收器将接收的广播类型
//本示例中给出的是用于接收网络状态改变时发出的广播
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>

  • 注册实例
    1
    2
    3
    4
    5
    6
    7
    8
    <receiver
    //此广播接收者类是mBroadcastReceiver
    android:name=".mBroadcastReceiver" >
    //用于接收网络状态改变时发出的广播
    <intent-filter>
    <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
    </intent-filter>
    </receiver>

+action中的值是要接收的广播名字,就像现实广播中的’fm100.8’一样,是要接收广播的名字,当然可以是系统广播的名字,如android.intent.action.NEW_OUTGOING_CALL是系统打电话时发送的广播的名字

  • 当此 App首次启动时,系统会自动实例化mBroadcastReceiver类,并注册到系统中。

动态注册

动态注册是只在Java代码中进行注册,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
protected void onResume(){
TwoReceiver twoReceiver = new TwoReceiver();
//设置要广播接收的名字
IntentFilter intentFilter = new IntentFilter(android.net.conn.CONNECTIVITY_CHANGE);
registerReceiver(twoReceiver,intentFilter);
}
//记得要在onPause方法中释放注册的广播接收者
@Override
protected void onPause() {
super.onPause();
//释放注册的广播接收者
unregisterReceiver(twoReceiver);
}

  • 动态广播最好在Activity 的 onResume()注册、onPause()注销。原因:
  1. 对于动态广播,有注册就必然得有注销,否则会导致内存泄露
  2. 在onResume()注册、onPause()注销是因为onPause()在App死亡前一定会被执行,从而保证广播在App死亡前一定会被注销,从而防止内存泄露。
  • 广播接收者
    重写了onReceive方法实现接收广播的逻辑

可以通过getResultData方法获取有序广播中的数据,通过setResult设置回数据

1
2
3
4
5
@Override
public void onReceive(Context context, Intent intent) {
String result = getResultData();
Log.d("TwoReceiver","onReceivee: "+result);
}

发送广播

  • 广播分为有序广播和无序广播

无序广播

一次发送,所有人同时接收

无序广播

  • 特点:
  • 所有接收器没有先后顺序,接受顺序不确定,但是都能接受到
  • 通过sendBroadcast(intent)方法来发送,它是完全异步的。
  • 效率高
  • 无法使用setResult系列、getResult系列及abort(中止)系列API

有序广播

一次发送,接收有顺序

有序广播

特点:

  • 有接收先后顺序,根据priority的值来判定先后顺序,值越大优先级越高

静态注册接收器:

1
2
3
4
5
6
7
8
<receiver
android:name=".OneReceiver"
android:enabled="true"
android:exported="true">
<intent-filter android:priority="1000">
<action android:name="bmbm" />
</intent-filter>
</receiver>

动态注册接收器:

1
2
3
4
5
TwoReceiver twoReceiver = new TwoReceiver();
IntentFilter intentFilter = new IntentFilter("bmbm");
//设置代表优先级顺序的priority值
intentFilter.setPriority(1000);
registerReceiver(twoReceiver,intentFilter);

  • 当两个接收者优先级一样时或者都没有优先级的时候,那么按照清单文件上注册的先后顺序先后收到广播

  • 当一个静态注册和另一个动态注册的接受者优先级相同或者都没有优先级的时候,那么动态注册会先收到广播

  1. 通过sendOrderedBroadcast(intent,null);发送有序广播

  2. 可以使用setResult系列、getResult系列及abort(中止)系列API

  3. 当优先级高的接受者使用abortBroadcast()方法后,那么比他优先级低的广播接收者则接收不到广播

发送一个广播

1
2
3
4
5
6
7
8
9
Intent intent = new Intent();
//指定广播的名字,类比现实中广播的名字,如'fm100.8'
intent.setAction("hello");
//发送一个有序广播
sendOrderedBroadcast(intent,null);
//此时接收名为'hello'的广播接受者会按照优先级顺序接受到有序广播

sendBroadcast(intent)
//此时接收名为'hello'的广播都会同时无序收到广播

使用本地广播

本地广播,就是自己程序发的只有自己能收到,别的收不到

先创建广播接收器Myreceiver.java

1
2
3
4
5
6
7
public class MyReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
Toast.makeText(context, "收到了", Toast.LENGTH_SHORT).show();
}
}

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//注册应用内广播接收器
//步骤1:实例化BroadcastReceiver子类 & IntentFilter mBroadcastReceiver
mBroadcastReceiver = new mBroadcastReceiver();
IntentFilter intentFilter = new IntentFilter();

//步骤2:实例化LocalBroadcastManager的实例
localBroadcastManager = LocalBroadcastManager.getInstance(this);

//步骤3:设置接收广播的类型
intentFilter.addAction(android.net.conn.CONNECTIVITY_CHANGE);

//步骤4:调用LocalBroadcastManager单一实例的registerReceiver()方法进行动态注册
localBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);

//取消注册应用内广播接收器
localBroadcastManager.unregisterReceiver(mBroadcastReceiver);

//发送应用内广播
Intent intent = new Intent();
intent.setAction(BROADCAST_ACTION);
localBroadcastManager.sendBroadcast(intent);

特别注意

  • 对于不同注册方式的广播接收器回调OnReceive(Context context,Intent intent)中的context返回值是不一样的:

对于静态注册(全局+应用内广播),回调onReceive(context, intent)中的context返回值是:ReceiverRestrictedContext;
对于全局广播的动态注册,回调onReceive(context, intent)中的context返回值是:Activity Context;
对于应用内广播的动态注册(LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Application Context。
对于应用内广播的动态注册(非LocalBroadcastManager方式),回调onReceive(context, intent)中的context返回值是:Activity Context;

作者:Carson_Ho
链接:https://www.jianshu.com/p/ca3d87a4cdf3
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

android activity的生命周期和启动模式

SEPTEMBER 23, 2018

简介:

Android生命周期分为七个函数:onCreate,onStart,onResume,onPause,onStop,onRestart,onDestory

单个activity的生命周期

Android官方文档给的生命周期示例图:

图片丢失

1、Activity的启动 onCreate->onStart->onResume->处于可见状态

2、Activity的不可见 onPause->onStop

3、Activity的重新可见 onRestart->onStart->onResume

4、Activity的销毁 onPause->onStop->onDestory

一个Activity在创建和显示的时候,先调用的是onCreate, onStart, onResume,方法

按下back键的时候会调用:onPause,onStop,onDestory方法

按下home键会调用:onPause onStop

按下home键后再打开程序:onrestart onstart onresume

方法 说明 是否能事后终止? 后接
onCreate() 首次创建 Activity 时调用。 您应该在此方法中执行所有正常的静态设置 — 创建视图、将数据绑定到列表等等。 系统向此方法传递一个 Bundle 对象,其中包含 Activity 的上一状态,不过前提是捕获了该状态(请参阅后文的保存 Activity 状态)。始终后接 onStart()。 onStart()

onRestart|在 Activity 已停止并即将再次启动前调用。始终后接 onStart()|否|onStart()
onStart()|在 Activity 即将对用户可见之前调用。
如果 Activity 转入前台,则后接 onResume(),如果 Activity 转入隐藏状态,则后接 onStop()。|否|onResume()或onStop()
onResume()|在 Activity 即将开始与用户进行交互之前调用。 此时,Activity 处于 Activity 堆栈的顶层,并具有用户输入焦点。始终后接 onPause()。|否|onPause()
onPause()|当系统即将开始继续另一个 Activity 时调用。 此方法通常用于确认对持久性数据的未保存更改、停止动画以及其他可能消耗 CPU 的内容,诸如此类。 它应该非常迅速地执行所需操作,因为它返回后,下一个 Activity 才能继续执行。如果 Activity 返回前台,则后接 onResume(),如果 Activity 转入对用户不可见状态,则后接 onStop()。|是|onResume()或onStop()
onStop()|在 Activity 对用户不再可见时调用。如果 Activity 被销毁,或另一个 Activity(一个现有 Activity 或新 Activity)继续执行并将其覆盖,就可能发生这种情况。如果 Activity 恢复与用户的交互,则后接 onRestart(),如果 Activity 被销毁,则后接 onDestroy()。|是|onRestart()或onDestroy()
onDestroy()|在 Activity 被销毁前调用。这是 Activity 将收到的最后调用。 当 Activity 结束(有人对 Activity 调用了 finish()),或系统为节省空间而暂时销毁该 Activity 实例时,可能会调用它。 您可以通过 isFinishing() 方法区分这两种情形。|是|无

两个activity跳转时候的生命周期:

当第1个activity去访问第二个activity的时候会先调用

1.onPause->2.onCreate->2.onStart->2.onResume->1.onStop

当在第二个activity结束的时候会调用:

2.onPause->1.onRestart->1.onStart->onResume->2.onStop->2.Destory

注意
如果第二个activity类型是一个dialog类型的activity,那么第一个activity只会执行onPause,启动时候直接启动onResume,因为dialog类型的activity没有完全遮住下面的activity,所以只是暂停,并没有停止

规律

每次跳转到第二个activity的时候会先调用第一个activity的onpause,然后会调用即将显示activity的要显示的周期,然后再执行第一个activity的剩下的操作..原因是因为假设要访问的activity不能正确执行发生崩溃,能及时回到之前的activity,不至于之前界面的也被销毁了而执行不了,所以将之前的stop和destory方法第2个activity显示之后.

横竖屏的生命周期

基本生命周期:

竖.Pause->竖.Stop->竖.Destroy【竖屏销毁,并开始创建横屏】

横.Create->横.Start->横.Resume【横屏显示】

发生横竖屏切换的时候会先销毁之前的activity,然后重新创建横屏的activity,所以有一些数据要保存,此时可以重写onSaveInstanceState方法保存数据

1
2
3
4
5
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("name","value");
}

然后在重写onRestoreInstanceState方法取出保存的数据

1
2
3
4
5
6
7
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if(savedInstanceState!=null){
text.setText(savedInstanceState.getString("name"));
}
}

具体生命周期:

竖屏销毁时候:
onPause->onSaveInstanceState->onStop->onDestroy

横屏创建的时候
onCreate->onStart->onRestoreInstanceState->onResume

另外:

  1. 当重写了onSaveInstanceState发现,如果是要打开别的界面或者home暂停的时候也会调用该方法,此时如果返回此activity不会调用onRestoreInstanceState方法
    如果是单击back返回,也就是界面要销毁的时候则不会调用.
  2. 横屏可以重写布局,在res下新建一个layout-land的资源文件夹,里面创建同名的布局文件即可
  3. 我测试用的Android5.0手机,创建了个edittext,发现横屏的时候系统会给保存之前的数据,如果想要横屏修改这个数据,在oncreate中是不能修改的,必须在onRestoreInstanceState中修改,因为系统会在onRestoreInstanceState方法中把之前的数据写回来,,所以要在它之后再修改值才会生效.
  4. oncreate方法中也有个Bundle方法,也是保存之前的数据的,和onRestoreInstanceState中的用法一样
  5. 当然也可以设置禁止横屏,在清单文件中加入

android:screenOrientation=”portrait”
是可以让屏幕保持竖屏,不横屏,

如果是想保持横屏不竖屏,可以添加

1
android:screenOrientation="landscape"

activity启动模式

Android的活动在内存里是以栈的形式存储

activity一共有四种启动模式:standard,,singleTop,,singleTask和singleInstance,可以通过activity标签指定android:launchMode属性来选择启动模式

standard:

standard是默认的启动模式,在不指定别的启动模式时,所有活动都会自动使用这种启动模式.
当有一个新的活动时,系统不会在乎此活动是否在堆栈中存在,每次启动都会为活动创建新的实例放入栈顶,
简单地说就是你启动一个活动,他就往栈顶放一个

singleTop

很多情况下第一种启动模式有些不合理,有的活动已经在栈顶了,为什么还要再次启动?
singleTop就解决了这个问题,当要启动的活动已经在栈顶时候(我觉得就是自己启动自己的时候),那么就不创建新的活动实例

singleTask

singleTop很好的解决重复创建栈顶活动的问题,但是如果要启动的活动已经在栈内,并没有在栈顶,还是会创建多个活动实例,singleTask是能让整个堆栈只存在一个相同的活动实例
比如说当前a活动是栈顶,b活动在a活动的下面,此时要启动b活动,那么会先将b活动弹出栈,此时使得a活动进入栈顶.
整个过程是要把a活动以上的活动都销毁(destroy),直到a活动处于栈顶为止

singleInstance

这个启动模式简单来说就是又创建了一个新的堆栈,是APP中LAUNCHER的activity所处在的栈(暂且叫他主栈)旁边又创建了一个堆栈(暂且叫他副栈)
第一个例子:有abc三个活动,比如b的启动模式是singleInstance,a启动了b,b启动了c,此时在c界面单击back,按理说应该是返回到b,其实是返回到a.因为b是生成到副栈,此时是两个堆栈,主栈是a->c,副栈里面有b,当在c中单击返回键,是会将c下面的a放到栈顶,如果再a中再次back,那么才会显示b,因为是先将主栈的显示,主栈都销毁了再启动副栈的活动
第二个例子:有ab两个活动,假设b的启动模式是singleInstance,在a中启动b,此时单击home键退出,然后再点进来,按理说应该还是显示b的界面,其实是显示的a,因为当重新启动了以后会先显示主栈的活动,没有的话才显示副栈

android弹出框dialog

内置dialog

普通对话框

普通对话框

1
2
3
4
5
6
7
8
9
10
11
12
AlertDialog dialog = new AlertDialog.Builder(this).setTitle("我是标题").setMessage("我是内容").setIcon(R.mipmap.ic_launcher).setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "yes", Toast.LENGTH_SHORT).show();
}
}).setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "no", Toast.LENGTH_SHORT).show();
}
}).create();
dialog.show();
  • 普通对话框可以设置标题,设置提示内容,以及设置确定和取消的点击事件
  • 点旁边空白处也可以关闭dialog,但是不会调用取消的点击事件

单选对话框

单选对话框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
AlertDialog dialog = new AlertDialog.Builder(this).setTitle("我是标题").setIcon(R.mipmap.ic_launcher).setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "yes", Toast.LENGTH_SHORT).show();
}
}).setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "no", Toast.LENGTH_SHORT).show();
}
}).setSingleChoiceItems(new String[]{"选项1", "选项2"}, 0, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, ""+which, Toast.LENGTH_SHORT).show();
}
}).create();
dialog.show();

  • 如果设置了setMessage,那么就只显示内容,不会显示这个单选
  • setSingleChoiceItems,第一个参数是一个字符串数组,第二个参数是默认选第几个,从0开始,第三个选项是一个点击事件,which是点击了第几个,从0开始

多选对话框

多选对话框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
AlertDialog dialog = new AlertDialog.Builder(this).setTitle("我是标题").setIcon(R.mipmap.ic_launcher).setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "yes", Toast.LENGTH_SHORT).show();
}
}).setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "no", Toast.LENGTH_SHORT).show();
}
}).setMultiChoiceItems(new String[]{"选项1", "选项2"}, new boolean[]{true, false}, new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
Toast.makeText(MainActivity.this, ""+which+isChecked, Toast.LENGTH_SHORT).show();
}
}).create();
dialog.show();

  • setMultiChoiceItems第一个参数是字符串数组,第二个参数是数组里的值默认选不选,第三个参数是点击事件,which是第几个选项发生了变化,第二个参数isChecked表示变化是点击了还是没点击

自定义对话框

多选对话框

先创建一个布局my_dialog.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</android.support.constraint.ConstraintLayout>

包含了上面一个textView和下面一个button

Mydialog.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class MyDialog extends Dialog {
private boolean isCreate;
private String message;
private TextView textView;
private Button button;
public MyDialog(Context context) {
super(context);
isCreate=false;
message=null;
}
public void setTextView(String text){
if(isCreate){
textView.setText(text);
}else{
message = text;
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_dialog);
isCreate = true;
textView = (TextView)findViewById(R.id.textView);
if(message!=null){
textView.setText(message);
}
button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), "button", Toast.LENGTH_SHORT).show();
dismiss();
}
});
}
}

  • 代码中重写了oncreate方法加载布局
  • 当前对象如果还没有显示过,那么其内部子元素还没有关联资源(findviewbyid),此时如果调用一些方法,比如settext会报空值错误,参考setTextView的方法
  • 给button设置了一个点击事件,显示一个”button”并且调用dismiss关闭当前弹出框

调用
MainActivity.java

1
2
3
MyDialog myDialog = new MyDialog(this);
myDialog.setTextView("你好啊");
myDialog.show();

dialog生命周期

  • Dialog的生命周期一共用以下6个方法:onCreate(),show(),onStart() ,cancel(),onDismiss(),Stop()

  • Dialog仅在在第一次启动时候会执行onCreate()方法(之后无论该Dialog执行Dismiss(),cancel(),stop(),Dialog都不会再执行onCreate()方法).

  • show() 和 onStart()在每次Dialog显示时都会依次执行.

  • onDismiss() 和 stop() 在每次Dialog消失的时候都会依次执行.

  • cancel() 是在点击BACK按钮或者Dialog外部时触发,然后依次执行onDismiss() 和 stop().

举例:

点击显示按钮,第一次显示Dialog,然后按BACK键返回

onCreate() —> onStart() —>show() ;
Stop() —> onDismiss() —> cancel();

再次点击显示按钮,然后点击Dialog外部.

onStart() —> show();
Stop() —> onDismiss() —> cancel();

再次点击显示按钮,然后执行Dialog.dismiss() 方法.

onStart() —> show();
Stop() —> onDismiss();

自定义dialog需要注意:

当前对象如果还没有显示过,那么其内部子元素还没有关联资源(findviewbyid),此时如果调用一些方法,比如settext会报空值错误

其他操作

dialog上的edittext获得交点并弹出键盘:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 让一个edittext获得焦点并弹出键盘
* @param et 要获得焦点的edittext对象
*/
private void showKeyBoard(EditText et) {
if (et != null) {
//设置可获得焦点
et.setFocusable(true);
et.setFocusableInTouchMode(true);
//请求获得焦点
et.requestFocus();
//调用系统输入法
InputMethodManager inputManager = (InputMethodManager) et
.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
inputManager.showSoftInput(et, 0);
}
}
  • 然后在dialog外部调用的时候要先调用show,并且在多线程中调用此方法,如果不show就调用获得焦点会报错,
  • 示例(runOnUiThread)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    myDialog.show();
    //多线程调用弹出键盘操作
    new Thread(){
    public void run() {
    runOnUiThread(new Runnable(){
    @Override
    public void run() {
    myDialog.showNameKeyBoard();
    }

    });
    }
    }.start();

android操作sqllite的工具类

继承安卓自带的SQLiteOpenHelper,其中使用了代理模式(IResultSetUtil)操作结果集

需要注意的是,代理模式传入了cs(结果集对象)和db对象(连接对象),记得用完close.

sqllite中间传入的参数值都是字符串类型

创建工具类

SqlLiteHelper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class SqlLiteHelper extends SQLiteOpenHelper {
private String firsql;
/**
* 构造方法
* @param context 上下文
* @param name 创建的文件名字,如database.db
* @param factory 不知道,父类要的,看样子像是结果集操作工厂
* @param version 定义的当前数据库的版本
* @param firsql 第一次运行执行的sql语句,一般是用来创建表
*/
public SqlLiteHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version, String firsql) {
super(context, name, factory, version);
this.firsql = firsql;
}

/**
* 创建时候调用的方法
* 只有在构造方法中的数据库名称那个文件不存在的时候才会调用这个方法
* @param db
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(firsql);
}

/**
* 当数据库版本变更的时候调用的方法,构造方法里的版本号
* @param db
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}

/**
* 插入数据的方法
* @param tbname 插入数据的表名
* @param cols 要插入数据的列名
* @param values 要插入数据的值,和列名一一对应的值
*/
public void insert(String tbname, String[] cols, Object... values) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues contentValues = new ContentValues();
for (int i = 0; i < cols.length && i < values.length; i++){
contentValues.put(cols[i], values[i].toString());
}
Long id = db.insert(tbname, null, contentValues);//返回插入数据的行号,与id无关
db.close();
}

/**
* 修改数据库的方法
* @param tablename 要修改数据表的表名
* @param cols 要修改数据的列名
* @param newvalue 修改后对应的值
* @param require 修改的条件,如'id=?'
* @param requirevalue 条件中问号对应的值
* @return 返回修改的行数
*/
public int update(String tablename, String[] cols, String[] newvalue, String require, String[] requirevalue) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
for (int i = 0; i < cols.length && i < newvalue.length; i++) {
values.put(cols[i], newvalue[i]);
}
int number = db.update(tablename, values, require, requirevalue);
db.close();
return number;
}

/**
* 删除数据
* @param tbname 要删除数据所在的表名
* @param require 删除的条件,如'id=?'
* @param values 条件中问号对应的值
* @return 返回删除的行数
*/
public int delete(String tbname,String require,String[] values){
SQLiteDatabase db = this.getWritableDatabase();
int number = db.delete(tbname,require,values);
db.close();
return number;
}

/**
* 执行传入的sql语句
* @param sql 要执行的语句
* @param iResultSetUtil 代理模式操作返回的数据库
* @param values 语句中问号对应的值
* @return 返回代理要的值
*/
public Object executeQuery(String sql,IResultSetUtil iResultSetUtil,String [] values){
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = db.rawQuery(sql,values);
return iResultSetUtil.doHandler(cursor,db);
}
}

IResultSetUtil.java

1
2
3
4
5
6
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

public interface IResultSetUtil {
public Object doHandler(Cursor cs, SQLiteDatabase db);
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//创建对象
SqlLiteHelper sqlLiteHelper = new SqlLiteHelper(this,"data.db",null,1,"create table persion(id integer PRIMARY KEY autoincrement,name verchar(20));");
//插入
sqlLiteHelper.insert("persion",new String[]{"name"},"xxxx");
//查询
String result = (String) sqlLiteHelper.executeQuery("select * from persion where id=?", new IResultSetUtil() {
@Override
public Object doHandler(Cursor cs, SQLiteDatabase db) {
String result=null;
Log.d("doHandler: ",""+cs.getCount());
cs.moveToFirst();
if(cs!=null&&cs.getCount()>0){
result = cs.getString(cs.getColumnIndex("name"));
}
cs.close();
db.close();
return result;
}
}, new String[]{"0"});
Toast.makeText(this,result,Toast.LENGTH_SHORT).show();

事务:

事务分三步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//开启事务:
db.beginTransaction();

//提交事务:

db.setTransactionSuccessful();

//关闭事务:

db.endTransaction();

//样例
db.beginTransaction();
/*
进行一系列操作
* */
db.setTransactionSuccessful();
db.endTransaction();

Android ADB命令大全

JANUARY 26, 2018
转自:https://www.jianshu.com/p/860bc2bf1a6a
作者:张明云
链接:https://www.jianshu.com/p/860bc2bf1a6a
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

另外下载adb
ADB和Fastboot最新版的谷歌官方下载链接
ADB和Fastboot for Windows
https://dl.google.com/android/repository/platform-tools-latest-windows.zip
ADB和Fastboot for Mac
https://dl.google.com/android/repository/platform-tools-latest-darwin.zip
ADB和Fastboot for Linux
https://dl.google.com/android/repository/platform-tools-latest-linux.zip