不完美小孩


  • 首页

  • 归档

潜在因子(Latent Factor)推荐算法—音乐推荐

发表于 2019-03-03

算法思想

这个算法的思想是:用元素去连接用户和音乐。不同的用户(user)对偏好的元素(factor)不同,不同的音乐(item)含有不同的元素。比如用户A喜欢带有古风、小清新、许嵩等元素的歌,那么如果一首歌带有这些元素,我们就这首歌推荐给用户。所以我们期望得到这样两个矩阵:用户-潜在因子矩阵,音乐潜在因子矩阵。

用户潜在因子矩阵P

古风 小清新 流行 伤感 许嵩
用户A 0.6 0.8 0.1 0.1 0.7
用户B 0.1 0 0.9 0.1 0.2
用户C 0.5 0.7 0.9 0.9 0

表示用户对不同元素的偏好程度,1表示喜欢,0表示不喜欢。

音乐潜在因子矩阵Q

古风 小清新 流行 伤感 许嵩
音乐A 0.9 0.1 0.2 0.4 0
音乐B 0.5 0.6 0.1 0.9 1
音乐C 0.1 0.2 0.5 0.1 0
音乐D 0 0.6 0.1 0.2 0

表示音乐含有各种因素的成分,比如音乐A含有古风的成分为0.9,含有小清新的成分为0.1,含有流行的成分为0.2,含有伤感的成分为0.4,含有许嵩的成分为0等。

利用P、Q矩阵求解用户对音乐的喜欢程度

我们可以利用用户潜在因子矩阵P和音乐潜在因子矩阵求解用户对音乐的喜欢程度,ru,i=例如用户A对音乐A的喜欢程度=用户A对古风的偏好程度 音乐A含有古风的成分+用户A对小清新的偏好程度 音乐A含有小清新的成分+用户A对流行的偏好程度 * 音乐A含有流行的成分+…….

即:0.6 0.9+0.8 0.1+0.1 0.2+0.1 0.4+0.7 * 0=0.69

古风 小清新 流行 伤感 许嵩
用户A 0.6 0.3 0.1 0.4 0.4
古风 小清新 流行 伤感 许嵩
音乐A 0.9 0.1 0.2 0.4 0

每个用户对每首歌都这样计算,就可以得到所有用户对所有歌曲的评分。预测评分矩阵r=PQT

音乐A 音乐B 音乐C 音乐D
用户A 0.68 1.58 0.28 0.51
用户B 0.31 0.43 0.47 0.11
用户C 1.06 1.57 0.73 0.69

由预测评分矩阵可知用户A对音乐B的评分最高,用户B对音乐C的评分最高,用户C对音乐B的评分最高。所以给用户推荐音乐B,给用户B推荐音乐C,给用户C推荐音乐B。

How to get matrix P and Q

显然音乐含有的因素是多种多样的,我们无法人为的给出精确的分类,也无法让用户给出对不同因素的偏好程度,我们方便得到的仅仅是用户的行为。这里沿用@邰原朗的量化标准:单曲循环=5, 分享=4, 收藏=3, 主动播放=2 , 听完=1, 跳过=-2 , 拉黑=-5。这样根据用户的行为就可以得到实际的评分矩阵R,如下表所示。

Markdown

用户的实际评分矩阵R实际上是一个稀疏矩阵,因为用户听过的音乐仅仅是全部音乐中的一小部分。那么我们如何利用实际评分矩阵R求得潜在因子呢,这就要用到矩阵分解。我们期望求得矩阵P和Q使得
$$
R \approx PQ^T
$$
即

Markdown

我们期望P和QT的乘积约等于实际评分矩阵R,也就是求
$$
min \sum(R_{u,i}-r_{u,i})^2
$$

即
$$
min \sum(R_{u,i}-q_i^Tp_u)^2
$$
这就矩阵分解问题转换成了求最优化的问题。这样求得的最小值,会出现数据过拟合的问题,导致结果虽然能对训练集完美的预测,但是对测试集预测的结果不好。为了防止数据过拟合的问题,采用L2正则化的方法,即在损失函数的后面加上每个参数的平方。这样误差不仅仅取决于拟合数据的好坏,而且取决于参数值的大小。并加入惩罚因子$\lambda$,控制正则化的强度。
$$
min \sum(R_{u,i}-q_i^Tp_u)^2+ \lambda q_i^2+\lambda p_u^2
$$
为了降低算法的复杂度,采用随机梯度下降法求最小值。所谓随机梯度下降算法,优化的不是全部训练数据上的损失函数,而是在每轮迭代中随机优化某一条训练数据上的损失函数,这样每轮参数的更新速度大大加快。这里我们在每次迭代中对每个$(R_{u,i}-q_i^Tp_u)^2+ \lambda q_i^2+\lambda p_u^2$求最小值。

梯度是一个向量,每个元素为函数对一元变量的偏导数,它既有大小也有方向。梯度方向是函数值增长最快的方向,所以沿着梯度相反的方向可以较快找到函数的最小值。

损失函数$f=(R_{u,i}-q_i^Tp_u)^2+ \lambda q_i^2+\lambda p_u^2$分别对qi和pu求偏导如下:
$$
\begin{cases}
\frac {\partial f}{\partial q_i}=-2p_u(R_{u,i}-q_i^Tp_u)+2\lambda q_i
\\
\frac {\partial f}{\partial p_u}=-2q_i(R_{u,i}-q_i^Tp_u)+2\lambda p_u
\end{cases}
$$

对于每个评分数据$R_{u,i}$,预测评分为$q_i^Tp_u$,因此偏差定义为$e_{u,i}=R_{u,i}-q_i^Tp_u$。

为了最小化损失函数,令学习步长为$\frac {\gamma}{2}$,朝着梯度相反的方向改变$p_u$和$q_i$,所以:
$$
\begin{cases}
q_i=q_i+\gamma(e_{u,i}p_u-\lambda q_i)
\\
p_u=p_u+\gamma(e_{u,i}q_i-\lambda p_u)
\end{cases}
$$

伪代码

输入:实际评分矩阵R,学习率y,惩罚因子x,潜在因子大小f,最大循环次数max_iter。

输出:预测评分矩阵r。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
while(iter<max_iter){
//遍历R(u,i)
for(u=0;u<u_num;u++)
for(i=0;i<i_num;i++){
r(u,i)=0;
for(f=0;f<f_num;f++){
r(u,i)+=q(i,f)*p(u,f);
}
e(u,i)=R(u,i)-r(u,i);
loss+=e(u,i)*e(u,i);
for(f=0;f<f_num;f++){
q(i,f)+=y(e(u,i)*p(u,f)-xq(i,f));
p(u,f)+=y(e(u,i)*q(i,f)-xp(u,f));
loss+=xp(u,f)*p(u,f)+xq(i,f)q(i,f);
}
}
}

一些小的知识点

发表于 2019-03-03

方法(method)和函数(function)的区别

函数是独立的,与对象和类无关,需要显示的传递数据

方法与对象和类有关,依赖对象而调用,可以直接处理对象上的数据即隐式传递数据

Android中的Handler、Looper、Message

Handler、Looper、Message这三者都与Android异步消息处理线程相关。

异步消息处理线程启动后会进入一个无限的循环体,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息处理函数,执行完成一个消息后则继续循环,若消息队列为空,线程则会阻塞等待。

Looper负责的就是创建一个MessageQueue,然后进入一个无限循环体不断从该MessageQueue中读取消息,而消息的创建者就是一个或多个Handler。

Android HandlerThread

HandlerThread类是一个线程专门处理Handler消息,依次从Handler的队列中获取信息,逐个进行处理,保证安全,不会出现混乱引发的异常。HandlerThread继承于Thread,所以它本质是个Thread。与普通Thread的差别在于,它有个Looper成员变量。

位运算

发表于 2019-03-03

运算符

基本的位操作符有六种:与、或、异或、取反、左移、右移。*

运算符 运算规则
&(与) 两个位同为1时,结果才为1
/(或) 两个位同为0时,结果才为0
^(异或) 两个位相同为0,相异为1
~(取反) 0变1,1变0
<<(左移) 各二进制位全部左移若干位,高位丢弃,低位补0
>> (右移) 各二进制位全部右移若干位,低位丢弃。对无符号数,高位补0;有符号数,正数补0负数补1

A+B问题

问题描述

Markdown

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Solution {
/**
* @param a: An integer
* @param b: An integer
* @return: The sum of a and b
*/
public int aplusb(int a, int b) {
// write your code here
int sum_without_carry,carry;
sum_without_carry=a^b;
carry=(a&b)<<1;
if(carry==0)
return sum_without_carry;
else
return aplusb(sum_without_carry,carry);
}
}

Hello World

发表于 2019-03-03

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 Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

Android录音AudioRecord和MediaRecorder

发表于 2019-03-03

AudioRecord

主要是实现边录边播(AudioRecord+AudioTrack)以及对音频的实时处理(如会说话的汤姆猫、语音)

优点:语音的实时处理,可以用代码实现各种音频的封装

缺点:输出是PCM语音数据,如果保存成音频文件,是不能够被播放器播放的,所以必须先写代码实现数据编码以及压缩

使用AudioRecord类录音,并实现WAV格式封装。录音20s,输出的音频文件大概为3.5M左右

MediaRecorder

已经集成了录音、编码、压缩等,支持少量的录音音频格式,大概有.aac(API = 16) .amr .3gp

优点:大部分以及集成,直接调用相关接口即可,代码量小

缺点:无法实时处理音频;输出的音频格式不是很多,例如没有输出mp3格式文件

使用MediaRecorder类录音,输出amr格式文件。录音20s,输出的音频文件大概为33K

camera2中涉及的三个重要的callback

发表于 2019-03-03

CameraDevice.StateCallback()

CameraManager是一个用于检测、描述和连接相机设备的系统服务管理器。

如果要打开相机,需要先获取CameraManager,然后调用其openCamera方法打开相机。

CameraManager.openCamera(String,CameraDevice.StateCallback,Handler)中第一个参数是相机id(可以用CameraManager.getCameraIdList()获得所有可用相机的id);第二个参数CameraDevice.StateCallback是当CameraDevice被打开时回调的StateCallback;第三个参数handler,决定了回调函数触发的线程,如果是null,则选择当前线程。

在CameraDevice.StateCallback中,要实现三个方法,分别是onOpened(CameraDevice camera)当相机被打开的时候调用,会返回一个CameraDevices对象给应用层;onDisconnected(CameraDevice camera)当相机断开连接时被调用;onError(CameraDevice camera,int error)当相机出现错误时调用。

CameraCaptureSession.StateCallback

Google采用了pipeline(管道)的概念,将Camera Device相机设备和Android Device安卓设备连接起来, Android Device通过管道发送CaptureRequest拍照请求给Camera Device,Camera Device通过管道返回CameraMetadata数据给Android Device,这一切建立在一个叫作CameraCaptureSession的会话中。

cameraCaptureSession通过CameraDevice.createCaptureSession(List,CameraCaptureSession.StateCallback,Handler)创建,第一个参数是一个List集合,封装了所有需要从该摄像头获取图片的surface,一般需要两个surface,一个提供预览,一个提供拍照。预览的surface就是相机预览区域,拍照的surface,一般用ImageReader对象来获取ImageReader是系统提供的一个类,它的创建过程已经为我们创建好了一个Surface,我们直接使用它来当作拍照Surface,当拍照成功后,我们就可以从ImageReader.OnImageAvailableListener内部类的onImageAvailable回调方法中获取到一个ImageReader对象,再调用getPlanes()获取到Plane数组,一般取第一个Plane,继续调用getBuffer()就可以获取到拍摄的照片的byte数组了;第二个参数用于监听CameraCaptureSession的创建过程,在CameraCaptureSession.StateCallback中要实现onConfigured(@NonNull CameraCaptureSession ) 方法,在这个方法中会返回一个CameraCaptureSession对象,使用它来作很多的工作,比如断开session连接调用abortCaptures()、拍照调用capture()方法、开始预览调用setRepeatingRequest、停止预览调用stopRepeating()等;第三个参数代表执行callback的handler,决定了回调函数触发的线程,如果是null,则选择当前线程。

不管是预览还是拍照,都要调用CameraDdevice的createCaptureRequest(int template Type)方法创建CaptureRequest.Builder,该方法支持的参数有TEMPLATE_PREVIEW(预览)、TEMPLATE_RECORD(拍摄视频)、TEMPLATE_STILL_CAPTURE(拍照)等。通过调用CaptureRequest.Builder的build()方法得到CaptureRequest对象。CameraCaptureSession的Capture (CaptureRequest, CameraCaptureSession.CaptureCallback , Handler)一般用于拍照,CameraCaptureSession的setRepeatingRequest (CaptureRequest, CameraCaptureSession.CaptureCallback, Handler)一般用于捕获画面输出至预览界面或者录制视频。重写CameraCaptureSession.CaptureCallback中的onCaptureCompleted方法,result就是未经处理的元数据。

java 环境搭建(win10 + jdk 10)

发表于 2019-03-03

作者:不完美

一、下载、安装jdk(jdk中包含了jre)

1、下载jdk(下载地址 )

点击下图中DOWNLOAD调转到下载页面

Markdown

点击下图中的协议选择相应版本进行下载(这里下载的是jdk10.0.2 for windows 64)

Markdown

2、安装jdk

jdk下载下来是一个.exe文件,双击这个文件,按照提示进行安装(这里按照默认路径装在了C盘)

Markdown

二、配置环境变量

1、安装完成后,找到环境变量

右击”我的电脑“,点击”属性“,选择”高级系统设置“,如下图所示

Markdown

点开”高级“选项卡,选择下方的”环境变量“

Markdown

2、修改系统变量(JAVA_HOME、JRE_HOME、CLASSPATH、PATH)

这里jdk安装路径:C:\Program Files\Java\jdk-10.0.2

jre安装路径:C:\Program Files\Java\jre-10.0.2

JAVA_HOME: 在系统变量中如果没有JAVA_HOME则新建系统变量JAVA_HOME,变量值是jdk的安装路径,如下图所示。

Markdown

JRE_HOME:在系统变量中如果没有JRE_HOME则新建系统变量JRE_HOME,变量值是jre的安装路径,如下图所示。

Markdown

CLASS_PATH:在系统变量中如果没有CLASS_PATH则新建系统变量CLASS_PATH,变量值为:.;%JAVA_HOME%\lib;%JRE_HOME%\lib ,如下图所示。

Markdown

PATH:编辑系统变量中的PATH,新增路径C:\Program Files\Java\jdk-10.0.2\bin和C:\Program Files\Java\jre-10.0.2\bin 。如下图所示(注意:win10需要用绝对路径,否则javac会报不是内部命令也不是外部命令)

p9

三、输入java、java -version、javac 测试配置是否成功

如下图所示则表明配置成功。

Markdown

Markdown

Markdown

PS: 这里将jdk和jre按照默认路径装在了C盘,试了一下装在D盘,同样的配置,Javac还是不能运行。emmm,不知道为什么….

Android—调用系统相机拍照

发表于 2019-03-03

作者:不完美

本文实现的效果是:点击一个button,调用系统相机进行拍照,并储存在picture根目录下

一、页面布局

页面布局比较简单,只有一个button,代码如下:

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

<Button
android:id="@+id/btnPhoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拍照" />
</LinearLayout>

二、增加权限

在AndroidManifest.xml中增加调用相机和存储数据的权限,代码如下:

1
2
3
4
5
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

三、设置照片存储路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private File createImageFile() throws IOException{
String timeStamp=new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
Log.i(timeStamp,"保存路径");
String imageFileName="JPEG_"+timeStamp+"_";
File storageDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);

// Save a file: path for use with ACTION_VIEW intents
mCurrentPhotoPath = "file:" + image.getAbsolutePath();
return image;
}

四、启动系统相机拍照并存储

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
private void dispatchTakePictureIntent() {
//隐式intent,不指定要启动哪个活动,而是通过配置一些相关信息,如action,category等信息,然后交给系统分析并找出合适的活动去启动
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
//resolveActivity 该方法接受一个包管理器对象作为参数,通过查找该包管理器,返回能够处理该intent的activity的component对象,没有找到能处理该intent的组件则返回null
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
// Error occurred while creating the File

}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoUri=FileProvider.getUriForFile(this,"com.example.z.test.fileProvider",photoFile);
// Log.d(photoUri.toString(),"保存路径");
Log.i(photoUri.toString(),"保存路径");
// Uri photoUri= FileProvider.getUriForFile(this,getPackageName()+".provider",photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
photoUri);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
}

五、遇到的问题

Android 7.0禁止应用程序向外部公布file://的 URI,尝试传递 file://的 URI会触发 FileUriException。应用程序之间共享数据,应该发送 content://的 URI,且授予URI临时访问权限。解决方法配置FileProvider。

1、在AnadroidMainfest.xml中配置provider

1
2
3
4
5
6
7
8
9
10
11
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.z.test.fileProvider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="android:authorities">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"
tools:replace="android:resource"/>
</provider>

2、增加provider_paths.xml

在res文件夹下新建xml文件夹,在xml文件夹下增加provider_paths.xml,provider_paths.xml文件内容如下:

1
2


六、代码清单

目录结构如下图所示:

Markdown

1、AndroidManifest.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.z.test">

<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>


<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.z.test.fileProvider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="android:authorities">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"
tools:replace="android:resource"/>
</provider>
</application>

</manifest>

2、MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package com.example.z.test;


import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import android.content.Intent;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btnPhoto;
static final int REQUEST_TAKE_PHOTO = 1;
String mCurrentPhotoPath;

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

private void initView(){
btnPhoto=findViewById(R.id.btnPhoto);
btnPhoto.setOnClickListener(this);
}

@Override
public void onClick(View v){
switch (v.getId()){
case R.id.btnPhoto:
//Toast.makeText(MainActivity.this,"btnPhoto",Toast.LENGTH_SHORT).show();
dispatchTakePictureIntent();
break;
default:
break;
}
}

private File createImageFile() throws IOException{
String timeStamp=new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
Log.i(timeStamp,"保存路径");
String imageFileName="JPEG_"+timeStamp+"_";
File storageDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);

// Save a file: path for use with ACTION_VIEW intents
mCurrentPhotoPath = "file:" + image.getAbsolutePath();
return image;
}


private void dispatchTakePictureIntent() {
//隐式intent,不指定要启动哪个活动,而是通过配置一些相关信息,如action,category等信息,然后交给系统分析并找出合适的活动去启动
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Ensure that there's a camera activity to handle the intent
//resolveActivity 该方法接受一个包管理器对象作为参数,通过查找该包管理器,返回能够处理该intent的activity的component对象,没有找到能处理该intent的组件则返回null
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
// Error occurred while creating the File

}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoUri=FileProvider.getUriForFile(this,"com.example.z.test.fileProvider",photoFile);
// Log.d(photoUri.toString(),"保存路径");
Log.i(photoUri.toString(),"保存路径");
// Uri photoUri= FileProvider.getUriForFile(this,getPackageName()+".provider",photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,
photoUri);
startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
}
}
}
}

3、activity_main.xml

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

<Button
android:id="@+id/btnPhoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="拍照" />
</LinearLayout>

4、provider_paths.xml

1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>
不完美

不完美

8 日志
GitHub 微博 知乎
© 2019 不完美
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4