标签: Android App

免邀请码下载 app 是怎么实现下载人和上级绑定的 app

请问各位大佬–免邀请码下载 app 是怎么实现下载人和上级绑定的 app 安装后打开不用输入邀请码就知道上级?

 

Jione · 21 小时 44 分钟前 · 1180 次点击

A B C 三个人用这个 D 下载链接 分别生成了 A B C 三个分享链接(分享链接就是打开重定向 D 下载地址的链接)
我打开 A 分享的链接 下载完了 app 打开 App app 自动默认我邀请人是 A
菜狗打开了 B 分享的链接 打开 App app 自动默认邀请人是 B
请问 我和 A 怎么关联上的?
菜狗和 B 怎么关联上的?

1.A B C 这三个链接打开都没有去获取手机的信息 imei 这些东西,也没有本地写文件
2.A B C 这三个链接重定向都是到同一个下载地址上去的
3.没有给粘贴板上面写内容
4.没有和手机厂商合作
5.和 ip 没关系,我用 1ip 下载了用 2ip 安装打开也能统计上

29 条回复    2021-09-28 09:43:31 +08:00

xmt328
    1

xmt328   21 小时 35 分钟前

动态生成 APK?
lisongeee
    2

lisongeee   21 小时 31 分钟前

aHR0cHM6Ly9zZWdtZW50ZmF1bHQuY29tL2EvMTE5MDAwMDAzOTk3NTA2OQ==
JoeBreeze
    3

JoeBreeze   21 小时 28 分钟前

解包看看
Jione
    4

Jione   21 小时 7 分钟前

apk 不是动态的,他直接重定向完的下载地址了
Jione
    5

Jione   21 小时 7 分钟前

@xmt328 apk 不是动态的,他直接重定我的下载地址了,所有邀请链接用的同一个 apk 下载地址
Jione
    6

Jione   21 小时 6 分钟前

@JoeBreeze 反编译 apk 了,apk 安装完首次打开就获取了手机信息上传
Jione
    7

Jione   21 小时 5 分钟前

@lisongeee 怎么说老哥
xiterjia
    8

xiterjia   21 小时 1 分钟前

@Jione 老哥给的是 Base64,所以怎么说老哥
puduhe1
    9

puduhe1   20 小时 59 分钟前

这是我们的核心逻辑,怎么可能随便说出来呢?自已去摸索吧
https://www.sharetrace.com/
Jione
    10

Jione   20 小时 56 分钟前

@lisongeee 通过此链接下载安装 App 并启动之后,集成了 openinstall SDK 的 App 就能获取到 ID=A 的参数,从而可以得知*个信息:此次下载是由用户 A 邀请来的

我不理解 app 和浏览器不想干 如何获取到他就是 id=A 的

scyuns
    11

scyuns   20 小时 52 分钟前

openinstall 可以做到 但是貌似要花钱的
Jione
    12

Jione   20 小时 48 分钟前

@puduhe1 你们的和 appsflyer 原理一样吗
gam2046
    13

gam2046   20 小时 45 分钟前

大致逻辑是下载落地页地址包含推广者信息,而落地页中同时包含对服务商 S 的网络请求,被推广者 A/B/C 分别下载后,被推广应用内的服务商 S 的 SDK 请求其服务器,通过来源 IP 比对认定(例如两次请求间隔小于 1 小时)是否属于通过特定落地页的新用户,如匹配成功则返回落地页的查询参数。

如果用户量不大或者用户所属地区比较分散的情况下,这种方式还是比较准确的。

Kinnice
    14

Kinnice   20 小时 44 分钟前

web 指纹等多种记录:比如记录为(abc….)

你访问链接地址 会记录一个请求 (abc….) = 邀请码 1

然后 app 的 sdk 也会去发请求去寻找匹配 (abc….) 获取到 邀请码 1

puduhe1
    15

puduhe1   20 小时 44 分钟前

@Jione 实际上业界原理,技术都差不多,但我们*便宜,只要 69.99 一个月,不要研究了,直接用我们的吧…程序员时间很贵的,研究出来了,难道你想自已做一个吗?划不来
Jione
    16

Jione   20 小时 43 分钟前

@puduhe1 ? 主要是我想学习这个技术
Kinnice
    17

Kinnice   20 小时 43 分钟前

至于要多准就看你的 多种记录挑得有多好了
lisongeee
    18

lisongeee   20 小时 43 分钟前

@Jione
aHR0cHM6Ly93d3cub3Blbmluc3RhbGwuaW8vZG9jLw==

当某一终端访问该 h5 页面时,openinstall web sdk 将同时确定该设备的个性化信息和采集自定义参数,上传至 openinstall 服务器, 待用户通过该 h5 页面安装 App 后首次打开时(如当前设备已安装该 App,将直接跳转 App ),使用 openinstall Android/iOS sdk 从 openinstall 服务器再取回暂存的自定义参数;

drift666
    19

drift666   20 小时 28 分钟前

延迟 Deeplink
vickchen1992
    20

vickchen1992   20 小时 27 分钟前

建议使用 openinstall
puduhe1
    21

puduhe1   20 小时 20 分钟前

@Jione 学会没有意义的,因为原理很简单,但要做到高准确率,要做很多事情
puduhe1
    22

puduhe1   20 小时 19 分钟前

@vickchen1992 就是贵,我们之前也用他们
datoujiejie221
    23

datoujiejie221   20 小时 8 分钟前

建议先抓个包 然后根据参数分析一下
MX123
    24

MX123   19 小时 41 分钟前

友盟 U-Link 了解一下
Esec
    25

Esec   19 小时 32 分钟前

[打开重定向的分享链接] 以前见过有商业套餐,功能多的很,包括用户每次访问到之后即时给商户反馈点击者的指纹啥乱七八糟的
mateor95
    26

mateor95   13 小时 26 分钟前

一种简单的实现方式:
推广页面采集一些信息,比如 IP 、UA 等,UA 基本上还是能分辨出是什么手机、屏幕大小的
打开 APP 的时候只要对比一下,又不是微信淘宝这种 APP,不太会存在同一时刻同 IP 同手机型号的用户吧?
mateor95
    27

mateor95   13 小时 24 分钟前

@mateor95 #26
补充一下,浏览器和应用内的 cookies 不晓得能不能共用,没实测过,按我理解,都是调用系统浏览器的情况下应该是共用的,也可以作为识别依据
mateor95
    28

mateor95   13 小时 17 分钟前

另外,“我用 1ip 下载了用 2ip 安装打开也能统计上”其实不太能说明与 IP 无关,毕竟如果这个 APP 使用了专门的商用 SDK 的话,人家一般会考虑换 IP 问题(指判断时给一个容错区间,IP 这么好使的信息不可能完全忽略的),在乘坐公交地铁的时候,是有可能出现频繁切换基站导致 IP 变化的

以上所有回复是我瞎猜,说错了不怪我(狗头

2i2Re2PLMaDnghL
    29

2i2Re2PLMaDnghL   3 小时 23 分钟前   ❤️ 1

@puduhe1 你这懂哥味也太冲了

Android进阶之_实现滑动的7种方式详解

在android开发中,滑动对一个app来说,是非常重要的,流畅的滑动操作,能够给用户带来用好的体验,那么本次就来讲讲android中实现滑动有哪些方式。其实滑动一个View,本质上是移动一个View,改变其当前所属的位置,要实现View的滑动,就必须监听用户触摸的事件,且获取事件传入的坐标值,从而动画的改变位置而实现滑动。

android坐标系
首先要知道android的坐标系与我们平常学习的坐标系是不一样的,在android中是将左上方作为坐标原点,向右为x抽正方向,向下为y抽正方向,像在触摸事件中,getRawX(),getRawY()获取到的就是Android坐标中的坐标.

视图坐标系
android开发中除了上面的这种坐标以外,还有一种坐标,叫视图坐标系,他的原点不在是屏幕左上方,而是以父布局坐上角为坐标原点,像在触摸事件中,getX(),getY()获取到的就是视图坐标中的坐标.

触摸事件–MotionEvent
触摸事件MotionEvent在用户交互中,有非常重要的作用,因此必须要掌握他,我们先来看看Motievent中封装的一些常用的触摸事件常亮:

//单点触摸按下动作
public static final int ACTION_DOWN = 0;
//单点触摸离开动作
public static final int ACTION_UP = 1;
//触摸点移动动作
public static final int ACTION_MOVE = 2;
//触摸动作取消
public static final int ACTION_CANCEL = 3;
//触摸动作超出边界
public static final int ACTION_OUTSIDE = 4;
//多点触摸按下动作
public static final int ACTION_POINTER_DOWN = 5;
//多点触摸离开动作
public static final int ACTION_POINTER_UP = 6;

以上是比较常用的一些触摸事件,通常情况下,我们会在OnTouchEvent(MotionEvent event)方法中通过event.getAction()方法来获取触摸事件的类型,其代码模式如下:

@Override
public boolean onTouchEvent(MotionEvent event)
{
//获取当前输入点的坐标,(视图坐标)
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
//处理输入按下事件
break;
case MotionEvent.ACTION_MOVE:
//处理输入的移动事件
break;
case MotionEvent.ACTION_UP:
//处理输入的离开事件
break;
}
return true; //注意,这里必须返回true,否则只能响应按下事件
}

以上只是一个空壳的架构,遇到的具体的场景,也有可能会新增多其他事件,或是用不到这么多事件等等,要根据实际情况来处理。在介绍如何实现滑动之前先来看看android中给我们提供了那些常用的获取坐标值,相对距离等的方法,主要是有以下两个类别:

View 提供的获取坐标方法

getTop(): 获取到的是View自身的顶边到其父布局顶边的距离

getBottom(): 获取到的是View自身的底边到其父布局顶边的距离

getLeft(): 获取到的是View自身的左边到其父布局左边的距离

getRight(): 获取到的是View自身的右边到其父布局左边的距离

MotionEvent提供的方法

getX(): 获取点击事件距离控件左边的距离,即视图坐标

getY(): 获取点击事件距离控件顶边的距离,即视图坐标

getRawX(): 获取点击事件距离整个屏幕左边的距离,即*对坐标

getRawY(): 获取点击事件距离整个屏幕顶边的距离,即*对坐标

介绍上面一些基本的知识点后,下面我们就来进入正题了,android中实现滑动的其中方法:

实现滑动的7种方法
其实不管是哪种滑动,他们的基本思路是不变的,都是:当触摸View时,系统记下当前的触摸坐标;当手指移动时,系统记下移动后的触摸点坐标,从而获得相对前一个点的偏移量,通过偏移量来修改View的坐标,并不断的更新,重复此动作,即可实现滑动的过程。
首先我们先来定义一个View,并置于LinearLayout中,我们的目的是要实现View随着我们手指的滑动而滑动,布局代码如下:

<?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:orientation=”vertical”>

<com.liaojh.scrolldemo.DragView
android:layout_width=”100dp”
android:layout_height=”100dp”
android:background=”#88ffffff”/>

</LinearLayout>

layout方法
我们知道,在进行View绘制时,会调用layout()方法来设置View的显示位置,而layout方法是通过left,top,right,bottom这四个参数来确定View的位置的,所以我们可以通过修改这四个参数的值,从而修改View的位置。首先我们在onTouchEvent方法中获取触摸点的坐标:

float x = event.getX();
float y = event.getY();

接着在ACTION_DOWN的时候记下触摸点的坐标值:

case MotionEvent.ACTION_DOWN:
//记录按下触摸点的位置
mLastX = x;
mLastY = y;
break;

*后在ACTION_MOVE的时候计算出偏移量,且将偏移量作用到layout方法中:

case MotionEvent.ACTION_MOVE:
//计算偏移量(此次坐标值-上次触摸点坐标值)
int offSetX = (int) (x – mLastX);
int offSetY = (int) (y – mLastY);

//在当前left,right,top.bottom的基础上加上偏移量
layout(getLeft() + offSetX,
getTop() + offSetY,
getRight() + offSetX,
getBottom() + offSetY
);
break;

这样每次在手指移动的时候,都会调用layout方法重新更新布局,从而达到移动的效果,完整代码如下:

package com.liaojh.scrolldemo;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

/**
* @author LiaoJH
* @DATE 15/11/7
* @VERSION 1.0
* @DESC TODO
*/
public class DragView extends View
{
private float mLastX;
private float mLastY;

public DragView(Context context)
{
this(context, null);
}

public DragView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}

public DragView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
}

@Override
public boolean onTouchEvent(MotionEvent event)
{
//获取当前输入点的坐标,(视图坐标)
float x = event.getX();
float y = event.getY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
//记录按下触摸点的位置
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
//计算偏移量(此次坐标值-上次触摸点坐标值)
int offSetX = (int) (x – mLastX);
int offSetY = (int) (y – mLastY);

//在当前left,right,top.bottom的基础上加上偏移量
layout(getLeft() + offSetX,
getTop() + offSetY,
getRight() + offSetX,
getBottom() + offSetY
);

break;
}
return true;
}
}

当然也可以使用getRawX(),getRawY()来获取*对坐标,然后使用*对坐标来更新View的位置,但要注意,在每次执行完ACTION_MOVE的逻辑之后,一定要重新设置初始坐标,这样才能准确获取偏移量,否则每次的偏移量都会加上View的父控件到屏幕顶边的距离,从而不是真正的偏移量了。

@Override
public boolean onTouchEvent(MotionEvent event)
{
//获取当前输入点的坐标,(*对坐标)
float rawX = event.getRawX();
float rawY = event.getRawY();
switch (event.getAction())
{
case MotionEvent.ACTION_DOWN:
//记录按下触摸点的位置
mLastX = rawX;
mLastY = rawY;
break;
case MotionEvent.ACTION_MOVE:
//计算偏移量(此次坐标值-上次触摸点坐标值)
int offSetX = (int) (rawX – mLastX);
int offSetY = (int) (rawY – mLastY);

//在当前left,right,top.bottom的基础上加上偏移量
layout(getLeft() + offSetX,
getTop() + offSetY,
getRight() + offSetX,
getBottom() + offSetY
);

//重新设置初始位置的值
mLastX = rawX;
mLastY = rawY;
break;
}
return true;
}

offsetLeftAndRight()与offsetTopAndBottom()
这个方法相当于系统提供了一个对左右,上下移动的API的封装,在计算出偏移量之后,只需使用如下代码设置即可:

offsetLeftAndRight(offSetX);
offsetTopAndBottom(offSetY);

偏移量的计算与上面一致,只是换了layout方法而已。

LayoutParams
LayoutParams保存了一个View的布局参数,因此可以在程序中通过动态的改变布局的位置参数,也可以达到滑动的效果,代码如下:

LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) getLayoutParams();
lp.leftMargin = getLeft() + offSetX;
lp.topMargin = getTop() + offSetY;
setLayoutParams(lp);

使用此方式时需要特别注意:通过getLayoutParams()获取LayoutParams时,需要根据View所在的父布局的类型来设置不同的类型,比如这里,View所在的父布局是LinearLayout,所以可以强转成LinearLayout.LayoutParams。

在通过改变LayoutParams来改变View的位置时,通常改变的是这个View的Margin属性,其实除了LayoutParams之外,我们有时候还可以使用ViewGroup.MarginLayoutParams来改变View的位置,代码如下:

ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
lp.leftMargin = getLeft() + offSetX;
lp.topMargin = getTop() + offSetY;
setLayoutParams(lp);
//使用这种方式的好处就是不用考虑父布局类型

scrollTo与scrollBy
在一个View中,系统提供了scrollTo与scrollBy两种方式来改变一个View的位置,其中scrollTo(x,y)表示移动到一个具体的坐标点(x,y),而scrollBy(x,y)表示移动的增量。与前面几种计算偏移量相同,使用scrollBy来移动View,代码如下:

scrollBy(offSetX,offSetY);

然后我们拖动View,发现View并没有移动,这是为杂呢?其实,方法没有错,view也的确移动了,只是他移动的不是我们想要的东西。scrollTo,scrollBy方法移动的是view的content,即让view的内容移动,如果是在ViewGroup中使用scrollTo,scrollBy方法,那么移动的将是所有的子View,而如果在View中使用的话,就是view的内容,所以我们需要改一下我们之前的代码:

((View)getParent()).scrollBy(offSetX, offSetY);

这次是可以滑动了,但是我们发现,滑动的效果跟我们想象的不一样,完全相反了,这又是为什么呢?其实这是因为android中对于移动参考系选择的不同从而实现这样的效果,而我们想要实现我们滑动的效果,只需将偏移量设置为负值即可,代码如下:

((View) getParent()).scrollBy(-offSetX, -offSetY);

同样的在使用*对坐标时,使用scrollTo也可以达到这样的效果。

scroller
如果让一个View向右移动200的距离,使用上面的方式,大家应该发现了一个问题,就是移动都是瞬间完成的,没有那种慢慢平滑的感觉,所以呢,android就给我们提供了一个类,叫scroller类,使用该类就可以实现像动画一样平滑的效果。

其实它实现的原理跟前面的scrooTo,scrollBy方法实现view的滑动原理类似,它是将ACTION_MOVE移动的一段位移划分成N段小的偏移量,然后再每一个偏移量里面使用scrollBy方法来实现view的瞬间移动,这样在整体的效果上就实现了平滑的效果,说白了就是利用人眼的视觉暂留特性。

下面我们就来实现这么一个例子,移动view到某个位置,松开手指,view都吸附到左边位置,一般来说,使用Scroller实现滑动,需经过以下几个步骤:

初始化Scroller

//初始化Scroller,使用默认的滑动时长与插值器
mScroller = new Scroller(context);

重写computeScroll()方法

该方法是Scroller类的核心,系统会在绘制View的时候调用draw()方法中调用该方法,这个方法本质上是使用scrollTo方法,通过Scroller类可以获取到当前的滚动值,这样我们就可以实现平滑一定的效果了,一般模板代码如下:

@Override
public void computeScroll()
{
super.computeScroll();
//判断Scroller是否执行完成
if (mScroller.computeScrollOffset()) {
((View)getParent()).scrollTo(
mScroller.getCurrX(),
mScroller.getCurrY()
);
//调用invalidate()computeScroll()方法
invalidate();
}
}

Scroller类提供中的方法:

computeScrollOffset(): 判断是否完成了真个滑动

getCurrX(): 获取在x抽方向上当前滑动的距离

getCurrY(): 获取在y抽方向上当前滑动的距离

startScroll开启滑动

*后在需要使用平滑移动的事件中,使用Scroller类的startScroll()方法来开启滑动过程,startScroller()方法有两个重载的方法:

– public void startScroll(int startX, int startY, int dx, int dy)

– public void startScroll(int startX, int startY, int dx, int dy, int duration)

可以看到他们的区别只是多了duration这个参数,而这个是滑动的时长,如果没有使用默认时长,默认是250毫秒,而其他四个坐标则表示起始坐标与偏移量,可以通过getScrollX(),getScrollY()来获取父视图中content所滑动到的点的距离,不过要注意这个值的正负,它与scrollBy,scrollTo中说的是一样的。经过上面这三步,我们就可以实现Scroller的平滑一定了。

继续上面的例子,我们可以在onTouchEvent方法中监听ACTION_UP事件动作,调用startScroll方法,其代码如下:

case MotionEvent.ACTION_UP:
//第三步
//当手指离开时,执行滑动过程
ViewGroup viewGroup = (ViewGroup) getParent();
mScroller.startScroll(
viewGroup.getScrollX(),
viewGroup.getScrollY(),
-viewGroup.getScrollX(),
0,
800
);
//刷新布局,从而调用computeScroll方法
invalidate();
break;

属相动画
使用属性动画同样可以控制一个View的滑动,下面使用属相动画来实现上边的效果(关于属相动画,请关注其他的博文),代码如下:

case MotionEvent.ACTION_UP:
ViewGroup viewGroup = (ViewGroup) getParent();
//属性动画执行滑动
ObjectAnimator.ofFloat(this, “translationX”, viewGroup.getScrollX()).setDuration(500)
.start();
break;

ViewDragHelper
一看这个类的名字,我们就知道他是与拖拽有关的,猜的没错,通过这个类我们基本可以实现各种不同的滑动,拖放效果,他是非常强大的一个类,但是它也是*为复杂的,但是不要慌,只要你不断的练习,就可以数量的掌握它的使用技巧。下面我们使用这个类来时实现类似于QQ滑动侧边栏的效果,相信广大朋友们多与这个现象是很熟悉的吧。

先来看看使用的步骤是如何的:

初始化ViewDragHelper

ViewDragHelper这个类通常是定义在一个ViewGroup的内部,并通过静态方法进行初始化,代码如下:

//初始化ViewDragHelper
viewDragHelper = ViewDragHelper.create(this,callback);

它的*个参数是要监听的View,通常是一个ViewGroup,第二个参数是一个Callback回调,它是整个ViewDragHelper的逻辑核心,后面进行具体介绍。

拦截事件

重写拦截事件onInterceptTouchEvent与onTouchEvent方法,将事件传递交给ViewDragHelper进行处理,代码如下:

@Override
public boolean onInterceptTouchEvent(MotionEvent ev)
{
//2. 将事件交给ViewDragHelper
return viewDragHelper.shouldInterceptTouchEvent(ev);
}

@Override
public boolean onTouchEvent(MotionEvent event)
{
//2. 将触摸事件传递给ViewDragHelper,不可少
viewDragHelper.processTouchEvent(event);
return true;
}

处理computeScroll()方法

前面我们在使用Scroller类的时候,重写过该方法,在这里我们也需要重写该方法,因为ViewDragHelper内部也是使用Scroller类来实现的,代码如下:

//3. 重写computeScroll
@Override
public void computeScroll()
{
//持续平滑动画 (高频率调用)
if (viewDragHelper.continueSettling(true))
// 如果返回true, 动画还需要继续执行
ViewCompat.postInvalidateOnAnimation(this);
}

处理回调Callback

通过如下代码创建一个Callback:

private ViewDragHelper.Callback callback = new ViewDragHelper.Callback()
{
@Override
//此方法中可以指定在创建ViewDragHelper时,参数ViewParent中的那些子View可以被移动
//根据返回结果决定当前child是否可以拖拽
// child 当前被拖拽的View
// pointerId 区分多点触摸的id
public boolean tryCaptureView(View child, int pointerId)
{
//如果当前触摸的view是mMainView时开始检测
return mMainView == child;
}

@Override
//水平方向的滑动
// 根据建议值 修正将要移动到的(横向)位置 (重要)
// 此时没有发生真正的移动
public int clampViewPositionHorizontal(View child, int left, int dx)
{
//返回要滑动的距离,默认返回0,既不滑动
//参数参考clampViewPositionVertical
f (child == mMainView)
{
if (left > 300)
{
left = 300;
}
if (left < 0)
{
left = 0;
}
}
return left;
}

@Override
//垂直方向的滑动
// 根据建议值 修正将要移动到的(纵向)位置 (重要)
// 此时没有发生真正的移动
public int clampViewPositionVertical(View child, int top, int dy)
{
//top : 垂直向上child滑动的距离,
//dy: 表示比较前一次的增量,通常只需返回top即可,如果需要精确计算padding等属性的话,就需要对left进行处理
return super.clampViewPositionVertical(child, top, dy); //0
}
};

到这里就可以拖拽mMainView移动了。

下面我们继续来优化这个代码,还记得之前我们使用Scroller时,当手指离开屏幕后,子view会吸附到左边位置,当时我们监听ACTION_UP,然后调用startScroll来实现的,这里我们使用ViewDragHelper来实现。

在ViewDragHelper.Callback中,系统提供了这么一个方法—onViewReleased(),我们可以通过重写这个方法,来实现之前的操作,当然这个方法内部也是通过Scroller来实现的,这也是为什么我们要重写computeScroll方法的原因,实现代码如下:

@Override
//拖动结束时调用
public void onViewReleased(View releasedChild, float xvel, float yvel)
{
if (mMainView.getLeft() < 150)
{
// 触发一个平滑动画,关闭菜单,相当于Scroll的startScroll方法
if (viewDragHelper.smoothSlideViewTo(mMainView, 0, 0))
{
// 返回true代表还没有移动到指定位置, 需要刷新界面.
// 参数传this(child所在的ViewGroup)
ViewCompat.postInvalidateOnAnimation(DragLayout.this);
}
}
else
{
//打开菜单
if (viewDragHelper.smoothSlideViewTo(mMainView, 300, 0)) ;
{
ViewCompat.postInvalidateOnAnimation(DragLayout.this);
}
}
super.onViewReleased(releasedChild, xvel, yvel);
}

 

当滑动的距离小于150时,mMainView回到原来的位置,当大于150时,滑动到300的位置,相当于打开了mMenuView,而且滑动的时候是很平滑的。此外还有一些方法:

@Override
public void onViewCaptured(View capturedChild, int activePointerId)
{
// 当capturedChild被捕获时,调用.
super.onViewCaptured(capturedChild, activePointerId);
}

@Override
public int getViewHorizontalDragRange(View child)
{
// 返回拖拽的范围, 不对拖拽进行真正的限制. 仅仅决定了动画执行速度
return 300;
}

@Override
//当View位置改变的时候, 处理要做的事情 (更新状态, 伴随动画, 重绘界面)
// 此时,View已经发生了位置的改变
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy)
{
// changedView 改变位置的View
// left 新的左边值
// dx 水平方向变化量
super.onViewPositionChanged(changedView, left, top, dx, dy);
}

说明:里面还有很多关于处理各种事件方法的定义,如:

onViewCaptured():用户触摸到view后回调

onViewDragStateChanged(state):这个事件在拖拽状态改变时回调,比如:idle,dragging等状态

onViewPositionChanged():这个是在位置改变的时候回调,常用于滑动时伴随动画的实现效果等

对于里面的方法,如果不知道什么意思,则可以打印log,看看参数的意思。

总结
这里介绍的就是android实现滑动的七种方法,至于使用哪一种好,就要结合具体的项目需求场景了,毕竟硬生生的实现这个效果,而不管用户的使用体验式不切实际的,这里面个人觉得比较重要的是Scroller类的使用。属性动画以及ViewDragHelper类,特别是*后一个,也是*难*复杂的,但也是甩的*多的。

终于写完了,好累的赶脚~~~
————————————————

android 实现app内部检测*新版本 自动升级到*新版本

app现在基本都有版本更新这个功能,实现起来也很简单

截图效果:

%title插图%num
1. 获取当前app的版本号

/**
* 获取版本号
*
* @throws PackageManager.NameNotFoundException
*/
public static String getVersionName(Context context) throws PackageManager.NameNotFoundException {
// 获取packagemanager的实例
PackageManager packageManager = context.getPackageManager();
// getPackageName()是你当前类的包名,0代表是获取版本信息
PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
String version = packInfo.versionName;
return version;
}
2.根据版本号名称判断版本高低
/**
* 版本号比较
*0代表相等,1代表version1大于version2,-1代表version1小于version2
* @param version1
* @param version2
* @return
*/
public static int compareVersion(String version1, String version2) {
if (version1.equals(version2)) {
return 0;
}
String[] version1Array = version1.split(“\\.”);
String[] version2Array = version2.split(“\\.”);
int index = 0;
// 获取*小长度值
int minLen = Math.min(version1Array.length, version2Array.length);
int diff = 0;
// 循环判断每位的大小
while (index < minLen
&& (diff = Integer.parseInt(version1Array[index])
– Integer.parseInt(version2Array[index])) == 0) {
index++;
}
if (diff == 0) {
// 如果位数不一致,比较多余位数
for (int i = index; i < version1Array.length; i++) {
if (Integer.parseInt(version1Array[i]) > 0) {
return 1;
}
}

for (int i = index; i < version2Array.length; i++) {
if (Integer.parseInt(version2Array[i]) > 0) {
return -1;
}
}
return 0;
} else {
return diff > 0 ? 1 : -1;
}
}
3.从服务器获取*新版本号
/**
* 从服务器获取版本*新的版本信息
*/
private void getVersionInfoFromServer(){
//模拟从服务器获取信息 模拟更新王者荣耀
versionInfoBean = new VersionInfoBean(“1.1.1″,”http://dlied5.myapp.com/myapp/1104466820/sgame/2017_com.tencent.tmgp.sgame_h162_1.33.1.8_9c4c7f.apk”,”1.修复若干bug\n\n2.新增图片编辑功能”
,getExternalCacheDir()+”/1.1.1.jpg”);
SharedPreferences sharedPreferences = getSharedPreferences(“data”,MODE_PRIVATE);
sharedPreferences.edit().putString(“url”,versionInfoBean.getDownloadUrl()).commit();
sharedPreferences.edit().putString(“path”,versionInfoBean.getPath()).commit();//getExternalCacheDir获取到的路径 为系统为app分配的内存 卸载app后 该目录下的资源也会删除
//比较版本信息
try {
int result = Utils.compareVersion(Utils.getVersionName(this),versionInfoBean.getVersionName());
if(result==-1){//不是*新版本
showDialog();
}else{
Toast.makeText(MainActivity.this,”已经是*新版本”,Toast.LENGTH_SHORT).show();
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}

}

4.弹出dialog提示更新
private void showDialog(){
final Dialog dialog = new Dialog(MainActivity.this);
LayoutInflater inflater = (LayoutInflater)MainActivity.this
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
TextView version,content;
Button left,right;
View view = inflater.inflate(R.layout.version_update,null,false);
version = (TextView)view.findViewById(R.id.version);
content = (TextView)view.findViewById(R.id.content);
left = (Button)view.findViewById(R.id.left);
right = (Button)view.findViewById(R.id.right);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
content.setText(Html.fromHtml(versionInfoBean.getDesc(),Html.FROM_HTML_MODE_LEGACY));
}else{
content.setText(Html.fromHtml(versionInfoBean.getDesc()));
}
content.setMovementMethod(LinkMovementMethod.getInstance());
version.setText(“版本号:”+versionInfoBean.getVersionName());
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
left.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dialog.dismiss();
}
});
right.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
dialog.dismiss();
downloadNewVersionFromServer();

}
});
dialog.setContentView(view);
dialog.setCancelable(false);
Window dialogWindow = dialog.getWindow();
dialogWindow.setGravity(Gravity.CENTER);
//dialogWindow.setWindowAnimations(R.style.ActionSheetDialogAnimation);
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
WindowManager wm = (WindowManager)
getSystemService(Context.WINDOW_SERVICE);
lp.width =wm.getDefaultDisplay().getWidth()/10*9;
dialogWindow.setAttributes(lp);
dialog.show();
}

5.启动service后台下载
/**
* 启动服务后台下载
*/
private void downloadNewVersionFromServer(){
if(new File(versionInfoBean.getPath()).exists()){
new File(versionInfoBean.getPath()).delete();
}
Toast.makeText(MainActivity.this,”开始下载…”,Toast.LENGTH_SHORT).show();
LoadingService.startUploadImg(this);
}

6.定义广播接受者接受下载状态
声明变量isLoading表示下载状态
/**
* 定义广播接收者 接受下载状态
*/
public class MyReceive extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(“android.intent.action.loading_over”.equals(action)){
isLoading = false;
}else if(“android.intent.action.loading”.equals(action)){
isLoading = true;
}
}
}
7.创造service
public class LoadingService extends IntentService {
private HttpUtils httpUtils;
NotificationManager nm;
private String url,path;
private SharedPreferences sharedPreferences;
public LoadingService(String name) {
super(name);
}
public LoadingService() {
super(“MyService”);

}

public static void startUploadImg(Context context)
{
Intent intent = new Intent(context, LoadingService.class);
context.startService(intent);
}

public void onCreate() {
super.onCreate();
httpUtils = new HttpUtils();
httpUtils.configCurrentHttpCacheExpiry(0);
nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
sharedPreferences = getSharedPreferences(“data”,MODE_PRIVATE);
}

@Override
protected void onHandleIntent(@Nullable Intent intent) {
updateApk();
}

//开始下载apk 网络请求使用的是xutils框架
private void updateApk(){
url = sharedPreferences.getString(“url”,””);
path = sharedPreferences.getString(“path”,””);

httpUtils.download(url,
path , new RequestCallBack<File>() {
@Override
public void onLoading(final long total, final long current,
boolean isUploading) {
createNotification(total,current);
sendBroadcast(new Intent().setAction(“android.intent.action.loading”));//发送正在下载的广播
super.onLoading(total, current, isUploading);
}

@Override
public void onSuccess(ResponseInfo<File> arg0) {
nm.cancel(R.layout.notification_item);
Toast.makeText(LoadingService.this,”下载成功…”,Toast.LENGTH_SHORT).show();
installApk();//下载成功 打开安装界面
stopSelf();//结束服务
sendBroadcast(new Intent().setAction(“android.intent.action.loading_over”));//发送下载结束的广播
}

@Override
public void onFailure(HttpException arg0, String arg1) {
Toast.makeText(LoadingService.this,”下载失败…”,Toast.LENGTH_SHORT).show();
sendBroadcast(new Intent().setAction(“android.intent.action.loading_over”));//发送下载结束的广播
nm.cancel(R.layout.notification_item);
stopSelf();
}
});
}
/**
* 安装下载的新版本
*/
protected void installApk() {
Intent intent = new Intent();
intent.addCategory(Intent.CATEGORY_DEFAULT);
File file = new File(path);
Uri uri = Uri.fromFile(file);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(uri, “application/vnd.android.package-archive”);
this.startActivity(intent);
}
//发送通知 实时更新通知栏下载进度
private void createNotification(final long total, final long current){
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);//必须要设置这个属性,否则不显示
RemoteViews contentView = new RemoteViews(this.getPackageName(),R.layout.notification_item);
contentView.setProgressBar(R.id.progress, (int)total, (int)current, false);
builder.setOngoing(true);//设置左右滑动不能删除
Notification notification = builder.build();
notification.contentView = contentView;
nm.notify(R.layout.notification_item,notification);//发送通知
}
ok 大功告成
————————————————

Android 实现自动更新及强制更新功能

引言
目的不言而喻,肯定是为了自己版本的迭代更新。想做到版本的完全控制手段还是比较多的。今天我要分享的这个方法是用蒲公英提供的版本查询接口和版本下载接口来做的。有条件的同学也可以在自己的服务端开这两个接口。

需求分析
我们要想实现这个自动更新的功能大致分三步:

查询线上版本号,然后拿本地版本号与之对比。
若线上版本号比本地版本号大,则下载线上版本号
把下载好的版本号安装,并替换当前旧版本
权限
根据上面的需求我们可以知道,需要用到的权限应该有网络权限、本地文件写入权限,本地文件读取权限。使用网络权限去获取线上的版本号,然后下载保存到本地,安装的时候再去本地取来。

<uses-permission android:name=”android.permission.INTERNET” />
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE”/>
<uses-permission android:name=”android.permission.READ_EXTERNAL_STORAGE”/>

步骤
根据上面的需求,我们一步一步来实现这个功能。首先在APP的入口处去检测版本号。

ProUtils.canUpdata(this);
1
在这里我把检测方法给封装到了utils中,这样用起来也方便。

1,检测线上版本
public void check() {
//当所用app前版本号
int codeversin = getVersion();
getLineVersion(checkurl, codeversin);
}

这个方法里面包含了两步,*步是去获取本地版本号,第二步是去获取线上版本号
获取本地版本号:

public int getVersion() {
PackageInfo pkg;
int versionCode = 0;
String versionName = “”;
try {
pkg = activity.getPackageManager().getPackageInfo(activity.getApplication().getPackageName(), 0);
versionCode = pkg.versionCode;

} catch (PackageManager.NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return versionCode;
}

获取线上版本号:

OkHttpClient okHttpClient = new OkHttpClient();
RequestBody formBody = new FormBody.Builder()
.add(“_api_key”, BuildConfig.PYAPIKEY)
.add(“appKey”, BuildConfig.PYAPPID)
.build();
Request request = new Request.Builder()
.url(url)
.post(formBody)
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {

@Override
public void onFailure(Call call, IOException e) {

}

@Override
public void onResponse(Call call, Response response) throws IOException {

}

});

在上面有两个KEY,这两个可以都是从蒲公英上取来的。就像引言所说,若有条件可以自己搭建。

版本比较
在上面请求成功后,在response中就会获取到我们的线上版本号。然后我们拿上一步中获取到的线上版本号和本地版本号来做对比:

if (lineVersion > nowcodeversin) {
//去弹窗提示用户
}

这个时候,如果比较的结果是线上版本比较大,则去下载线上版本

提示用户
我们要给用户提示的,当然你若想直接下载也可以,只不过为了用户体验而已,自己斟酌。
提醒用户

AlertDialog dialog = new AlertDialog.Builder(activity).setTitle
(“Tips”).setMessage(“Have new version,please update!”)
.setNeutralButton(“Cancel”, new DialogInterface
.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).setNegativeButton(“Update”, new DialogInterface
.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
pyUtils.downUrl();
}
}).show();
dialog.setCanceledOnTouchOutside(false);//可选
dialog.setCancelable(false);//可选

在这里我给了用户两种选择,一种是立即更新,一种是稍后再说。点击稍后,那就会在下次再出发的时候再去提醒用户。点击更新的话,会立刻开始下载应用的新版本。
如果你想强制用户去更新的话,可以把稍后的选项去掉,顺便把
dialog.setCanceledOnTouchOutside(false);//可选,点击dialog其它地方dismiss无效
dialog.setCancelable(false);//可选,点击返回键无效
这两项给加上,用户就不得不更新了。

下载
上面都做好了,那就下载吧,就是一个文件下载的方法:

public void downUrl() {
DownloadUtil.get().download(downUrl,activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath(), new DownloadUtil.OnDownloadListener() {
@Override
public void onDownloadSuccess(File file) {
LogUtils.e(“AutoUpdate”,”下载完成去安装”);
}

@Override
public void onDownloading(int progress) {
}

@Override
public void onDownloadFailed() {
LogUtils.e(“AutoUpdate”,”下载失败”);
}
});

}

这个下载没什么好说的

安装
下载好之后,我们直接使用系统的方法去安装即可

调用系统的安装方法
private void installAPK(File savedFile) {
//调用系统的安装方法
Intent intent = new Intent(Intent.ACTION_VIEW);
Uri data;
// 判断版本大于等于7.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// “net.csdn.blog.ruancoder.fileprovider”即是在清单文件中配置的authorities
data = FileProvider.getUriForFile(activity, “com.thinker.member.bull.android_bull_member.fileprovider”, savedFile);
// 给目标应用一个临时授权
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
LogUtils.e(“AutoUpdate”,”7.0data=”+data);
} else {
data = Uri.fromFile(savedFile);
LogUtils.e(“AutoUpdate”,”111data=”+data);
}
intent.setDataAndType(data, “application/vnd.android.package-archive”);
activity.startActivity(intent);
activity.finish();
}

在这里需要注意,有个7.0的方法。需要在你的项目清单文件中配置如下

<!– 解决apk安装失败的问题 –>
<provider
android:name=”android.support.v4.content.FileProvider”
android:authorities=”com.ligo.anomo.fileprovider”
android:exported=”false”
android:grantUriPermissions=”true”>
<meta-data
android:name=”android.support.FILE_PROVIDER_PATHS”
android:resource=”@xml/file_paths” />
</provider>

然后再资源res中添加xml/file_paths.xml

<?xml version=”1.0″ encoding=”utf-8″?>
<resources xmlns:android=”http://schemas.android.com/apk/res/android”>
<paths>
<external-path path=”” name=”download”/>
</paths>
</resources>

基本就可以了。读书百卷,不如手写一遍。自己去试试吧。
————————————————

Android app自动更新总结

1.配置:

1.1 AndroidManifest.xml中添加权限和FileProvider:

  1. ——————————————————————————————————————–
  2. <uses-permission android:name=“android.permission.INTERNET”/>
  3. <uses-permission android:name=“android.permission.READ_EXTERNAL_STORAGE”/>
  4. <uses-permission android:name=“android.permission.WRITE_EXTERNAL_STORAGE”/>
  5. <uses-permission android:name=“android.permission.REQUEST_INSTALL_PACKAGES” />
  6. ——————————————————————————————————————–
  7. <provider
  8. android:name=“androidx.core.content.FileProvider”
  9. android:authorities=“com.fengzhi.wuyemanagement.fileprovider”
  10. android:grantUriPermissions=“true”
  11. android:exported=“false”>
  12. <meta-data
  13. android:name=“android.support.FILE_PROVIDER_PATHS”
  14. android:resource=“@xml/file_paths” />
  15. </provider>

1.2 新建文件(路径:res\xml\file_paths.xml):

  1. <paths>
  2. <external-path path=“.” name=“external_storage_root” />
  3. </paths>

1.3 (app的)build.gradle:

  1. implementation “com.lzy.net:okgo:3.0.4”//okgo 网络请求
  2. implementation ‘com.google.code.gson:gson:2.8.2’//gson
  3. implementation “org.permissionsdispatcher:permissionsdispatcher:4.3.1”//权限
  4. annotationProcessor “org.permissionsdispatcher:permissionsdispatcher-processor:4.3.1”//权限

2.这里以点击按钮进行更新为例:

2.1 核心代码:

  1. private int version;
  2. /* 更新进度条 */
  3. private ProgressBar mProgress;
  4. private AlertDialog mDownloadDialog;
  5. ——————————————————————————————————————–
  6. //点击按钮,检查权限,,,检查更新的方法
  7. @NeedsPermission({Manifest.permission.READ_EXTERNAL_STORAGE,
  8. Manifest.permission.WRITE_EXTERNAL_STORAGE,
  9. Manifest.permission.REQUEST_INSTALL_PACKAGES})
  10. protected void checkUpdate() {
  11. showLoadingDialog(“检测更新中…”);
  12. version = AppUpdateUtil.getAppVersionCode(this);//检查当前版本号
  13. // 调用方法,,,接口的具体实现,接收传过来的参数,再调自己的方法,
  14. requestAppUpdate(version, new DataRequestListener<UpdateAppBean>() {
  15. @Override
  16. public void success(UpdateAppBean data) {
  17. // 返回的json,getStatus为0时,去下载apk文件,这里是下载apk文件的方法
  18. updateApp(data.getData().getApk_url());
  19. }
  20. @Override
  21. public void fail(String msg) {
  22. // 返回的json,getStatus为1时,提示:”已是*新版本!”
  23. SToast(msg);
  24. dismissLoadingDialog();
  25. }
  26. });
  27. }
  28. //检查版本号,*次请求(post),,,UpdateAppBean根据服务器返回生成
  29. private void requestAppUpdate(int version, final DataRequestListener<UpdateAppBean> listener) {
  30. OkGo.<String>post(Const.HOST_URL + Const.UPDATEAPP).params(“version”, version).execute(new StringCallback() {
  31. @Override
  32. public void onSuccess(Response<String> response) {
  33. Gson gson = new Gson();
  34. UpdateAppBean updateAppBean = gson.fromJson(response.body(), UpdateAppBean.class);
  35. if (updateAppBean.getStatus() == 0) {
  36. listener.success(updateAppBean);
  37. } else {
  38. listener.fail(updateAppBean.getMsg());
  39. }
  40. }
  41. @Override
  42. public void onError(Response<String> response) {
  43. listener.fail(“服务器连接失败”);
  44. dismissLoadingDialog();
  45. }
  46. });
  47. }
  48. //如果有新版本,提示有新的版本,然后下载apk文件
  49. private void updateApp(String apk_url) {
  50. dismissLoadingDialog();
  51. DialogUtils.getInstance().showDialog(this, “发现新的版本,是否下载更新?”,
  52. new DialogUtils.DialogListener() {
  53. @Override
  54. public void positiveButton() {
  55. downloadApp(apk_url);
  56. }
  57. });
  58. }
  59. //下载apk文件并跳转(第二次请求,get)
  60. private void downloadApp(String apk_url) {
  61. OkGo.<File>get(apk_url).tag(this).execute(new FileCallback() {
  62. @Override
  63. public void onSuccess(Response<File> response) {
  64. String filePath = response.body().getAbsolutePath();
  65. Intent intent = IntentUtil.getInstallAppIntent(mContext, filePath);
  66. // 测试过这里必须用startactivity,不能用stratactivityforresult
  67. mContext.startActivity(intent);
  68. dismissLoadingDialog();
  69. mDownloadDialog.dismiss();
  70. mDownloadDialog=null;
  71. }
  72. @Override
  73. public void downloadProgress(Progress progress) {
  74. // showDownloadDialog();
  75. // mProgress.setProgress((int) (progress.fraction * 100));
  76. if (mDownloadDialog == null) {
  77. // 构造软件下载对话框
  78. AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
  79. builder.setTitle(“正在更新”);
  80. // 给下载对话框增加进度条
  81. final LayoutInflater inflater = LayoutInflater.from(mContext);
  82. View v = inflater.inflate(R.layout.item_progress, null);
  83. mProgress = (ProgressBar) v.findViewById(R.id.update_progress);
  84. builder.setView(v);
  85. mDownloadDialog = builder.create();
  86. mDownloadDialog.setCancelable(false);
  87. mDownloadDialog.show();
  88. }
  89. mProgress.setProgress((int) (progress.fraction * 100));
  90. }
  91. });
  92. }

2.2 DataRequestListener:

  1. public interface DataRequestListener<T> {
  2. //请求成功
  3. void success(T data);
  4. //请求失败
  5. void fail(String msg);
  6. }

2.3 AppUpdateUtil:

  1. /**
  2. * 获取App版本码
  3. *
  4. * @param context 上下文
  5. * @return App版本码
  6. */
  7. public static int getAppVersionCode(Context context) {
  8. return getAppVersionCode(context, context.getPackageName());
  9. }

2.4 IntentUtil:

  1. public class IntentUtil {
  2. /**
  3. * 获取安装App(支持7.0)的意图
  4. *
  5. * @param context
  6. * @param filePath
  7. * @return
  8. */
  9. public static Intent getInstallAppIntent(Context context, String filePath) {
  10. //apk文件的本地路径
  11. File apkfile = new File(filePath);
  12. if (!apkfile.exists()) {
  13. return null;
  14. }
  15. Intent intent = new Intent(Intent.ACTION_VIEW);
  16. Uri contentUri = FileUtil.getUriForFile(context, apkfile);
  17. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  18. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
  19. intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
  20. }
  21. intent.setDataAndType(contentUri, “application/vnd.android.package-archive”);
  22. return intent;
  23. }

2.5 FileUtil:

  1. /**
  2. * 将文件转换成uri(支持7.0)
  3. *
  4. * @param mContext
  5. * @param file
  6. * @return
  7. */
  8. public static Uri getUriForFile(Context mContext, File file) {
  9. Uri fileUri = null;
  10. if (Build.VERSION.SDK_INT >= 24) {
  11. fileUri = FileProvider.getUriForFile(mContext, mContext.getPackageName() + “.fileprovider”, file);
  12. } else {
  13. fileUri = Uri.fromFile(file);
  14. }
  15. return fileUri;
  16. }

 

android 实现类似个人中心的界面设计

From:android 实现类似个人中心的界面设计

上效果图:
这里写图片描述
这里写图片描述

先理清设计思路:
1、外层用linearlayout包裹,linearlayout采用shape,搭上描边、圆角和填充背景色。
2、里层采用relativelayout填充进textview、imageview。
思路搞清后,很简单就两步。
先上布局代码:

  1. <LinearLayout style=“@style/PersonalMainLayoutStyle” >
  2. <RelativeLayout style=“@style/FindBottomStyle” >
  3. <TextView
  4. style=“@style/PersonalTextStyle”
  5. android:text=“我的订单” />
  6. <ImageView
  7. android:id=“@+id/iv_drop_down”
  8. style=“@style/PersonalRightIconStyle”
  9. android:src=“@drawable/android_list_idex” />
  10. </RelativeLayout>
  11. </LinearLayout>

 

linearlayout布局属性代码:

  1. <style name=“PersonalMainLayoutStyle”>
  2. <item name=“android:layout_width”>match_parent</item>
  3. <item name=“android:layout_height”>wrap_content</item>
  4. <item name=“android:layout_margin”>10dp</item>
  5. <item name=“android:background”>@drawable/background_corners</item>
  6. <item name=“android:orientation”>vertical</item>
  7. <item name=“android:padding”>1dp</item>
  8. </style>

 

relativelayout布局属性代码:

  1. <style name=“FindBottomStyle”>
  2. <item name=“android:layout_width”>match_parent</item>
  3. <item name=“android:layout_height”>30dp</item>
  4. <item name=“android:layout_margin”>5dp</item>
  5. <item name=“android:background”>@drawable/more_activity_item_selector_bottom_corners</item>
  6. </style>

 

textview和imageview的属性代码可以自己设计了。

下面是drawable的设计代码.
看到上边relativelayout的item中引用了drawable-more_activity_item_selector_bottom_corners,个人感觉好像没什么卵用,主要是linearlayout的drawable,但是我没试,还是贴出来吧
relativelayout-drawable:

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <selector
  3. xmlns:android=“http://schemas.android.com/apk/res/android”>
  4. <item android:state_pressed=“true”>
  5. <shape>
  6. <solid android:color=“#ffffe381” />
  7. <stroke android:width=“0.0dip” android:color=“#ffcccccc” />
  8. <corners android:bottomLeftRadius=“6.0dip” android:bottomRightRadius=“6.0dip” />
  9. </shape>
  10. </item>
  11. <item>
  12. <shape>
  13. <solid android:color=“#ffffffff” />
  14. <stroke android:width=“0.0dip” android:color=“#ffcccccc” />
  15. <corners android:bottomLeftRadius=“6.0dip” android:bottomRightRadius=“6.0dip” />
  16. </shape>
  17. </item>
  18. </selector>

 

linearlayout-drawable:

  1. <?xml version=”1.0″ encoding=”utf-8″?>
  2. <selector xmlns:android=“http://schemas.android.com/apk/res/android”>
  3. <item><shape>
  4. <solid android:color=“#ffffffff” />
  5. <stroke android:width=“1.0dip” android:color=“#ffcccccc” />
  6. <corners android:radius=“6.0dip” />
  7. </shape></item>
  8. </selector>

 

Android Studio 开发 简易版音游APP

FREE  ——简易版音游APP
一、APP介绍
通过识别本地曲库,对音频文件进行识别提取出时间点,来产生滑块进行动态点击的畅玩过程,享受音乐的律动美感。界面主要仿照节奏大师等音游app,整体风格呈黑金色。(注:此app开发为课程作业,部分图片来自网图非原创,未曾商用)

 

二、APP特点
1.多种模式选择

常规设置:模式可选择双轨道或者四轨道;滑块数量可以选滑块较少或滑块较多;滑块速度可以选择慢速、中速、快速。

高级设置(开发人员选项):通过调整样本窗口大小、样本窗口数量、阈值权重等参数来调整滑块的数量。

2.支持本地所有MP3、WAV格式的音频

不同于曲库人为摆放滑块的位置,本app滑块位置是根据音频自动生成的,暂时配置有MP3、WAV格式音频文件的识别加载,能够较大程度地支持本地音频文件。

3.兼容不同分辨率的设备

通过android开发特有的dp单位适应不同分辨率手机的开发环境,通过ppi屏幕分辨率密度进行px与dp单位之间的转换,本APP能够适应ldpi、mdpi、hdpi、xhdpi、xxhdpi等不同分辨率密度,适配更多机型。

 

三、项目难点
1.滑块的滑动

通过比较Transaction、ObjectAnimator、ValueAnimator等动画效果,发现transaction只是表面移动,实际布局位置未改变,无法识别移动的位置,而且有的无法设置动画延迟时间,所以*后采用ObjectAnimatior为滑块滑动的主要部分。把滑块设为单独的EachButton、LandEachButton类,来动态设置它的动画起止位置,延迟时间等。这里动画延迟是结合音频节奏点的时间,延迟到那个节奏点的时候滑块动画才开始。

滑块的滑动主要用到的文件有Classes.EachButton、Classes.LandEachButton。

2.点击效果

这里点击时生成的best、good、miss字样主要是通过自定义Toast的setView方法实现,通过得到点击时该轨道列离得*近的滑块的位置与按钮位置之差d,得出best、good、miss指标的不同d的范围来得出点击评价,这里miss字样是在eachButton内部的onAnimationEnd()方法内部进行判别的。

点击效果主要用到的文件有toast.xml、toast_land.xml。

3.节奏点的识别

节奏点的识别主要用到音频采样、傅里叶变换(FFT)的知识,音频采样得到时域信号,这个信号可以看成是多个正弦波叠加的结果,通过傅里叶变换得到一段信号(一个样本窗口)里的关键频率,实现时域映射到频域,并与周围几个样本窗口的关键频率求均值加权得到阈值,大于阈值的信号点就可以看做节奏的起点,然后这个信号点的位置比例乘以总时间即为节奏点的时间,依此设置滑块延时。

实验过程中先是找到了一个wav格式文件画出波形图的样例,自己解读实现了一下,然后找到了mp3转化为wav格式的方法,然后放到android里面发现低于26的API不支持javax.sound等的包,于是又学习了mp3文件的格式,进行帧读取,后来发现大部分mp3文件是压缩过的,后来就仿照wav文件处理的代码自己实现了一个mp3文件的识别(有些数据流结构对不上mp3的标准帧格式,所以只是大致识别)。

节奏点识别主要用到的文件有WavHandle.WaveFileReader、Mp3Handle.Mp3FileReader、Classes.FFT 、Classes.HandleData。

4.兼容其他设备

由于activity中滑块位置的设置和获取是以px为单位的,所以需要转化为dp单位来兼容不同分辨率。通过得到设备的宽度getWindowManager().getDefaultDisplay().getWidth();对应不同的1dp=npx转换,其中对应关系(width,n)(240,0.75)(320,1.0)(480,1.5)(720,2.0)(1080,3.0)。

5.其他

申请读取内存的服务的实现。

通过MediaStore.Audio.Media.EXTERNAL_CONTENT_URI读取本地曲库,通过MediaStore.Audio.Media.DISPLAY_NAME等得到歌曲信息,通过RecycleView呈现歌单。其中用到的文件有Classes.Music、Classes.MusicAdapter。

通过广播、BaseActivity、ActivityCollector实现强行下线,避免重复打开活动。其中用到的文件有Classes.ActivityCollector、Classes.BaseActivity。

强制横屏的实现,通过layout_weight设置均分宽度居中。

 

四、APP界面
(注:此app开发为课程作业,图片来自网图非原创,未曾商用)

1.Logo

 

2.首页(MainActivity)

%title插图%num

3.歌单页面(MusicViewActivity)

%title插图%num

4.基础模式选择(OriginChoiceActivity)

%title插图%num

5.高级设置(ChoiceActivity)

%title插图%num

6.双轨道模式界面(GameActivity)

%title插图%num

7.四轨道模型界面(FourGameActivity)

%title插图%num

8.分数结果界面(ResultActivity)

%title插图%num

六、改进空间
(时间限制,部分功能未曾实现)

1.暂停功能

2.连击效果

3.歌曲搜索功能

4.排行榜

5.人工控制节奏点

无框架完整搭建安卓app及其服务端

技术背景:

我的一个项目做的的是图片处理,用 python 实现图片处理的核心功能后,想部署到安卓app中,但是对于一个对安卓和服务器都一知半解的小白来说要现学的东西太多了。

而实际上,我们的项目要求并不算高,以我现有的知识也是能实现相应功能的,所以我将在本文记录下一次没用到任何服务器框架的服务器搭建经历。

%title插图%num%title插图%num

 

需要的技术:

  <java>,<socket>,<android>

确切的说只要你会java,就能实现你想要的所有功能了。因为android是基于java的,其使用的代码和原生java一模一样,只是在android上把前后台完全分割开了。

而对于socket也很容易使用,就算没有了解过计算机网络,在看过我这篇博客后你也能有一定的了解。

要实现的功能:

1,android界面及后台

2,安卓和服务器建立连接,并进行连接有效性检查

3,基于字节流的图片收发

4,java调用python用预先训练好的fgsm模型处理图片,并将结果发给客户端

开始实现:

 一:安卓app

首先我们建个安卓工程,看看结构是什么样的:

%title插图%num%title插图%num

容易看出,这里有两个大目录分别是”app”,”login”,这两的大目录的子目录的结构是一样的,都有三个子目录(“manifests”,”java”,”res”)。

没错,这两个大目录就是我写的两个界面(顾名思义,登录界面和登录后的界面),这样是不是就觉得恍然大悟了,怪不得平时app都是一个界面一个界面的,

这点和pc还是有点不同的。

 

manifests下写的是xml文件,也就是常用的标签配置文件,用来定义界面的外观。

java中就是你写的java代码,也就是安卓后台代码,一般是给前端界面添加监听,以及网络通信和处理代码。

res就是资源文件夹了,用来放置app需要的资源,比如图标,图片,视频,音乐等。

 

简单实现Android获取已安装APP清单列表显示

activity代码:

import android.app.ListActivity;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.widget.SimpleAdapter;

import com.mob.at.demo.util.AppInfo;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class AppListActivity extends ListActivity {

private SimpleAdapter listItemAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_applist);
initListView();
this.setListAdapter(listItemAdapter);
}

private void initListView() {

PackageManager pm = getPackageManager();
List<PackageInfo> installedPackages = pm.getInstalledPackages(0); // 获取所以已安装的包
ArrayList<HashMap<String, Object>> listItems = new ArrayList<HashMap<String, Object>>();
ArrayList<AppInfo> list = new ArrayList<AppInfo>();
for (PackageInfo packageInfo : installedPackages) {
AppInfo info = new AppInfo();
String packageName = packageInfo.packageName; // 包名
ApplicationInfo applicationInfo = packageInfo.applicationInfo; // 应用信息
String name = applicationInfo.loadLabel(pm).toString(); // 应用名称
Drawable icon = applicationInfo.loadIcon(pm); // 应用图标
System.out.println(“name==========”+name);
System.out.println(“packageName==========”+packageName);
info.name = name;
info.packageName = packageName;
info.icon = icon;
// 状态机,通过01状态来表示是否具备某些属性和功能
int flags = applicationInfo.flags; // 获取应用标记
if ((flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == ApplicationInfo
.FLAG_EXTERNAL_STORAGE) {
//安装在sdcard
info.isRom = false;

} else {
//安装在手机
info.isRom = true;
}

if ((flags & ApplicationInfo.FLAG_SYSTEM) == ApplicationInfo
.FLAG_SYSTEM) {
//系统应用
info.isUser = false;

} else {
//用户应用
info.isUser = true;
}

if (info.isUser) {
HashMap<String, Object> map = new HashMap<String, Object>();
map.put(“ItemTitle”, name); //文字
map.put(“ItemImage”, icon); //图片
listItems.add(map);
}

}

System.out.println(“listItems==========”+listItems.size());

listItemAdapter = new SimpleAdapter(this, listItems,
R.layout.list_item,
new String[] {“ItemTitle”, “ItemImage”},
new int[ ] {R.id.ItemTitle, R.id.ItemImage}
);
}

}
list_item布局文件代码:
<?xml version=”1.0″ encoding=”utf-8″?>
<RelativeLayout android:id=”@+id/LinearLayout01″
android:layout_width=”fill_parent”
xmlns:android=”http://schemas.android.com/apk/res/android”
android:layout_height=”wrap_content”>
<ImageView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:src=”@mipmap/ic_launcher”
android:layout_alignParentLeft=”true”
android:id=”@+id/ItemImage” />
<TextView
android:layout_height=”wrap_content”
android:textSize=”20dip”
android:layout_width=”wrap_content”
android:layout_alignParentRight=”true”
android:text=”123″
android:id=”@+id/ItemTitle” />

</RelativeLayout>

评论:

le0102:请问有没有完整demo
le0102回复ovejur:谢谢,已经尝试过了,可行
ovejur回复:你可以直接拷贝到你的项目中运行即可
afrw4232:请问是用什么开发工具来实现?
ovejur回复:AS
qq_43050248:可以用

android app锁定后台运行的方法

想直接看图操作,可以android 下一个 小米穿戴
然后 我->开启后台运行权限 ->点击当前手机后面的 里面有 小米MIUI,华为EMUI,OPPO ColorOS ,Vivo Funtouch OS ,图文并茂的教你各个系统怎么开启后台运行权限的;

因为安卓系统后台程序限制,软件在长时间挂后台运行时会被系统杀掉,可以将程序加入清理白名单中,并在手机系统设置中的“电池->后台高耗电中允许软件后台高耗电”具体方法如下:

1.将应用加入到清理白名单中方法:
(1)vivo手机设置方法:打开任务切换界面—-点击app右上角的锁图标使其变为锁定状态,或者将当前app向下拖动即可

(2)oppo手机设置方法:打开任务切换界面—-点击app右上角图标后会出现“锁定”按钮,点击锁定即可,或者将当前app向下拖动即可

(3)小米手机设置方法:打开任务切换界面—-长按app视图会出现锁图标,点击锁图标即可,或者将当前app向下拖动即可

(4)华为手机设置方法:打开任务切换界面—-将当前app向下拖动即可出现锁定图标

2.允许app后台高耗电
(1)vivo手机的设置方法:打开手机系统设置—-点击“电池”选项—-再点击“后台高耗电”—-开启高耗电的app的开关

(2)低版本oppo手机的设置方法:打开手机系统设置—-点击“电池”选项—-再点击“耗电保护”—-点击要开启的app—-关闭“后台冻结”开关和“检测到异常时自动优化”开关;

高版本oppo手机的设置方法:打开手机系统设置—-点击“电池”选项—-关闭“智能耗电保护”开关—-再点击“自定义耗电保护”—-点击要开启的app—-选中允许后台运行

(3)低版本小米手机的设置方法:打开手机系统设置—-点击“电池”选项—-再点击“应用智能省电”—-点击要开启的app—-选中无限制

高版本小米手机的设置方法:打开手机系统设置—-点击“电池”选项—-点击右上角设置图标—-再点击“应用智能省电”—-点击要开启的app—-选中无限制

(3)华为手机的设置方法:打开手机系统设置—-点击“电池”选项—-再点击“耗电排行”—-点击要开启的app—-点击应用启动管理—-关闭自动管理—-打开允许后台活动

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速