Neil's blog

Let's start from here


  • 首页

  • 归档

  • 标签

  • 关于

Android四大组件之ContentProvider

发表于 2018-09-18 |

一、简介

ContentProvider为不同的软件之间数据共享,提供统一的接口

内容提供器(ContentProvider)主要用于在不同的应用程序之间实现数据共享的功能。它提供一整套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访问数据的安全性。

内容提供器非常类似于数据库,可以使用insert()、update()、delete()、query()方法进行插入更新删除查询数据,大多数情况下,此数据存储在SqliteS数据库中。

不过和Sqlite不同的是,内容提供器的几种增删改查的方法是不接受表名参数的,而是使用Uri。

二、内容Uri

ContentProvider的内容Uri格式是固定的,下面详细介绍一下。

content://<authority>/<data_path>

例如:content://com.neil.myprovider/table_contact/666
  • content://是通用前缀,表示该Uri用于ContentProvider定位资源。

  • authority:授权信息,以区别不同的ContentProvider.

  • path:表名,用来区分ContentProvider中的不同的表

三、基本使用

内容提供者是android应用程序的基本构建块之一,它们封装数据并将封装的数据通过单一的ContentResolver接口提供给应用程序。当你需要在多个应用之间共享数据的时候就需要用到内容提供者。例如,手机中的联系人数据会被多个应用所用到所以必须要用内容提供者存储起来。如果你不需要在多个应用之间共享数据,你可以使用一个数据库,直接通过SQLite数据库。 当通过contentresolver发送一个请求时,系统会检查给定的URI并把请求传给有注册授权的Contentprovider。 UriMatcher类有助于解析uri。

需要实现的方法有:

public boolean onCreate() 在创建ContentProvider时调用

public Cursor query(Uri, String[], String, String[], String) 用于查询指定Uri的ContentProvider,返回一个Cursor

public Uri insert(Uri, ContentValues) 用于添加数据到指定Uri的ContentProvider中,(外部应用向ContentProvider中添加数据)

public int update(Uri, ContentValues, String, String[]) 用于更新指定Uri的ContentProvider中的数据

public int delete(Uri, String, String[]) 用于从指定Uri的ContentProvider中删除数据

public String getType(Uri) 用于返回指定的Uri中的数据的MIME类型

需要在AndroidManifest.xml中进行声明。

Android四大组件之BroadCast

发表于 2018-09-17 |

Android四大组件之BroadCast

一、广播概述

Android应用可以从Android系统和其他Android应用发送或接收广播消息,是观察者设计模式,即一对多的关系。例如,应用程序还可以发送自定义广播,以通知其他应用程序可能感兴趣的内容(例如,已下载了一些新数据)。

广播是一种广泛运用的在应用程序之间传输信息的机制。而BroadcastReceiver是对发送出来的广播进行过滤接收并响应的一类组件;

BroadcastReceiver自身并不实现图形用户界面,但是当它收到某个通知后,BroadcastReceiver可以启动Activity作为响应,或者通过NotificationMananger提醒用户,或者启动Service等等。

经常说”发送广播“和”接收广播“,表面上看广播作为Android广播机制中的实体,实际上这一实体本身是并不是以所谓的”广播“对象存在的,而是以”意图“(Intent)去表示。定义广播的定义过程,实际就是相应广播”意图“的定义过程,然后通过广播发送者将此”意图“发送出去。被相应的BroadcastReceiver接收后将会回调onReceive()函数。

二、广播分类

Android中的BroadCast类型主要分为5类:

  • Normal BroadCast(普通广播)
  • System BroadCast(系统广播)
  • Ordered BroadCast(有序广播)
  • Sticky BroadCast(粘性广播)
  • Local BroadCast(App应用内广播)

三、广播的注册和接收

广播的注册分为两种:静态注册和动态注册

1.静态注册

静态注册就是在AndroidManifest中注册,

<receiver 
//此广播接收者类是MyBroadCastReceiver
android:name=".MyBroadCastReceiver" >
//用于接收网络状态改变时发出的广播
<intent-filter>
    <action android:name="com.test.mybroadcast" />
</intent-filter>
</receiver>
2.动态注册

需要在代码中实现

IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("com.action.myreceiver");
myReceiver = new MyReceiver();
registerReceiver(myReceiver, intentFilter);

注意:动态注册后记得解绑注册。

四、常用广播的简单使用

1.普通广播

普通广播的使用,在第三点中已举例说明,不再重复。

2.系统广播

Android中内置了一些系统广播。常见的有开关机、网络、电话短信、拍照等等。

例如:

  • 监听网络变化 android.net.conn.CONNECTIVITY_CHANGE

  • 关闭或打开飞行模式 Intent.ACTION_AIRPLANE_MODE_CHANGED

  • 充电时或电量发生变化 Intent.ACTION_BATTERY_CHANGED

  • 电池电量低 Intent.ACTION_BATTERY_LOW

  • 电池电量充足(即从电量低变化到饱满时会发出广播 Intent.ACTION_BATTERY_OKAY

  • 系统启动完成后(仅广播一次) Intent.ACTION_BOOT_COMPLETED

  • 按下照相时的拍照按键(硬件按键)时 Intent.ACTION_CAMERA_BUTTON

  • 屏幕锁屏 Intent.ACTION_CLOSE_SYSTEM_DIALOGS

  • 设备当前设置被改变时(界面语言、设备方向等) Intent.ACTION_CONFIGURATION_CHANGED

  • 插入耳机时 Intent.ACTION_HEADSET_PLUG

  • 插入外部储存装置(如SD卡) Intent.ACTION_MEDIA_CHECKING

  • 成功安装APK Intent.ACTION_PACKAGE_ADDED

  • 成功删除APK Intent.ACTION_PACKAGE_REMOVED

  • 重启设备 Intent.ACTION_REBOOT

  • 屏幕被关闭 Intent.ACTION_SCREEN_OFF

  • 屏幕被打开 Intent.ACTION_SCREEN_ON

  • 关闭系统时 Intent.ACTION_SHUTDOWN

  • 重启设备 Intent.ACTION_REBOOT

3.有序广播

有序广播,也比较常用。发送广播和普通广播的区别是有序广播 通过sendOrderedBroadCast发送广播。
有序广播的顺序,体现在广播接收者中,是按照广播接收者的优先顺序来的,优先级在清单文件中广播接收者的intent-filter中通过设置priority属性来定义。
例如:

<intent-filter android:priority="2000">

priority值越大,表示优先级越高

当优先级最高的广播接收者接收到广播后,可以通过setResult继续传递广播,也可以通过abortBroadcast()中断广播的继续传播

代码示例:

<receiver
    android:name=".MyOrderedReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter android:priority="1000">
        <action android:name="com.neil.ordered" />
    </intent-filter>
</receiver>
<receiver
    android:name=".MySecondOrderedReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter android:priority="500">
        <action android:name="com.neil.ordered" />
    </intent-filter>
</receiver>

发送广播:

void sendOrderedBroadcast (Intent intent, 
                String receiverPermission)

接收广播和普通广播一样,只是按照优先级有接收的先后顺序。

4.粘性广播

注册与接收和普通广播是一样的,但是需要添加权限,否则会抛出异常。

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

和普通广播的区别是,sendStickyBroadcast它将发出的广播保存起来,一旦发现有人注册这条广播,则立即能接收到。比较简单,就不举例说明了。

5.应用内广播

应用内广播更加安全。用法基本一样,只是应用内广播是通过LocalBroadcastManager来实现的。示例如下:

首先还是注册和解绑的操作,

LocalBroadcastManager.getInstance(this).registerReceiver(localBroadcastDemo,intentFilter);

LocalBroadcastManager.getInstance(this).unregisterReceiver(localBroadcastDemo);

组装好Intent后,发送广播。

LocalBroadcastManager.getInstance(MainActivity.this).sendBroadcast(intent);

广播接收者和普通广播一样。

Java死锁浅析

发表于 2018-08-18 |

一、死锁的基本概念

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。”那么我们换一个更加规范的定义:“集合中的每一个进程都在等待只能由本集合中的其他进程才能引发的事件,那么该组进程是死锁的。

产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。

  • 互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
  • 不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
  • 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
  • 循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, …, pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, …, n-1),Pn等待的资源被P0占有
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
public class SyncABDemo {
public static void main(String args[]){
Object a = new Object();
Object b = new Object();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
synchronized(a) {
try{
System.out.println("ThreadA--a is Locked");
Thread.sleep(1000);
synchronized(b) {
System.out.println("ThreadA--b is Locked");
}
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
synchronized(b) {
try{
System.out.println("ThreadB--a is Locked");
Thread.sleep(1000);
synchronized(a) {
System.out.println("ThreadB--b is Locked");
}
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
});
threadA.start();
threadB.start();
}
}

输出结果为:

1
2
ThreadA--a is Locked
ThreadB--a is Locked

发现程序执行停滞了。

这个时候程序发生了死锁。

二、检测死锁

JDK自带检测工具
可以在IDEA命令行输入JConsole打开JConsole连接本地进程就可以进行检测死锁的操作了。

三、死锁预防

1.以确定的顺序获得锁

如果一定要获取多个锁,那么在设计的时候需要考虑多线程情况下获取锁的情况

例如上述代码示例:

  • 线程A -> 获取锁a -> 获取锁b -> 永久等待

  • 线程B -> 获取锁b -> 获取锁a -> 永久等待

那么如果把获取锁的时序更改为下面的情况:

  • 线程A -> 获取锁a -> 获取锁b -> 结束

  • 线程B -> 获取锁a -> 获取锁b -> 结束

那么死锁就不会发生了

2.超时放弃

当使用synchronized关键词提供的内置锁时,只要线程没有获得锁,那么就会永远等待下去,然而Lock接口提供了

boolean tryLock(long time, TimeUnit unit) throws InterruptedException

方法,该方法可以按照固定时长等待锁,因此线程可以在获取锁超时以后,主动释放之前已经获得的所有的锁。通过这种方式,也可以很有效地避免死锁。

四、死锁举例

线程池死锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
final ExecutorService executorService =
Executors.newSingleThreadExecutor();
Future<Long> f1 = executorService.submit(new Callable<Long>() {
public Long call() throws Exception {
System.out.println("start f1");
Thread.sleep(1000);//延时
Future<Long> f2 =
executorService.submit(new Callable<Long>() {
public Long call() throws Exception {
System.out.println("start f2");
return -1L;
}
});
System.out.println("result" + f2.get());
System.out.println("end f1");
return -1L;
}
});

Java创建线程的几种姿势

发表于 2018-08-08 |

Java创建线程的几种姿势

一、简单介绍

在并发编程中,最基本的就是创建线程了。一般创建线程有四种方式:

  1. 继承Thread
  2. 实现Runnable接口
  3. 实现Callable接口,结合FutureTask使用
  4. 利用线程池ExecutorService、Callable、Future来实现

二、基本使用

1.继承Thread
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ExtendThread extends Thread{
String threadName;
public ExtendThread(String name) {
super(name);
threadName = name;
}
@Override
public void run() {
System.out.println("Thread -- Run--"+threadName);
}
public static void main(String args[]){
ExtendThread extendThread1 = new ExtendThread("Thread1");
ExtendThread extendThread2 = new ExtendThread("Thread2");
extendThread1.start();
extendThread2.start();
}
}

继承Thread类,覆写run()方法,通过start()方法启动新线程。

2.实现Runnable接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ImplRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread--Run"+Thread.currentThread().getName());
}
public static void main(String args[]){
ImplRunnable implRunnable = new ImplRunnable();
Thread thread1 = new Thread(implRunnable,"Thread1");
Thread thread2 = new Thread(implRunnable,"Thread2");
thread1.start();
thread2.start();
}
}

实现Runnable接口,实现run()方法,获取实现Runnable接口的实例,创建Thread,通过start()启动新的线程

3.实现Callable接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ImplCallable implements Callable {
@Override
public String call() throws Exception {
System.out.print("Hello");
return "World";
}
public static void main(String args[]) {
ImplCallable implCallable = new ImplCallable();
FutureTask<String> futureTask = new FutureTask<String>(implCallable);
new Thread(futureTask).start();
try {
System.out.println(futureTask.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}

实现Callable接口,实现call()方法,创建FutureTask实例,将FutureTask作为Thread的参数创建Thread,通过start()启动一个新的线程,通过FutureTask.get()获取线程的返回结果。

4.通过线程池创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ThreadPoolDemo {
public static void main(String args[]) {
System.out.println("Threadname:"+Thread.currentThread().getName());
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("Hello" + Thread.currentThread().getName());
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("World" + Thread.currentThread().getName());
}
});
}
}

创建线程池,创建Runnable任务,通过execute执行任务

三、使用区别

Runnable和Callable区别

Runnable与Callable最根本的区别就是:

  • Runnable无返回结果
  • Callable有返回结果
1.Runnable

Runnable不关心返回,所以任务自己默默的执行就可以了,也不用告诉我完成没有,我不care,您自己随便玩,所以一般使用就是

1
new Thread(new Runnable() { public void run() {...} }).start()

换成JDK8的 lambda表达式就更简单了

1
new Thread(() -> {}).start();
2.Callable

Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!

1
2
3
FutureTask<Object> future = new FutureTask<>(() -> null);
new Thread(future).start();
Object obj = future.get(); // 这里会阻塞,直到线程返回值

Android过度绘制

发表于 2018-07-27 |

一、基础知识

android 的渲染主要分为两个组件:1.CPU 2.GPU,由这两者共同完成在屏幕上绘制 。

  • CPU:中央处理器,它集成了运算,缓冲,控制等单元,包括绘图功能.CPU将对象处理为多维图形,纹理(Bitmaps、Drawables等都是一起打包到统一的纹理)。
  • GPU:一个类似于CPU的专门用来处理Graphics的处理器,用来帮助加快格栅化操作,当然,也有相应的缓存数据(例如缓存已经光栅化过的bitmap等)机制。

  • OpenGL ES:手持嵌入式设备的3DAPI,跨平台的、功能完善的2D和3D图形应用程序接口API,有一套固定渲染管线流程。

  • DisplayList:把XML布局文件转换成GPU能够识别并绘制的对象。这个操作是在DisplayList的帮助下完成的。DisplayList持有所有将要交给GPU绘制到屏幕上的数据信息。

  • 栅格化:将图片等矢量资源,转化为一格格像素点的像素图,显示到屏幕上。

  • 垂直同步VSYNC:让显卡的运算和显示器刷新率一致以稳定输出的画面质量。它告知GPU在载入新帧之前,要等待屏幕绘制完成前一帧。下面的三张图分别是GPU和硬件同步所发生的情况,Refresh Rate:屏幕一秒内刷新屏幕的次数,由硬件决定,例如60Hz.而Frame Rate:GPU一秒绘制操作的帧数,单位fps。

    二、什么是过度绘制?

过度绘制(Overdraw)描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。在多层次重叠的 UI 结构里面,如果不可见的 UI 也在做绘制的操作,会导致某些像素区域被绘制了多次,同时也会浪费大量的 CPU 以及 GPU 资源,绘制示意图如下图所示

overdraw

  • 原色:没有过度绘制

  • 蓝色:1 次过度绘制

  • 绿色:2 次过度绘制

  • 粉色:3 次过度绘制

  • 红色:4 次及以上过度绘制

三、过度绘制优化

1. 去除Activity自带的默认window背景

一般应用默认继承的主题都会有一个默认的 windowBackground ,比如默认的 Light 主题:

<style name="Theme.Light"> 
<item name="isLightTheme">true</item> 
<item name="windowBackground">@drawable/screen_background_selector_light</item>
 ...
  </style>

但是一般界面都会自己设置界面的背景颜色或者列表页则由 item 的背景来决定,所以默认的 Window 背景基本用不上,如果不移除就会导致所有界面都多 1 次绘制。

可以在应用的主题中添加如下的一行属性来移除默认的 Window 背景:

<item name="android:windowBackground">@android:color/transparent</item>
 <!-- 或者 --> 
 <item name="android:windowBackground">@null</item>

或者在 BaseActivity 的 onCreate() 方法中使用下面的代码移除:

getWindow().setBackgroundDrawable(null); 
或者
getWindow().setBackgroundDrawableResource(android.R.color.transparent);

移除默认的 Window 背景的工作在项目初期做最好,因为有可能有的界面未设置背景色,这就会导致该界面显示成黑色的背景,如下所示,如果是后期移除的,就需要检查移除默认 Window 背景之后的界面是否显示正常。

2.移除不必要的背景

还是上面的那个界面,因为移除了默认的 Window 背景,所以在布局中设置背景为白色:

1…345…14
Neil Liu

Neil Liu

优秀不够,你是否无可替代

68 日志
25 标签
GitHub
© 2019 Neil Liu
由 Hexo 强力驱动
主题 - NexT.Muse