VRecorder
涉及到的前台服务VRecorder
targetSDK
已经是28(Android P,API 9)FloatWindowService.java
应用打开后,创建悬浮球依赖的前台服务,同时会在通知栏创建前台服务通知。
/**
* 没有开始录制则会开启foreground通知栏
*/
private void startForegroundServiceAndNotification() {
if (!Prefs.getIsRecordStart(getApplicationContext())) {
StartRecordNotifications notifications = new StartRecordNotifications(getApplicationContext());
if (Build.MANUFACTURER.equals("OPPO") || Build.BRAND.equalsIgnoreCase("Xiaomi")) {
//判断是否是oppo手机
//判断是否是小米手机
startForeground(StartRecordNotifications.NOTIFICATION_ID, notifications.getXiaoMiNotification());
} else {
startForeground(StartRecordNotifications.NOTIFICATION_ID, notifications.getNormalNotification());
}
}
}
因为是target 28,所有Manifest里配置了权限前台服务权限
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
StartRecorderService.java
该服务是录制时打开的后台服务,在Android P以下没有独立打开通知,不属于前台服务。
@Override
public int onStartCommand(final Intent intent, int flags, int startId) {
if (intent != null) {
if (intent.getBooleanExtra(VIDEO_EXIT_APP, false)) {
//退出录制
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (manager != null) {
manager.cancelAll();//关闭所有通知
}
...
//关闭所有APP打开的任务
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
if (activityManager != null) {
List<ActivityManager.AppTask> appTasks = activityManager.getAppTasks();
for (int i = 0; i < appTasks.size(); i++) {
appTasks.get(i).finishAndRemoveTask();
}
}
//关闭自身服务
stopSelf();
return START_NOT_STICKY;
}
StartRecordNotifications.java
录制通知栏专属类创建该类的时候就一并创建通知渠道id和渠道名称
public StartRecordNotifications(Context context) {
this.context = context;
createNotificationChannel();
}
创建渠道id和名称
public static final String RECORD_CHANNEL_ID = "record_channel_id";
public static final String RECORD_CHANNEL = "record channel";
public static final String RECORD_NOTIFICATION_DESCRIPTION = "record notification";
private void createNotificationChannel() {
mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel channel = new NotificationChannel(RECORD_CHANNEL_ID, RECORD_CHANNEL, importance);
channel.setDescription(RECORD_NOTIFICATION_DESCRIPTION);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
mNotificationManager.createNotificationChannel(channel);
}
mBuilder = new NotificationCompat.Builder(context.getApplicationContext(), RECORD_CHANNEL_ID);
//创建通知:
mBuilder.setOngoing(true)//设置是否常驻,true为常驻
.setContentTitle(context.getString(R.string.string_notification_start_recording))
// .setStyle(new NotificationCompat.DecoratedCustomViewStyle())
.setSmallIcon(R.mipmap.ic_launcher_white)//设置小图标
.setCategory(NotificationCompat.CATEGORY_EVENT)
.setPriority(Notification.PRIORITY_LOW)//设置优先级
.setWhen(System.currentTimeMillis());//设置展示时间
}
android:foregroundServiceType
属性依然需要前台服务权限声明
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<service
android:name=".windowmanager.StartRecorderService"
android:enabled="true"
android:foregroundServiceType="mediaProjection"
android:exported="false" />
<service
android:name=".windowmanager.FloatWindowService"
android:foregroundServiceType="mediaProjection"/>
开启前台服务时需要添加参数foregroundServiceType
使用如下方法
startForeground(int id,Notification notification,int foregroundServiceType)
VRecorder使用案例:在FloatWindowService.java和StartRecorderService.java里都需要调用
public static void startForegroundServiceAndNotification(Service service) {
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {//Android 10
service.startForeground(StartRecordNotificationUtils.NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
} else {//非Android10
service.startForeground(StartRecordNotificationUtils.NOTIFICATION_ID, notification);
}
}
}
在FloatWindowService.java
里退出APP时:
@Override
public int onStartCommand(final Intent intent, int flags, int startId) {
if (intent == null) return START_REDELIVER_INTENT;
if (intent.getBooleanExtra(VIDEO_EXIT_APP, false)) {//退出录制
Prefs.setIsRecordStart(getApplicationContext(), false);
MyWindowManager.releaseFloatViews(this);
finishAllTaskAndActivity();//关闭所有activity task
stopRecorderServices();//关闭录制服务
stopForeground(true);//设置true,移除通知
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (manager != null) {
manager.cancelAll();
}
stopSelf();
return START_NOT_STICKY;
}
/**
*关闭录制服务
*/
private void stopRecorderServices() {
Intent name = new Intent(this, StartRecorderService.class);
name.putExtra(VIDEO_EXIT_APP, true);
stopService(name);
}
由各个事项定义好的图形对象实现draw方法来画出对应图形。
如下圆形对象的定义:
public class Circle extends ShapeAbstract {
public Circle(Shapable paintTool) {
super(paintTool);
}
@Override
public void draw(Canvas canvas, Paint paint) {
if (canvas==null || paint == null) {
return;
}
super.draw(canvas, paint);
float cx = (x1 + x2)/2;
float cy = (y1+y2)/2;
float radius = (float) Math.sqrt(Math.pow(x1 - x2, 2)
+ Math.pow(y1 - y2, 2))/2;
canvas.drawCircle(cx, cy, radius, paint);
}
@Override
public String toString() {
return " circle";
}
}
其中x1,x2,x2,y2的含义是手势触摸移动的坐标
x1 = firstCurrentPos.firstX;
y1 = firstCurrentPos.firstY;
x2 = firstCurrentPos.currentX;
y2 = firstCurrentPos.currentY;
其他图示类似方法来定义。
五角星需要数学知识计算如何画图形。
通过Canvas对象来画对应的形状:
canvas.drawLine(float startX, float startY, float stopX, float stopY,@NonNull Paint paint) ;//画直线
canvas.drawPath(@NonNull Path path, @NonNull Paint paint) ;//画路径曲线,五角星
canvas.drawOval(@NonNull RectF oval, @NonNull Paint paint);//画椭圆
canvas.drawRect(float left, float top, float right, float bottom, @NonNull Paint paint);//画矩形或正方形
画笔类型分为:
画笔对象:android.graphics.Paint
普通画笔
PlainPen:设置Paint.Style.STROKE
模糊
BlurPen: 设置Paint.Style.STROKE,设置maskFilter
// BlurMaskFilter 指定了一个模糊的样式和半径来处理Paint的边缘。
mBlur = new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL);
mPenPaint.setMaskFilter(mBlur);
浮雕
EmbossPen :设置Paint.Style.STROKE,设置maskFilter
// EmbossMaskFilter 指定了光源的方向和环境光强度来添加浮雕效果。
mEmboss = new EmbossMaskFilter(new float[] {
1, 1, 1
}, 0.4f, 6, 3.5f);
mPenPaint.setMaskFilter(mEmboss);
橡皮擦
Eraser:设置PorterDuffXfermode为PorterDuff.Mode.DST_OUT
mEraserPaint
.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
结合手势move坐标创建Path然后画贝塞尔曲线
// 画出贝塞尔曲线
private void drawBeziercurve(float x, float y) {
mPath.quadTo(mCurrentX, mCurrentY, (x + mCurrentX) / 2,
(y + mCurrentY) / 2);
}
之后画橡皮曲线
@Override
public void draw(Canvas canvas) {
if (null != canvas) {
canvas.drawPath(mPath, mEraserPaint);
}
}
package com.xvideostudio.videoeditor.paintpadinterfaces
package com.xvideostudio.videoeditor.paintshapes
package com.xvideostudio.videoeditor.painttools
package com.xvideostudio.videoeditor.paintutils
package com.xvideostudio.videoeditor.paintviews
Target 28遇到的问题:
这是修改了buildTools版本为28.0.3导致单引号字符串定位行数不准确的问题
解决办法:升级gradle为最新的。
解决办法:修改方法
def getSvnRevision() {
ISVNOptions options = SVNWCUtil.createDefaultOptions(true)
SVNClientManager clientManager = SVNClientManager.newInstance(options)
SVNStatusClient statusClient = clientManager.getStatusClient()
SVNStatus status = statusClient.doStatus(rootDir, false)
if (status != null) {
SVNRevision revision = status.getRevision()
def svnNum = revision.getNumber()
println("Svn Version: " + svnNum)
return svnNum
} else {
return null
}
}
否则和classpath ‘com.android.tools.build:gradle:3.5.1‘不匹配,(gradle-5.4.1-all.zip)
解决办法:删除参数
RecorderSplashActivity.java的manifest的orientation配置为unspacifed
GoogleVipKeepDialog的orientation去除
参见target28升级SDK版本修改点的md文档
GeneratePrivateKey.java
自定义编辑水印涉及到控件的自定义,控件位置的移动,图片大小和文字大小缩放,文字的颜色改变,还有水印的透明度。
水印元素包括了
图片水印
文字水印
涉及到的控件
- 模拟屏幕的一个容器控件,用于放置水印元素的
- 图片水印元素控件
- 文字水印元素控件
- 文字选择颜色值控件
水印需要保存的信息:数据库信息
水印透明度alpha
水印宽度widthRatio比例(涉及到横竖屏)
水印高度heightRatio比例(涉及到横竖屏)
水印位置centerX比例(涉及到横竖屏)
水印位置centerY比例(涉及到横竖屏)
文字水印颜色color
文字水印文案信息
文字缩放大小值textSize
图片水印文件路径
水印元素ID(数据库ID自动生成)
/**
* @author jaysen.lin@foxmail.com
* @since 2019/8/8
*/
public class MoveGestureDetector {
private float x = 0, y = 0;
public MoveGestureDetector() {
}
public boolean detectMoveAction(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x = event.getRawX();
y = event.getRawY();
return true;
case MotionEvent.ACTION_MOVE:
float x1 = event.getRawX();
float y1 = event.getRawY();
int dx = (int) (x1 - x);
int dy = (int) (y1 - y);
int l = view.getLeft() + dx;
int t = view.getTop() + dy;
int r = view.getRight() + dx;
int b = view.getBottom() + dy;
if (l < 0) {//超出父类控件左边际,重置为0
l = 0;
r = l + view.getWidth();
}
if (t < 0) {//超出父类控件上边际,重置为0
t = 0;
b = t + view.getHeight();
}
ViewParent parent = view.getParent();
ViewGroup p = (ViewGroup) parent;
if (r > p.getWidth()) {//超出父类控件右边际,重置
r = p.getWidth();
l = r - view.getWidth();
}
if (b > p.getHeight()) {//超出父类控件下边际,重置
b = p.getHeight();
t = b - view.getHeight();
}
float halfW = view.getMeasuredWidth() / 2.0f;
float halfH = view.getMeasuredHeight() / 2.0f;
float centerXRatio = (l + halfW) / p.getMeasuredWidth();
float centerYRatio = (t + halfH) / p.getMeasuredHeight();
//todo 把centerXRatio,centerYRatio的横竖屏信息保存起来
//还有就是刷新控件重新计算大小和布局
view.requestLayout();
x = x1;
y = y1;
return true;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
return true;
}
return false;
}
VRecorder
|--cnpay //支付模块
| |--src
| |--libs
|
|--staticanalysislib//数据打点探针模块
| |--src
| |--libs
|
|---themelibrary//主题配色、字符串资源模块
| |-src
| |-libs
|
|--App//主项目模块
|-src
|-libs
请求服务器接口下订单—>调用本地SDK支付—>SDK返回支付结果—>服务器验证查询订单支付结果
请求服务器接口下订单—>调用本地SDK支付—>SDK返回支付结果—>服务器验证查询订单支付结果
请求服务器接口获得授权信息—>调用支付宝SDK对授权信息进行授权—>获得openID再请求服务器接口获取所有订单购买情况
微信SDK发起授权获取code—>通过请求微信官网的链接接口进行验证授权返回openID—>请求服务接口获取所有订单购买情况
SecurityException
。 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
打开前台服务,需要有通知栏体现:
Intent intent = new Intent(this, ForegroundService.class);
ContextCompat.startForegroundService(this, intent);
要请求让服务运行于前台,在ForegroundService.class
里需要调用如下方法启动通知栏:
startForeground(NOTIFICATION_ID, notification);
对非Android SDK API的调用做了限制,通常会出现类似NoSuchClassException
或NoSuchFieldException
异常。
非SDK API包括Android未公开的底层内部实现细节的API反射调用。还有NDK里未公开的方法调用等。