-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
133 lines (93 loc) · 111 KB
/
search.xml
File metadata and controls
133 lines (93 loc) · 111 KB
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
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[蓝牙打印机ESC/POS指令学习]]></title>
<url>%2F2017%2F01%2F17%2F%E8%93%9D%E7%89%99%E6%89%93%E5%8D%B0%E6%9C%BAESC-POS%E6%8C%87%E4%BB%A4%E5%AD%A6%E4%B9%A0%2F</url>
<content type="text"><![CDATA[蓝牙打印机作为一款蓝牙设备,使用手机连接到打印机之后,可以向其发送指令控制其进行打印数据,这里的指令指的是ESC/POS指令,这套指令集兼容市面上大多数蓝牙打印机设备,但对于一些比较旧的蓝牙打印机,会存在指令不兼容的情况,这些旧的设备,它们的程序版本没有升级更新,因此打印的时候会出现乱码、格式不对等问题。使用蓝牙打印机可以打印文字、图片、二维码、条形码等信息。通过这篇Android蓝牙:发现、配对、连接,成功连接打印机之后,可以拿到BluetoothSocket对象,通过这个socket可以得到OutputStream,然后调用write(byte[] data)向这个输出流中写入指令数据即可。 获取输出流、写数据123456789101112131415161718192021222324public OutputStream mOutputStream;public void getOutStream(BluetoothSocket socket){ try { if (socket != null){ mOutputStream = socket.getOutputStream(); }else{ Log.e("eee", "传入的socket为空"); } } catch (IOException e) { e.printStackTrace(); }}//向连接流中写数据public void write(byte[] data){ if (mOutputStream != null){ try { mOutputStream.write(data); mOutputStream.flush(); } catch (IOException e) { e.printStackTrace(); } }} 常用的ESC/POS指令在网上有很多的指令文档,但要注意的是,并不是每个打印文档上的指令都适用你所用的打印机,因此最好的方式是:购买打印机时请商家提供相应的指令集文档和demo程序,这可以让我们避免一些坑,否则出现乱码或者打印格式不对,很可能就是指令造成的。打印指令描述如下图所示: 下面列举一些常用的指令:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071/*** 初始化打印机:清除打印缓存,各参数恢复默认值* */public void initPrinter(){ write(new byte[]{0x1B, 0x40});}/** * 进行换行 * */public void setLF(){ write("\n".getBytes());}/** * 设置行间距为n点 * * 范围:0-255,默认值为:33 * * @param n 间距点数 * */public void setLineSpace(byte n){ write(new byte[]{0x1B,0x33,n});}/** * 设置左边距 * * 纸宽 58mm:0 ≤ n ≤ 47,且 0 ≤ (左边距 + 右边距) ≤ 47 * 纸宽 80mm:0 ≤ n ≤ 71,且 0 ≤ (左边距 + 右边距) ≤ 71 * * @param n 间距点数 * * */public void setLeftMargin(byte n){ write(new byte[]{0x1B,0x6C,n});}/** * 设置右边距 * * 纸宽 58mm:0 ≤ n ≤ 47,且 0 ≤ (左边距 + 右边距) ≤ 47 * 纸宽 80mm:0 ≤ n ≤ 71,且 0 ≤ (左边距 + 右边距) ≤ 71 * */public void setRightMargin(byte n){ write(new byte[]{0x1B, 0x51,n});}/** * 设置打印字体 * * 须明确打印机是否支持多字体 * * 参数 n 意义如下: * n 类型 * 0 中文:24×24,外文:12×24 * 1 中文:16×16,外文:8×16 * 2 中文:12×12,外文:6×12 * */public void setPrintFont(byte n){ write(new byte[]{0x1B, 0x4D, n});}/** * 设置对其方式 * * 0, 48 居左 1, 49 居中 2, 50 居右 * */public void setAlignMethod(byte n){ write(new byte[]{0x1B, 0x61,n);} 更多打印所需的指令都可以在指令文档中找到。 打印文字123456789101112131415161718/*** 打印文本* */public void printText(String text){ if (text.length() > 0) { // Get the message bytes and tell the BluetoothService to write byte[] send; try { send = text.getBytes("GB2312"); Log.d("dy", "send.length:" + send.length); } catch (UnsupportedEncodingException e) { send = text.getBytes(); } write(send); }} 打印图片在打印图片时,需要对图片做一些特殊处理,主要有对图片大小进行调整,然后对图片色彩进行转换,因为打印机打印出的内容只有黑白两种颜色。彩色图片—-> 灰度图片—–> 二值图 调整图片大小1234567891011121314151617181920212223242526272829public static Bitmap resizeImage(Bitmap bitmap, int w, int h) { // load the origial Bitmap Bitmap BitmapOrg = bitmap; int width = BitmapOrg.getWidth(); int height = BitmapOrg.getHeight(); int newWidth = w; int newHeight = h; // calculate the scale float scaleWidth = ((float) newWidth) / width; float scaleHeight = ((float) newHeight) / height; // create a matrix for the manipulation Matrix matrix = new Matrix(); // resize the Bitmap matrix.postScale(scaleWidth, scaleHeight); // if you want to rotate the Bitmap // matrix.postRotate(45); // recreate the new Bitmap Bitmap resizedBitmap = Bitmap.createBitmap(BitmapOrg, 0, 0, width, height, matrix, true); // make a Drawable from Bitmap to allow to set the Bitmap // to the ImageView, ImageButton or what ever return resizedBitmap;} 转为灰度图12345678910111213141516// 转成灰度图public static Bitmap toGrayscale(Bitmap bmpOriginal) { int width, height; height = bmpOriginal.getHeight(); width = bmpOriginal.getWidth(); Bitmap bmpGrayscale = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas c = new Canvas(bmpGrayscale); Paint paint = new Paint(); ColorMatrix cm = new ColorMatrix(); cm.setSaturation(0); ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm); paint.setColorFilter(f); c.drawBitmap(bmpOriginal, 0, 0, paint); return bmpGrayscale; } 转为二值图123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657/** * 将ARGB图转换为二值图,0代表黑,1代表白 * * @param mBitmap * @return */public static byte[] bitmapToBWPix(Bitmap mBitmap) { int[] pixels = new int[mBitmap.getWidth() * mBitmap.getHeight()]; byte[] data = new byte[mBitmap.getWidth() * mBitmap.getHeight()]; mBitmap.getPixels(pixels, 0, mBitmap.getWidth(), 0, 0, mBitmap.getWidth(), mBitmap.getHeight()); // for the toGrayscale, we need to select a red or green or blue color //进行二值化 format_K_threshold(pixels, mBitmap.getWidth(), mBitmap.getHeight(), data); return data;}public static void format_K_threshold(int[] orgpixels, int xsize,int ysize, byte[] despixels) { int graytotal = 0; int grayave = 128; int i, j; int gray; int k = 0; for (i = 0; i < ysize; i++) { for (j = 0; j < xsize; j++) { gray = orgpixels[k] & 0xff; graytotal += gray; k++; } } grayave = graytotal / ysize / xsize; // 二值化 k = 0; for (i = 0; i < ysize; i++) { for (j = 0; j < xsize; j++) { gray = orgpixels[k] & 0xff; if (gray > grayave){ despixels[k] = 0;// white }else{ despixels[k] = 1; } k++; } }} 逐行打印图片打印图片指令如下图: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556// 之所以弄成一维数组,是因为一维数组速度会快一点private static int[] p0 = { 0, 0x80 };private static int[] p1 = { 0, 0x40 };private static int[] p2 = { 0, 0x20 };private static int[] p3 = { 0, 0x10 };private static int[] p4 = { 0, 0x08 };private static int[] p5 = { 0, 0x04 };private static int[] p6 = { 0, 0x02 };// nWidth必须为8的倍数,这个只需在上层控制即可, nMode = 0 或者 1private static byte[] eachLinePixToCmd(byte[] src, int nWidth, int nMode) { int nHeight = src.length / nWidth; int nBytesPerLine = nWidth / 8; byte[] data = new byte[nHeight * (8 + nBytesPerLine)]; int offset = 0; int k = 0; for (int i = 0; i < nHeight; i++) { offset = i * (8 + nBytesPerLine); data[offset + 0] = 0x1d; data[offset + 1] = 0x76; data[offset + 2] = 0x30; data[offset + 3] = (byte) (nMode & 0x01); data[offset + 4] = (byte) (nBytesPerLine % 0x100); data[offset + 5] = (byte) (nBytesPerLine / 0x100); data[offset + 6] = 0x01; data[offset + 7] = 0x00; for (int j = 0; j < nBytesPerLine; j++) { data[offset + 8 + j] = (byte) (p0[src[k]] + p1[src[k + 1]] + p2[src[k + 2]] + p3[src[k + 3]] + p4[src[k + 4]] + p5[src[k + 5]] + p6[src[k + 6]] + src[k + 7]); k = k + 8; } } return data;}//整合以上所有方法,直接将此方法返回的byte[]数据写进流中public static byte[] bitmapToByte(Bitmap mBitmap, int nWidth, int nMode){ Log.e("eee", "图片宽度:" + nWidth); int width = ((nWidth + 7) / 8) * 8; int height = mBitmap.getHeight() * width / mBitmap.getWidth(); height = ((height + 7) / 8) * 8; //调整大小 Bitmap rszBitmap = resizeImage(mBitmap, width, height); //灰度化 Bitmap grayBitmap = toGrayscale(rszBitmap); //转为二值图 byte[] dithered = bitmapToBWPix(grayBitmap); //进行每行打印 byte[] data = eachLinePixToCmd(dithered, nWidth, nMode); return data;} 使用指令去控制蓝牙打印机,是挺严格的,每个指令都要正确,不然打印出来的东西不是乱码就是格式错误,这部分也比较偏,与硬件有一定的联系,希望可以帮助到项目中有使用到蓝牙打印机的开发者。]]></content>
</entry>
<entry>
<title><![CDATA[Android蓝牙:发现、配对、连接]]></title>
<url>%2F2017%2F01%2F14%2FAndroid%E8%93%9D%E7%89%99%EF%BC%9A%E6%89%AB%E6%8F%8F%E3%80%81%E9%85%8D%E5%AF%B9%E3%80%81%E8%BF%9E%E6%8E%A5%2F</url>
<content type="text"><![CDATA[蓝牙是一种短距离无线通信标准、协议。 Android平台包含蓝牙网络堆栈支持,因此设备能够以无线方式与其他蓝牙设备进行数据交换。连接蓝牙设备流程是获取到蓝牙设备,进行配对,进行连接,然后进行数据传输,蓝牙打印机作为蓝牙设备的一种,其操作流程也是这样。蓝牙开发相关所有的API都在android.bluetooth包下,使用这些API就能使APP以无线方式连接到其他蓝牙设备,从而实现点到点和多点无线功能。一般情况下,我们所说的蓝牙指的是传统蓝牙,传统蓝牙适用于电池使用强度较大的操作,在Android4.3(API 级别18)中引入了面向低功耗蓝牙的API支持,称为BLE蓝牙。 在开发中主要使用到的两个类: BluetoothAdapter:这个类的对象代表了本地的蓝牙适配器,是所有蓝牙交互的入口点,比如app运行在手机上,那么手机上的蓝牙适配器就是本地蓝牙适配器,发现蓝牙设备、配对、连接基本都要使用它来进行,通过判断该类对象是否为NULL,就可以知道设备是否支持蓝牙,因此该类是开发蓝牙功能的核心类。 BluetoothDevice:该类表示一个远程蓝牙设备,包含蓝牙设备的一些属性,主要有蓝牙名称、MAC地址、绑定状态等,利用它可以通过BluetoothSocket请求与某个远程设备建立连接。 设置蓝牙添加蓝牙权限要操作蓝牙,需要在AndroidManifest.xml中添加蓝牙权限:123<!--添加蓝牙权限--><uses-permission android:name="android.permission.BLUETOOTH"/><uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> BLUETOOTH权限:允许应用程序去连接已经配对的设备。请求连接、接收连接、传输数据需要该权限,主要用于配对之后的操作。BLUETOOTH_ADMIN权限:允许应用程序去发现和配对远程蓝牙设备。用来管理蓝牙设备,拥有此权限后,才能使用本机的蓝牙设备,主要用于配对之前的操作。优先级:使用BLUETOOTH_ADMIN权限的前提是必须有BLUETOOTH权限。 启动蓝牙获取BluetoothAdapter:要获取BluetoothAdapter,只需调用静态getDefaultAdapter()方法。将返回一个表示设备自身的蓝牙适配器,整个系统只有一个蓝牙适配器,如果此方法返回null,则说明设备不支持蓝牙,也就不能进行蓝牙相关的开发。123456BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();if (mBluetoothAdapter == null){ Toast.makeText(MainActivity.this, "设备不支持蓝牙", Toast.LENGTH_SHORT).show();}else{ //进行下一步操作} 启用蓝牙:可以通过调用isEnabled()来判断蓝牙是否启用,返回true说明设备蓝牙已经开启,返回false则蓝牙处于停用状态。123456if(! mBluetoothAdapter.isEnabled()){ //请求开启设备蓝牙 Intent openBT = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); //REQUEST_OPEN_BT_CODE = 1 startActivityForResult(openBT, REQUEST_OPEN_BT_CODE);} 以上代码将通过系统设置发出启动蓝牙的请求,将弹出一个Dialog,请求用户开启蓝牙,单击‘OK’后,将开启蓝牙,如果开启成功,将在onActivityResult()回调函数中收到RESULT_OK结果码,如果没有开启,将会收到RESULT_CANCELED结果码。除此之外,如果检测到蓝牙没有开启,还可以进行强制开启,而不会弹出Dialog,这是比较流氓的做法,不过我喜欢。1234if(! mBluetoothAdapter.isEnabled()){ //强制开启蓝牙 mBluetoothAdapter.enable();} 扫描蓝牙设备开启设备的蓝牙之后,就可以扫描附近处于开启状态的蓝牙设备了,这个过程是一个比较耗费资源的过程,因此在找到要连接的设备之后,就停止扫描,或者在连接蓝牙设备之前停止扫描,如果已经与某一台蓝牙设备连接,那么扫描过程可能会大幅减少用于连接使用的带宽,因此也不应该在设备连接之后进行扫描操作。进行扫描只需调用startDiscovery()方法,该方法的返回值是boolean类型,返回true则表示开启扫描操作成功。具体是注册一个针对ACTION_FOUND Intent的BroadcastReceiver,在广播接收器中可以拿到扫描到的设备对象,从而得到设备的相关信息。123456789101112131415161718192021222324252627282930313233343536public void startScanBT(){ IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothDevice.ACTION_FOUND); //发现设备 filter.addAction(BluetoothAdapter. ACTION_DISCOVERY_STARTED ); //扫描开始 filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); //扫描结束 registerReceiver(scanBTReceiver, filter); if(mBTAdapter.startDiscovery()){ Log.i("iii" , " 扫描蓝牙进程开启成功... "); }else{ Log.e("eee" , " 扫描蓝牙进程开启失败... "); }}private BroadcastReceiver scanBTReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(BluetoothDevice.ACTION_FOUND)){ //扫描到的蓝牙设备 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); //将扫描到的设备add到List中 mListDevices.add(device); }else if (action.equals(BluetoothAdapter. ACTION_DISCOVERY_STARTED )){ //扫描开始时要执行的操作 Log.i("iii" , "wa, 设备扫描开始啦..." ); }else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)){ //扫描结束时进行的操作 Log.i("iii", "ok, 设备扫描终于结束了,好累..."); } }}; 停止扫描进程,调用cancelDiscovery()即可:123if (mBTAdapter != null){ mBTAdapter.cancelDiscovery();} 配对的设备除了扫描蓝牙之外,可以通过调用getBondedDevices()得到已经配对成功的设备集,这个API返回值为Set<BluetoothDevice>:1Set<BluetoothDevice> bonedDevices = mBTAdapter.getBondedDevices(); 关于配对结果回调:蓝牙配对状态也是通过广播接收器来实现的 >>12345678910111213141516171819202122private BroadcastReceiver bondedBTReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); switch (device.getBondState()) { //设备的配对状态 case BluetoothDevice.BOND_BONDING: Toast.makeText(MainActivity.this,"正在配对...", Toast.LENGTH_SHORT).show(); break; case BluetoothDevice.BOND_NONE: Toast.makeText(MainActivity.this, "没有设备进行配对...", Toast.LENGTH_SHORT).show(); break; case BluetoothDevice.BOND_BONDED: Toast.makeText(MainActivity.this ,"配对完成...", Toast.LENGTH_SHORT).show(); break; default: break; } }};IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);registerReceiver(bondedBTReceiver, filter); 对远程进行配对,只需调用BluetoothDevice的createBond (),此方法为一个异步方法。123456public void createBond(BluetoothDevice device){ if (device.getBondState() == BluetoothDevice.BOND_NONE ){ //需要API >= 19 device.createBond(); }} 记得要在Activity销毁时进行取消注册:12unregisterReceiver(scanBTReceiver); //取消注册扫描广播 unregisterReceiver(bondedBTReceiver); //取消注册绑定广播 配对与连接之间的区别:配对意味着两个设备之间知道彼此的存在,通过配对密钥,建立一个加密的连接。而连接意味着设备之间共享一个通信通道,能够彼此传输数据。 当前的 Android Bluetooth API 要求对设备进行配对,然后才能建立 RFCOMM 连接。 (在使用 Bluetooth API 发起加密连接时,会自动执行配对)。 连接为client两台设备之间建立连接,一台充当client的角色,另一台充当server的角色,client角色的设备发起连接,主要使用server端设备的Mac地址,server端开放server socket,当client和server在同一个通信通道上拥有已经连接的BluetoothSocket时,就视为连接成功,可以通过socket拿到I/O流,进而传输数据。蓝牙打印机只能被动地被其他设备连接,其开放了一个服务器套接字并侦听其他设备的连接。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687/** * Description: * Created by L02465 on 2016.12.24. */public class ConnectBTThread extends Thread { private BluetoothDevice mBTDevice; private Handler mHandler; private BluetoothSocket mBTSocket; private BluetoothAdapter mBTAdapter; public static UUID STR_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"); public ConnectBTThread (BluetoothDevice device,BluetoothAdapter adapter, Handler handler){ this.mBTDevice = device; this.mBTAdapter = adapter; this.mHandler = handler; BluetoothSocket socketTemp = null; try{ socketTemp = device.createInsecureRfcommSocketToServiceRecord(STR_UUID); }catch (IOException ex){ ex.printStackTrace(); } Log.e("eee", "进入连接线程的构造方法...."); mBTSocket = socketTemp; //设为全局的socket App.getInstance().mSocket = mBTSocket; } @Override public void run() { Log.e("eee","进入线程的run()..."); try { //连接之前先断掉设备扫描 mBTAdapter.cancelDiscovery(); if (!mBTSocket.isConnected()) { //进行连接 mBTSocket.connect(); //使用反射进行连接// mBTSocket =(BluetoothSocket) mBTDevice.getClass().getMethod("createRfcommSocket", new Class[] {int.class}).invoke(mBTDevice,1);// mBTSocket.connect(); } if (mHandler == null) { return; } //连接成功发送message Message message = new Message(); message.what = 0x111; message.obj = mBTDevice; mHandler.sendMessage(message); } catch (IOException ex) { Log.e("eee", "连接失败..."); Message message = new Message(); message.what = 0x222; mHandler.sendMessage(message); try { mBTSocket.close(); } catch (IOException ex1) { ex1.printStackTrace(); } ex.printStackTrace(); } Log.e("eee","run()运行结束..."); } //关闭socket public void close(){ try{ mBTSocket.close(); }catch (IOException ex){ //... } }} 主要是通过蓝牙设备对象,调用createRfcommSocketToServiceRecord(UUID)获取到BluetoothSocket,此处的UUID必须与服务器端一致,调用connect()进行连接,此方法为阻塞调用,连接超时时将引发异常,因为是阻塞的,因此连接蓝牙设备应该在主线程之外的线程进行。 UUID: 称为通用唯一标识符,是用于唯一标识信息的字符串 ID 的 128 位标准化格式。 UUID 的特点是其足够庞大,因此可以选择任意随机值而不会发生冲突,通过UUID.fromString(String)初始化一个UUID。 连接为server以下代码来自于Google官方 -> API指南 -> 蓝牙模块:12345678910111213141516171819202122232425262728293031323334353637383940private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; public AcceptThread() { // Use a temporary object that is later assigned to mmServerSocket, // because mmServerSocket is final BluetoothServerSocket tmp = null; try { // MY_UUID is the app's UUID string, also used by the client code tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID); } catch (IOException e) { } mmServerSocket = tmp; } public void run() { BluetoothSocket socket = null; // Keep listening until exception occurs or a socket is returned while (true) { try { socket = mmServerSocket.accept(); } catch (IOException e) { break; } // If a connection was accepted if (socket != null) { // Do work to manage the connection (in a separate thread) manageConnectedSocket(socket); mmServerSocket.close(); break; } } } /** Will cancel the listening socket, and cause the thread to finish */ public void cancel() { try { mmServerSocket.close(); } catch (IOException e) { } }} 其中,accept()也是阻塞调用,因此需在子线程中调用。listenUsingRfcommWithServiceRecordAPI第一个参数字符串可以随意起一个,第二个参数UUID需和客户端使用的一致。 在连接成功后,两个设备都会有一个BluetoothSocket,可以通过这个socket得到I/O流,读取数据和写入数据,在两个设备之间传输数据。 获取 InputStream 和 OutputStream,二者分别通过套接字以及 getInputStream() 和 getOutputStream() 来处理数据传输 使用 read(byte[]) 和 write(byte[]) 读取数据并写入到流式传输 其中read(byte[])和write(byte[])都是阻塞调用,应该在子线程中进行读写操作,当然最后,必须调用mmSocket.close();来关闭此socket。 基础知识大概就这些吧,在项目中基本上都是在ListView显示扫描到的蓝牙设备,选择其中一项进行连接,之后再进行数据传输,对蓝牙的操作,尽可能在子线程中进行处理。最近一段时间在搞蓝牙打印机,蓝牙打印机作为服务端,被动地进行连接,手机向其发送它们能够识别的指令,称为ESC/POS指令集,现在市面上大多数的蓝牙打印机都是兼容这个指令集的,通过指令控制蓝牙打印机打印文字、图片、二维码等信息。因此,下一篇将对蓝牙打印机指令方面做简单介绍。]]></content>
</entry>
<entry>
<title><![CDATA[自定义Android中的对话框]]></title>
<url>%2F2016%2F12%2F18%2F%E8%87%AA%E5%AE%9A%E4%B9%89Android%E4%B8%AD%E7%9A%84%E5%AF%B9%E8%AF%9D%E6%A1%86%2F</url>
<content type="text"><![CDATA[在项目中,我们可能会经常使用到对话框,旨在告诉用户是否确定当前的操作,或者输入额外信息,对话框没有占满整个屏幕,单击对话框上的按钮或者上面的选项或者对话框外的屏幕能使对话框消失掉。关于对话框的几个类主要有Dialog,AlertDialog,其中Dialog是对话框的基类,AlertDialog是Dialog的子类,可用于显示最多三个按钮的对话框,也可对其进行自定义布局,此外,还有DatePickerDialog和 TimePickerDialog,可用于选择日期和时间,还有一个ProgressDialog,是显示进度条的对话框,常用于进行网络请求或者文件下载等。 创建对话框的常用代码片段123456789101112131415161718192021AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setTitle(title).setMessage(message) .setView(view) .setPositiveButton(context.getText(R.string.dialog_ok_btn), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Log.d("dialog", "onClick: " + "click positive button"); } }) .setNegativeButton(context.getText(R.string.dialog_cancel_btn), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); //取消对话框 } }) .setNeutralButton(context.getText(R.string.dialog_neutral_btn), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Log.d("dialog", "onClick: " + "click neutral button"); } }).create().show(); 对话框主要包含标题,内容区域和按钮。可以调用setView(View view)来将新的布局添加到对话框中。通过使用LayoutInflater ,并调用 inflate()来得到View对象,第一个参数传入一个布局资源Id, 第二个参数是布局的父视图,一般都传入null。 自定义对话框根据所需对话框的外观,先定义对话框的布局文件,包含标题、内容区域和按钮风格。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156/** * Description: 自定义对话框 * Created by lpc on 2016/12/17. */public class CustomDialog extends AlertDialog { protected CustomDialog(Context context) { super(context); } protected CustomDialog(Context context, int theme) { super(context, theme); } protected CustomDialog(Context context, boolean cancelable, OnCancelListener cancelListener) { super(context, cancelable, cancelListener); } public static class Builder{ private String title; private String message; private View view; private String positiveButtonText; private String negativeButtonText; private String neutralButtonText; private DialogInterface.OnClickListener positiveOnClick; private DialogInterface.OnClickListener negativeOnClick; private DialogInterface.OnClickListener neutralOnClick; private Context context; public Builder(Context context){ this.context = context; } public Builder setTitle(String title){ this.title = title; return this; } public Builder setTitle(int resId){ this.title = (String) context.getText(resId); return this; } public Builder setMessage(String message){ this.message = message; return this; } public Builder setMessage(int resId){ this.message = (String) context.getText(resId); return this; } public Builder setView(View view){ this.view = view; return this; } public Builder setPositiveText(String text, OnClickListener listener){ this.positiveButtonText = text; this.positiveOnClick = listener; return this; } public Builder setPositiveText(int resId, OnClickListener listener){ this.positiveButtonText = (String) context.getText(resId); this.positiveOnClick = listener; return this; } public Builder setNegativeText(String text, OnClickListener listener){ this.negativeButtonText = text; this.negativeOnClick = listener; return this; } public Builder setNegativeText(int resId, OnClickListener listener){ this.negativeButtonText = (String) context.getText(resId); this.negativeOnClick = listener; return this; } public Builder setNeutralText(String text, OnClickListener listener){ this.neutralButtonText = text; this.neutralOnClick = listener; return this; } public Builder setNeutralText(int resId, OnClickListener listener){ this.neutralButtonText = (String) context.getText(resId); this.neutralOnClick = listener; return this; } public CustomDialog create(){ final CustomDialog dialog = new CustomDialog(context); //加载自定义的对话框布局 View dialog_view = LayoutInflater.from(context).inflate(R.layout.custom_dialog_layout, null); TextView textTitle = (TextView) dialog_view.findViewById(R.id.dialog_title); textTitle.setText(title); TextView textMessage = (TextView) dialog_view.findViewById(R.id.dialog_message); LinearLayout contentView = (LinearLayout) dialog_view.findViewById(R.id.custom_view); if (message != null){ textMessage.setText(message); }else{ contentView.addView(view, new ViewGroup.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT)); } Button positive = (Button) dialog_view.findViewById(R.id.ok_btn); if (positiveButtonText != null){ positive.setText(positiveButtonText); if (positiveOnClick != null){ positive.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { positiveOnClick.onClick(dialog,DialogInterface.BUTTON_POSITIVE); } }); } }else{ positive.setVisibility(View.GONE); } Button negative = (Button) dialog_view.findViewById(R.id.cancel_btn); if (negativeButtonText != null){ negative.setText(negativeButtonText); if (negativeOnClick != null){ negative.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { negativeOnClick.onClick(dialog,DialogInterface.BUTTON_NEGATIVE); } }); } }else{ negative.setVisibility(View.GONE); } Button neutral = (Button) dialog_view.findViewById(R.id.neutral_btn); if (neutralButtonText != null){ neutral.setText(neutralButtonText); if (neutralOnClick != null){ neutral.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { neutralOnClick.onClick(dialog,DialogInterface.BUTTON_NEUTRAL); } }); } }else{ neutral.setVisibility(View.GONE); } dialog.setView(dialog_view); return dialog; } }} 在其中加载自定义的对话框布局,设置对话框标题、内容文本、布局和按钮文本以及按钮行为监听。当对话框不需要显示文本内容,而是要显示一个Edittext去输入信息时,使用setView方法去加载内容布局。调用和AlertDialog一样:1234567891011121314151617181920CustomDialog.Builder builder = new CustomDialog.Builder(context); builder.setTitle(title).setMessage(message).setView(view) .setNegativeText(R.string.dialog_cancel_btn, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .setNeutralText(R.string.dialog_neutral_btn, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Log.d("dialog", "onClick: " + "custom --- click neutral button"); } }) .setPositiveText(R.string.dialog_ok_btn, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Log.d("dialog", "onClick: " + "custom --- click positive button"); } }).create().show(); 使用DialogFragmentDialogFragment类提供创建对话框和管理其外观所需的所有控件,使用DialogFragment管理对话框可确保它能正确处理生命周期事件,该类是在API 11引入的,为了兼容更低的版本,应该使用支持库的中的DialogFragment,也就是android.support.v4.app.DialogFragment,通过继承该类来显示对话框。123456789101112131415161718192021222324252627/** * Description: * Created by lpc on 2016/12/18. */public class TipsDialogFragment extends DialogFragment { @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { //创建对话框 CustomDialog.Builder builder = new CustomDialog.Builder(getActivity()); builder.setTitle(R.string.dialog_title).setMessage(R.string.dialog_message) .setPositiveText(R.string.dialog_ok_btn, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //nothing } }) .setNegativeText(R.string.dialog_cancel_btn, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); return builder.create(); }} 显示对话框:123FragmentManager manager = getSupportFragmentManager();TipsDialogFragment fragment = new TipsDialogFragment();fragment.show(manager,"myDialog"); 清除对话框:除了触摸对话框让其消失外,还可以调用DialogFragment的dismiss()方法来手动清除对话框,如果在清除时要执行一些操作,则在DialogFragment中实现onDismiss()方法。如果单击“返回”按钮或者触摸对话框外部屏幕区域时,会调用onCancel()来取消对话框,此时系统会立即调用onDismiss(),因此将对话框从视图中移除时,通常应该调用dismiss()方法。]]></content>
</entry>
<entry>
<title><![CDATA[Android中的Menu]]></title>
<url>%2F2016%2F12%2F14%2FAndroid%E4%B8%AD%E7%9A%84Menu%2F</url>
<content type="text"><![CDATA[Android系统中的三种基本菜单 选项菜单和应用栏:选项菜单是activity的主菜单项,放置一些对application全局产生影响的操作,例如“setting”、“search”等 上下文菜单:当长按某一个元素时出现的菜单,它提供的操作将影响所选内容或上下文框架 弹出菜单:是以垂直列表形式显示一系列项目,这些项目锚定到调用该菜单的视图中,特别适用于提供与特定内容相关的大量操作,或者为命令的另一部分提供选项。 创建菜单和创建布局一样,可以使用XML进行定义,也可以直接在Activity中进行定义,但一般情况下,都是在XML文件中进行定义的,这也是推荐的做法。 在XML文件中定义菜单在XML菜单资源中定义菜单及其所有项,定义后,可以在Activity中扩充菜单资源。这样做的好处是: 更易于使用XML可视化菜单结构 将菜单内容与应用的行为代码分离 可以利用应用资源框架,为不同的版本、不同的屏幕尺寸创建菜单配置 在res\menu目录下创建.xml文件,没有menu目录则新建,文件名是随意的,但文件格式必须为xml1<menu> 定义Menu,即菜单项的容器,<menu>必须为xml文件的根节点,并且包含一个或多个<item>和<group>元素1<item> 创建MenuItem,表示一个菜单项,可以嵌套<menu>,以便创建子菜单1<group> 是<item>的不可见容器,支持对菜单项进行分类,使菜单共享活动状态和可见性等属性定义很简单菜单的例子如下:12345678910111213<?xml version="1.0" encoding="utf-8"?><menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/refresh" android:title="刷新" android:orderInCategory="100" android:icon="@drawable/refresh" android:showAsAction="always" app:showAsAction="always"/></menu> 和大多数组件一样,菜单项<item>也有如例子中的一些属性,android:orderInCategory属性,对每个MenuItem进行排序,数字越小,越显示在前面。特别地,android:showAsAction指定此项应作为操作项目显示在应用栏中的时间和方式,有如下值可以设定: never:不将MenuItem显示在ActionBar上 always:总是将MenuItem显示在ActionBar上 ifRoom:ActionBar上有空间则显示,没有则放入溢出菜单中 withText:显示在ActionBar,并显示文本 在XML定义完成后,在Activity中使用时,调用MenuInflater.inflate()来引入菜单文件。 创建选项菜单当API <= 10,也就是Android 2.3.x版本以下,按下菜单按钮时,选项菜单出现在屏幕底部,包含6个菜单项,多余菜单项则放入溢出菜单中。 当API >= 11,也就是Android 3.0及更高版本,选项菜单则出现在ActionBar中,从Android5.0(API > 21)开始被ToolBar所代替。在Activity中指定选项菜单,重写onCreateOptionsMenu():123456@Overridepublic boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.my_menu, menu); return true;} 处理菜单项的单击事件,需要重写onOptionsItemSelected()方法,通过getItemId()来识别菜单项,返回菜单项目的唯一ID。1234567891011@Overridepublic boolean onOptionsItemSelected(MenuItem item) { // Handle item selection switch (item.getItemId()) { case R.id.refresh: startRefresh(); return true; default: return super.onOptionsItemSelected(item); }} 创建上下文菜单提供上下文操作的方法有两种: 使用浮动上下文菜单。用户长按一个声明支持上下文菜单的视图时,菜单显示为菜单项的浮动列表,用户一次可对一个项目执行上下文操作。 使用上下文操作模式。它将在屏幕顶部显示上下文操作栏,其中包括影响所选项的操作项目。 创建浮动上下文菜单1.先通过调用registerForContextMenu(),注册与上下文菜单关联的View,并将其传递给View2.在Activity或Fragment中实现onCreateContextMenu()方法 长按注册的View后,将回调onCreateContextMenu()方法:123456@Overridepublic void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo){ super.onCreateContextMenu(menu, v, menuInfo); MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.context_menu, menu);} 3.实现onContextItemSelected(),响应菜单单击事件:选择菜单项时,将调用此方法 >>>1234567891011121314@Overridepublic boolean onContextItemSelected(MenuItem item) { AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); switch (item.getItemId()) { case R.id.edit: editNote(info.id); return true; case R.id.delete: deleteNote(info.id); return true; default: return super.onContextItemSelected(item); }} 使用上下文操作模式(略)创建弹出菜单PopupMenu 是锚定到 View 的模态菜单,控件足够的话,会显示在定位视图下方,否则显示在其上方。 创建步骤: 实例化PopupMenu及其构造函数,传递当前应用的Context以及锚定到的View 使用MenuInflater将菜单资源扩充到PopupMenu.getMenu()返回的Menu对象中. 调用PopupMenu.show() 例如单击一个按钮,弹出一个弹出菜单:123456public void onClick(View view){ PopupMenu popup = new PopupMenu(this, v); MenuInflater inflater = popup.getMenuInflater(); inflater.inflate(R.menu.actions, popup.getMenu()); popup.show();} 处理点击事件单击菜单项执行操作,必须实现PopupMenu.OnMenuItemClickListener接口,并通过调用setOnMenuItemclickListener()将其注册到PopupMenu。用户选择项目时,系统会在接口中调用onMenuItemClick()回调。12345678public void onClick(View view){ PopupMenu popup = new PopupMenu(this, v); //当前Activity实现了PopupMenu.OnMenuItemClickListener接口,对弹出菜单进行事件注册 popup.setOnMenuItemClickListener(this); MenuInflater inflater = popup.getMenuInflater(); inflater.inflate(R.menu.actions, popup.getMenu()); popup.show();} 12345678910111213@Overridepublic boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.archive: archive(item); return true; case R.id.delete: delete(item); return true; default: return false; }}]]></content>
</entry>
<entry>
<title><![CDATA[读《富爸爸穷爸爸》摘录]]></title>
<url>%2F2016%2F11%2F27%2F%E8%AF%BB%E3%80%8A%E5%AF%8C%E7%88%B8%E7%88%B8%E7%A9%B7%E7%88%B8%E7%88%B8%E3%80%8B%2F</url>
<content type="text"><![CDATA[这个书名在大学的时候看到过,已经忘记从那里看到推荐阅读这本书,当时只是把这个书名记在了备忘录中,却迟迟没有买这本书,当然也谈不上阅读了,最近在亚马逊浏览热门书籍的时候看到了,阅读量很多,好评如潮,毫不犹豫的买了。刚开始看的时候,作者就提出了富爸爸和穷爸爸对自己教育之间的差异,对自己在生活上的影响,马上被吸引了,富爸爸和穷爸爸对于金钱、对作者教育的差别,完全是不同的方向,也造就了作者不同的人生。我们很多人的观点和穷爸爸是一致的, 让我也意识到生活还不能缺这样一种东西,那就是财商,这本书读一遍或许还不能领悟其中的精髓,读第一本关于财商理财方面的书籍,确实被影响了。 若不具备足够的财务知识,不了解金钱运动的规律,他们就没有准备好进入他们面前的现实世界,因为在这个世界里会消费将比会储蓄更重要。 富人之所以越来越富,穷人之所以越来越穷,中产阶级之所以总是在债务的泥潭中挣扎,其中一个主要的原因就是,他们对金钱的认识不是来自学校,而是来自家庭。 假如你学会了生活这门课程,做任何事情你都会游刃有余。如果你学不会,生活照样会推着你转。人们通常会做两件事,一些人在生活推着他转的同时,抓机会。 如果你是那种没有勇气的人,生活每次推动你,你都会选择放弃。如果你是这种人,你的一生会过得稳稳当当,不做错事、假想着有事情发生时自救,然后慢慢变老,在无聊中死去。你会有许多朋友,他们很喜欢你,因为你真的是一个努力工作的好人。你的一生过得很安稳,处世无误。但事实是,你向生活屈服了,不敢承担风险。你的确想赢,但失败的恐惧超过了成功的兴奋。只有你可能赢,所以你选择了稳定。 感情使我们更加真实,它是我们行动的动力。忠实于你的感情,以你喜欢的方式运用你的头脑和感情,不要让它们控制你。 要学会让感情跟随你的思想,而不要让思想跟随你的感情。 资产是能把钱放进你口袋里的东西。负债是把钱从你口袋里取走的东西。 财富就是支撑一个人生存多长时间的能力,或者说,如果我今天停止工作,我还能活多久? 对成年人而言,把支出保持在低水平、减少借款并勤劳地工作会帮你打下一个稳固的资产基础。对于还未经济独立的年轻人来说,父母应该教他们搞清楚资产和负债的区别,让他们在离家、结婚、买房子、生孩子、陷入财务危机、完全依赖工作和贷款之前建立起坚实的资产基础,这是非常重要的。 真正的资产可以分为以下几类:1.不需我到场就可以正常运作的业务。我拥有它们,但由别人经营和管理。 2.股票 3.债券4.共同基金5.能够产生收入的房地产6.票据7.版税,如音乐、手稿、专利8.其他任何有价值、可产生收入或有增值潜力并且有很好销路的东西。 我们都拥有巨大的潜能–这是上天赏赐的礼物,然而,我们都或多或少地存在着某种自我怀疑的心理,从而阻碍前进的步伐。这种障碍很少是缺乏某种技术性的东西,更多的是缺乏自信。 不要背上数额过大的债务包袱,要保持低支出。首先增加自己的资产,然后,再用资产项产生的现金流来买大房子或好车子。 轻松的道路往往会越走越难,而艰难的道路往往会越走越轻松。 当涉及金钱、爱情、幸福、销售和合约等时,都应该记住为自己想要的东西先付出,然后才能得到加倍的回报。 第一本财商启迪书,需要学的东西还有很多…]]></content>
</entry>
<entry>
<title><![CDATA[构建清晰的代码结构总结]]></title>
<url>%2F2016%2F11%2F22%2F%E6%9E%84%E5%BB%BA%E6%B8%85%E6%99%B0%E7%9A%84%E4%BB%A3%E7%A0%81%E7%BB%93%E6%9E%84%2F</url>
<content type="text"><![CDATA[项目结构在创建新的项目初期,一般都会采用MVP模式对项目进行构建,现在很流行这种形式,为了使项目的结构清晰,在别人阅读代码时一目了然,使代码的结构更整洁,会对代码文件进行分类或分模块,让相关的代码文件放入其中,找相应代码文件的时候也更迅速,层次更加分明。如图: 各个目录的意义: adapter:存放适配器类 model:mvp的m层,用于提供数据,存放实体类、数据库相关类文件 presenter:mvp的p层,负责逻辑处理,连接view和model,并按照功能建立子目录进行细分 service:存放app中的服务类 taskapi:存放异步任务类或者mobileApi类 utils:工具类目录,比如获取app信息工具类,日志工具类,Toast工具类等 view:mvp中的v层,存放界面类,就是一些Activity和Fragment,并按照模块建立子目录进行细分,也包含自己定义的组件类 其他:其他类文件 这样细分是为了保证一个文件就是一个单独的类,不含有嵌套类。将Activity按照模块进行拆分后,可以迅速定位到一个界面,也有利于团队合作,边界清晰。同时为每个任务定义监听器,回调任务执行完成后的结果 在进行接口或者监听器命名时,应该规范化:12345678910//接口以大写字母`I`开头public interface IBaseView{}//监听器命名以`On`开头,用于任务结果的回调,比如登录:public interface OnLoginListener{ void loginSuccess(); void loginFailed(String errorMsg);} Application尽可能为每个应用定制自己的Application类 ,方便在应用启动时进行一些数据的初始化,并拿到应用全局的上下文,并在其中对应用的Activity进行管理,而且在使用一些第三方库的时候,在Application类中进行初始化是最好的方式。12345678910111213141516171819202122232425262728293031323334353637383940414243444546/** * Description: * Created by L02465 on 2016.11.22. */public class MleApp extends Application { private List<Activity> activities = new ArrayList<>(); private static MleApp instance; @Override public void onCreate() { super.onCreate(); instance = this; } public static MleApp getInstance(){ return instance; } //新建一个activity public void addActivity(Activity activity){ activities.add(activity); } //结束指定的activity public void finishActivity(Activity activity){ if (activity != null){ activities.remove(activity); activity.finish(); activity = null; } } //exit app public void exitApp(){ for (Activity activity:activities){ if (activity != null){ activity.finish(); } } System.exit(0); }} 记得在清单文件AndroidManifest.xml中调用:123456<application android:name=".MleApp" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" ... 有了这个类,可以很方便的在应用的任何地方都可以拿到上下文。 BaseActivity和BaseFragment编写应用时,Activity是必不可少的,它是处理界面组件并呈现给用户的主体,我们以前使用的MVC模式渐渐被MVP所替代,主要原因就是项目复杂时,Activity中需要处理太多的逻辑,导致单个Activity文件很臃肿,而使用MVP可以缓解这种问题。在编写Activity时,需要厘清代码结构,尤其是在onCreate()中,一般处理以下几件事情: 加载界面布局并初始化组件 初始化数据 为组件设置动作监听 逻辑处理 为此,可以编写一个抽象的BaseActivity类,继承于android.app.Activity,在其中定义抽象方法,让其他的Activity继承于BaseActivity,并实现抽象方法即可,这样onCreate()代码就清晰了。如下:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677/** * Description: BaseActivity * Created by L02465 on 2016.11.22. */public abstract class BaseActivity extends Activity implements IBaseView{ private ProgressDialog mProgressDialog; //全局的加载Dialog @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); initView(); initData(); MleApp.getInstance().addActivity(this); //将每个Activity加入到List中 } public void initView(){ loadViewLayout(); findViewById(); setListener(); } public void initData(){ initVariables(); processLogic(); } @Override protected void onDestroy() { super.onDestroy(); MleApp.getInstance().finishActivity(this); if (mProgressDialog != null){ mProgressDialog.dismiss(); } } //实现IBaseView方法 @Override public void showMsgDialog(String message) { if (mProgressDialog == null){ mProgressDialog = ProgressDialog.show(this,null,message); mProgressDialog.setCancelable(false); }else{ mProgressDialog.setMessage(message); mProgressDialog.show(); } } @Override public void showResDialog(int resId) { if (mProgressDialog == null){ mProgressDialog = ProgressDialog.show(this,null,getResources().getString(resId)); mProgressDialog.setCancelable(false); }else{ mProgressDialog.show(); } } @Override public void hideDialog() { if (mProgressDialog != null){ mProgressDialog.hide(); } } //加载布局 protected abstract void loadViewLayout(); //加载页面组件 protected abstract void findViewById(); //事件监听器 protected abstract void setListener(); //初始化变量 protected abstract void initVariables(); //处理逻辑 protected abstract void processLogic();} 当然也有BaseFragment:1234567891011121314151617181920212223242526/** 1. Description: 2. Created by L02465 on 2016.11.22. */public abstract class BaseFragment extends Fragment { private View mView; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mView = initView(inflater,container,savedInstanceState); return mView; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); } protected abstract View initView(LayoutInflater inflater,ViewGroup container,Bundle bundle); protected abstract void setListener(); protected abstract void initDatas();} 组件绑定事件在组件绑定事件时,有几种方法: 类直接实现相应动作接口 类中写内部类实现动作接口 为组件直接绑定动作接口 我认为第三种方法比较好,因为在组件较多的页面中,进行走读代码时,想知道组件干了什么事,首先要做的就是找到这个组件,并查看它的绑定事件,使用第三种方法,找到组件时就直接看到它的绑定事件,不像1、2种方法还需要在别处找相应代码,影响效率,而且第三种方法直接在组件上绑定事件,是面向对象的写法,在事件的处理中,尽可能封装成一个个方法,而不要使其显得过于臃肿。 时时刻刻预防空指针异常是很必要的 >>>>> NullPointerException]]></content>
</entry>
<entry>
<title><![CDATA[阅读书单Reading Lists -1]]></title>
<url>%2F2016%2F11%2F17%2F%E9%98%85%E8%AF%BB%E4%B9%A6%E5%8D%95Reading-Lists%2F</url>
<content type="text"><![CDATA[《解忧杂货铺》 东野圭吾 我比任何人都爱你,想要永远和你在一起。如果我放弃比赛就能让你好起来,我会毫不犹豫地放弃。但如果不是这样,我希望坚持我的梦想。因为一直以来追寻着梦想,我才活出了自我,而你喜欢的也正是这样的我。我没有一刻忘记过你,但请让我去追寻梦想吧。 人与人之间情断义绝,并不需要什么具体的理由。就算表面上有,也很可能只是心已经离开的结果,事后才编造出的借口而已。因为倘若心没有离开,当将会导致关系破裂的事态发生时,理应有人努力去挽救。如果没有,说明其实关系早已破裂。 《重说中国近代史》 张鸣 中国文化之精髓不在于考据,而在于对经典的阐释,并凭借对经典的发挥创造出新的思想体系 举中国数千年礼义人伦,诗书典则,一旦扫地荡尽,此岂独我大清之变,乃开辟以来,名教之奇变,我孔子、孟子之所痛哭于九泉,凡读书识字者,又焉能袖手坐视,不思一为之所也? 《嫌疑人X的献身》 东野圭吾《白夜行》 东野圭吾《一个人的朝圣》 蕾秋·乔伊斯 有些事情可以有好几个起点,也可以用不同的方式开始。有时候你以为自己已经展开了新的一页,实际上却可能只是重复以前的步伐。 他发现当一个人与熟悉的生活疏离,成为一个过客,陌生的事物都会被赋予新的意义。 给予和接受都是一份馈赠,既需要谦逊,也需要勇气。 《岛上书店》无人为孤岛,一书一世界 独自生活的难处,在于不管弄出什么样的烂摊子,都不得不自己清理。 我们在二十岁有共鸣的东西到了四十岁的时候不一定能产生共鸣,反之亦然。书本如此,生活亦如此。 一旦一个人在乎一件事,就发现自己不得不开始在乎一切事。 关于政治、上帝和爱,人们都讲些无聊的谎话。想要了解一个人,你只需问一个问题:“你最喜欢那本书?” 生活中每一桩糟糕事,几乎都是时机不当的结果,每件好事,都是时机恰到好处的结果。 我们读书而后知道自己并不孤单。我们读书,因为我们孤单;我们读书,然后就不孤单,我们并不孤单。 《追风筝的人》 胡塞尼读一本书了解一个国家 “当你杀害一个人,你偷走了一条性命,你偷走了他妻子身为人妇的权利,夺走他子女的父亲。当你说谎,你偷走别人知道真相的权利,当你诈骗,你偷走了公平的权利。“ 哈桑就是这样,他真的纯洁得该死,跟他在一起,你永远觉得自己是个骗子。 阿富汗人总喜欢说:生活总会继续。他们不关心开始或结束、成功或失败、危在旦夕或柳暗花明,只顾像游牧部落那样风尘仆仆地缓慢前进。 也许每个人心中都有一个风筝,无论它意味着什么,让我们勇敢地追。 《无声告白》 伍绮诗对子女的教育什么才是最重要的? 让过去的事情过去,停止问问题,向前看,决不向后看。 你永远得不到你想要的;你只是学会了如何得过且过而已。 对于每一个作用力,都有一个大小相等、方向相反的反作用力,一个向上,另一个向下,一个得到,另一个失去,一个逃离,另一个受困,永远受困。 父母越是关心你,对你的期望就越高,他们的关心像雪一样不断落在你的身上,最终把你压垮。]]></content>
</entry>
<entry>
<title><![CDATA[一些Android Debug Bridge命令]]></title>
<url>%2F2016%2F11%2F14%2Fadb%2F</url>
<content type="text"><![CDATA[概述 Android Debug Bridge(ADB)是android sdk中的一个工具,用这个工具可以直接操作管理Android模拟器或者连接到计算机的Android设备。ADB是一个客户端-服务器端程序,客户端是操作的电脑,服务端是Android设备 主要功能它的主要功能有: 运行设备的Shell 管理模拟器或设备的端口映射 计算和设备之间上传/下载文件 将本地apk安装到模拟器/真实的Android设备 一些常用命令ADB启动/停止/查看版本adb启动:1adb start-server adb停止:1adb kill-server 查看版本:123C:\Users\DELL>adb versionAndroid Debug Bridge version 1.0.36Revision af05c7354fe1-android 查看设备1adb devices 查看当前连接的设备,以列表的形式显示出来。eg: 123 D:\AndroidStudioProjects\KeepAction>adb devicesList of devices attached3c4711c84670 device 安装apkapk在当前目录:1adb install <apk名称> apk不在当前目录:1adb install <apk的路径> eg:1234C:\Users\DELL>adb install C:\Users\DELL\Desktop\版本\3322\运维\com.maintain.apk[100%] /data/local/tmp/com.mainta pkg: /data/local/tmp/com.maintaSuccess 保留数据和缓存文件,reinstall apk:1adb install -r com.maintain.apk 安装apk到SD卡:1adb install -s com.maintain.apk 卸载apk12adb uninstall <packagename>adb uninstall -k <packagename> eg:12C:\Users\DELL>adb uninstall com.maintainSuccess 加-k参数的话,卸载后会保留app的配置和缓存文件 登录设备shell12adb shelladb shell <command> 这个命令将登录设备的shell。后面加command将直接运行设备命令,相当于执行远程命令 从电脑上发送文件到设备1adb push <本地路径> <远程路径> 本地路径:文件在电脑上的路径远程路径:文件保存到Android设备上的路径使用push把电脑上的文件或者文件夹复制到手机上 从设备上下载文件到电脑1adb pull <远程路径> <本地路径> 使用pull把手机上的文件或者文件夹复制到电脑上 重启手机1adb reboot 对手机进行重启 获取序列号12C:\Users\DELL>adb get-serialno3c4711c84670 获取MAC地址12C:\Users\DELL>adb shell cat /sys/class/net/wlan0/address3c:47:11:c8:34:db 查看设备型号12C:\Users\DELL>adb shell getprop ro.product.modelHUAWEI Y635-CL00 显示包信息(1)列出系统应用的所有包名:123456789C:\Users\DELL>adb shell pm list packages -spackage:com.qualcomm.timeservicepackage:com.android.defcontainerpackage:com.example.android.notepadpackage:com.android.contactspackage:com.huawei.hwidpackage:com.huawei.mmitest2package:com.android.phone... (2)列出除了系统应用的第三方应用包名:1234567C:\Users\DELL>adb shell pm list packages -3package:com.tencent.lightalkpackage:com.uniview.airimos.mlepackage:com.uniview.retrofitdemo_gitpackage:com.uniview.keepactionpackage:com.uniview.imos.sdk... (3)使用find或者findstr列出要找的包名:12C:\Users\DELL>adb shell pm list packages | find "qq"package:com.tencent.mobileqq 清除应用数据与缓存1adb shell pm clear <packagename> 强行停止应用1adb shell am force-stop <packagename> 查看手机Android系统版本1adb shell getprop ro.build.version.release 查看屏幕分辨率12C:\Users\DELL>adb shell wm sizePhysical size: 1080x1920 查看屏幕密度12C:\Users\DELL>adb shell wm densityPhysical density: 480 显示帮助信息1adb help]]></content>
</entry>
<entry>
<title><![CDATA[Android后台定时任务分析]]></title>
<url>%2F2016%2F11%2F09%2FAndroid%E5%90%8E%E5%8F%B0%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1%E5%88%86%E6%9E%90%2F</url>
<content type="text"><![CDATA[实现在后台能够稳定的周期性的发送通知,在锁屏状态下收到通知后亮屏。说到后台定时任务,首先想到的是用Service + Timer组合来实现,在Service中启动定时器,在定时器中执行任务,但这种方式存在一个问题,就是定时任务间隔时间较长时,后台Service很容易被Android 系统kill掉,主要是因为Android自带的内存清理,内存不足时就会被kill,这样任务肯定不能定时执行,达不到想要的结果。为了解决这种问题,可以使用 前台服务,这种服务一直保持运行状态,不会由于内存不足被回收,它与Service的最大区别在于,它会一直显示在系统的状态栏,和Notification的效果很相似。 使用前台服务进行通知12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455/** * Description: * Created by L02465 on 2016.11.09. */public class MyService extends Service { private static final int ONGOING_ID = 1; private Timer mTimer; @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { startTimer(); return super.onStartCommand(intent, flags, startId); } public void startTimer(){ if (mTimer == null) { mTimer = new Timer(); } mTimer.schedule(new TimerTask() { @Override public void run() { showNotification(); } },1000,1000 * 60 * 1); //1s之后 每隔1分钟运行一次 } @Override public void onDestroy() { super.onDestroy(); stopForeground(true); //停止前台服务 } //发送通知 public void showNotification(){ //点击通知进行跳转 Intent toMain = new Intent(this,MainActivity.class); PendingIntent pi = PendingIntent.getActivity(this,0,toMain, PendingIntent.FLAG_UPDATE_CURRENT); Notification notification = new Notification.Builder(this) .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher)) .setSmallIcon(R.drawable.ic_cached_black_24dp) //设置小图标是需要的,否则显示的通知内容不是自定义的内容 .setContentIntent(pi) //单击通知后进行跳转 .setContentTitle("Hello:") .setContentText("快起来走动走动,不然对身体不好...") .setDefaults(Notification.DEFAULT_VIBRATE) //通知来了进行震动 .build(); startForeground(ONGOING_ID,notification); //启动前台服务 } 运行结果: 代码主要调用了startForeground()来启动前台服务,每隔1分钟进行通知,在状态栏显示出来。调用stopForeground(true);停止前台服务,会让状态栏的通知显示取消掉。使用Service+Timer组合看似可以稳定的在后台运行,但也存在问题:就是在连接USB进行调试,按下电源键锁屏状态下会运行正常,当拔掉USB线,锁屏下会发现并没有按设定的间隔进行通知,在打开手机后又开始通知的情况 使用AlarmManager Android中的一种系统级别的提示服务,在特定的时间广播一个指定的Intent,可以实现从指定时间开始,以一个固定的时间间隔去执行某项操作,同样可以当做一个定时器去使用。(1)可以在指定时长后去执行某项操作(2)可以周期性的执行某项操作 AlarmManager常用的三个方法以及闹钟类型123456/** * @param type 闹钟类型 * @param triggerAtMillis 闹钟执行时间 * @param operation 执行的动作 * */public void set(int type, long triggerAtMillis, PendingIntent operation) 用于执行一次性闹钟,在指定的时间去执行动作12345678/** * @param type 闹钟类型 * @param triggerAtMillis 闹钟执行时间 * @param intervalMillis 间隔时间 * @param operation 执行的动作 * */public void setRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) 用于执行重复闹钟,在指定时间周期性的执行动作12345678/** * @param type 闹钟类型 * @param triggerAtMillis 闹钟执行时间 * @param intervalMillis 间隔时间 * @param operation 执行的动作 * */public void setInexactRepeating(int type, long triggerAtMillis, long intervalMillis, PendingIntent operation) 与第二种类似,在触发时间不精确的情况下需要。 闹钟类型 状态值 描述 ELAPSED_REALTIME 3 在睡眠状态下不可用,使用相对时间 ELAPSED_REALTIME_WAKEUP 2 在睡眠状态下唤醒系统并执行提示,使用相对时间 RTC 1 在睡眠状态下不可用,使用绝对时间 RTC_WAKEUP 0 在睡眠状态下唤醒系统并进行提示,使用绝对时间 POWER_OFF_WAKEUP 4 在手机关机状态下也能进行提示,使用绝对时间 相对时间:相对系统开启时间,当前相对时间就是SystemClock.elapsedRealtime()绝对时间:当前时间System.currentTimeMillis() Usage5分钟之后去开启另一个服务:12345678public void startAlarm(){ AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE); int minutes = 1000 * 60 * 5; // 5minutes Intent toService = new Intent(this,MyService.class); PendingIntent pi = PendingIntent.getService(this,0,toReceiver,0); long firstTime = SystemClock.elapsedRealtime() + minutes; //相对时间 manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,firstTime,pi); } 每隔5分钟开启另一个服务:12345678public void startAlarm(){ AlarmManager manager = (AlarmManager)getSystemService(ALARM_SERVICE); int minutes = 1000 * 60 * 5; // 5minutes Intent toService = new Intent(this,NoticeService.class); PendingIntent pi = PendingIntent.getService(this,0,toService,0); long firstTime = SystemClock.elapsedRealtime(); manager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,firstTime,minutes,pi); } 在新的服务NoticeService开启时,执行通知的代码,可以正常进行通知,拔掉USB线并锁屏后,也会每隔5分钟按时进行通知,基本稳定。AlarmManager的setRepeating()方法和Timer的Schedule(task,delay,peroid)类似。也可以定时进行广播,在广播接收器中执行操作:12Intent toReceiver = new Intent(this,MyBroadcast.class);PendingIntent pi = PendingIntent.getBroadcast(this,0,toReceiver,0); AlarmManager取消需要注意的是取消的Intent必须与启动Intent保持一致才能取消AlarmManager1234AlarmManager manager = (AlarmManager) getSystemService(ALARM_SERVICE);Intent intent = new Intent(this,NoticeService.class);PendingIntent pi = PendingIntent.getService(this,0,intent,0);manager.cancel(pi); 通知亮屏代码1234567891011121314public void lightScreen(Context context){ PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); boolean isScreenOn = powerManager.isScreenOn(); LogUtil.e("task : screen on >>> " + isScreenOn); if (isScreenOn == false){ PowerManager.WakeLock wakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.ON_AFTER_RELEASE,"MyLock"); wakeLock.acquire(1000); PowerManager.WakeLock wakeLock_cpu = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyCpuLock"); wakeLock_cpu.acquire(1000); }}]]></content>
</entry>
<entry>
<title><![CDATA[Android Studio中的一些快捷键]]></title>
<url>%2F2016%2F11%2F02%2FAndroid-Studio%E4%B8%AD%E7%9A%84%E4%B8%80%E4%BA%9B%E5%BF%AB%E6%8D%B7%E9%94%AE%2F</url>
<content type="text"><![CDATA[Log 打印生成类的TAG:logt1private static final String TAG = "MainActivity"; 在方法中打印日志:logd1Log.d(TAG, "onCreate: "); 在方法中打印方法名和参数信息:logm1Log.d(TAG, "onCreate() called with: " + "savedInstanceState = [" + savedInstanceState + "]"); 还有loge、logi、logr、logw。 代码的移动代码提示:ctrl + alt + 空格代码行上移/下移:ctrl + shift + up/down复制代码行到下一行:ctrl + D剪切一行代码:ctrl + X删除一行代码:ctrl + Y光标在方法上移动:alt + up/down 代码查看打开一个类:ctrl + N打开一个文件:ctrl + shift + N查看类的声明:ctrl + B查看父类:ctrl + U查看方法的调用:ctrl + alt + H在类中查看方法的实现:ctrl + shift + I查看类的继承结构:ctrl + H工程面板的显示与隐藏:alt + 数字1查看一个类中的所有方法:ctrl + F12查看父类中的方法并选择复写:ctrl + O 代码块生成:ctrl + J查找:ctrl + F替换:ctrl + R打开最近的模板:ctrl + E 根据自身使用习惯,也可以在Android Studio的 File --> Settings ---> keymap 中改变默认的快捷键,来提高开发效率。 快捷代码块 fori 和 foreach 123for (int i = 0; i < ; i++) { } .null 和 .nn:在变量名后面使用,快捷检测是否为null。 12345678Bundle bundle = getIntent().getExtras();if (bundle == null) { } if (bundle != null) { } const 随机生成一个符合android style的final int, 1private static final int = 181; key 生成一个以KEY_ 开头的final String 1private static final String KEY_ = ""; fbc 快捷生成findViewById 1() findViewById(R.id.); visible 和 gone : 123Button myBut = new Button(this);.setVisibility(View.GONE);.setVisibility(View.VISIBLE); starter 和 newInstance : 123456789101112131415public static void start(Context context) { Intent starter = new Intent(context, Main2Activity.class); starter.putExtra(); context.startActivity(starter); } //------------------------------// public static Fragment newInstance() { Bundle args = new Bundle(); Fragment fragment = new Fragment(); fragment.setArguments(args); return fragment; }]]></content>
</entry>
<entry>
<title><![CDATA[将自己的库发布到maven]]></title>
<url>%2F2016%2F10%2F27%2F%E5%B0%86%E8%87%AA%E5%B7%B1%E7%9A%84%E5%BA%93%E5%8F%91%E5%B8%83%E5%88%B0maven%2F</url>
<content type="text"><![CDATA[什么是maven?官网网站:Apache Maven Project Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project’s build, reporting and documentation from a central piece of information. Apache Maven是一个软件项目管理和理解工具。基于项目对象模型(POM)的概念,Maven可以管理一个项目的构建,从主要的信息模块报告和收集资料。 maven是一个项目管理工具,它包含了一个项目对象模型(Project Object Model),一组标准集合,一个项目生命周期,一个依赖管理系统,maven可以很方便的管理项目报告,生成站点,管理JAR文件等。 发布library到mavenmaven作为一个中心仓库,在Android开发中,可以将自己写的lib发布到maven,并使用Sonatype Nexus进行管理。仓库信息:123456Repository ID: airimosRepository Name: airimosRepository Type: hostedRepository Policy: ReleaseRepository Format: maven2Contained in groups: 123456<distributionManagement> <repository> <id>airimos</id> <url>http://maven.airimos.com/nexus/content/repositories/airimos</url> </repository></distributionManagement> 1.使用Android studio,在自己库的src同级目录下的build.gradle中进行一些配置,添加以下代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990apply plugin: 'com.android.library'apply plugin: 'maven'android { compileSdkVersion 23 buildToolsVersion "23.0.2" defaultConfig { minSdkVersion 14 targetSdkVersion 23 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } }}dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.1'}tasks.withType(JavaCompile){ options.encoding = "UTF-8"}Properties properties = new Properties()properties.load(project.rootProject.file('local.properties').newDataInputStream())def isReleaseBuild() { return VERSION_NAME.contains("SNAPSHOT") == false}def getRepositoryUsername() { return hasProperty("NEXUS_USERNAME") ? properties.getProperty("NEXUS_USERNAME") : ""}def getRepositoryPassword() { return hasProperty("NEXUS_PASSWORD") ? properties.getProperty("NEXUS_PASSWORD") : ""}afterEvaluate { project -> uploadArchives { repositories { mavenDeployer { pom.groupId = properties.getProperty("GROUP") pom.artifactId = properties.getProperty("POM_ARTIFACT_ID") pom.version = properties.getProperty("VERSION_NAME") repository(url: properties.getProperty("RELEASE_REPOSITORY_URL")) { authentication(userName: properties.getProperty("NEXUS_USERNAME"), password: properties.getProperty("NEXUS_PASSWORD")) } snapshotRepository(url: properties.getProperty("SNAPSHOT_REPOSITORY_URL")) { authentication(userName: properties.getProperty("NEXUS_USERNAME"), password: properties.getProperty("NEXUS_PASSWORD")) } } } } task sourcesJar(type: Jar) { from android.sourceSets.main.java.srcDirs classifier = 'sources' from android.sourceSets.main.java.sourceFiles } task javadoc(type: Javadoc) { source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) } task javadocJar(type: Jar, dependsOn: javadoc) { classifier = 'javadoc' from javadoc.destinationDir } javadoc { options { encoding "UTF-8" charSet 'UTF-8' author true version true } } artifacts { archives javadocJar archives sourcesJar }} 特别地,在开头处要添加apply plugin: 'maven',否则在进行同步时报错。 2.配置项目根目录下的local.properties文件 12345678sdk.dir=D\:\\Android\\SdkGROUP=com.uniview.usbcameralibVERSION_NAME=1.0.0POM_ARTIFACT_ID=usbcameralibRELEASE_REPOSITORY_URL=http\://maven.airimos.com/nexus/content/repositories/airimosSNAPSHOT_REPOSITORY_URL=http\://maven.airimos.com/nexus/content/repositories/airimosSnapNEXUS_USERNAME=usernameNEXUS_PASSWORD=password 这些配置的信息,会在第一步添加的代码读取到。 GROUP:将lib发布到maven的哪个仓库组中,这里的仓库名字为airimos,会发布到airimos下的com/uniview/usbcameralib中。 VERSION_NAME: 库的版本号 POM_ARTIFACT_ID: 库的名称 RELEASE_REPOSITORY_URL:正式仓库地址 SNAPSHOT_REPOSITORY_URL: 不稳定仓库地址 NEXUS_USERNAME :Sonatype Nexus的用户名 NEXUS_PASSWORD:Sonatype Nexus的密码 在Android studio右侧的Gradle projects中,双击 uploadArchives上传到maven服务器,将会进行构建,显示BUILD SUCCESSFUL则表示成功。 以后每次对库进行更新后,将库的版本号增加,在使用的项目中重新下载就好。 依赖上传的库 在新建的项目中使用库,在项目的根目录下的build.gradle配置: 12345repositories { jcenter() //配置maven仓库地址 maven { url 'http://maven.airimos.com/nexus/content/repositories/airimos/' } } 在src同级目录下的build.gradle进行依赖: 1234567dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.1.1' //依赖该库 compile 'com.uniview.usbcameralib:usbcameralib:1.0.0'} 仓库类型maven中的仓库分为两种:snapshot快照仓库和release 发布仓库。snapshot快照仓库用于保存开发过程中的不稳定版本,release正式仓库用来保存稳定的发行版本,当定义一个组件或者模块为快照版本时,只需要在pom文件中在该模块的版本号后加上-SNAPSHOT(要大写)即可。1<version>1.0.0-SNAPSHOT</version> maven2会根据模块的版本号中是否带有-SNAPSHOT来判断是快照版本还是正式版本,如果是快照版本在部署时会自动发布到快照版本库中,而使用快照版本的模块,在不更改版本号的情况下,直接编译打包时,maven会自动从镜像服务器上下载最新的快照版本;如果是正式版本,那么在部署时会自动发布到正式版本库中,而使用正式版本的模块,在不更改版本号的情况下,编译打包时如果本地已经存在该版本的模块,则不会主动去镜像服务器上下载。因此在开发阶段,可以将共用库的版本设置为快照版本,而依赖组件则引用快照版本进行开发,在共用库的快照版本更新后,也不需要修改pom文件的版本号来下载新的版本,直接执行相关编译、打包命令即可重新下载最新的快照库,方便开发。开发完毕后,就可将该库发布到release正式版本库中。]]></content>
</entry>
<entry>
<title><![CDATA[了解流媒体]]></title>
<url>%2F2016%2F10%2F23%2F%E4%BA%86%E8%A7%A3%E6%B5%81%E5%AA%92%E4%BD%93%2F</url>
<content type="text"><![CDATA[基本概念流媒体相关的一些基础概念:帧: 一帧就是一副静止的图像帧率: 每秒显示的图片数,影响画面流畅度,与画面流畅度成正比;帧率越大,画面越流畅,帧率越小,画面越有跳动感; 由于人类眼睛的特殊生理结构,如果看到画面的帧率 > 16的时候,会感觉这个画面是连贯的,此现象称之为视觉暂留,并且当帧率达到一定数值后,在增长的话,人眼也不容易察觉到有明显的流畅度提升。 码率: 图片进行压缩后每秒显示的数据量分辨率: 图片的长度和宽度,也就是图片的尺寸GOP: (Group of Pictures)画面组,一个GOP就是一组连续的画面,每个画面都是一帧,一个GOP就是很多帧的集合。 直播平台的视频数据,就是一组图片,包括I帧、B帧、P帧,当用户第一次观看的时候,会寻找I帧,而播放器会到服务器寻找到最近的I帧反馈给用户,因此GOP cache增加了端到端延迟,因为它必须要拿到最近的I帧。GOP cache的长度越长,画面质量越好 流媒体开发: 网络层(socket)负责传输,协议层(rtmp)负责打包数据,封装层(flv、ts)负责编解码数据的封装,编码层(h.264)负责图像音频的压缩压缩前的数据量: 帧率 * 分辨率压缩比: 压缩前的数据量和压缩后的数据量的比值,也就是(帧率 * 分辨率)/ 码率 对于同一个视频源并采用同一种视频编码算法,则压缩比越高画面质量越差 视频文件格式: 音视频文件的后缀,比如 .avi、.mp4、.mp3、.rmvb 根据文件的格式,系统判断使用什么软件打开视频文件随意修改文件格式,对文件本身不会造成太大的影响,比如把avi改成mp4,文件还是avi 视频封装格式: 一种存储视频信息的容器,流式封装可以有TS、FLV等,索引式的封装有mp4、avi等 主要的作用:一个视频文件往往包含图像和音频信息,还有一些配置信息(比如编解码信息,图像和音频的关联等),这些内容需要按照一定的规则组织、封装起来一般视频文件格式的后缀名采用相应的视频封装格式名称,所以视频文件格式就是视频封装格式 视频处理视频处理原理: 视频最终也是通过GPU,一帧一帧渲染到屏幕上的,所以可以使用OpenGL ES,对视频进行加工处理,使视频呈现出不同的效果 现在各种美颜和视频添加特效的app都是利用GPUImage这个框架来实现的 视频处理框架: GPUImage:是基于OpenGL ES的一个强大的图像视频处理框架,封装好了各种滤镜,也可以编写自定义的滤镜,其本身内置了多达120种常见的滤镜效果 OpenGL:(Open Graphics Library)定义了一个跨编程语言、跨平台的编程接口的规格,用于二维三维图像,是一个很专业的图形程序接口,功能强大,调用方便的底层图形库 Open GL ES:是Open GL三维图形API子集,针对手机和游戏主机等嵌入式设备而设计 视频编解码视频压缩编码标准: 对视频进行压缩(视频编码)和解压缩(视频解码)的编码技术 主要作用:是将视频像素数据压缩成视频码流,从而降低视频的数据量,如果视频不经过压缩编码的话,都是很大的。影响视频质量的是其视频编码数据和音频编码数据,跟封装格式没有多大关系 MPEG: 一种视频压缩方式,它采用了帧间压缩,仅存储连续帧之间有差异的地方,从而达到较大的压缩比H.264: 一种视频压缩方式,采用事先预测和与MPEG中的P-B帧一样的帧预测方式压缩,它可以根据需要产生适合网络情况传输的视频流,还有更高的压缩比,有更好的图像质量 如果从单个画面清晰度比较,MPEG有优势,从连贯性上比较的话,H.264有优势;H.264算法复杂,需要更多的处理器和内存资源,对系统要求较高 H.265: 基于H.264,对一些相关技术进行改进,改善码流、编码质量、延时和算法复杂度之间的关系,达到最优化 H.265更高效,能够在同等画质效果下将内容的体积压缩的更小,传输时更快更省带宽 FFmpeg: 是一个跨平台的开源视频框架,能实现视频编码、解码、转码、串流、播放等功能,支持的视频格式以及播放协议非常丰富,几乎包含了所有音视频编解码、封装格式以及播放协议 I/B/P帧,帧内、帧间I帧: 也是关键帧,保留一副完整的画面,解码时只需要本帧数据就可以完成P帧: 差别帧,保留这一帧跟之前帧的差别,解码时需要用之前缓存的画面叠加上本帧定义的差别,生成最终的画面 P帧没有完整画面数据,只有与前一帧画面差别的数据 B帧: 双向差别帧,保留本帧与前后帧之间的差别,解码时B帧不仅要取得之前的缓存画面,还有解码之后的画面,通过前后画面与本帧数据的叠加取得最终的画面 B帧压缩率高,但解码是CPU工作较多 帧内压缩: 当压缩一帧图像时,仅考虑本帧数据而不考率相邻帧之间冗余信息 帧内压缩一般采用有损压缩算法 帧间压缩:时间压缩,通常比较时间轴上不同帧之间的数据进行压缩 帧间压缩一般是无损的 合成: 将视频流、音频流甚至是字幕流封装到一个文件中,作为一个信号进行传输 流媒体服务器SRS: 优秀开源的流媒体服务器系统BMS: SRS的商业版,有更多的功能nginx: 免费开源web服务器,常用来配置流媒体服务器数据分发(CDN): 将内容分发到网络,发布到最接近用户的网络边缘,使用户更容易拿到内容,解决网络拥堵状况,提高用户访问网站的响应速度CDN工作原理: 上传流媒体数据到源服务器 源服务器保存流媒体数据 客户端播放流媒体数据时,向CDN请求编码后的流媒体数据 CDN的服务器响应请求,如果节点上不存在该流媒体数据,则向源服务器请求流媒体数据,如果存在,则执行6 源服务器响应CDN的请求,将流媒体分发到响应的CDN节点上 CDN将流媒体数据发到客户端 负载均衡: 由多台服务器以对称的方式组成一个服务器集合,每台服务器都具有等价的地位,都可以单独对外提供服务而无需其他服务器的辅助。通过某种负载分担技术,将外部发送来的请求均匀分配到对称结构中的某一台服务器上,而接收到请求的服务器独立地回应客户的请求 负载均衡能够平均分配客户请求到服务器列阵,以提供快速获取数据,解决大量并发访问服务器问题 解码硬解码: 用GPU来解码,减少CPU运算 优点:播放流畅,低功耗,解码速度快缺点:兼容性不好 软解码: 用CPU解码 优点:兼容性好缺点:加大CPU负担,不够流畅,解码速度相对慢,耗电增加]]></content>
</entry>
<entry>
<title><![CDATA[使用Retrofit进行图片的上传]]></title>
<url>%2F2016%2F10%2F18%2FRetrofit%E4%B8%8A%E4%BC%A0%E5%9B%BE%E7%89%87%2F</url>
<content type="text"><![CDATA[目标:使用Retrofit将本地图片上传至服务器指定的文件夹中。服务端接口:入参,使用表单的形式。 表单字段名 类型 说明 “file” File 上传图片的File对象 “filename” String 上传图片的文件名称 请求地址:http://208.208.91.150:8080/VIID-V5/image/updateimage 上传图片Api12345678910public interface UploadPicApi{ @Multipart @POST("image/updateimage") Call<ErrorBean> uploadPic ( @Header("Authorization")String authorization, @Part MultipartBody.Part image, @Part("filename") RequestBody filename );} 接口中的参数可以参考这篇文章:Retrofit入门介绍 – 参数注解 上传图片Service12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970public class UploadPicService{ private static UploadPicApi api; //上传图片结果的回调 public interface IUploadPicListener{ void uploadSuccess(); void uploadFailed(String errorMsg); } /** * 包装图片上传接口 * * @param token 登录返回的access_token * @param path 图片路径 * @param fileName 图片名称 * @param listener 结果回调 * */ public static void uploadPic(String token, String path, String fileName, final IUploadPicListener listener){ api = RetrofitClient.getClient().create(UploadPicApi.class); //生成文件对象 File file = new File(path + fileName); //指定内容类型为"image/png" RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), file); //MultipartBody.Part 被用来发送真实的文件名 MultipartBody.Part photo = MultipartBody.Part.createFormData("file", fileName, photoRequestBody); //由于服务端要求,文件名不能包含后缀,因此去掉后缀 String name = getFileNameNoEx(fileName); //调用上传接口 api.uploadPic(token,photo,RequestBody.create(MediaType.parse("text"),name)).enqueue(new Callback<ErrorBean>(){ @Override public void onResponse(Call<ErrorBean> call, Response<ErrorBean> response) { if (null == response){ onFailure(null,new Throwable("upload picture failed:响应为空")); return; } if (null == response.body()){ onFailure(null,new Throwable("upload picture failed:响应消息体为空")); return; } //错误码 == 0 表示上传成功,其他均为失败 if (response.body().getErrorCode() == 0){ listener.uploadSuccess(); }else{ listener.uploadFailed("错误码:" +response.body().getErrorCode() + "\n错误描述:"+response.body().getErrorMsg()); } LogUtil.d("-----> upload picture successful !"); } @Override public void onFailure(Call<ErrorBean> call, Throwable t) { listener.uploadFailed(t.toString()); } }); } //去掉文件后缀 //参数:完成的文件名称 , ***.jpg public static String getFileNameNoEx(String fileName){ if((fileName != null) && (fileName.length() > 0)){ int dot = fileName.lastIndexOf("."); if((dot > -1) && (dot < (fileName.length()))){ return fileName.subString(0, dot); } } return fileName; }} 使用这个接口就可以完成单张图片的上传了。如果需要上传多个图片,就在接口中声明多个Part参数,也可以使用PartMap。多张图片上传可以参考这篇文章:Android Retrofit 实现文字(参数)和多张图片一起上传在编写接口时,需要与服务端配合,保持一致,主要是请求消息头的构造,构造错误的话,那么上传基本是失败的。123@Multipart@POST("image/updateimage")Call<ErrorBean> uploadMultiPic(@Part("data") String text, @PartMap Map<String,RequestBody> params); 关键代码:123456789101112131415161718public void uploadMultiPic(){String image1 = "/sdcard/myimage/" + "happy.png";String image2 = "/sdcard/myimage/" + "xixi.png";List<String> pathList = new ArrayList<>(); //文件路径集合pathList.add(image1);pathList.add(image2);Map<String,RequestBody> requestBodyMap = new Hash<>();if(pathList.size() > 0){ for(int i = 0; i < pathList.size();i++){ File file = new File(pathList.get(i)); requestBodyMap.put("file"+i + "\";"filename=\""+file.getName(),RequestBody.create(MediaType.parse("image/png"),file); }}//调用上传接口api.uploadMultiPic("some pictures",requestBodyMap){ //TODO:回调处理}}]]></content>
</entry>
<entry>
<title><![CDATA[Retrofit+OkHttp缓存数据]]></title>
<url>%2F2016%2F10%2F12%2FRetrofit-OkHttp%E7%BC%93%E5%AD%98%E6%95%B0%E6%8D%AE%2F</url>
<content type="text"><![CDATA[Retrofit本身并不能缓存,从版本2.0开始默认支持缓存,底层的网络连接都依赖于Okhttp ,因此数据缓存也需要在Okhttp上处理。Okhttp的版本不同,其缓存方式也不同。 为什么使用缓存?减少服务器负荷,降低网络延迟提升用户体验,复杂的缓存处理会根据用户当前的网络情况采取不同的策略,在网络好的时候缓存时间短,数据更新快,在网络差的时候,提高缓存使用的时间,缓存策略不仅仅与网络好坏有关,也与应用本身的用途、业务需求、接口有关,比如应用的数据变更不多,则对缓存使用就比较多,有的应用要保证数据的实时性,比如股票信息,这时就不考虑缓存,如果是天气信息,则可以根据情况设定短的缓存时间,因此应用是否需要缓存,要根据不同的情况作分析,给出不同的方案。在开发App时,在有网络时调用MobileApi接口,请求新的数据,在断网情况下,可以给用户进行断网提示,但有些需求需要在断网时也显示数据,这时就可以使用缓存。由于Retrofit 本身没有缓存处理,则需要自定义拦截器来实现缓存功能。 定义拦截器版本:Retrofit: 2.1.0 +Okhttp 3.3.0:首先,判断是否有网,有网时请求数据,并保存到缓存中,无网时读取缓存。要使用到CacheControl类,该类主要负责缓存策略的控制,主要有以下策略: FORCE_CACHE :只走缓存 FORCE_NETWORK :只走网路 定义拦截器:1234567891011121314151617181920212223242526272829303132333435363738394041424344//拦截器: Interceptor interceptor = new Interceptor() { @Override public okhttp3.Response intercept(Chain chain) throws IOException { // 对请求进行拦截:无网络是强制读取缓存 Request request = chain.request(); if (!NetworkUtil.hasNetwork(getApplicationContext())){ request = request.newBuilder().cacheControl(CacheControl.FORCE_CACHE) .build(); } //对响应进行拦截 //有网络时,移除header,设置缓存超时时间为1小时 okhttp3.Response response = chain.proceed(request); if (NetworkUtil.hasNetwork(getApplicationContext())){ int maxAge = 60 * 60; //1小时 response.newBuilder() .removeHeader("Pragma") .header("Cache-Control","public,max-age="+maxAge) .build(); }else { //无网络时,缓存时间为1周 int maxScale = 60 * 60 * 24 * 7; // 1周 response.newBuilder().removeHeader("Pragma") .header("Cache-Control","public, only-if-cached, max-stale="+maxScale) .build(); } return response; } }; //判断是否连接网络 public static void hasNetwork(Context context){ if(context != null){ ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); if (cm != null){ NetworkInfo info = cm.getActiveNetworkInfo(); if (info != null){ return info.isAvailable(); } } } return false; } 给Okhttp设置拦截器:12345678910111213//缓存目录 File cacheFile = new File(context.getExternalCacheDir(),"mycache"); //缓存大小为20M Cache cache = new Cache(cacheFile, 1024 * 1024 * 20); //创建OkhttpClient,添加拦截器和缓存 OkHttpClient client = new OkHttpClient().newBuilder() .addInterceptor(interceptor).cache(cache).build(); //生成Retrofit,并将OkHttpClient对象写入 Retrofit mRetrofit = new Retrofit.Builder() .baseUrl(BaseUrl) .addConverterFactory(GsonConverterFactory.create()) .client(client) .build(); 缓存基本实现,运行程序后,会在SdCard中的/Android/data/应用包名/cache/mycache目录下看到缓存文件,当应用卸载后,/Android/data/应用包名/这个目录所有文件被删除,不会留下垃圾信息。在设置---->应用 ---->应用详情里面进行清除缓存也会清理缓存文件,对应上面所说目录。在拦截器中的两个字段max-age和max-stale不是很理解,资料较少,有以下比较: 介绍 max-age 强制响应缓存者,根据该值,校验新鲜性。即使用自身的Age值,与请求时间做比较,如果超出max-age,则强制去服务端校验,以确保返回一个新鲜的响应 max-stale 允许缓存者,发送一个过期不超过指定时间的旧缓存 即使看了这个比较,对此理解也不是很深刻,如果有那位看到,可以留言讨论下。]]></content>
</entry>
<entry>
<title><![CDATA[Github文件夹为灰色解决办法]]></title>
<url>%2F2016%2F10%2F10%2FGithub%E6%96%87%E4%BB%B6%E5%A4%B9%E4%B8%BA%E7%81%B0%E8%89%B2%E8%A7%A3%E5%86%B3%E5%8A%9E%E6%B3%95%2F</url>
<content type="text"><![CDATA[问题原因:在灰色的文件中包含其他仓库,也就是含有.git文件夹和.gitignore文件。解决办法:删除目录下的.git文件夹和.gitignore文件,重新进行commit和push。 如果删除之后没有用,可以尝试下面的方法:删除该目录的缓存,在Git Bush窗口中敲如下命令:我的项目根目录下themes/landspace为灰色,则敲入的命令为: 1234git rm -r --cached themes/landspacegit add .git commit -m "remove cahce"git push origin branch-name 提交后,刷新github网页查看该目录是否为灰色。]]></content>
</entry>
<entry>
<title><![CDATA[图片加载库Glide学习总结]]></title>
<url>%2F2016%2F10%2F08%2F%E5%9B%BE%E7%89%87%E5%8A%A0%E8%BD%BD%E5%BA%93Glide%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93%2F</url>
<content type="text"><![CDATA[最近的项目需要加载图片,小白就上网寻找这方面的资料,项目的架构为MVP+Retrofit,典型的网络数据请求,看了比较多的资料,于是决定使用Glide,因为数据的呈现方式都是使用ListView,用了才知道该库很牛,于是对接触到的知识点稍微归纳一下,整理学习。 图片加载一直是一个比较棘手的事情,俗话说就是“坑”比较多,于是就google了一下这方面的库,有很多图片库,例如Picasso, Universal-Image-Loader,Fresco,Glide等,这些库都是很火的,使用量很大的图片库, 他们各有优点,不能评说那一个不好,根据项目的需求适当选择。 几个库的简单比较 Picasso:是Square出品的图片库,当然是很厉害的,该公司还有响当当的Okhttp和Retrofit,都是很牛逼的网络请求库,搭配使用效果可能更好一些,之所以这么说是因为Picasso可以将网络请求到的数据缓存交给Okhttp来处理。 Universal-Image-Loader:一个强大的图片加载库,包含的配置比较多,使用很广泛,很老牌。 Fresco:是Facebook出品的,也超强大。从知乎问答下看到该库最大的优点是5.0以下(最低2.3)的bitmap加载,在5.0以下,会将图片放到一个特别的内存存储区,称为:Ashmem区,在图片不显示的时候也会自动清除内存(这个是必须的),使得App更加流畅,减少图片内存占用引发的内存不足(OOM)。 Glide:谷歌推荐使用的图片加载库,Google一些图片应用就使用该库进行图片加载,用起来很流畅,可以说是在Picasso上的进一步扩展,比如支持Gif动态图,在使用时可以传入更多的上下文等等,他们两者最大的区别在于默认的设置不同,比如默认的图片格式不同,尺寸大小也不同等,在下面的链接文章中有具体的对比分析。 总结:Glide是Picasso的进一步扩展,Picasso能做的Glide也可以做,无非就是配置的不同。 网上关于Picasso和Glide性能进行比较的文章挺多,不过遗憾的是,大多数都是抄来抄去的,比较清晰的是这篇:Google推荐的图片加载库Glide介绍,Picasso和Glide的使用极其相似,但也有不少细节上的区别,看了这篇文章可以大致了解一二。 现在整理一下Glide的使用: 配置Glide在Module根目录下的build.gradle中进行库依赖: 12345def def latestVersion = '3.7.0'dependencies { compile 'com.github.bumptech.glide:glide:$latestVersion' compile 'com.android.support:support-v4:19.1.0'} Glide需要依赖Support Library v4。然后同步一下代码build successful,就可以使用Glide了。 使用Glide使用下面一段代码就能实现图片加载,看起来极其简单方便:123456//图片的urlprivate String image_url = "https://www.image.com/myimage?id=1";private ImageView imageView = (ImageView)super.findViewById(R.id.image);Glide.with(context) .load(image_url) .into(imageView); 用这段代码就可以将url所表示的图片装到ImageView显示出来,是不是很爽呢?with里面的参数相比Picasso不仅仅接受上下文Context对象,还可以是Activity,Fragment,FragmentActivity等,这样可以更好的让加载图片的请求与生命周期动态管理起来。Glide不仅仅支持加载网络图片,还支持以下几种加载方式: 加载资源文件:DrawableTypeRequest<Integer> load(Integer resourceId) 1Glide.with(context).load(R.drawable.my_image).into(imageview); 加载本地文件:DrawableTypeRequest<File> load(File file) 12File file = new File("sdcard/myimage/","image.jpg");Glide.with(context).load(file).into(imageView); 加载Uri:DrawableTypeRequest<Uri> load(Uri uri) 1234File file = new File(Environment.getExternalStorageDirectory() + File.separator + "image", "image.jpg");Uri uri = Uri.fromFile(file);Glide.with(context).load(uri).into(imageView); String加载:DrawableTypeRequest<String> load(String string) 设置占位图片placehold图片加载并不是很实时的,加载成功的时间是不确定的,在加载时,可以设置一个图片显示在ImageView上进行一些正在加载...提示等等。 1234Glide.with(context) .load(image_url) .placehold(R.drawable.loading) .into(imageView); error在加载网络图片时,如果突然网络断掉,肯定加载不到正确的图片,这时可以设置一个错误图片到ImageView,提示用户加载失败。 1234Glide.with(context) .load(image_url) .error(R.drawable.loaderror) .into(imageView); 当然,也可以给Glide设置监听,当图片加载失败时,可以知道为什么加载失败了。 123456789101112131415161718//设置加载图片错误时的监听RequestListener<String,GlideDrawable> loadErrorListener = new RequestListener<String,GlideDrawable>(){ @Override public boolean onException(Exception e,String model,Target<GlideDrawable> target,boolean isFirstResource){ //加载异常时回调 Log.e(TAG,"exception:" + e.toString); } @Override public boolean onResourceReady(GlideDrawable resource,String model,Target<GlideDrawable> target,boolean isFromMemoryCache,boolean isFirstResource){ //加载成功时的回调 //... }};Glide.with(context) .load(image_url) .listener(loadErrorListener) .into(imageView); 图片的调整Glide加载图片大小根据ImageView尺寸自动调整的,在缓存的时候也是按照图片大小进行缓存,每一种尺寸都会保留一份缓存。并且可以调用override(int width,int height)在图片显示到ImageView之前改变图片大小,width和height的单位都是px。 1234Glide.with(context) .load(image_url) .override(64,64) .into(imageView); 缩放:Glide提供了两种图形转换的标准选项:centerCrop()和fitCenter; centerCrop() 这个可以对图像进行裁剪,当图片比ImageView大的时候,会将超出ImageView的部分裁剪掉,尽可能让ImageView完全填充,但图像可能不会全部显示 1234Glide.with(context) .load(image_url) .centerCrop() .into(imageView); fitCenter() 它会自适应ImageView的大小,并且会完整的显示在ImageView中,但是ImageView可能不会被完全填充 设置缩略图支持 1234Glide.with(context) .load(image_url) .thumbnail(0.1f) .into(imageView); 设置动态转换在图片显示之前,可以通过transformation对其做一些处理,已达到想要的图片效果,为此,需要创建一个类,该类实现了Transformation接口,如果只是对图片进行转换,则可以直接使用Glide封装好的BitmapTransformation抽象类,图像的转换只需要在transform里实现,并重写getId()方法,该方法返回此次转换的唯一标识,要确保唯一性。 12345678910111213141516public class GlideRoundTransform extends BitmapTransformation{ @Override protected Bitmap transform(BitmapPool pool,Bitmap toTransform,int outWidth,int outHeight){ //转换处理 } @Override public String getId(){ return getClass().getName + Math.random(); }}Glide.with(context) .load(image_url) .transform(new GlideRoundTransform()) .into(imageView); 当图片需要多个转换时,将每种的转换类对象传入到transform()即可。 1234Glide.with(context) .load(image_url) .transform(new GlideRoundTransform(this),new GlideOtherTransform(this)) .into(imageView); 加载的动画为了使图片平滑的加载到ImageView,可以设置加载的动画效果,最新的api已经默认实现了一个渐入渐出的动画效果,默认时间300ms,也可以使用crossFade,它接受无参或者一个int型的参数,用来指定动画执行的时间。 1234Glide.with(context) .load(image_url) .crossFade(2000) //2s .into(imageView); 如果渐入渐出的动画效果不满意,可以自定义动画,使用animate()即可,它接受动画的资源id和动画类对象。 12345Glide.with(context) .load(image_url) .animate(R.anim.myAnim) //.animate(new MyAnimation()) .into(imageView); 如果不想使用任何动画,直接将图片显示出来,则使用dontAnimate()方法。 1234Glide.with(context) .load(image_url) .dontAnimate() .into(imageView); 加载Gif动态图加载Gif动态图是Glide的一个亮点,也是将gif动态图的url传入load即可加载,Glide还提供了Gif相关的两个方法:asBitmap()和asGif(); asBitmap():将gif图的第一帧显示出来 asGif():严格显示成gif,当传入的Url不是gif的url时,则按错误处理,可以检查load参数是否为gif。 1234Glide.with(context) .load(imageUrl) .asBitmap() .into(imageView); 加载优先级设置图片加载的顺序,有以下几种优先级: Priority.LOW Priority.NORMAL Priority.HIGH Priority.IMMEDIATE 1234Glide.with(context).load(image_url) .priority(Priority.NORMAL) .into(imageView); 加载目标TargetTarget就是Glide获取资源后作用的目标,一般的ImageView就是目标。 12345678910SimpleTarget target = new SimpleTarget<Drawable>(){ @Override public void onResourceReady(Drawable resource, GlideAnimation<? super Drawable> glideAnimation) { textView.setBackground(resource); }}; Glide.with(context) .load(image_url) .priority(Priority.NORMAL) .into(target); 这段代码是将TextView作为Target,并将加载到的图片设置为TextView的背景,SimpleTarget接收泛型数据,可以将其更改为其他想要的类型。也可以指定加载的宽度和高度,单位也是px。 缓存策略几种缓存策略: DiskCacheStrategy.ALL:缓存源资源和转换后的资源 DiskCacheStrategy.NONE:什么都不缓存 DiskCacheStrategy.SOURCE:只缓存源资源 DiskCacheStrategy.RESULT:只缓存转换后的资源 1234Glide.with(context) .load(imageUrl) .diskCacheStrategy(DiskCacheStrategy.ALL) .into(imageView); 跳过内存缓存: 1234Glide.with(this) .load(imageUrl) .skipMemory(true) .into(imageView); 缓存的动态清理123Glide.get(this).clearDiskCache(); //清理磁盘缓存,需要在子线程中执行Glide.get(this).clearMemory(); //清理内存缓存,可以在UI线程中执行 结合列表视图的使用Glide在滑动加载图片时表现突出,这也是Glide的优势之一,在项目中很可能是在ListView或者RecyclerView中显示加载的图片: 在使用ListView进行加载时,可以在Adapter的getView中进行使用 12345678910public View getView(int postion,View convertView,ViewGroup parent){ ViewHolder holder; if(convertView == null){ holder = new ViewHolder(); //..... } UserInfo infos = (UserInfo)getItem(postion); String imageUrl = infos.getImageUrl(); Glide.with(convertView.getContext()).load(imageUrl).into(holder.imageView);} 在RecyclerView中使用,在 Adapter的onBindViewHolder方法中使用: 12345public void onBindViewHolder(final MyHolder holder, int position){ Glide.with(holder.imageView.getContext()) .load(args[position]) .into(holder.imageView);} Glide缓存处理进阶可以参考这篇文章:Android图片缓存之Glide进阶 以上就是学习Glide时的知识点整理,后面再接触到新的知识点时再进行补充。]]></content>
</entry>
<entry>
<title><![CDATA[Retrofit入门介绍]]></title>
<url>%2F2016%2F09%2F30%2FRetrofit%E5%85%A5%E9%97%A8%E4%BB%8B%E7%BB%8D-1%2F</url>
<content type="text"><![CDATA[在这次的项目中,由于服务端采用Restful原则提供接口,所以在手机客户端调用接口时,考虑使用现在很流行的Retrofit网络请求框架,很多情况下,和Retrofit搭配使用的是RxJava,他们堪称”黄金组合”,功能强大。还有其他使用率也很高的网络框架,包括google官方提供的一个,关于在项目如何选择,可以参考stormzhang大神写的一篇博客。博客地址为:ANDROID开源项目推荐之「网络请求哪家强」 Restful百度百科:Restful 一种软件架构风格,是一种设计风格而不是标准,只是提供了一组设计原则和约束条件,主要用于客户端和服务器交互的软件或系统。基于这个风格设计的软件可以更加简洁,更有层次,更易于实现缓存等机制。 Retrofit Type-safe HTTP client for Android and Java by SquareRetrofit官网地址:http://square.github.io/retrofit/Retrofit github地址:https://github.com/square/retrofit 创建Retrofit对象在项目中使用需要在模块下面的build.gradle中添加如下依赖: compile ‘com.squareup.retrofit2:retrofit:2.1.0’compile ‘com.squareup.retrofit2:converter-gson:2.1.0’ 如果需要使用RxJava,也需要添加相应的依赖: compile ‘com.squareup.retrofit2:adapter-rxjava:2.1.0’ 创建Retrofit对象需要使用Builder,指定BASR_URL和添加Converter. 1234567891011public class RetrofitClient{ public static String Base_url = "http://"+server_addr+":"+port+"VIID-V5/"; public static Retrofit getClient(){ Retrofit retrofit = new Retrofit.Builder() .baseUrl(Base_url) .addConverterFactory(GsonConverterFactory.create()) .build(); return retrofit; }} server_addr和port:是要请求的服务器地址和端口。在编写请求接口时,是相对base_url而言的。Retrofit2必须要以/结束,不然会抛出非法参数异常。 请求接口定义12345public interface LoginApi{ @Headers({"Contant-Type:application/json","Accept:application/json"}) @POST("login/") Call<TokenBean> login(@Body LoginInfo loginInfo);} 登录发送Post请求,因此接口含有注解@POST,可以看出登录请求的完整地址为http://"+server_addr+":"+port+"VIID-V5/login,发送POST请求,因此含有请求消息体,为JSON数据格式,可以在android studio安装插件Gson format,将json数据格式转换为实体类。相应也是json数据,也可以转为实体类得到相应数据。 123456789101112131415161718192021222324252627282930313233343536/** 登录请求实体类*/public class LoginInfo{ /** username:admin password:admin */ private String username; //当字段与json中的不一致时,可以使用字段@SerializedName @SerializedName("passwd") private String password; public void setUsername(String username){ this.username = username; } public void setPassword(String password){ this.password = password; } //TODO:get() //TODO:toString()}/** 相应实体类:服务端相应一般都会包含错误码和错误信息*/public class TokenBean{ private int errCode; private String errMsg; private String access_token; //请求数据的令牌 //TODO:set() //TODO:get() //TODO:toString()} 接口调用12345678910111213141516171819202122232425262728293031323334353637383940public class LoginService{ public static LoginApi api; //登陆结果回调 public interface ILoginListener{ void loginSuccess(); void loginFailed(String errInfo); } public static void login(LoginInfo info,ILoginListener listener){ api = Retrofit.getClient().create(LoginApi.class); //代理对象 api.login(info).enqueue(new Callback<TokenBean>(){ @Override public void onResponse(Call<GitModel> call, Response<GitModel> response) { //服务端响应信息 if(response == null || response.body() == null){ listener.loginFailed("响应消息为空,检查网络连接是否正常!"); return; } if(response.code() == 200 && response.body().getErrCode() == 0){ //login successful listener.loginSuccess(); //对于其他查询的请求,判断请求成功后,拿到服务端返回的数据 }else if(response.code() == 404){ listener.loginFailed("404: 页面找不到"); }else if(response.code() == 500){ listener.loginFailed("500: 服务器异常"); }else{ listener.loginFailed(reponse.code + ":其他异常信息"); } } @Override public void onFailure(Call<GitModel> call, Throwable t) { //服务端响应失败的信息 listener.loginFailed(t.getMessage()); //回调出响应超时信息 } }); } } } 一般的Retrofit使用就是这样的流程,在大型项目中,可以根据项目需要,进行自定义。 Retrofit注解详情请求方法 请求方法 方法简单描述 POST post请求,信息包含在请求体RequestBody中,一般用于添加 GET get请求方式,参数包含在Url ,一般用于查询 DELETE 删除数据 PUT 修改数据 PATCH HEAD OPTIONS HTTP @HTTP(method=”get”,path=”login/“,hasBody=true) 标记 标记 简单描述 表单请求 FormUrlEncoded 表示请求体是一个form表单,Content-Type:application/x-www-form-urlencoded 表单请求 Multipart 请求体是一个支持文件上传的form表单,Content-Type:multipart/form-data Streaming 表示响应体的数据用流的形式返回,如果没有使用该注解,默认会把数据全部载入内存,之后通过流获取数据也不过是读取内存中的数据,所以如果返回的数据比较大,就需要使用这个注解 参数注解 位置 参数注解 描述 作用于方法 Headers 用于添加请求头 作用于方法参数 Header 用于添加不固定值得Header,也用于鉴权的目的 作用于方法参数 Body 用于非表单请求体 作用于方法参数 Field/FieldMap 用于表单字段,与FormUrlEncoded配合使用 作用于方法参数 Part/PartMap 用于表单字段,与Multipart配合使用,用于有文件上传的情况,PartMap的接受类型是Map,非String会调用toString() 作用于方法参数 Path 用于URL,参数是URL的一部分 作用于方法参数 Query/QueryMap 用于URL,比如:@Query(“id”),http://127.0.0.1:8080/users/id=10 作用于方法参数 Url 完整的URL {占位符}和Path尽量只用在URL的path部分,url中的参数用Query和QueryMap代替 Query/Field和Part这三者都支持数组和实现了Iterable接口的类型,比如List/Set。 添加head信息,动态添加和静态添加 123456Call<String> getData(@Query("item[]") List<Integer> item); // ----------------------------------- @GET("user")Call<User> getUser(@Header("Authorization") String authorization) @Headers("Cahce-Control:max-age=640000") 数据类型转换器Retrofit支持的数据类型转换器 Gson: com.squareup.retrofit2:converter-gson:2.1.0 Jackson: com.squareup.retrofit2:converter-jackson:2.1.0 Moshi: com.squareup.retrofit2:converter-moshi:2.1.0 Protobuf: com.squareup.retrofit2:converter-protobuf:2.1.0 Wire: com.squareup.retrofit2:converter-wire:2.1.0 Simple XML: com.squareup.retrofit2:converter-simplexml:2.1.0 Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars:2.1.0 Retrofit提供的CallAdapter: name build.gradle中的依赖 rxjava com.squareup.retrofit2:adapter-rxjava:2.1.0 java8 com.squareup.retrofit2:adapter-java8:2.1.0 guava com.squareup.retrofit2:adapter-guava:2.1.0]]></content>
</entry>
<entry>
<title><![CDATA[hexo start]]></title>
<url>%2F2016%2F09%2F28%2Fhello-world%2F</url>
<content type="text"><![CDATA[Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new "My New Post" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment]]></content>
</entry>
</search>