Android中的Notification

最近公司有一个通知的功能,于是想起之前看过的Notification,当时看的时候看到网上有很多的讲解和代码,就觉得这个功能真的是so easy,然而实际操作的时候才发现网上的代码坑真多。

首先说明一下,我是在三款手机上进行的测试,华为荣耀v10(Android8.0系统),oppo手机(Android7.0系统),魅蓝note5(Android6.0系统),还有一个oppo(Android5.0系统)。

在我写这个功能的过程中,遇到了一下几个问题:

1:对于8.0系统,需要使用NotificationChannel,创建Notification的时候需要用这行代码:Notification.Builder builder =new Notification.Builder(this);

并且必须给builder 设置ChannelId,通过setChannelId()这个方法。

2:在点击通知栏上的通知跳转到新界面后,通知不消失。

这个需要在新界面进行取消操作,在新界面的onCerate()方法中进行以下操作:

//关闭通知栏上的通知

NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

manager.cancel(5); //5表示跳转过来的通知的ID

3:华为8.0手机上修改提示音后提示音不变的问题:必须卸载重装才能生效

4:oppo手机上通知不显示问题:需要进行以下设置:

设置 ——》其他设置——》应用程序管理——》找到自己的应用——》点击后找到通知管理——》将通知管理里面的都打开

5:震动问题:魅蓝手机上需要打开震动按钮才能震动,oppo和华为不用

6:v4包和v7包的区别:

Android Support v4:这个包是为了照顾1.6及更高版本而设计的,这个包是使用最广泛的。

Android Support v7:这个包是为了考虑照顾2.1及以上版本而设计的,但不包含更低,故如果不考虑1.6,我们可以采用再加上这个包,另外注意,v7是要依赖v4这个包的,即,两个得同时被包含。

 Android Support v13:这个包的设计是为了android 3.2及更高版本的,一般我们都不常用,平板开发中能用到

7:Android3.0之前使用new Notification()创建Notification对象,3.0之后就不用这么创建对象了

 Notification notification = new Notification();

8:呼吸灯问题:setLights()这个方法设置的是屏幕这一侧的呼吸灯,不是手机背面的摄像头的闪光灯,至于不知道什么是呼吸灯的同学,可以百度一下

9:权限问题:只需要在清单文件中声明震动权限即可,不需要动态获取权限

下面是代码(注意:代码中已经对8.0进行了适配)

package gcg.testproject.activity.notifycation;

import android.Manifest;

import android.app.Notification;

import android.app.NotificationChannel;

import android.app.NotificationManager;

import android.app.PendingIntent;

import android.content.Context;

import android.content.Intent;

import android.content.SharedPreferences;

import android.graphics.BitmapFactory;

import android.graphics.Color;

import android.media.AudioManager;

import android.net.Uri;

import android.os.Build;

import android.os.Bundle;

import android.os.SystemClock;

import android.preference.PreferenceManager;

import android.provider.MediaStore;

import android.provider.Settings;

import android.support.annotation.RequiresApi;

import android.support.v7.app.NotificationCompat;

import android.view.View;

import android.widget.RemoteViews;

import android.widget.TextView;

import com.bumptech.glide.load.model.file_descriptor.FileDescriptorStringLoader;

import butterknife.Bind;

import butterknife.ButterKnife;

import gcg.testproject.R;

import gcg.testproject.base.BaseActivity;

import gcg.testproject.utils.LogUtils;

public class Notifycation2Activityextends BaseActivityimplements View.OnClickListener {

@Bind(R.id.tv_common)

TextViewmTvCommon;

@Bind(R.id.tv_remove)

TextViewmTvRemove;

@Bind(R.id.tv_bigicon)

TextViewmTvBigicon;

@Bind(R.id.tv_progress)

TextViewmTvProgress;

@Bind(R.id.tv_multi)

TextViewmTvMulti;

@Bind(R.id.tv_custom)

TextViewmTvCustom;

private int num_else;

private int num;

private Stringid ="my_channel_01";

private Stringname="我是渠道名字";

private NotificationChannelmChannel;

private NotificationManagermanager;//通知管理器,用于发送通知Notification对象

    private boolean isO;//用来判断是不是8.0系统

    @RequiresApi(api = Build.VERSION_CODES.O)

@Override

    protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_notifycation2);

ButterKnife.bind(this);

mTvCommon.setOnClickListener(this);

mTvRemove.setOnClickListener(this);

mTvBigicon.setOnClickListener(this);

mTvProgress.setOnClickListener(this);

mTvMulti.setOnClickListener(this);

mTvCustom.setOnClickListener(this);

manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {

isO =true;

mChannel =new NotificationChannel(id,name, NotificationManager.IMPORTANCE_DEFAULT);

mChannel.setBypassDnd(true);//设置绕过免打扰模式

            mChannel.canBypassDnd();//检测是否绕过免打扰模式

            mChannel.setLockscreenVisibility(Notification.VISIBILITY_SECRET);//设置在锁屏界面上显示这条通知

            //设置通知出现时的呼吸灯

            mChannel.enableLights(true);

mChannel.enableVibration(true);

mChannel.setLightColor(Color.WHITE);

//卸载重装,让设置的震动生效  注意:设置震动时不用动态申请震动权限,只在清单文件中声明即可

            mChannel.setVibrationPattern(new long[]{100,200,300,400,500,400,300,200,400});

Uri mUri = Settings.System.DEFAULT_NOTIFICATION_URI;

mChannel.setSound(mUri, Notification.AUDIO_ATTRIBUTES_DEFAULT);

//自定义声音    注意:要想让设置的声音生效,必须卸载重装

//            AudioAttributes.Builder audioAttributesBuilder = new AudioAttributes.Builder();

//            audioAttributesBuilder.setLegacyStreamType(AudioManager.STREAM_MUSIC);

//            AudioAttributes aa = audioAttributesBuilder.build();

//            Uri uri = Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.umeng_push_notification_default);

//            mChannel.setSound(uri, aa);

            manager.createNotificationChannel(mChannel);

}else {

isO =false;

}

}

@RequiresApi(api = Build.VERSION_CODES.O)

@Override

    public void onClick(View view) {

switch (view.getId()) {

case R.id.tv_common://发送一个普通通知

                LogUtils.i("送一个普通通知");

if (isO)

{

btn_8_common();

}else {

else_common();

}

break;

case R.id.tv_remove://移除通知

                LogUtils.i("移除通知");

//                manager.cancel(5);

                manager.cancelAll();

break;

case R.id.tv_bigicon://设置大图通知

                LogUtils.i("设置大图通知");

if (isO)

{

btn_8_bigicon();

}else {

else_big();

}

break;

case R.id.tv_progress://发送带进度条的通知

                LogUtils.i("发送带进度条的通知");

if (isO)

{

btn_8_progress();

}else {

else_progress();

}

break;

case R.id.tv_multi://封装多行文本样式

                LogUtils.i("封装多行文本样式");

if (isO)

{

btn_8_multi();

}else {

else_multi();

}

break;

case R.id.tv_custom://完全自定义的通知

                LogUtils.i("完全自定义的通知");

if (isO)

{

btn_8_custom();

}else {

else_custom();

}

break;

}

}

//    ========================== 以下是8.0以下的通知========================================

    private void else_common(){

LogUtils.i("else_common");

//        注意:Android3.0之前使用new Notification()创建Notification对象

//        Notification notification = new Notification();

//        Android Support v4:这个包是为了照顾1.6及更高版本而设计的,这个包是使用最广泛的。

//        Android Support v7:这个包是为了考虑照顾2.1及以上版本而设计的,但不包含更低,故如果不考虑1.6,

//        我们可以采用再加上这个包,另外注意,v7是要依赖v4这个包的,即,两个得同时被包含。

//        Android Support v13:这个包的设计是为了android 3.2及更高版本的,一般我们都不常用,平板开发中能用到

        //Android3.0之后用

        NotificationCompat.Builder builder  =new NotificationCompat.Builder(this);

builder.setContentTitle("设置标题" + System.currentTimeMillis());

builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));//设置大图标

        builder.setContentText("内容文本部分");

builder.setContentInfo("info信息");//设置info信息,即设置显示在时间右下角的文字

        builder.setSmallIcon(R.mipmap.ic_launcher);//必须要设置的小图标

        builder.setWhen(System.currentTimeMillis());//设置通知时间

        builder.setTicker("设置滚动提示的文字设置滚动提示的文字设置滚动提示的文字设置滚动提示的文字设置滚动提示的文字");

builder.setOngoing(false);//设置左右滑动时是否会消失

        builder.setAutoCancel(true);

builder.setDefaults(Notification.DEFAULT_SOUND);//设置默认铃声

        //设置自定义铃声

//        builder.setSound(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.umeng_push_notification_default)); //调用系统多媒体裤内的铃声

        //设置震动;部分手机必须打开震动功能才能震动

        long[] vibrate =new long[]{0,500,500,500,500};

builder.setVibrate(vibrate);

//        builder.setDefaults(Notification.DEFAULT_VIBRATE); //设置默认震动

//          builder.setDefaults(Notification.DEFAULT_ALL);

        //设置呼吸灯(不是闪光灯);注意:颜色跟设备有关,不是所有的颜色都可以,要看具体设备。

        //注意:闪烁时长和熄灭时长也会跟设备有关,不完全跟设置的一样

        builder.setLights(0xff0000ff,300,0);

num_else++;

manager.notify(num_else, builder.build());//发送通知

    }

//部分手机的大图通知在6.0系统上亲测不能显示大图,如果要显示大图,建议设置自定义view

    private void else_big()

{

LogUtils.i("else_big");

NotificationCompat.Builder builder  =new NotificationCompat.Builder(this);

builder.setSmallIcon(R.mipmap.t2);

builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.t1));//设置大图标

        builder.setContentTitle("大图片通知");

builder.setContentText("内容文本部分");

builder.setTicker("大图通知来了");

builder.setContentInfo("info信息");//设置info信息,即设置显示在时间右下角的文字

        builder.setDefaults(Notification.DEFAULT_SOUND);//设置默认铃声

        manager.notify(7, builder.build());

}

private void else_progress()

{

LogUtils.i("else_progress");

final NotificationCompat.Builder builder  =new NotificationCompat.Builder(this);

builder.setSmallIcon(R.mipmap.ic_launcher);

builder.setContentTitle("带进度条的通知");

builder.setProgress(100,0,false);

builder.setDefaults(Notification.DEFAULT_SOUND);//设置默认铃声

        manager.notify(3, builder.build());

new Thread(new Runnable() {

@Override

            public void run() {

for (int i =0; i <=100; i++) {

builder.setProgress(100, i,false);

manager.notify(3,builder.build());

SystemClock.sleep(200);

if (i ==100)//当进度条走完后,清除这个通知

                    {

manager.cancel(3);

}

}

}

}).start();

}

private void else_multi()

{

NotificationCompat.InboxStyle inboxStyle =new NotificationCompat.InboxStyle();

for (int i =0; i <10; i++) {

inboxStyle.addLine("这是第" + i +"行" +"文本");

}

NotificationCompat.Builder builder  =new NotificationCompat.Builder(this);

builder.setSmallIcon(R.mipmap.ic_launcher);

builder.setContentTitle("多行文本");

inboxStyle.setBigContentTitle("多行文本标题");

builder.setStyle(inboxStyle);

builder.setDefaults(Notification.DEFAULT_SOUND);//设置默认铃声

        manager.notify(4, builder.build());

}

private void else_custom()

{

RemoteViews views =new RemoteViews(getPackageName(),R.layout.remote);

NotificationCompat.Builder builder  =new NotificationCompat.Builder(this);

builder.setContent(views);

builder.setSmallIcon(R.mipmap.ic_launcher);//设置了setContent属性后,setSmallIcon就不生效了

        builder.setContentTitle("自定义布局");//设置了setContent属性后,setContentTitle就不生效了

        builder.setContentText("点击联系人行可跳转");//设置了setContent属性后,setContentText就不生效了

        builder.setAutoCancel(true);//设置点击通知自动消失,8.0时并没有用

        builder.setOngoing(false);//true:左右滑动时通知不会消失  false:左右滑动时可以滑动消失

        builder.setDefaults(Notification.DEFAULT_SOUND);//设置默认铃声

        Notification build = builder.build();

build.flags = Notification.FLAG_AUTO_CANCEL;

PendingIntent pendingIntent = PendingIntent.getActivity(this,1,

new Intent(Notifycation2Activity.this, NotifycationActivity.class),

PendingIntent.FLAG_UPDATE_CURRENT);

//当前设置代表点击id为R.id.but_re的按钮时,要执行跳转到TwoActivity页面的操作

        views.setOnClickPendingIntent(R.id.item, pendingIntent);

manager.notify(5, build);

}

//    ========================== 以下是8.0上的通知==========================================

    @RequiresApi(api = Build.VERSION_CODES.O)

private void btn_8_common() {

Notification.Builder builder =new Notification.Builder(this);

builder.setChannelId(id);//8.0必须设置信道

        builder.setContentTitle("设置标题" + System.currentTimeMillis());

builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher));//设置大图标

        builder.setContentText("内容文本部分");

builder.setContentInfo("info信息");//设置info信息,即设置显示在时间右下角的文字

        builder.setSmallIcon(R.mipmap.ic_launcher);//必须要设置的小图标

        builder.setWhen(System.currentTimeMillis());//设置通知时间

        builder.setTicker("设置滚动提示的文字");

builder.setOngoing(false);

num++;

manager.notify(num, builder.build());//发送通知

    }

@RequiresApi(api = Build.VERSION_CODES.O)

private void btn_8_bigicon() {

Notification.Builder builder =new Notification.Builder(this);

builder.setChannelId(id);//8.0必须设置信道

        builder.setSmallIcon(R.mipmap.ic_launcher);

builder.setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.t1));//设置大图标

        builder.setContentTitle("大图片通知");

builder.setContentText("内容文本部分");

builder.setTicker("大图通知来了");

manager.notify(7, builder.build());

}

@RequiresApi(api = Build.VERSION_CODES.O)

private void btn_8_progress() {

final Notification.Builder builder =new Notification.Builder(this);

builder.setSmallIcon(R.mipmap.ic_launcher);

builder.setContentTitle("带进度条的通知");

builder.setProgress(100,0,false);

builder.setChannelId(id);//8.0必须设置信道

        manager.notify(3, builder.build());

new Thread(new Runnable() {

@Override

            public void run() {

for (int i =0; i <=100; i++) {

builder.setProgress(100, i,false);

manager.notify(3,builder.build());

SystemClock.sleep(500);

}

}

}).start();

}

@RequiresApi(api = Build.VERSION_CODES.O)

private void btn_8_multi() {

Notification.InboxStyle inboxStyle =new Notification.InboxStyle();

for (int i =0; i <10; i++) {

inboxStyle.addLine("这是第" + i +"行" +"文本");

}

Notification.Builder builder =new Notification.Builder(this);

builder.setChannelId(id);

builder.setSmallIcon(R.mipmap.ic_launcher);

builder.setContentTitle("多行文本");

inboxStyle.setBigContentTitle("多行文本标题");

builder.setStyle(inboxStyle);

manager.notify(4, builder.build());

}

@RequiresApi(api = Build.VERSION_CODES.O)

private void btn_8_custom() {

RemoteViews views =new RemoteViews(getPackageName(),R.layout.remote);

Notification.Builder builder =new Notification.Builder(this);

builder.setContent(views);

builder.setSmallIcon(R.mipmap.ic_launcher);//设置了setContent属性后,setSmallIcon就不生效了

        builder.setContentTitle("自定义布局");//设置了setContent属性后,setContentTitle就不生效了

        builder.setContentText("点击联系人行可跳转");//设置了setContent属性后,setContentText就不生效了

        builder.setChannelId(id);

builder.setAutoCancel(true);//设置点击通知自动消失,8.0时并没有用

        builder.setOngoing(false);//true:左右滑动时通知不会消失  false:左右滑动时可以滑动消失

        Notification build = builder.build();

build.flags = Notification.FLAG_AUTO_CANCEL;

PendingIntent pendingIntent = PendingIntent.getActivity(this,1,

new Intent(Notifycation2Activity.this, NotifycationActivity.class),

PendingIntent.FLAG_UPDATE_CURRENT);

//当前设置代表点击id为R.id.but_re的按钮时,要执行跳转到TwoActivity页面的操作

        views.setOnClickPendingIntent(R.id.item, pendingIntent);

manager.notify(5, build);

}

}

=================================================

package gcg.testproject.activity.notifycation;

import android.app.Notification;

import android.app.NotificationManager;

import android.app.Service;

import android.graphics.BitmapFactory;

import android.os.Bundle;

import android.support.v7.app.NotificationCompat;

import android.view.View;

import android.widget.TextView;

import butterknife.Bind;

import butterknife.ButterKnife;

import gcg.testproject.R;

import gcg.testproject.base.BaseActivity;

import gcg.testproject.utils.LogUtils;

//参考链接:https://www.jianshu.com/p/f2cf23d6d836

public class NotifycationActivityextends BaseActivity{

@Bind(R.id.tv_send)

TextViewmTvSend;

@Override

    protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_notifycation);

ButterKnife.bind(this);

//关闭通知栏上的通知

        NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

manager.cancel(5);

}

}

======================= 布局文件 ====================


<TextView

    android:id="@+id/tv_common"

    android:gravity="center"

    android:textColor="#fff"

    android:textSize="20sp"

    android:text="普通通知"

    android:background="#4caf65"

    android:layout_width="match_parent"

    android:layout_height="50dp"/>

<TextView

android:layout_marginTop="@dimen/dp10"

    android:id="@+id/tv_remove"

    android:gravity="center"

    android:textColor="#fff"

    android:textSize="20sp"

    android:text="移除通知"

    android:background="#4caf65"

    android:layout_width="match_parent"

    android:layout_height="50dp"/>

<TextView

android:layout_marginTop="@dimen/dp10"

    android:id="@+id/tv_bigicon"

    android:gravity="center"

    android:textColor="#fff"

    android:textSize="20sp"

    android:text="设置大图通知"

    android:background="#4caf65"

    android:layout_width="match_parent"

    android:layout_height="50dp"/>

<TextView

android:layout_marginTop="@dimen/dp10"

    android:id="@+id/tv_progress"

    android:gravity="center"

    android:textColor="#fff"

    android:textSize="20sp"

    android:text="进度条通知"

    android:background="#4caf65"

    android:layout_width="match_parent"

    android:layout_height="50dp"/>

<TextView

 android:layout_marginTop="@dimen/dp10"

    android:id="@+id/tv_multi"

    android:gravity="center"

    android:textColor="#fff"

    android:textSize="20sp"

    android:text="多行文本样式通知"

    android:background="#4caf65"

    android:layout_width="match_parent"

    android:layout_height="50dp"/>

<TextView

 android:layout_marginTop="@dimen/dp10"

    android:id="@+id/tv_custom"

    android:gravity="center"

    android:textColor="#fff"

    android:textSize="20sp"

    android:text="完全自定义通知"

    android:background="#4caf65"

    android:layout_width="match_parent"

    android:layout_height="50dp"/>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容

  • 原文出处: http://www.androidchina.net/6174.html Notification在...
    木木00阅读 12,275评论 3 32
  • 我们在用手机的时候,如果来了短信,而我们没有点击查看的话,是不是在手机的最上边的状态栏里有一个短信的小图标提示啊?...
    0o失魂鱼o0阅读 759评论 0 2
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,263评论 25 707
  • 9月10日,星期日 今天早上看了两章,第二章只能理解大概,细节无法get到,第三章比特币客户端,比特币客户端的下载...
    007刘利珍阅读 373评论 0 0
  • 木木的又一个21天。浪费了三张原版纸砖,奋斗了一晚上,数集电视剧,在周莹、无心的陪伴下终于有张可以看得下去的作业。...
    鱼_sunny阅读 138评论 2 3