Android中Notification工具类

效果图

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.BitmapFactory;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.support.v7.app.NotificationCompat;

import com.eallcn.rentagent.R;

/**
* Author: liuqiang
* Date: 2016-07-07
* Time: 17:23
* 在一些国产的手机,一定在安全管理的通知管理中打开
* 尤其是OPPO
*/
public class NotificationUtil {

protected static int notifyID = 0525; // start notification id
protected static int foregroundNotifyID = 0555; //当前app是不是在用户可是界面

public static void send(String string, Context context, Class activity) {

NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(context);

Intent intent = new Intent(context, activity);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(
context,
notifyID,
intent,
PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(pendingIntent);// 设置通知栏点击意图

mBuilder.setContentTitle("这是标题");// 设置通知栏标题
mBuilder.setContentText(string);
mBuilder.setSmallIcon(R.drawable.ic_launcher);// 设置通知小ICON(5.0必须采用白色透明图片)
mBuilder.setTicker(string + "有警报!"); // 通知首次出现在通知栏,带上升动画效果的

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//悬挂式Notification,5.0后显示
mBuilder.setFullScreenIntent(pendingIntent, true);
mBuilder.setCategory(NotificationCompat.CATEGORY_MESSAGE);
mBuilder.setVisibility(Notification.VISIBILITY_PUBLIC);
}

mBuilder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher));// 设置通知大ICON
mBuilder.setWhen(System.currentTimeMillis());// 通知产生的时间,会在通知信息里显示,一般是系统获取到的时间
mBuilder.setPriority(NotificationCompat.PRIORITY_MAX); // 设置该通知优先级
mBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);//在任何情况下都显示,不受锁屏影响。
mBuilder.setAutoCancel(true);// 设置这个标志当用户单击面板就可以让通知将自动取消
mBuilder.setOngoing(false);// ture,设置他为一个正在进行的通知。他们通常是用来表示一个后台任务,用户积极参与(如播放音乐)或以某种方式正在等待,因此占用设备(如一个文件下载,同步操作,主动网络连接)
// 向通知添加声音、闪灯和振动效果的最简单、最一致的方式是使用当前的用户默认设置,使用NotificationCompat.DEFAULT_ALL属性,可以组合
mBuilder.setVibrate(new long[]{0, 100, 500, 100});//振动效果需要振动权限

Uri defaultSoundUrlUri = RingtoneManager
.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); //声音

mBuilder.setSound(defaultSoundUrlUri);
mBuilder.setDefaults(NotificationCompat.DEFAULT_LIGHTS);//闪灯

NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Activity.NOTIFICATION_SERVICE);
//Notification notification = mBuilder.getNotification();//API 11

Notification notification = mBuilder.build();//API 16

mNotificationManager.notify(1, notification);
}


public static void sendNotification(Context context, String message, boolean isForeground) {

NotificationManager notificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);

try {
String notifyText = message;
PackageManager packageManager = context.getPackageManager();
String appName = (String) packageManager
.getApplicationLabel(context.getApplicationInfo());

// notification title
String contentTitle = appName;
String packageName = context.getApplicationInfo().packageName;

Uri defaultSoundUrlUri = RingtoneManager
.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
// create and send notification
android.support.v4.app.NotificationCompat.Builder mBuilder = new android.support.v4.app.NotificationCompat.Builder(context)
.setSmallIcon(context.getApplicationInfo().icon)
.setSound(defaultSoundUrlUri)
.setWhen(System.currentTimeMillis())
.setAutoCancel(true);

Intent msgIntent = context.getPackageManager()
.getLaunchIntentForPackage(packageName);

PendingIntent pendingIntent = PendingIntent.getActivity(
context,
notifyID,
msgIntent,
PendingIntent.FLAG_UPDATE_CURRENT);

mBuilder.setPriority(NotificationCompat.PRIORITY_MAX); // 设置该通知优先级
mBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);//在任何情况下都显示,不受锁屏影响。
mBuilder.setContentTitle(contentTitle);
mBuilder.setTicker(notifyText);
mBuilder.setContentText(notifyText);
mBuilder.setContentIntent(pendingIntent);
mBuilder.setFullScreenIntent(pendingIntent, true);
mBuilder.setOnlyAlertOnce(false);
mBuilder.setDefaults(NotificationCompat.DEFAULT_LIGHTS);//闪灯
Notification notification = mBuilder.build();

if (isForeground) {
notificationManager.notify(foregroundNotifyID, notification);
notificationManager.cancel(foregroundNotifyID);
} else {
notificationManager.notify(notifyID, notification);
}

} catch (Exception e) {
e.printStackTrace();
}
}

}

Sublime3 Mac下的使用

1. 打开命令板comand + Shift + P

这个功能超级好用,在Mac OS 系统下是上面的快捷键调出命令面板。这里面包含很多文档操作的命令和其他控制命令。比如修改当前文档语法检测类型,从普通的文本文档,改成java 或者Python。Set Syntax:

2. PackageControl 包管理器

用途:用于安装插件,当然你也可以不用,可以直接手动安装插件。

官网:包管理器(Sublime3)

安装方式: 打开控制台(ctrl + ~或者 菜单栏选择 view -> show console)然后输入下面代码,注意不能有回车,下面代码必须在一行中:

import urllib.request,os; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); open(os.path.join(ipp, pf), 'wb').write(urllib.request.urlopen( 'http://sublime.wbond.net/' + pf.replace(' ','%20')).read())

3. 卸载Sublime 3

卸载之后再次安装的时候,会在~/Library/Application\ Support/Sublime\ Text\ 3/ 下存在缓存,注意要删除。

4. 常用快捷功能

  • Command+P匹配到文件后,我们可以进行后续输入以跳转到更精确的位置:

    @符号跳转:输入@symbol跳转到symbol符号所在的位置
    :行号跳转:输入:12跳转到文件的第12行。
    #关键字跳转:输入#keyword跳转到keyword所在的位置

  • alt 左右 光标跳到下一个或上一个单词+

  • commans shift f 目录查找,可以全局搜索也可以针对目录搜索
  • ReindentLines 缩进功能 command + [ 或者 command + ]这功能比较好,可以直接通过command + shift + p 的命令面板中输入reindent lines 然后就看到这个命令了。
  • "auto_complete_commit_on_tab": true,设置只有tab 键才能出发代码片段补全功能。菜单栏 Sublime Text -> preference -> Settings-Default

5. 用到的几个插件

6. 快捷键

  • ⌘⌥F 替换
  • ⌘L 选择行 (重复按下将下一行加入选择)
  • ⌘D 选择词
  • ⌘T 前往文件
  • Ctrl + - 回到来的光标位置
  • ⌘⌥2 拆分窗口双列
  • super+alt+[ 代码折叠

Android反编译后重新打包apk

为什么要反编译别人的代码?

  • 人家比咱写的好,学习
  • 看中了人家的某个控件,抄袭
  • 看中了人家的本地数据库,想要
  • 但是人家的本地数据库加密了,要读源码才能解密

为什么要打包别人的apk?

  • 反编译后的代码有些地方读不通顺。想添加log。

一个基本的逆向工程的流程。

apktool反编译apk –> 修改图片等资源文件(或者smali源码)–> apktool 打包apk –> 对打包好的apk用jarsigner签名 [–> zipalign优化apk,最后这步可选操作]

分别对应得详细操作:

  1. apktool反编译apk

    $ apktool d test.apk

  2. 修改图片,填好string.xml,修改smali

    详细操作,尽情的折腾吧,隐藏某个view,添加某个view。
    一篇有用的文章,后来补充

  3. apktool 打包

    apktool b test 这里的test是反编译test.apk 后生成的文件夹。最终的生成的新的未签名的apk的路径为./test/dist/test.apk

  4. 用你自己的证书对别人的apk进行签名

    这是当然了,你反编译了别人的app,可定拿不到人家的证书,但是Android系统对没有用证书签名的apk是不允许安装的。这时候我们自己生成一个证书。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    第一步:生成RSA密钥对 
    keytool -genkeypair -alias adorkable_alias -keyalg RSA -validity 400 -keystore adorkable.keystore

    -genkeypair 指定生成密钥对
    -alias 密钥对的别名
    -keyalg 密钥对用于的算法,这里用的是RSA
    -validity 密钥对的有效期,单位为天
    -keystore 密钥对存储的文件名

    输入后,根据提示输入相应的内容就好了。

    第二步:对未签名的apk进行签名
    jarsigner -verbose -keystore adorkable.keystore -signedjar result_singed.apk my_unsigned.apk adorkable_alias

    -verbose 输出签名详细信息
    -keystore 指定密钥对的存储路径
    -signedjar 后面三个参数分别是 签名后的APK包 未签名的APK包 和 密钥对的别名

    签名时,会要求输入密钥对的密码,这个是你在生成密钥时输入的密码

到这里就可以用adb install 将app安装到手机里了

  1. 优化apk包

    这一步是可选的,用来将apk包进行整理,以适应设备的读取等
    zipalign -f -v 4 test.apk test_zipaligned.apk

    -f 强制覆盖已有的文件
    -v 输出详细内容
    4 指定档案整理的字节数,一般为4,及32位。如果以后android的设备有64位的,可能要改成8吧。
    test.apk 是未整理(没有zipalign)的apk文件名
    test_zipaligned.apk 是整理后的apk文件名

adb命令行的使用

  1. 查看当前Activity堆栈信息

    $ adb shell dumpsys activity activities

  2. 查看未root手机的/data/data/com.xxx.xxx/ 下的内容

    $ adb shell
    shell@R9:/ $ run-as com.xxx.xxx

    com.xxx.xxx 是包名,并且这个包名的APP处于debug模式

PopupWindow点击空白处隐藏

  1. 方法一

    1
    2
    3
    mPopupWindow.setBackgroundDrawable(new BitmapDrawable());    //这个BitmapDrawable废弃了可以用下面的这个
    //mPopupWindow.setBackgroundDrawable(new BitmapDrawable(context.getResources(), (Bitmap) null));
    mPopupWindow.setOutsideTouchable(true);
  2. 监听Activity的OnTouchEvent

1
2
3
4
5
6
7
8
@Override
public boolean onTouchEvent(MotionEvent event) {
if (popupWindow != null && popupWindow.isShowing()) {
popupWindow.dismiss();
//popupWindow = null;
}
return super.onTouchEvent(event);
}

点击空白处的时候让PopupWindow消失的一点说明

关于PopupWindow最搞笑的地方是setOutsideTouchable方法,原本以为如果你setOutsideTouchable(true)则点击PopupWindow之外的地方PopupWindow会消失,其实这玩意儿好像一点用都没有。

要让点击PopupWindow之外的地方PopupWindow消失你需要调用setBackgroundDrawable(new BitmapDrawable());

设置背景,为了不影响样式,这个背景是空的。还可以这样写,觉得这样要保险些:

setBackgroundDrawable(new ColorDrawable(0x00000000));

背景不为空但是完全透明。如此设置还能让PopupWindow在点击back的时候消失。其实一直觉得很奇怪,不明白为什么一个背景会影响点击事件,只知道这样用可

Android显示HTML格式

实现如下效果:

Hello! 小傻You have 5 new messages.

  1. string.xml

    <string name="welcome_messages"><![CDATA[Hello! <font color="#ff0000">%1$s</font>You have %2$d new messages.]]></string>

    注意html中的引号,如果用单引号就要加转译字符\,为了方便就用双引号吧。

  2. 在Activity中动态获取并设置

    1
    2
    3
    4
    Resources resources = getResources();
    String htmlStr = resources.getString(R.string.welcome_messages,"小傻",5);
    Spanned spannedText = Html.fromHtml(htmlStr);
    textView.setText(spannedText);

其实上面的代码可以写成一行:
textView.setText(Html.fromHtml(getResources().getString(R.string.welcome_messages,"小傻",5)));

MPAndroidChart画线形图

最近要做一个折线图,就搜到了 MPAndroidChart

效果图如下

MPAndroidChart绘制后的效果图

gradle 引入

compile 'com.github.PhilJay:MPAndroidChart:v3.0.0-beta'

以下是Activity的代码

布局文件很简单就包含一个com.github.mikephil.charting.charts.LineChart标签,不再给出:

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package febsky.me.testmpchart;

import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.AxisBase;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.formatter.AxisValueFormatter;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.listener.OnChartValueSelectedListener;

import java.util.ArrayList;

public class MultiLineActivity extends AppCompatActivity implements OnChartValueSelectedListener {

private LineChart mChart;

private int[] mColors = new int[]{
Color.rgb(255, 0, 0), //红色
Color.rgb(2, 196, 244), //淡蓝色
Color.rgb(2, 123, 243) //深蓝
};
protected String[] mMonths = new String[]{
"1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"
};

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


mChart = (LineChart) findViewById(R.id.chart);
mChart.setOnChartValueSelectedListener(this);

initChartView();

/**-------------这里的数据不重要,主要用随机数的方式生成点坐标-------------**/
//设置模拟数据
ArrayList<Entry> yVals = new ArrayList<Entry>();
for (int i = 0; i < 12; i++) {
yVals.add(new Entry(i, (float) (Math.random() * 10000f)));
}

ArrayList<Entry> yVals2 = new ArrayList<Entry>();
for (int i = 0; i < 12; i++) {
yVals2.add(new Entry(i, (float) (Math.random() * 10000f)));
}

addDataSet(yVals, "一居");
addDataSet(yVals2, "两居");
/**--------------------------**/

//图标的下边的指示块 图例
Legend l = mChart.getLegend();
l.setForm(Legend.LegendForm.LINE);
l.setXEntrySpace(40);
}

private void initChartView() {
mChart.setDrawGridBackground(false);
mChart.setDescription(""); //右下角说明文字
mChart.setDrawBorders(false); //四周是不是有边框
// mChart.setBorderColor(); //边框颜色,默认黑色?


//控制轴上的坐标绘制在什么地方 上边下边左边右边
XAxis xAxis = mChart.getXAxis();
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
xAxis.enableGridDashedLine(10f, 10f, 0f); //给整成虚线
xAxis.setAxisMinValue(0f); //设置轴的最小值。这样设置将不会根据提供的数据自动计算。
xAxis.setGranularity(1f); //缩放的时候有用,比如放大的时候,我不想把横轴的月份再细分
xAxis.setValueFormatter(new AxisValueFormatter() { //绘制横轴显示
@Override
public String getFormattedValue(float value, AxisBase axis) {
return mMonths[(int) value % mMonths.length];
}

@Override
public int getDecimalDigits() {
return 0;
}
});
xAxis.setDrawLabels(true); //是不是显示轴上的刻度

mChart.getAxisRight().setEnabled(false);
YAxis leftAxis = mChart.getAxisLeft();
leftAxis.setEnabled(true);
leftAxis.enableGridDashedLine(10f, 10f, 0f);

// enable touch gestures
mChart.setTouchEnabled(true);
// if disabled, scaling can be done on x- and y-axis separately
mChart.setPinchZoom(true);
// enable scaling and dragging
mChart.setDragEnabled(true);
mChart.setScaleEnabled(true);

//一个chart中包含一个Data对象,一个Data对象包含多个DataSet对象,
// 每个DataSet是对应一条线上的所有点(相对于折线图来说)
mChart.setData(new LineData());
}


private void addDataSet(ArrayList<Entry> entryList, String dataSetName) {

LineData data = mChart.getData();

if (data != null) {
int count = data.getDataSetCount();

LineDataSet set = new LineDataSet(entryList, dataSetName);
set.setLineWidth(1.5f);
set.setCircleRadius(3.5f);

int color = mColors[count % mColors.length];

set.setColor(color);
set.setCircleColor(color);
set.setValueTextSize(10f);
set.setDrawValues(false); //节点不显示具体数值
set.setValueTextColor(color);
set.setDrawHorizontalHighlightIndicator(false);//取消横向辅助线
set.setDrawVerticalHighlightIndicator(false); //取消纵向辅助线

data.addDataSet(set);
data.notifyDataChanged();
mChart.notifyDataSetChanged();
mChart.invalidate();
}

}

@Override
public void onValueSelected(Entry e, Highlight h) {
Log.i("Q_M:", e.toString());
}

@Override
public void onNothingSelected() {

}
}

AndroidStudio中查看依赖库的本地存储路径

这两天由于公司网速慢,每次要下载v4 和 v7 包非常耗时。把gradle 改成离线模式就好多了。据说离线模式不再重新下载依赖。但是很好奇,AndroidStudio的gradle 引入的第三方库到底缓存到本地的什么位置了。

可以在app目录下的build.gradle的文件的最后添加:

1
2
3
4

task showMeCache << {
configurations.compile.each { println it }
}

然后,在命令行切换到你的项目根目录下运行:

./gradlew showMeCache

这时候控制台就好打印出依赖库的缓存位置(下面是我显示的环信一个demo的所有的依赖库,包括lib引入,和在gradle的maven库引入的。):

1
2
3
4
5
6
7
8
9
10
11
12
liuqiangs-MacBook-Pro:TestHx liuqiang$ ./gradlew showMeCache
Incremental java compilation is an incubating feature.
:app:showMeCache
/Users/liuqiang/Workspace/meiliwu/TestHx/app/libs/hyphenatechat_3.1.3.jar
/Users/liuqiang/Development/android-sdk-macosx/extras/android/m2repository/com/android/support/appcompat-v7/23.4.0/appcompat-v7-23.4.0.aar
/Users/liuqiang/.gradle/caches/modules-2/files-2.1/com.jakewharton/butterknife/7.0.1/d5d13ea991eab0252e3710e5df3d6a9d4b21d461/butterknife-7.0.1.jar
/Users/liuqiang/Development/android-sdk-macosx/extras/android/m2repository/com/android/support/design/23.4.0/design-23.4.0.aar
/Users/liuqiang/Development/android-sdk-macosx/extras/android/m2repository/com/android/support/animated-vector-drawable/23.4.0/animated-vector-drawable-23.4.0.aar
/Users/liuqiang/Development/android-sdk-macosx/extras/android/m2repository/com/android/support/support-v4/23.4.0/support-v4-23.4.0.aar
/Users/liuqiang/Development/android-sdk-macosx/extras/android/m2repository/com/android/support/support-vector-drawable/23.4.0/support-vector-drawable-23.4.0.aar
/Users/liuqiang/Development/android-sdk-macosx/extras/android/m2repository/com/android/support/recyclerview-v7/23.4.0/recyclerview-v7-23.4.0.aar
/Users/liuqiang/Development/android-sdk-macosx/extras/android/m2repository/com/android/support/support-annotations/23.4.0/support-annotations-23.4.0.jar

我们可以看到,v4 和 v7的支持包,是下载下来放到你本地Android sdk的extras 目录下的,所以,我猜测,可以直接从别人电脑拷贝这个文件夹的内容,然后使用gradle的离线模式编译。这样就不会再网速差的情况下去下载 v4 和 v7 。这种想法仅仅是用于 v4和v7 其他的第三方依赖库可以看到是放到 ~/.gradle/cache 目录下的。

【参考文献】

在AndroidStudio中如何利用一个task显示出依赖库的本地存储路径