标签: Android应用

Android 查看项目依赖关系

做android项目,module多的话,很容易遇到包冲突的问题。比如v4既有26.1.0版本,也有27.1.0版本。
这时候,要找出两个版本都是从哪个module 或者第三方包引入的,然后再用exclude命令,把不要的版本去除。
例如

implementation (‘com.google.firebase:firebase-core:15.0.0’) {
exclude group: ‘com.android.support’, module: ‘support-v4’
}

那么,怎么查真个工程的依赖呢?网上很多说,用命令行执行:gradle -q dependencies。
这个在windows下,很容易出错,原因是gradle 这个可执行文件,并不在当前工程下,而是在AndroidStudio的安装目录,没有加到环境变量,就不能执行。
其实没必要用命令行,可以用AndroidStudio集成好的入口:

在AndroidStudio的右边,以下入口,双击dependencies,就可以了。
如果是要整个app的关系,则双击的是module app下的help/dependencies。如果是只想看其他子模块,则在对应的模块,双击help/dependencies即可。

%title插图%num

依赖关系结果示例:

%title插图%num
如果duplicate的包,真的有多个版本,把以上依赖关系数据,复制到文本编辑器,搜module名字(如android-support),就能找到对应得包,以及是哪个包带入的。

Android进程保活的一般套路

自己曾经也在这个问题上伤过脑经,前几日刚好有一个朋友说在做IM类的项目,问我进程保活如何处理比较恰当,决定去总结一下,网上搜索一下进程常驻的方案好多好多,但是很多的方案都是不靠谱的或者不是*好的,结合很多资料,今天总结一下Android进程保活的一些方案,都附有完整的实现源码,有些可能你已经知道,但是有些你可能是*次听说,(1像素Activity,前台服务,账号同步,Jobscheduler,相互唤醒,系统服务捆绑,如果你都了解了,请忽略)经过多方面的验证,Android系统中在没有白名单的情况下做一个任何情况下都不被杀死的应用是基本不可能的,但是我们可以做到我们的应用基本不被杀死,如果杀死可以马上满血复活,原谅我讲的特别含蓄,毕竟现在的技术防不胜防啊,不死应用还是可能的。

有几个问题需要思考,系统为什么会杀掉进程,杀的为什么是我的进程,这是按照什么标准来选择的,是一次性干掉多个进程,还是一个接着一个杀,保活套路一堆,如何进行进程保活才是比较恰当……如果这些问题你还还存在,或许这篇文章可以解答。

一、进程初步了解
每一个Android应用启动后至少对应一个进程,有的是多个进程,而且主流应用中多个进程的应用比例较大

1、如何查看进程解基本信息
对于任何一个进程,我们都可以通过adb shell ps|grep 的方式来查看它的基本信息

%title插图%num

2、进程划分
Android中的进程跟封建社会一样,分了三流九等,Android系统把进程的划为了如下几种(重要性从高到低),网上多位大神都详细总结过(备注:严格来说是划分了6种)。

2.1、前台进程(Foreground process)
场景:
– 某个进程持有一个正在与用户交互的Activity并且该Activity正处于resume的状态。
– 某个进程持有一个Service,并且该Service与用户正在交互的Activity绑定。
– 某个进程持有一个Service,并且该Service调用startForeground()方法使之位于前台运行。
– 某个进程持有一个Service,并且该Service正在执行它的某个生命周期回调方法,比如onCreate()、 onStart()或onDestroy()。
– 某个进程持有一个BroadcastReceiver,并且该BroadcastReceiver正在执行其onReceive()方法。

用户正在使用的程序,一般系统是不会杀死前台进程的,除非用户强制停止应用或者系统内存不足等*端情况会杀死。

2.2、可见进程(Visible process)
场景:
– 拥有不在前台、但仍对用户可见的 Activity(已调用 onPause())。
– 拥有绑定到可见(或前台)Activity 的 Service

用户正在使用,看得到,但是摸不着,没有覆盖到整个屏幕,只有屏幕的一部分可见进程不包含任何前台组件,一般系统也是不会杀死可见进程的,除非要在资源吃紧的情况下,要保持某个或多个前台进程存活

2.3、服务进程(Service process)
场景
– 某个进程中运行着一个Service且该Service是通过startService()启动的,与用户看见的界面没有直接关联。

在内存不足以维持所有前台进程和可见进程同时运行的情况下,服务进程会被杀死

2.4、后台进程(Background process)
场景:
– 在用户按了”back”或者”home”后,程序本身看不到了,但是其实还在运行的程序,比如Activity调用了onPause方法

系统可能随时终止它们,回收内存

2.5、空进程(Empty process)
场景:
– 某个进程不包含任何活跃的组件时该进程就会被置为空进程,完全没用,杀了它只有好处没坏处,*个干它!

3、内存阈值
上面是进程的分类,进程是怎么被杀的呢?系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的情况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给需要的app, 这套杀进程回收内存的机制就叫 Low Memory Killer。那这个不足怎么来规定呢,那就是内存阈值,我们可以使用cat /sys/module/lowmemorykiller/parameters/minfree来查看某个手机的内存阈值。

注意这些数字的单位是page. 1 page = 4 kb.上面的六个数字对应的就是(MB): 72,90,108,126,144,180,这些数字也就是对应的内存阀值,内存阈值在不同的手机上不一样,一旦低于该值,Android便开始按顺序关闭进程. 因此Android开始结束优先级*低的空进程,即当可用内存小于180MB(46080*4/1024)。

读到这里,你或许有一个疑问,假设现在内存不足,空进程都被杀光了,现在要杀后台进程,但是手机中后台进程很多,难道要一次性全部都清理掉?当然不是的,进程是有它的优先级的,这个优先级通过进程的adj值来反映,它是linux内核分配给每个系统进程的一个值,代表进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收,adj值定义在com.android.server.am.ProcessList类中,这个类路径是${android-sdk-path}\sources\android-23\com\android\server\am\ProcessList.java。oom_adj的值越小,进程的优先级越高,普通进程oom_adj值是大于等于0的,而系统进程oom_adj的值是小于0的,我们可以通过cat /proc/进程id/oom_adj可以看到当前进程的adj值。

看到adj值是0,0就代表这个进程是属于前台进程,我们按下Back键,将应用至于后台,再次查看

adj值变成了8,8代表这个进程是属于不活跃的进程,你可以尝试其他情况下,oom_adj值是多少,但是每个手机的厂商可能不一样,oom_adj值主要有这么几个,可以参考一下。

%title插图%num
备注:(上表的数字可能在不同系统会有一定的出入)

根据上面的adj值,其实系统在进程回收跟内存回收类似也是有一套严格的策略,可以自己去了解,大概是这个样子的,oom_adj越大,占用物理内存越多会被*先kill掉,OK,那么现在对于进程如何保活这个问题就转化成,如何降低oom_adj的值,以及如何使得我们应用占的内存*少。

一、进程保活方案
1、开启一个像素的Activity
据说这个是手Q的进程保活方案,基本思想,系统一般是不会杀死前台进程的。所以要使得进程常驻,我们只需要在锁屏的时候在本进程开启一个Activity,为了欺骗用户,让这个Activity的大小是1像素,并且透明无切换动画,在开屏幕的时候,把这个Activity关闭掉,所以这个就需要监听系统锁屏广播,我试过了,的确好使,如下。

public class MainActivity extends AppCompatActivity {

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

如果直接启动一个Activity,当我们按下back键返回桌面的时候,oom_adj的值是8,上面已经提到过,这个进程在资源不够的情况下是容易被回收的。现在造一个一个像素的Activity。

public class LiveActivity extends Activity {

public static final String TAG = LiveActivity.class.getSimpleName();

public static void actionToLiveActivity(Context pContext) {
Intent intent = new Intent(pContext, LiveActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
pContext.startActivity(intent);
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, “onCreate”);
setContentView(R.layout.activity_live);

Window window = getWindow();
//放在左上角
window.setGravity(Gravity.START | Gravity.TOP);
WindowManager.LayoutParams attributes = window.getAttributes();
//宽高设计为1个像素
attributes.width = 1;
attributes.height = 1;
//起始坐标
attributes.x = 0;
attributes.y = 0;
window.setAttributes(attributes);

ScreenManager.getInstance(this).setActivity(this);
}

@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, “onDestroy”);
}
}

为了做的更隐藏,*好设置一下这个Activity的主题,当然也无所谓了

<style name=”LiveStyle”>
<item name=”android:windowIsTranslucent”>true</item>
<item name=”android:windowBackground”>@android:color/transparent</item>
<item name=”android:windowAnimationStyle”>@null</item>
<item name=”android:windowNoTitle”>true</item>
</style>

在屏幕关闭的时候把LiveActivity启动起来,在开屏的时候把LiveActivity 关闭掉,所以要监听系统锁屏广播,以接口的形式通知MainActivity启动或者关闭LiveActivity。

public class ScreenBroadcastListener {

private Context mContext;

private ScreenBroadcastReceiver mScreenReceiver;

private ScreenStateListener mListener;

public ScreenBroadcastListener(Context context) {
mContext = context.getApplicationContext();
mScreenReceiver = new ScreenBroadcastReceiver();
}

interface ScreenStateListener {

void onScreenOn();

void onScreenOff();
}

/**
* screen状态广播接收者
*/
private class ScreenBroadcastReceiver extends BroadcastReceiver {
private String action = null;

@Override
public void onReceive(Context context, Intent intent) {
action = intent.getAction();
if (Intent.ACTION_SCREEN_ON.equals(action)) { // 开屏
mListener.onScreenOn();
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) { // 锁屏
mListener.onScreenOff();
}
}
}

public void registerListener(ScreenStateListener listener) {
mListener = listener;
registerListener();
}

private void registerListener() {
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
mContext.registerReceiver(mScreenReceiver, filter);
}
}

public class ScreenManager {

private Context mContext;

private WeakReference<Activity> mActivityWref;

public static ScreenManager gDefualt;

public static ScreenManager getInstance(Context pContext) {
if (gDefualt == null) {
gDefualt = new ScreenManager(pContext.getApplicationContext());
}
return gDefualt;
}
private ScreenManager(Context pContext) {
this.mContext = pContext;
}

public void setActivity(Activity pActivity) {
mActivityWref = new WeakReference<Activity>(pActivity);
}

public void startActivity() {
LiveActivity.actionToLiveActivity(mContext);
}

public void finishActivity() {
//结束掉LiveActivity
if (mActivityWref != null) {
Activity activity = mActivityWref.get();
if (activity != null) {
activity.finish();
}
}
}
}

现在MainActivity改成如下

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final ScreenManager screenManager = ScreenManager.getInstance(MainActivity.this);
ScreenBroadcastListener listener = new ScreenBroadcastListener(this);
listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() {
@Override
public void onScreenOn() {
screenManager.finishActivity();
}

@Override
public void onScreenOff() {
screenManager.startActivity();
}
});
}
}

按下back之后,进行锁屏,现在测试一下oom_adj的值

果然将进程的优先级提高了。

但是还有一个问题,内存也是一个考虑的因素,内存越多会被*先kill掉,所以把上面的业务逻辑放到Service中,而Service是在另外一个 进程中,在MainActivity开启这个服务就行了,这样这个进程就更加的轻量,

public class LiveService extends Service {

public static void toLiveService(Context pContext){
Intent intent=new Intent(pContext,LiveService.class);
pContext.startService(intent);
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//屏幕关闭的时候启动一个1像素的Activity,开屏的时候关闭Activity
final ScreenManager screenManager = ScreenManager.getInstance(LiveService.this);
ScreenBroadcastListener listener = new ScreenBroadcastListener(this);
listener.registerListener(new ScreenBroadcastListener.ScreenStateListener() {
@Override
public void onScreenOn() {
screenManager.finishActivity();
}
@Override
public void onScreenOff() {
screenManager.startActivity();
}
});
return START_REDELIVER_INTENT;
}
}

<service android:name=”.LiveService”
android:process=”:live_service”/>

OK,通过上面的操作,我们的应用就始终和前台进程是一样的优先级了,为了省电,系统检测到锁屏事件后一段时间内会杀死后台进程,如果采取这种方案,就可以避免了这个问题。但是还是有被杀掉的可能,所以我们还需要做双进程守护,关于双进程守护,比较适合的就是aidl的那种方式,但是这个不是完全的靠谱,原理是A进程死的时候,B还在活着,B可以将A进程拉起来,反之,B进程死的时候,A还活着,A可以将B拉起来。所以双进程守护的前提是,系统杀进程只能一个个的去杀,如果一次性杀两个,这种方法也是不OK的。

事实上
那么我们先来看看Android5.0以下的源码,ActivityManagerService是如何关闭在应用退出后清理内存的

Process.killProcessQuiet(pid);

应用退出后,ActivityManagerService就把主进程给杀死了,但是,在Android5.0以后,ActivityManagerService却是这样处理的:

Process.killProcessQuiet(app.pid);
Process.killProcessGroup(app.info.uid, app.pid);

在应用退出后,ActivityManagerService不仅把主进程给杀死,另外把主进程所属的进程组一并杀死,这样一来,由于子进程和主进程在同一进程组,子进程在做的事情,也就停止了。所以在Android5.0以后的手机应用在进程被杀死后,要采用其他方案。

2、前台服务
这种大部分人都了解,据说这个微信也用过的进程保活方案,移步微信Android客户端后台保活经验分享,这方案实际利用了Android前台service的漏洞。
原理如下
对于 API level < 18 :调用startForeground(ID, new Notification()),发送空的Notification ,图标则不会显示。
对于 API level >= 18:在需要提优先级的service A启动一个InnerService,两个服务同时startForeground,且绑定同样的 ID。Stop 掉InnerService ,这样通知栏图标即被移除。

public class KeepLiveService extends Service {

public static final int NOTIFICATION_ID=0x11;

public KeepLiveService() {
}

@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException(“Not yet implemented”);
}

@Override
public void onCreate() {
super.onCreate();
//API 18以下,直接发送Notification并将其置为前台
if (Build.VERSION.SDK_INT <Build.VERSION_CODES.JELLY_BEAN_MR2) {
startForeground(NOTIFICATION_ID, new Notification());
} else {
//API 18以上,发送Notification并将其置为前台后,启动InnerService
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(NOTIFICATION_ID, builder.build());
startService(new Intent(this, InnerService.class));
}
}

public class InnerService extends Service{
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
//发送与KeepLiveService中ID相同的Notification,然后将其取消并取消自己的前台显示
Notification.Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
startForeground(NOTIFICATION_ID, builder.build());
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
stopForeground(true);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
manager.cancel(NOTIFICATION_ID);
stopSelf();
}
},100);

}
}
}

在没有采取前台服务之前,启动应用,oom_adj值是0,按下返回键之后,变成9(不同ROM可能不一样)

在采取前台服务之后,启动应用,oom_adj值是0,按下返回键之后,变成2(不同ROM可能不一样),确实进程的优先级有所提高。

3、相互唤醒
相互唤醒的意思就是,假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其他阿里系的app给唤醒了。这个完全有可能的。此外,开机,网络切换、拍照、拍视频时候,利用系统产生的广播也能唤醒app,不过Android N已经将这三种广播取消了。

如果应用想保活,要是QQ,微信愿意救你也行,有多少手机上没有QQ,微信呢?或者像友盟,信鸽这种推送SDK,也存在唤醒app的功能。
拉活方法

4、JobSheduler
JobSheduler是作为进程死后复活的一种手段,native进程方式*大缺点是费电, Native 进程费电的原因是感知主进程是否存活有两种实现方式,在 Native 进程中通过死循环或定时器,轮训判断主进程是否存活,当主进程不存活时进行拉活。其次5.0以上系统不支持。 但是JobSheduler可以替代在Android5.0以上native进程方式,这种方式即使用户强制关闭,也能被拉起来,亲测可行。

JobSheduler@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
@Override
public void onCreate() {
super.onCreate();
startJobSheduler();
}

public void startJobSheduler() {
try {
JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), MyJobService.class.getName()));
builder.setPeriodic(5);
builder.setPersisted(true);
JobScheduler jobScheduler = (JobScheduler) this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(builder.build());
} catch (Exception ex) {
ex.printStackTrace();
}
}

@Override
public boolean onStartJob(JobParameters jobParameters) {
return false;
}

@Override
public boolean onStopJob(JobParameters jobParameters) {
return false;
}
}

5、粘性服务&与系统服务捆绑
这个是系统自带的,onStartCommand方法必须具有一个整形的返回值,这个整形的返回值用来告诉系统在服务启动完毕后,如果被Kill,系统将如何操作,这种方案虽然可以,但是在某些情况or某些定制ROM上可能失效,我认为可以多做一种保保守方案。

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_REDELIVER_INTENT;
}

START_STICKY
如果系统在onStartCommand返回后被销毁,系统将会重新创建服务并依次调用onCreate和onStartCommand(注意:根据测试Android2.3.3以下版本只会调用onCreate根本不会调用onStartCommand,Android4.0可以办到),这种相当于服务又重新启动恢复到之前的状态了)。

START_NOT_STICKY
如果系统在onStartCommand返回后被销毁,如果返回该值,则在执行完onStartCommand方法后如果Service被杀掉系统将不会重启该服务。

START_REDELIVER_INTENT
START_STICKY的兼容版本,不同的是其不保证服务被杀后一定能重启。

相比与粘性服务与系统服务捆绑更厉害一点,这个来自爱哥的研究,这里说的系统服务很好理解,比如NotificationListenerService,NotificationListenerService就是一个监听通知的服务,只要手机收到了通知,NotificationListenerService都能监听到,即时用户把进程杀死,也能重启,所以说要是把这个服务放到我们的进程之中,那么就可以呵呵了

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class LiveService extends NotificationListenerService {

public LiveService() {

}

@Override
public void onNotificationPosted(StatusBarNotification sbn) {
}

@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
}
}

但是这种方式需要权限

<service
android:name=”.LiveService”
android:permission=”android.permission.BIND_NOTIFICATION_LISTENER_SERVICE”>
<intent-filter>
<action android:name=”android.service.notification.NotificationListenerService” />
</intent-filter>
</service>

所以你的应用要是有消息推送的话,那么可以用这种方式去欺骗用户。

结束:
听说账号同步唤醒APP这种机制很不错,用户强制停止都杀不起创建一个账号并设置同步器,创建周期同步,系统会自动调用同步器,这样就能激活我们的APP,局限是国产机会修改*短同步周期(魅蓝NOTE2长达30分钟),并且需要联网才能使用。在国内各大ROM”欣欣向荣”的大背景下,关于进程保活,不加入白名单,我也很想知道有没有一个应用永活的方案,这种方案性能好,不费电,或许做不到,或许有牛人可以,但是,通过上面几种措施,在*大部分的机型下,*大部分用户手机中,我们的进程寿命确实得到了提高。

Android如何跳转到应用商店的APP详情页面

项目流程:从App内部点击Button按钮或者相应的条目,跳转到应用商店的某个APP的详情页面。

实现:

*步:导入获取手机App工具类
public class MarketUtils {
/**
* 获取已安装应用商店的包名列表
*
* @param context
* @return
*/
public static ArrayList<String> queryInstalledMarketPkgs(Context context) {
ArrayList<String> pkgs = new ArrayList<String>();
if (context == null)
return pkgs;
Intent intent = new Intent();
intent.setAction(“android.intent.action.MAIN”);
intent.addCategory(“android.intent.category.APP_MARKET”);
PackageManager pm = context.getPackageManager();
List<ResolveInfo> infos = pm.queryIntentActivities(intent, 0);
if (infos == null || infos.size() == 0)
return pkgs;
int size = infos.size();
for (int i = 0; i < size; i++) {
String pkgName = “”;
try {
ActivityInfo activityInfo = infos.get(i).activityInfo;
pkgName = activityInfo.packageName;
} catch (Exception e) {
e.printStackTrace();
}
if (!TextUtils.isEmpty(pkgName))
pkgs.add(pkgName);

}
return pkgs;
}

/**
* 过滤出已经安装的包名集合
*
* @param context
* @param pkgs
* 待过滤包名集合
* @return 已安装的包名集合
*/
public static ArrayList<String> filterInstalledPkgs(Context context,
ArrayList<String> pkgs) {
ArrayList<String> empty = new ArrayList<String>();
if (context == null || pkgs == null || pkgs.size() == 0)
return empty;
PackageManager pm = context.getPackageManager();
List<PackageInfo> installedPkgs = pm.getInstalledPackages(0);
int li = installedPkgs.size();
int lj = pkgs.size();
for (int j = 0; j < lj; j++) {
for (int i = 0; i < li; i++) {
String installPkg = “”;
String checkPkg = pkgs.get(j);
try {
installPkg = installedPkgs.get(i).applicationInfo.packageName;
} catch (Exception e) {
e.printStackTrace();
}
if (TextUtils.isEmpty(installPkg))
continue;
if (installPkg.equals(checkPkg)) {
empty.add(installPkg);
break;
}

}
}
return empty;
}

/**
* 启动到app详情界面
*
* @param appPkg
* App的包名
* @param marketPkg
* 应用商店包名 ,如果为””则由系统弹出应用商店列表供用户选择,否则调转到目标市场的应用详情界面,某些应用商店可能会失败
*/
public static void launchAppDetail(String appPkg, String marketPkg) {
try {
if (TextUtils.isEmpty(appPkg))
return;
Uri uri = Uri.parse(“market://details?id=” + appPkg);
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
if (!TextUtils.isEmpty(marketPkg))
intent.setPackage(marketPkg);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Myapp.getMyapp().startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
第二部: 实现工具类的LanunchAppDetail方法

MarketUtils.launchAppDetail(“*个参数目标App的包名;”,”第二个参数:应用商店包名”);
注意:如果 应用商店包名为空 就会将手机上已下载的应用商店都列出来,让你选择一个进行跳转。

/*
*主流应用商店对应的包名如下:
com.android.vending Google Play
com.tencent.android.qqdownloader 应用宝
com.qihoo.appstore 360手机助手
com.baidu.appsearch 百度手机助
com.xiaomi.market 小米应用商店
com.wandoujia.phoenix2 豌豆荚
com.huawei.appmarket 华为应用市场
com.taobao.appcenter 淘宝手机助手
com.hiapk.marketpho 安卓市场
cn.goapk.market 安智市场
* */
希望可以给大家带来帮助。

检测Android应用(APP)的启动与关闭

问题

当开发安卓程序的时候,我们不免需要去检测应用什么时候在前台运行,用户什么时候离开。不幸的是,没有一个简单的方法可以做到这点。当用户*次启动的时候去检测还是不难,但如果是重新打开或关闭就不简单了。

这篇文章将会展示一个用来解决上述问题的技巧。

入门指南

应用的activity是否显示在界面是决定应用是打开还是关闭的核心因素。我们先来看一个简单的例子,一个应用只有一个activity并且不支持横屏,这个activity的onstart和onstop方法就决定了这个应用是打开的还是关闭的。

@Override
protected void onStart() {
    super.onStart();
    // The Application has been opened!
}

@Override
protected void onStop() {
    super.onStop();
    // The Application has been closed!
}

但有个问题,一旦我们支持横屏,上面这个方法就失效了。如果我们旋转设备,这个activity会重新创建,onstart方法会第二次执行,导致程序错误的认为应用第二次被打开。

为了处理设备旋转,我们需要添加一个验证步骤。这个验证需要启动一个计时器,用来检测当activity停止后,我们是否能很快看到该程序另一个activity启动。如果不能,则说明用户推出了程序,否则说明用户还在使用程序。

这样的验证同样支持有多个activity的应用。因为从一个activity跳转到另外一个也可以用这个验证方式处理。

所以利用这个技巧,我创建了一个管理activity的类,当activity的可见性发生变化的时候都要报告给这个管理类。这个类为每个activity处理验证步骤,避免意外的验证。我们同样利用了“发布-订阅”(观察者)模式,使得其他相关的类能够收到程序打开或关闭的通知。

使用这个管理类的三个步骤

1) 将下面的代码添加到你的代码库

import android.app.Activity;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.NonNull;
import android.text.format.DateUtils;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.HashSet;
import java.util.Set;

/**
 * 这个类用于追踪当前所有启动的Activity,使得我们能判断应用是否在后台运行。
 */
public class AppForegroundStateManager {
    private static final String TAG = AppForegroundStateManager.class.getSimpleName();
    private static final int MESSAGE_NOTIFY_LISTENERS = 1;
    public static final long APP_CLOSED_VALIDATION_TIME_IN_MS = 30 * DateUtils.SECOND_IN_MILLIS; // 30 Seconds
    private Reference<Activity> mForegroundActivity;
    private Set<OnAppForegroundStateChangeListener> mListeners = new HashSet<>();
    private AppForegroundState mAppForegroundState = AppForegroundState.NOT_IN_FOREGROUND;
    private NotifyListenersHandler mHandler;

    // 获得一个线程安全的类实例
    private static class SingletonHolder {
        public static final AppForegroundStateManager INSTANCE = new AppForegroundStateManager();
    }

    public static AppForegroundStateManager getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private AppForegroundStateManager() {
        // 在主线程创建一个 handler
        mHandler = new NotifyListenersHandler(Looper.getMainLooper());
    }

    public enum AppForegroundState {
        IN_FOREGROUND,
        NOT_IN_FOREGROUND
    }

    public interface OnAppForegroundStateChangeListener {
        /** 当应用状态发生改变时这个方法被调用(隐藏到后台或显示到前台) */
        public void onAppForegroundStateChange(AppForegroundState newState);
    }

    /** 当 Activity 可见时应该调用这个方法 */
    public void onActivityVisible(Activity activity) {
        if (mForegroundActivity != null) mForegroundActivity.clear();
        mForegroundActivity = new WeakReference<>(activity);
        determineAppForegroundState();
    }

    /** 当 Activity 不再可见时应该调用这个方法 */
    public void onActivityNotVisible(Activity activity) {
        /*
         * 前台 Activity 可能会被一个新的 Activity 替换。
         * 如果新 Activity 与前台 Activity 匹配,仅仅清除前台 Activity
         */
        if (mForegroundActivity != null) {
            Activity ref = mForegroundActivity.get();

            if (activity == ref) {
                // This is the activity that is going away, clear the reference
                mForegroundActivity.clear();
                mForegroundActivity = null;
            }
        }

        determineAppForegroundState();
    }

    /** 用于判断应用是否处于前台 */
    public Boolean isAppInForeground() {
        return mAppForegroundState == AppForegroundState.IN_FOREGROUND;
    }

    /**
     * 用于判断当前状态,更新追踪的目标,并通知所有观察者状态是否发生了改变
     */
    private void determineAppForegroundState() {
        /* 获取当前状态 */
        AppForegroundState oldState = mAppForegroundState;

        /* 决定当前状态 */
        final boolean isInForeground = mForegroundActivity != null && mForegroundActivity.get() != null;
        mAppForegroundState = isInForeground ? AppForegroundState.IN_FOREGROUND : AppForegroundState.NOT_IN_FOREGROUND;

        /* 如果新的状态与之前的状态不一样,则之前的状态需要通知所有观察者状态发生了改变 */
        if (mAppForegroundState != oldState) {
            validateThenNotifyListeners();
        }
    }

    /**
     * 添加一个用于监听前台应用状态的监听器
     *
     * @param listener
     */
    public void addListener(@NonNull OnAppForegroundStateChangeListener listener) {
        mListeners.add(listener);
    }

    /**
     * 移除用于监听前台应用状态的监听器
     *
     * @param listener
     */
    public void removeListener(OnAppForegroundStateChangeListener listener) {
        mListeners.remove(listener);
    }

    /** 通知所有监听器前台应用状态发生了改变 */
    private void notifyListeners(AppForegroundState newState) {
        android.util.Log.i(TAG, "Notifying subscribers that app just entered state: " + newState);

        for (OnAppForegroundStateChangeListener listener : mListeners) {
            listener.onAppForegroundStateChange(newState);
        }
    }

    /**
     * 这个方法会通知所有观察者:前台应用的状态发生了改变
     * <br><br>
     * 我们只在应用进入/离开前台时立刻监听器。当打开/关闭/方向切换这些操作频繁发生时,我们
     * 简要的传递一个一定会被无视的 NOT_IN_FOREGROUND 值。为了实现它,当我们注意到状态发
     * 生改变,一个延迟的消息会被发出。在这个消息被接收之前,我们不会注意前台应用的状态是否
     * 发生了改变。如果在消息被延迟的那段时间内应用的状态发生了改变,那么该通知将会被取消。
     */
    private void validateThenNotifyListeners() {
        // If the app has any pending notifications then throw out the event as the state change has failed validation
        if (mHandler.hasMessages(MESSAGE_NOTIFY_LISTENERS)) {
            android.util.Log.v(TAG, "Validation Failed: Throwing out app foreground state change notification");
            mHandler.removeMessages(MESSAGE_NOTIFY_LISTENERS);
        } else {
            if (mAppForegroundState == AppForegroundState.IN_FOREGROUND) {
                // If the app entered the foreground then notify listeners right away; there is no validation time for this
                mHandler.sendEmptyMessage(MESSAGE_NOTIFY_LISTENERS);
            } else {
                // We need to validate that the app entered the background. A delay is used to allow for time when the application went into the
                // background but we do not want to consider the app being backgrounded such as for in app purchasing flow and full screen ads.
                mHandler.sendEmptyMessageDelayed(MESSAGE_NOTIFY_LISTENERS, APP_CLOSED_VALIDATION_TIME_IN_MS);
            }
        }
    }

    private class NotifyListenersHandler extends Handler {
        private NotifyListenersHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message inputMessage) {
            switch (inputMessage.what) {
                // 解码完成
                case MESSAGE_NOTIFY_LISTENERS:
                    /* 通知所有观察者状态发生了改变 */
                    android.util.Log.v(TAG, "App just changed foreground state to: " + mAppForegroundState);
                    notifyListeners(mAppForegroundState);
                    break;
                default:
                    super.handleMessage(inputMessage);
            }
        }
    }
}

2) activity必须通知可见性的改变

所有的activity都要实现下面的方法来通知管理者其可见性的改变,*好添加到你的base activity中。

@Override
protected void onStart() {
    super.onStart();
    AppForegroundStateManager.getInstance().onActivityVisible(this);
}

@Override
protected void onStop() {
    AppForegroundStateManager.getInstance().onActivityNotVisible(this);
    super.onStop();
}

3) 订阅前台的变化

订阅你感兴趣的前台的状态变化。application类的onCreate方法是首先需要订阅的,这样才能保证每次应用进入或退出前台的时候能收到通知。

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        AppForegroundStateManager.getInstance().addListener(this);
    }

    @Override
    public void onAppForegroundStateChange(AppForegroundStateManager.AppForegroundState newState) {
        if (AppForegroundStateManager.AppForegroundState.IN_FOREGROUND == newState) {
            // App just entered the foreground. Do something here!
        } else {
            // App just entered the background. Do something here!
        }
    }
}

深入思考

有一些细节还需要再讨论。你需要做一些改变来适配你的应用。

验证时间

计时器应该隔多久检测一次应用是否真正进入后台。在上面的代码中设置为30秒。

在应用运行的时候,第三方程序的activity可能会出现占满屏幕,比如说google的支付应用或者facebook的登录。这些程序必然会导致你的程序进入后台,因为你应用的activity都没有在前台显示。这种情况并不能当作用户离开了程序,因为他们并没有真正地离开。30秒的超时刚好可以解决这个问题。比如说*大部分的用户都会在30秒之内完成支付操作,这样他们就不会被当作离开应用。

如果这种情况不适合你,那么我建议你将验证时间设置为4秒。对于那些缓慢的设备来说,这段时间已经足够用来在旋转的时候创建一个activity。

CPU休眠

还有一个潜在问题,如果用户在退出应用之后马上就锁屏(或者在应用还在运行的时候锁屏),不能保证CPU有足够长的运行时间来完成后台检测任务。为了确保像预期的一样工作,你需要持有唤醒锁防止CPU休眠,直到应用退出事件得到验证。实际上使用唤醒锁使这个看起来并不是什么大问题。

论应用如何启动

到目前为止,我们知道了如何检测应用是什么时候被打开或者关闭的,但是我们还不知道应用是如何被打开的。是用户点击了通知,还是他们点击一个链接,又或者是他们只是从应用图标或*近任务点进来的?

记录启动方式

首先我们要在某个地方记录应用打开的方式。在这段代码中,我在application类中添加了一个枚举型变量用来记录应用是如何被打开的。这个建立在上一个例子的基础之上,所以我们打印一下日志,来看看应用是什么时候被打开的和如何被打开的。

public class MyApplication extends Application {
    public final String TAG = MyApplication.class.getSimpleName();

    public enum LaunchMechanism {
        DIRECT,
        NOTIFICATION,
        URL;
    }

    private LaunchMechanism mLaunchMechanism = LaunchMechanism.DIRECT;

    public void setLaunchMechanism(LaunchMechanism launchMechanism) {
        mLaunchMechanism = launchMechanism;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        AppForegroundStateManager.getInstance().addListener(this);
    }

    @Override
    public void onAppForegroundStateChange(AppForegroundStateManager.AppForegroundState newState) {
        if (AppForegroundStateManager.AppForegroundState.IN_FOREGROUND.equals(newState)) {
            // 应用刚进入前台
            Log.i(TAG, "App Just Entered the Foreground with launch mechanism of: " + mLaunchMechanism);
        } else {
            // 应用刚进入前台,并设置我们的登录模式为当前的默认状态
            mLaunchMechanism = LaunchMechanism.DIRECT;
        }
    }
}

设置启动方式

现在当用户打开应用时,我们就可以打印出启动的方式,但实际上我们还没有设置它的值。所以下一步就是要在用户通过链接或通知打开应用的时候设置启动方式。如果不是上述两个方式,则说明用户是直接打开应用。

记录链接点击

为了记录用户通过点击链接打开应用,需要在某个地方拦截这个链接,加入下面这行代码。确保这行代码在activity的onStart()之前调用的。根据你的代码结构,可能需要把代码添加到很多地方或者一个公用的链接拦截器。

getApplication().setLaunchMechanism(LaunchMechanism.URL);

记录通知事件

记录从通知进入是有诀窍的。手机显示通知,用户点击它,打开一个被绑定了的PendingIntent。这个诀窍就是在给所有的PendingIntent加一个标志,用来说明这个Intent是来自通知的。换句话说,当intent*终打开activity的时候,我们需要能够检测到这个intent来自于通知的。

下面就是一个创建来自通知的PendingIntent,把下面的代码添加到每一个intent。

    public static final String EXTRA_HANDLING_NOTIFICATION = "Notification.EXTRA_HANDLING_NOTIFICATION";

    // 通过 Extra 可以知道 Activity 是否通过推送启动
    intent.putExtra(EXTRA_HANDLING_NOTIFICATION, true);

*后我们还需要做的就是检查每个activity的标志(添加到你的base activity)。如果我们检测到这个标志量,那么就知道这个activity是通过通知产生的,我们可以设置启动方式为通知启动。这个步骤必须在onCreat里面完成,这样它才可以在应用显示到前台(打印启动方式)之前设置值。

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

    Intent intent = getIntent();
    if (intent != null && intent.getExtras() != null) {
        // 判断 Activity 是否由用户点击推送启动
        if (intent.getExtras().getBoolean(EXTRA_HANDLING_NOTIFICATION, false)) {
            // 发出“应用通过用户点击推送启动”的通知
            getApplication().setLaunchMechanism(LaunchMechanism.NOTIFICATION);
        }
    }
}

终于完成了。现在你不仅可以检测应用什么时候启动或关闭的,还可以检测出它是如何启动的。

几款Android 应用自动化测试工具

简述:

本文介绍几款流行的 Android应用自动化测试工具。

Monkey测试:随机测试,压力测试,运行在模拟器或实际设备中。

MonkeyRunner测试:操作简单,可录制测试脚本,可视化操作,主要生成坐标的自动化操作,移植性不强

Robotium 测试

Ronaorex 测试

Appium 测试

UI Automator 测试

TestBird测试

1、Monkey 测试

Monkey 即猴子,Monkey 测试,就像一只猴子,在电脑面前,乱敲键盘在测试。

Monkey 测试主要用于Android 应用程序压力测试的小工具,主要目的就是为了测试app是否会Crash。

Monkey 测试原理:Monkey 是 Android 中的一个命令行工具,可以运行在模拟器里或实际设备中。它向系统发送伪随机的用户事件流(如按键输入、触摸屏输入、手势输入等),实现对正在开发的应用程序进行压力测试。通常也称随机测试或者稳定性测试。Monkey 测试是一种为了测试软件的稳定性、健壮性的快速有效的方法。

1 > Monkey 特征

A. 测试的对象仅为应用程序包,有一定的局限性。

B. Monky 测试使用的事件流数据流是随机的,不能进行自定义。

C. 可对 MonkeyTest的对象,事件数量,类型,频率等进行设置。

D.Monkey 虽可根据一个指定的命令脚本发送按键消息,但其不支持条件判断,也不支持读取待测界面的信息来执行验证操作。

E.Monkey 运行在设备或模拟器上面,可以脱离PC运行,验证待测应用在这些随机性输入面前是否会闪退或者崩溃。

2 > Monkey 程序介绍
① Monkey 程序由 Android 系统自带,使用Java语言写成,在Android文件系统中的存放路径是: /system/framework/monkey.jar;
② Monkey.jar 程序是由一个名为“ monkey ”的Shell脚本来启动执行,shell脚本在Android文件系统中 的存放路径是:/system/bin/monkey;
③ Monkey 命令启动方式:

a. 可以通过 PC 机 CMD 窗口中执行:  adb shell monkey {+命令参数}来进行Monkey测试

b. 在PC上 adb shell进入Android系统,通过执行monkey {+命令参数}来进行Monkey 测试

c. 在Android机或者模拟器上直接执行monkey命令,可以在Android机上安装Android终端模拟器

④ 对特定APP包进行测试的命令为 adb shell monkey -p <pakage.name>

3 > 实例

① 测试前提条件

a. 将手机恢复出厂设置

b. 恢复出厂设置后,进入设置–>关于手机–>高级设置–>勾选‘保持唤醒状态’

c. 在设置->安全中设置解锁图案以及PIN码

d. 连接 adb tool

e. 手机开启后台log(*#*#3646633#*#*),开启main log,, mobile log和net log

② 测试步骤

a. 使用USB线连接手机和电脑

b. 在电脑中输入Monkey命令:

adb shell monkey -p <package.name> –throttle 380 -v -s 3500 300000 >C:\monkey_log.txt

c. 在Monkey结束以后查看它停留的界面并且做一些简单的测试,如拨打电话,发送信息等

③ 测试结果

Monkey的测试结果可以通过monkey_log.txt 查看,如果测试结果正常,在log*后一行会有monkey finished显示并且手机运行正常。如果应用程序产生了应用程序不响应ANR(application notresponding)的错误,Monkey将会停止并报错,如果应用程序崩溃Crash或接收到任何失控异常,Monkey也会停止并报错。

a. 在运行Monkey命令时,遇到Crash或者ANR,就会自动终止。程序无响应的问题:在日志中搜索 “ANR”

b.崩溃问题:在日志中搜索“Exception”   (如果出现空指针,NullPointerException)  肯定是有bug
例如在log*后一行显示crashed at event ####of 300000 using seed 3500 。

c.*后搜索“error”

一般我们执行Monkey时,在3万次以内发生Crash的话就认为Monkey是有问题的,要提交PR。

④ 提交 Monkey 的PR

在执行Monkey命令时发生Crash或者ANR时需要提交PR,具体提交MonkeyPR的规则如下:

a. 标题:在PR标题中加上[Monkey] 内容:主要要包含自己执行的命令以及在多少次发生crash

b. 内容:主要要包含自己执行的命令以及在多少次发生crash

c .其它:在PR上要附上相关的Monkey log还有手机后台开启的log,如果有相关的强制关闭的图片也可以贴上。

4 > Monkey 参数

Monkey命令:adb shell monkey  -p <package.name> –throttle 380 -v -s 3500300000 > C:\monkey_log.txt,这个monkey命令,当monkey test过程中遇到Crash或者ANR,就会自动终止。

C:\monkey_log.txt指将Monkey 的log存在PC端的C盘根目录下。

%title插图%num

常规类参数

1、 -help

作用:列出简单的用法

例:adb shell monkey -help   也可不写help

2、-v

作用:命令行上的每一个-v都将增加反馈信息的详细级别。
Level0(默认),除了启动、测试完成和*终结果外只提供较少的信息。

adb shell monkey -p com.shjt.map -v 100

Level1,提供了较为详细的测试信息,如逐个发送到 Activity 的事件信息。

adb shell monkey -p com.shjt.map -v -v 100

Level2,提供了更多的设置信息,如测试中选中或未选中的 Activity 信息。

adb shell monkey -p com.shjt.map -v -v -v 100

比较常用的是-v -v -v,即*多详细信息,一般会保存到指定文件中供开发人员查找bug原因时使用。

例:adb shell monkey -v 10

 

事件类参数

1、-s <seed>

作用:伪随机数生成器的seed值。如果用相同的seed值再次运行monkey,将生成相同的事件序列。

例:adb shell monkey -s 1483082208904 -v 10

 

2、–throttle <milliseconds>

作用:在事件之间插入固定的时间(毫秒)延迟,你可以使用这个设置来减缓Monkey的运行速度,如果你不指定这个参数,则事件之间将没有延迟,事件将以*快的速度生成。

注:常用参数,一般设置为300毫秒,原因是实际用户操作的*快300毫秒左右一个动作事件,所以此处一般设置为300毫秒。

例:adb shell monkey –throttle 300 -v 10

 

3、–pct-touch <percent>

作用:调整触摸事件的百分比。(触摸事件是指在屏幕中的一个down-up事件,即在屏幕某处按下并抬起的操作)

注:常用参数,此参数设置要适应当前被测应用程序的操作,比如一个应用80%的操作都是触摸,那就可以将此参数的百分比设置成相应较高的百分比。

例:adb shell monkey –pct-touch 100 -v 10

 

4、–pct-motion <percent>

作用:调整motion事件百分比。(motion事件是由屏幕上某处一个down事件、一系列伪随机的移动事件和一个up事件组成)
注:常用参数,需注意的是移动事件是直线滑动
例:adb shell monkey –pct-motion 100 -v 10

 

5、–pct-trackball<percent>
作用:调整滚动球事件百分比。(滚动球事件由一个或多个随机的移动事件组成,有时会伴随着点击事件)
注:不常使用参数,现在手机几乎没有滚动球,但滚动球事件中包含曲线滑动事件,在被测程序需要曲线滑动时可以选用此参数。
例:adb shell monkey –pct-trackball 100 -v 10

6、–pct-nav<percent>
作用:调整基本的导航事件百分比。(导航事件由方向输入设备的上下左右按键所触发的事件组成)
注:不常用操作。
例:adb shell monkey –pct-nav 100 -v 10

7、–pct-majornav<percent>
作用:调整主要导航事件的百分比。(这些导航事件通常会导致UI界面中的动作事件,如5-way键盘的中间键,回退按键、菜单按键)
注:不常用操作。
例:adb shell monkey –pct-majornav 100 -v 10

8、–pct-syskeys<percent>
作用:调整系统事件百分比。(这些按键通常由系统保留使用,如Home、Back、Start Call、EndCall、音量调节)
注:不常用。
例:adb shell monkey –pct-syskeys 100 -v 10

9、–pct-appswitch<percent>
作用:调整Activity启动的百分比。(在随机的时间间隔中,Monkey将执行一个startActivity()调用,作为*大程度覆盖被测包中全部Activity的一种方法)
注:不常用。
例:adb shell monkey –pct-appswitch 100 -v 5

10、–pct-anyevent
作用:调整其他事件的百分比。(这包含所有其他事件,如按键、其他在设备上不常用的按钮等)
注:不常用。
例:adb shell monkey –pct-anyevent 100 -v 5

约束类参数

1、-p<allowed-package-name>
作用:如果你指定一个或多个包,Monkey将只允许访问这些包中的Activity。如果你的应用程序需要访问这些包(如选择联系人)以外的Activity,你需要指定这些包。如果你不指定任何包,Monkey将允许系统启动所有包的Activity。指定多个包,使用多个-p,一个-p后面接一个包名。
注:常用参数。
例:adb shell monkey -p com.Android.browser -v 10

2、-c<main-category>
作用:如果你指定一个或多个类别,Monkey将只允许系统启动这些指定类别中列出的Activity。如果你不指定任何类别,Monkey将选择谢列类别中列出的Activity,Intent.CATEGORY_LAUNCHER和Intent.CATEGORY_MONKEY。指定多个类别使用多个-c,每个-c指定一个类别。
注:不常用。

3、–dbg-no-events
作用:设置此选项,Monkey将执行初始启动,进入一个测试Activity,并不会在进一步生成事件。为了得到*佳结果,结合参数-v,一个或多个包的约束,以及一个保持Monkey运行30秒或更长时间的非零值,从而提供了一个可以监视应用程序所调用的包之间转换的环境。
注:不常用。

4、–hprof
作用:设置此选项,将在Monkey生成事件序列前后生成profilling报告。在data/misc路径下生成大文件(~5Mb),所以要小心使用。
注:不常用。

5、–ignore-crashes
作用:通常,应用发生崩溃或异常时Monkey会停止运行。如果设置此项,Monkey将继续发送事件给系统,直到事件计数完成。
注:常用。

6、–ignore-timeouts
作用:通常,应用程序发生任何超时错误(如“Application Not responding”对话框)Monkey将停止运行,设置此项,Monkey将继续发送事件给系统,直到事件计数完成。
注:常用。

7、–ignore-security-exception
作用:通常,当程序发生许可错误(例如启动一些需要许可的Activity)导致的异常时,Monkey将停止运行。设置此项,Monkey将继续发送事件给系统,直到事件计数完成。
注:常用。

8、–kill-process-after-error
作用:通常,当Monkey由于一个错误而停止时,出错的应用程序将继续处于运行状态。设置此项,将会通知系统停止发生错误的进程。注意,正常(成功)的结束,并没有停止启动的进程,设备只是在结束事件之后简单的保持在*后的状态。

9、–monitor-native-crashes

作用:监视并报告Andorid系统中本地代码的崩溃事件。如果设置–kill-process-after-error,系统将停止运行。

10、–wait-dbg

作用:停止执行中的Monkey,直到有调试器和它相连接。

样例:

adb shell monkey -p com.android.settings –throttle 380 -v -v -v -s 3500 300000 > E:\Test\monkey_log.txt

测试结果:

测试完成后均正确时会显示Monkey finished:

Events injected: 300
:Sending rotation degree=0, persist=false
:Dropped: keys=0 pointers=2 trackballs=0 flips=0 rotations=0
## Network stats: elapsed time=42700ms (0ms mobile, 0ms wifi, 42700ms not connected)
// Monkey finished

有bug时,会出现 error:

** Monkey aborted due to error.
Events injected: 8530
:Sending rotation degree=0, persist=false
:Dropped: keys=5 pointers=8 trackballs=0 flips=0 rotations=0
## Network stats: elapsed time=1016690ms (0ms mobile, 0ms wifi, 1016690ms not

connected)

2、 MonkeyRunner 测试

MonkeyRunner工具是使用 Jython (使用Java编程语言实现的Python)写出来的,它提供了多个API,通过MonkeyRunner API 可以写一个Python的程序来模拟操作控制Android设备app,测试其稳定性并通过截屏可以方便地记录出现的问题。

MonkeyRunner和Monkey没有直接的关系。Monkey是在设备/模拟器直接运行adb shell命令生成用户或系统伪随机事件流来进行测试的。

%title插图%num

而MonkeyRunner则运行在PC上,需要通过服务器/客户端的的模式向设备或者模拟器上的android应用发送指令来执行测试。它支持自己编写插件,控制事件,随时截图,简而言之,任何你在模拟器/设备中能干的事情,MonkeyRunner都能干,而且还可以记录和回放。

%title插图%num

1 > MonkeyRunner 特征
1)MonkeyRunner工具在工作站上通过API定义的特定命令和事件控制设备或模拟器(可控)

2)精确控制事件之间的事件

3)可以进行:点触屏、拖拽、长按、键盘事件

4)可以智能截图对比和判断

5)回溯出详细具体的BUG路径

2  > MonkeyRunner 优缺点

1) 能完全模拟人工所有操作

2) 有详细的API文档参考

3) 可以写出智能图像对比脚本

4) 支持 java 和 Python 两种语言脚本

5) 脚本移植性差

3 > MonkeyRunner测试类型

1)多设备控制

MonkeyRunnerAPI可以跨多个设备或模拟器实施测试套件。您可以在同一时间接上所有的设备或一次启动全部模拟器(或统统一起),依据程序依次连接到每一个,然后运行一个或多个测试。您也可以用程序启动一个配置好的模拟器,运行一个或多个测试,然后关闭模拟器。

2)功能测试

MonkeyRunner可以为一个应用自动贯彻一次功能测试。您提供按键或触摸事件的输入数值,然后观察输出结果的截屏。

3)回归测试

MonkeyRunner可以运行某个应用,并将其结果截屏与既定已知正确的结果截屏相比较,以此测试应用的稳定性。

4)可扩展的自动化

由于MonkeyRunner是一个API工具包,您可以基于Python模块和程序开发一整套系统,以此来控制Android设备。除了使用MonkeyRunner API之外,您还可以使用标准的Python os和subprocess模块来调用Android Debug Bridge这样的Android工具。

4 > MonkeyRunner 工具

MonkeyRunner API 主要包括三个模块

1)MonkeyRunner:

此类提供连接真机和模拟器方法waitForConnection(float timeout,stringdeviceid),还提供了创建用户界面显示信息的alert()方法。

2)MonkeyDevice

代表一个设备或模拟器。此类提供了安装和卸载程序包、开启Activity、发送按键和点击事件、运行测试包等方法

拖拉控件drag(tuple start,tuple end,floatduration,integer steps)  //duration手势持续时间

按键press(string keycode,dictionary type) //keycode:KEYCODE_HOME,..   type:DOWN ,UP,DOWN_AND_UP…

安装应用 installPackage(pc端存放apk路径)

启动应用starActivity(package+’/’+activity)//一个参数

点击touch(integer x,integer y, integer type)//type:DOWN,UP,DOWN_AND_UP…

输入type(string message)

截屏takeSnapshot()

3)MonkeyImage

这个类提供了捕捉屏幕的方法。

在测试过程中用来保存测试截图,将位图保存为各种格式,并可以比较两个MonkeyImage对象,将image保存到文件等。

图像对比sameAs(MonkeyImage other,float percent)//对比的相似度,结果boolean类型

图像保存writetoFile(string path,string format)

5 > MonkeyRunner 环境搭建

Monkeyrunner的环境搭建,需要安装以下工具:jdk、android sdk、python编译器。

MonkeyRunner 环境搭建
Eclipse中MonkeyRunner环境搭建

6 > MonkeyRunner 运行

运行有两种方式

① 在CMD命令窗口直接运行monkeyrunner

② 使用Python编写测试代码文件,在CMD中执行monkeyrunner xxx.py运行

不论使用哪种方式,您都需要调用SDK目录的tools子目录下的monkeyrunner命令。

1)模拟器启动

在运行monkeyrunner之前必须先运行相应的模拟器或连接真机,否则monkeyrunner无法连接到设备

运行模拟器有两种方法:1、通过eclipse中执行模拟器 2、在CMD中通过命令调用模拟器

这里介绍通过命令,在CMD中执行模拟器的方法

emulator -avd AVD_test
上面命令中 AVD_test 是指模拟器的名称。

2)交互对话环境

cmd 运行 monkeyrunner 交互命令“monkeyrunner” 或:

monkeyrunner -plugin

3)cmd 终端导入monkeyrunner所要使用的模块

from com.android.monkeyrunner import MonkeyRunner, MonkeyDevice, MonkeyImage

然后便可以开始利用monkeyrunner进行测试了。

3) 模拟器连接

device=MonkeyRunner.waitForConnection(6,’emulator-5554′)

参数1:超时时间,单位秒,浮点数,默认是无限期地等待。
参数2:指定的设备名称device_id,默认为当前设备(手机优先,其次为模拟器)

4) app 安装

模拟器启动成功后,安装apk

 

device.installPackage(‘F:\\QQyinle_439.apk’)

其中,参数为apk的相对路径。成功返回true,且模拟器的IDLE界面可看到安装apk 图标

5)app 启动

device.startActivity(component=”package名/.activity”)

如何获取一个app的package名和activity?
使用

#apk路径中一定不能有空格
aapt dump badging F:\QQyinle_439.apk

aapt dump badging F:\QQyinle_439.apk > F:\log.txt

所以:

device.startActivity(component=” com.tencent.qqmusic/.activity.AppStarterActivity “)

命令执行后,模拟器上的app被启动。启动成功后,便可以向模拟器发送如按键、滚动、截图、存储等操作了

6)

问题:CMD运行提示monkeyrunner不是内部或外部命令,也不是可运行的程序或批处理文件。

解决:电脑环境变量未配置,将monkeyrunner所在目录配在环境变量里。

变量名:Path

变量值:D:\android\android-sdk-windows\tools;D:\android\android-sdk-windows\platform-tools

7 > 样例(Monkeyrunner运行python脚本)

test.py

#-*-UTF-8-*-
#如果导入的模块起了别名,后面就必须使用别名
from com.android.monkeyrunner import MonkeyRunner as mr
from com.android.monkeyrunner import MonkeyDevice as md
from com.android.monkeyrunner import MonkeyImage as mi

#连接设备或虚拟器。
#参数1,超时时间,单位秒,默认无限期等待;参数2,设备名称,默认当前设备
device=mr.waitForConnection(2,’192.168.56.101:5555′)

#向设备或模拟器安装apk,以下两种方式都是对的
device.installPackage(‘D:\\baiduliulanqi_186.apk’)
#device.installPackage(‘D:/baiduliulanqi_186.apk’)

#启动APP
device.startActivity(‘cmp=com.baidu.browser.apps/com.baidu.browser.framework.BdBrowserActivity’)
mr.sleep(3)

#点击搜索框 #指定位置发送触摸事件
device.touch(100,100,’DOWN_AND_UP’)
mr.sleep(1)

#输入查询词
device.type(‘test’)
mr.sleep(1)

#点击回车键 #发送指定类型键码的事件
device.press(‘KEYCODE_ENTER’,’DOWN_AND_UP’)
mr.sleep(2)

#截图
result=device.takeSnapshot()

#保存到文件
result.writeToFile(‘./test.png’,’png’)

#清除搜索框
device.touch(100,100,’DOWN_AND_UP’)
mr.sleep(1)
device.press(‘KEYCODE_DEL’,’DOWN_AND_UP’)
mr.sleep(2)

#字符串发送到键盘
#device.type(‘字符串’)
device.type(‘Findyou’)

#唤醒设备屏幕
#锁屏后,屏幕关闭,可以用下命令唤醒
device.wake()

#重起手机
device.reboot()

#模拟滑动
#device.drag(X,Y,D,S)
#X 开始坐标
#Y 结束坐标
#D 拖动持续时间(以秒为单位),默认1.0秒
#S 插值点时要采取的步骤。默认值是10
device.drag((100,1053),(520,1053),0.1,10)

运行测试脚本test.py:monkeyrunner test.py
monkeyrunner 录制和回放
录制:monkey_recorder.py

from com.android.monkeyrunner import MonkeyRunner as mr
from com.android.monkeyrunner.recorder import MonkeyRecorder as recorder
device=mr.waitForConnection()
recorder.start(device)

在 cmd 命令行运行 monkeyrunner  monkey_record.py,会弹出一个MonkeyRecord窗口界面该窗口的功能:

%title插图%num

A. 可以自动显示手机当前的界面

B. 自动刷新手机的*新状态

C. 点击手机界面即可对手机进行操作,同时会反应到真机,而且会在右侧插入操作脚本

D.

wait: 用来插入下一次操作的时间间隔,点击后即可设置时间,单位是秒

Press a Button:用来确定需要点击的按钮,包括menu、home、search,以及对按钮的press、down、up属性Type Something:用来输入内容到输入框

Fling:用来进行拖动操作,可以向上、下、左、右,以及操作的范围

Export Actions:用来导出脚本,不需要后缀名,也可以添加后缀名.mr

Refresh Display:用来刷新手机界面,估计只有在断开手机后,重新连接时才会用到

用录制函数导出操作的脚本,通过monkey_playback.py函数回放之前的操作

回放:monkey_playback.py,

import sys
from com.android.monkeyrunner import MonkeyRunner as mr

CMD_MAP = {
‘TOUCH’:lambda dev,arg:dev.touch(**arg),
‘DRAG’: lambda dev,arg:dev.drag(**arg),
‘TYPE’: lambda dev,arg:dev.type(**arg),
‘PRESS’: lambda dev,arg:dev.press(**arg),
‘WAIT’: lambda dev,arg:mr.sleep(**arg)
}

def process_file(f,device):
for line in f:
(cmd,rest)=line.split(‘|’)
try:
rest = eval(rest)
except:
print ‘unable to parse options’
continue
if cmd not in CMD_MAP:
print ‘unknown command: ‘ + cmd
continue
CMD_MAP[cmd](device, rest)
def main():
file = sys.argv[1]
f = open(file,’r’)
device = mr.waitForConnection()
process_file(f,device)
f.close()
if __name__ = ‘__main__’
main()

8 > 其他

#卸载设备或模拟器中的APK ,参数为APK包名
device.removePackage(‘cn.richinfo.thinkdrive’)
print (‘Uninstall Success!’)

#发送指定类型指定键码的事件
#device.press(参数1:键码,参数2:触摸事件类型)
#参数1:见android.view.KeyEvent
#参数2,如有TouchPressType()返回的类型-触摸事件类型,有三种。
#1、DOWN 发送一个DOWN事件。指定DOWN事件类型发送到设备,对应的按一个键或触摸屏幕上。
#2、UP 发送一个UP事件。指定UP事件类型发送到设备,对应释放一个键或从屏幕上抬起。
#3、DOWN_AND_UP 发送一个DOWN事件,然后一个UP事件。对应于输入键或点击屏幕。
以上三种事件做为press()参数或touch()参数

#按下HOME键
device.press(‘KEYCODE_HOME’,MonkeyDevice.DOWN_AND_UP)
#按下BACK键
device.press(‘KEYCODE_BACK’,MonkeyDevice.DOWN_AND_UP)
#按下下导航键
device.press(‘KEYCODE_DPAD_DOWN’,MonkeyDevice.DOWN_AND_UP)
#按下上导航键
device.press(‘KEYCODE_DPAD_UP’,MonkeyDevice.DOWN_AND_UP)
#按下OK键
device.press(‘KEYCODE_DPAD_CENTER’,MonkeyDevice.DOWN_AND_UP)

KeyCode:
home键 KEYCODE_HOME
back键 KEYCODE_BACK
send键 KEYCODE_CALL
end键 KEYCODE_ENDCALL
上导航键 KEYCODE_DPAD_UP
下导航键 KEYCODE_DPAD_DOWN
左导航 KEYCODE_DPAD_LEFT
右导航键 KEYCODE_DPAD_RIGHT
ok键 KEYCODE_DPAD_CENTER
上音量键 KEYCODE_VOLUME_UP
下音量键 KEYCODE_VOLUME_DOWN
power键 KEYCODE_POWER
camera键 KEYCODE_CAMERA
menu键 KEYCODE_MENU

3、Robotium测试

Robotium 是一款常用的免费的 Android 自动化测试工具软件,适用于为不同的安卓版本和子版本黑盒测试自动化。Robotium 测试是用java写的。提供了模拟各种手势操作(点击、长按、滑动等)、查找和断言机制的API,能够对各种控件进行操作。Robotium 对 Activity,Dialog,Toast,Menu 都是支持的。软件开发人员经常把它描述为Android Selenium。事实上,Robotium是一个单元测试库。

为了自动化测试需要修改程序源代码。该工具也不适合与系统软件的交互,它不能锁定和解锁智能手机或平板电脑。Robotium 也没有录制回放功能,也不提供截图。

详情请参考:

Robotium自动化测试框架使用教程

4、Ronaorex测试

Ranrex 是一款不仅可以支持*新Android版本,也支持从Android2.2开始的早期版本和分支版本。

Ranorex的优势是它有详细的截屏报告。它能通过Wifi连接智能手机和平板电脑。
一个自动化测试工程师通过这个Android工具可以不用XML数据格式来详细编写数据驱动的测试。Ranorex工作室使自动化测试工程师只要点击鼠标就可容易地创建测试。它允许详细声明额外的程序模块,来用于在后期开发周期中测试更复杂的场景。

它是一个商业的移动应用工具,其许可价格为1990欧元。不过Ranorex搜索功能相当慢;它需要30秒来完成这样的操作。我们必须为Ranorex配备apk文件设备,否则无法通过这个工具实现自动化测试,因为它只能在APK文件设备上工作。

5、Appium测试

这是一个可以为iOS和Android做自动化测试的框架。它是一个开源工具。它支持从2.3及以后的安卓版本。Appium利用WebDriver接口运行测试。它支持多种编程语言,如java,C #,Ruby和其他在WebDriver库中的语言。

它可以控制移动设备上的Safari和Chrome。这样测试移动网站可使用Appium和这些浏览器。
但一些自动化测试工程师抱怨说,它没有详细的报告。其弱点还有减少了在移动设备上XPath支持。

Appium环境搭建(Windows版)

6、UI Automator 测试

这款工具是谷歌发布的。它支持从4.1开始的安卓版本。UI Automator能够与各种Android软件产品交互,包括系统中的应用。这使UI Automator可以锁定和解锁智能手机或平板电脑。

通过这个工具创建的脚本可以在许多不同的安卓平台上执行。它可以重现复杂的用户操作动作。
UI Automator也可以利用一个设备的外部按键,如回放键、音量调节键、开关键来控制。
它可以集成测试框架TestNG。在这种情况下,UI Automator可以生成丰富和详细的报告,类似于Ranorex生成报告。另外,这个工具搜索功能非常快。

软件测试专家发现UI Automator是一款适用于许多Android平台的移动应用测试。它是一款*适合安卓应用测试的工具之一,因为它是由谷歌专门为这个操作系统发布的。

通常约有80%的新软件bug能在所有支持的平台上重现。因此,一个可执行在广泛使用的平台上的移动测试工具是可以发现高达80%的缺陷。其余20%将会在其他平台上被发现。这意味着,在大多数情况下,在更少的测试平台上完整地做测试比在众多平台上匆忙测试更好。

uiautomatorviewer :一个图形界面工具来扫描和分析应用的UI控件。

uiautomator :一个测试的Java库,包含了创建UI测试的各种API和执行自动化测试的引擎

参考:

http://blog.csdn.net/u010961631/article/details/9616581

https://www.jianshu.com/p/c900efe8c982

https://segmentfault.com/a/1190000004619487

7、TestBird 测试

TestBird自动回归测试平台为手游/APP开发者提供APP自动化回归测试,简单点击自动生成图片用例;多台手机同时执行用例回归;基线对比,找出问题;调整基线,维护测试用例;一键生成报告,全面提升测试效率和质量。

TestBird*初是从手游测试开始起步,在手游圈积累起很高的知名度,目前也在逐步向APP测试领域进军,同时TestBird也加入了智能硬件的测试领域。基于全球首创的对象识别技术,TestBird可以深入到移动App&游戏内部所有功能的深度解析能力。TestBird建立了云手机、云测试和云分析三大测试平台,通过自助App功能测试、远程真机调试、真机兼容性测试、真人体验测试、 真人压力测试和崩溃分析等,为移动应用提供从研发到上线再到运营的一站式质量管理服务。

 

Android应用的几种开发方式

1、前言
自苹果 iOS 和谷歌 Android 操作系统发布以来,在互联网界就多了一个新名词:App(意为运行在智能移动终端上的第三方应用程序)。

*初的 App 有两种形式:一种是基于本地(操作系统)运行的 App,称之为原生App,或者Native App;一种是基于浏览器运行的 Web App,同样是安装在手机上。因为本地 App 位于平台层上方,向下访问和兼容的能力比较好一些,可以支持在线或离线,消息推送或本地资源访问,摄像、拨号功能的调取。但是由于设备碎片化,本地 App 的开发成本很高,维持多个版本的更新升级也很麻烦。

随着 HTML5 的出现,基于 HTML5 低成本跨平台开发优势又兼具本地 App 特质的混合模式移动应用( Hybrid App) 迅速兴起。它大幅降低了 App 的开发成本,可以通过现有应用商店模式发行,在用户桌面形成独立入口等等。 Hybrid App 已成为开发 App 不错的选择,是未来移动应用开发的趋势。

2、原生App的开发方法
以开发工具的不同来分类,原生 App 的开发方式有两种:Eclipse + ADT 和 Android Studio。

Eclipse + ADT
Eclipse + ADT 的开发方式是曾经Android开发者*好的选择,也是谷歌官方所支持的。但是2013的Google I/O 大会上,Google官方曝光一款新的开发工具 Android Studio,并在2014年底正式推出自家的IDE Android Studio1.0,2016年11月2日,Google Android Developer 官博发文,宣告停止对 Eclipse ADT 的支持。

由于现在市场上的书籍和视频教程,包括很多项目和源码*大多数都是基于 Eclipse + ADT 的,所以了解这种开发方式对于初学者是很必要的,便于更快的入门和转入 Android Studio的使用。(当然,有自信和自学能力强的人可以直接使用 Android Studio)

Eclipse + ADT 开发环境的搭建
Eclipse 是一个免费的、开放源代码的、基于Java的可扩展开发平台。就其本身而言,它只是一个框架和一组服务,用于通过插件组件构建开发环境,通过安装插件,可以用它开发 C/C++,Java,Python,Perl,Ruby,Android,PHP等。

Eclipse 的官方下载地址:http://www.eclipse.org/downloads/,其他下载站点百度一下即可。

Eclipse是免安装的,下载下来的一般是个压缩包,但是有Windows,Linux,Mac操作系统和32bit和64bit之分,根据自己的电脑选择对应的下载即可。

我们都知道,原生 App 是基于 Java 开发的,因此,在开发 Java 程序之前,必须先完成一些准备工作,也就是在计算机上安装并配置 Java 开发环境,即安装和配置 JDK。

JDK 的全称是 Java SE Development Kit,即Java 标准版开发包,是 Sun 公司提供的一套用于开发 Java 应用程序的开发包,它提供了编译、运行Java程序所需的各种工具和资源,包括 Java编译器、Java运行时环境,以及常用的Java类库。
官方下载 JDK 的地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html,类似于Eclipse,需要根据自己电脑的操作系统和位数选择对应的 JDK。

%title插图%num

安装时需要选择 JDK 的安装路径,系统默认会安装在 C:\Program Files\Java 路径下,但是不推荐安装在有空格的路径下,这样可能导致一些未知的问题,建议直接安装在根路径下,例如 D:\Java\jdk1.8.0_112\。安装完成后,我们还要配置一下环境变量,此处只说明Windows操作系统下的配置过程:
点击左下角win窗口图标,右键点击计算机,或者直接右键桌面上的“计算机”图标,点击“属性”,进入了“控制面板\系统和安全\系统”窗口,单击该窗口左边栏的“高级系统设置”。

%title插图%num
在“高级”Tab页里,单击“环境变量”按钮,进入环境变量对话框。

%title插图%num
在下方的系统变量中,新建 变量名:JAVA_HOME,变量值:JDK安装路径,就是之前下载 JDK 后的安装路径,比如 D:\Java\jdk1.8.0_112。

%title插图%num
找到变量 Path,点击编辑,在*后添加 %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;注意里面的每项都是用英文的分号;来隔开的,这个电脑已经有了,只是编辑,添加,不是新建。然后新建 变量名:CLASSPATH,变量值为 .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar;。(注意*前面有个.;)

%title插图%num
这样 JDK 的环境变量就配置完成,打开Windows操作系统的命令行窗口(在“开始”菜单里运行cmd命令即可),在命令行中分别输入 java 和 javac 命令,如果得到的是 java 或 javac 的用法帮助信息,那么说明我们的环境变量配置成功了。
%title插图%num

完成以上步骤,我们就可以开发J2SE的程序了,但是这里我们是要开发 Android App,所以我们还要安装 Android 在 Eclipse 上的插件 ADT ,它的全称是 Android Developer Tools Plug-in。现在,谷歌的官网上已经没有 ADT 的下载链接了,它会提示你用 Android Studio ,并将之前的项目迁移到 Android Studio 上,因此推荐大家采用离线安装的方式。
这里贴出一个国内的下载链接,里面提供了Android开发的所有工具下载。http://tools.android-studio.org/index.php。Eclipse离线安装 ADT 插件的方法可以参考如下百度经验:ADT离线安装教程。

Android SDK 的下载配置
SDK:(software development kit)软件开发工具包。被软件开发工程师用于为特定的软件包、软件框架、硬件平台、操作系统等建立应用软件的开发工具的集合。因此,Android SDK 指的是Android专属的软件开发工具包。
它的官方下载地址是:https://developer.android.com/studio/index.html。但是,由于国内GFW的原因,外国的网站基本访问不了,我们只能在国内其他网站下载,这些资源还是非常容易找到的(由于在线下载的不稳定和不确定性,这里只说明离线下载配置的方法)。上面的链接已经有SDK的下载地址了,这里再贴两个另外的链接:http://www.cnblogs.com/bjzhanghao/archive/2012/11/14/android-platform-sdk-download-mirror.html,http://www.androiddevtools.cn/。每个版本都有两种类型的SDK,分别是exe和zip格式的(这里指Windows操作系统),即可执行安装程序的和免安装压缩包。SDK的安装或解压路径*好也是根路径,反正不要带有中文(安装国外的软件都要注意这点,这是个好的习惯,虽然不一定是强制性的)。
这里需要注意的是,无论通过哪种方式,下载的SDK都只是个工具而已,打开SDK的文件夹,里面的内容大致是这样的。(可能有细微区别)
%title插图%num
这里可以看到一个可执行程序SDK Manager,顾名思义,它就是 SDK 的管理工具,双击它,会出现如下窗口。
%title插图%num
Android操作系统的所有版本都在这里,需要哪个就下载哪个,然后点击右下角的install * packages,出现下面的窗口,点击要下载的package,选择accept,*后选择install即可安装。
%title插图%num

这里值得一提的是,这种下载速度基本是龟速(如果不*的话),所以,强烈建议大家学会科学上网,哪怕花钱也好,试想,一个Android开发人员连Android官网都上不去的话,那是多么可怕的一件事!
这里,贴上一篇解决这个问题的文章(我没有试过,我一直在墙外),http://blog.csdn.net/freedom2028/article/details/6261810。
安装完成之后,为了在命令行窗口可以使用 Android SDK 的各种工具,建议将 Android SDK 目录下的 tools 子目录、 platform-tools 子目录添加到系统的PATH环境变量中。
*后,值得一提的是,如果你觉得上面的步骤太复杂,你也可以下载多合一下载包ADT Bundle,里面包含了:sdk + 特定版本platform + eclipse + adt + 兼容包,解压缩即可使用。

*个Hello World项目
双击打开Eclipse,出现如下的窗口,即指定工作空间的路径。自己建个文件夹即可或者使用默认设置。
%title插图%num
打开后,点击File->New->Android Application Project,出现如下窗口。项目名称可以写为 HelloWorld,SDK根据自己下载的,选择要用的即可,theme选择none即可,然后一直点击next,直至finish即可,这样一个helloworld项目就建成了。
%title插图%num

Android虚拟设备神器Genymotion
Android应用程序可以在虚拟机和真机上运行,真机测试很容易,Android手机通过USB线接上电脑,打开USB调试,在Eclipse里右键点击项目HelloWorld->Run as->Android Application,在设备选项里选择你的手机即可。
这里主要说明 AVD(Android Virtual Device)的使用,以上环境都完成了,你会看到Eclipse的工具栏里会有一个Android的图标,Android Virtual Device Manager在SDK Manager的旁边。
%title插图%num
在这里,可以创建和管理Android 虚拟机,但是不用不知道,一用吓一跳,这里创建的Android 虚拟机启动慢的令人发指,少则十几分钟,多则无法估计(因为无法再等下去,直接停掉了),刚开始的时候我们会以为是自己电脑出了问题,其实不然,它本来就这么慢。
还好,我们有另一款使用非常容易也非常快的虚拟机Genymotion(一般启动在10s左右吧),它的官网是https://www.genymotion.com/,中文官网是http://www.genymotion.net/,它的使用教程可以参考百度经验http://jingyan.baidu.com/article/3ea51489e7d8bd52e61bba36.html。这里特别提醒一点,genymotion是基于Virtual box 运行的,如果安装之后,可以正常运行,就不要再更新了,它的更新提示直接忽略即可,不然可能导致无法使用了。
Eclipse还有个genymotion的插件,安装后可以在Eclipse中生成一个按钮,点击即可启动genymotion,上面图片上有,在SDK manager的左边(右边是AVD manager),因为可以先运行genymotion,之后运行Android程序,所以这里忽略。

运行HelloWorld
好了,所有工作都做完了,现在运行HelloWorld项目吧。
%title插图%num

到这里,我们的HelloWorld也运行出来了,可能你也发现了,我们一句代码也没有写,没错,按照以上步骤,默认建立出来的就是这样一个HelloWorld项目,它显示的内容就是Hello world!

Android Studio
前面也说到了,Android Studio是Google官方推出的IDE,官网上这么说的“Android Studio 提供用于为各类 Android 设备构建应用的*快速的工具,利用世界一流的代码编辑、调试、性能工具、一套灵活的构建系统以及一套即时构建/部署系统,您可以专注于构建独特的高品质应用。”总之,Android Studio是非常强,也非常大的Android开发利器,学习它需要一定的时间,另外,运行AS对电脑的配置要求比较高,官方推荐win7以上操作系统,8GB RAM,*低屏幕分辨率:1280 x 800,这意味着一般的电脑根本跑不动。(那些非常强大的IDE一般都很大,运行条件都很苛刻,其实也可以理解)。
AS的官方下载地址是https://developer.android.com/studio/index.html,其他的下载地址有:http://tools.android-studio.org/index.php,http://www.cnblogs.com/bjzhanghao/archive/2012/11/14/android-platform-sdk-download-mirror.html,http://tools.android-studio.org/index.php/sdk/等,百度一下,你就知道。
AS的使用教程如下:http://www.open-open.com/lib/view/open1433387390635.html。
AS的视频教程如下:http://www.jikexueyuan.com/course/2531_1.html?ss=1。

2、Web App
什么是Web App?维基百科的解释是这样的:In computing, a web application or web app is a client–server software application in which the client (or user interface) runs in a web browser. Common web applications include webmail, online retail sales, online auctions, wikis, instant messaging services and many other functions.理解一下是这样的:Web App就是运行于网络和标准浏览器上,基于网页技术开发实现特定功能的应用,可以理解为一个触屏版的网站。不同于原生App,Web App 不是基于操作系统运行的,而是运行在浏览器里的,它的宿主是浏览器,它所需的资源一般都在网络上,因此,它不需要下载安装,但是使用它必须要联网。这类应用我们常用的有百度新闻,163邮箱,淘宝触屏版等。
因此,Web App 就是网页的开发,主要利用前端知识,诸如HTML5,CSS,JavaScript等。
知乎上推荐的一些较好的 Web App: https://www.zhihu.com/question/19727553。

3、Hybrid App
那么什么是 Hybrid App 呢?从字面意思理解就是混合的应用。没错,它就是前面两者的混合应用。
Hybrid App(混合模式移动应用)是指介于web-app、native-app这两者之间的app,兼具“Native App良好用户交互体验的优势”和“Web App跨平台开发的优势”。
这三者涵盖了市场上所有的移动应用。它们的比较如下:
%title插图%num
网络上有很多关于这三者的分析理解,摘录如下:
https://www.zhihu.com/question/23622875
http://www.uisdc.com/web-hybrid-native-app
http://baike.baidu.com/link?url=FANt8_qieZwhFjBxYrZd03alBIOhqqK41XhyScMS4aDKISIx5-ZN0GYR9wfDiLTjwWfBaJeoWKnRV7zcKmBhnjiyyCyKpy_b4KYEAJKhaeG
http://www.zhongkerd.com/news/content-1198.html
这里是开源中国社区整理的非常全面的121款手机Web开发框架:
http://www.oschina.net/project/tag/322/mobile-web?lang=0&os=0&sort=view&p=1

4、有趣的 App Inventor
App Inventor的设计目的就是为了让毫无编程经验的人(比如儿童)可以开发有趣的手机应用。App Inventor 原是Google实验室(Google Lab)的一个子计划,由一群Google工程师和勇于挑战的Google使用者共同参与设计完成。
由于GFW的原因,国内访问不了App Inventor的官网,无法在线编程(之前国内还有站点可以访问,但是现在未找到),它的官网是:http://appinventor.mit.edu/explore/。值得庆幸的是,App Inventor提供离线开发的方式,它的离线安装包可以在这里下载(这是我的百度网盘,失效请告知我):http://pan.baidu.com/s/1sldCqoX。
AI2离线包的安装使用说明可以参考:http://www.arduino.cn/thread-12392-1-1.html。安装完成后,使用时打开离线环境,在浏览器中输入127.0.0.1:8888(localhost:8888)就回进入登录页面,如下图
%title插图%num

输入自己的账号,进入开发页面,如下图

%title插图%num

这样就可以开发自己的应用了。

Android中的权限机制

Android的权限管理遵循的是“*小特权原则”,即所有的Android应用程序都被赋予了*小权限。一个Android应用程序如果没有声明任何权限,就没有任何特权。

权限的历史

2013年,苹果公司发布IOS7系统。其中一项令开发者头疼的修改点:隐私中增加相册、录音等权限,App如需使用相应权限,需要申请并由用户同意(IOS7以前,可以直接访问相册),针对此点,很多App在首次启动时一通弹窗,申请各式各样的权限。

后来苹果为改善用户体验,在App Store审核时要求App必须在使用前一刻才能申请权限,有效改善了此类问题。比如一款直播App,当你启动App时并不需要相机、录音权限,等到你开播时才需要申请这两个权限。这一场景,其实就类似今天要提到的Android动态授权。

早期:无遮拦

Android6.0系统以前,在安装App前,会罗列出App申请的所有权限。如果继续安装,视为用户同意赋予App所需权限。这种机制是无遮拦的,在尝试安装App时,弹窗罗列了App申请的全部权限。只能对所需权限进行查看,无法拒*授权,可选择取消安装或继续安装。

这种方式,对于开发者*为友好,仅需在Manifest中配置App所需权限即可,代码就可以直接调用了。但是对于用户来说,这种方法存在*大的安全隐患。

%title插图%num

发展:第三方安全App

为解决部分敏感权限被不合理使用,国内部分公司的安全类App,开始监控应用获取手机敏感权限并做出提示。如360手机卫士、腾讯手机管家等产品,当监测到有App尝试使用短信权限、定位等敏感权限,会告知用户,并可以拒*赋予权限。刚开始,还比较顺利。但随着手机厂商逐渐开始修改ROM,第三方安全App的兼容、性能问题逐步爆发。

升级:厂商行动

再稍后一些,手机厂商开始行动,纷纷将第三方软件的权限提示功能直接做入ROM。并把安全作为产品的卖点进行打造。

%title插图%num

 

目前:谷歌升级权限管理

2015年推出的Android 6.0,加入了危险权限管理。因手机厂商对ROM的修改,部分6.0以上机器并不支持此项特性。到了此阶段,App需要在对权限代码进行修改后,才能正常使用对应权限。简单理解为3步:

  • 1、判断是否授权;
  • 2、如果未授权需申请权限,根据授权结果继续执行;
  • 3、已授权可以继续操作。

权限的使用和适配

零、权限的基础知识

Android系统基于Linux内核,系统中的权限分为3类:

  • Android手机所有者权限:这个和厂商相关,可以理解为系统权限。
  • Android ROOT权限:类似于Linux,这是Android系统中的*高权限。如果拥有该权限,就可以对Android系统中的任何文件、数据、资源进行任意操作。所谓“越狱”,就是令用户获得*高的ROOT权限。
  • Android应用程序权限:该权限在AndroidManifest文件中由程序开发者声明,在需要时由用户授权。

一、不适用动态权限

动态权限特性,仅从Android 6.0开始拥有,所以,可以简单粗暴的通过不提升targetSDK(targetSDK<23)的方式,便可不触发此特性。

如果不改变任何代码,直接将targetSDK提升到26,然后运行App,做同样操作时会发生异常甚至崩溃,产生这个崩溃的原因,是在Android 6.0及以上,未获取权限的情况下直接执行了需要权限的操作。

二、动态权限适配

1、在使用前权限前,检测权限

首先,我们需要判断自己是否拥有权限。判断时间点为执行需要权限的对应操作前。如我们在获取IMEI前,需要判断是否拥有PHONE_STATE权限。

我们可以调用ContextCompat.checkSelfPermission()方法检测授权状态,返回的结果为PackageManager中的两个常量:PERMISSION_GRANTED(已授权)和PERMISSION_DENIED(未授权)。

2、已授权的情况下,执行相应的操作

当已授权时,就可以执行原有的操作了。代码如下:

  1. // 检测PHONE_STATE 如果已授权
  2. if (ContextCompat.checkSelfPermission(this,Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) {
  3. //做你想做的
  4. }

3、未授权时,申请权限

如果App未获得授权,我们就需要向用户申请授权。可以调用requestPermissions()方法来请求授权。代码如下:

  1. // 检测PHONE_STATE 如果未授权
  2. if (ContextCompat.checkSelfPermission(this,Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
  3. //申请权限
  4. ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), PERMISSIONS_REQUEST_PHONE_STATE)
  5. }

requestPermissions()中的第三个参数是一个int型请求码,方便回调处理。

调用申请授权方法后,ROM会调起一个系统级弹窗,这个dialog开发者无法定制。当用户点击同意后,系统会记录,下次再判断权限时就会返回已授权状态;当App卸载时,记录会被清除。

4、重写函数,处理授权弹窗的结果

直接在Activity或Fragment中重写onRequestPermissionsResult()函数,来处理权限申请结果。requestPermissions()的第三个参数,将在这里被用到。代码如下:

  1. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  2. if (requestCode == PERMISSIONS_REQUEST_READ_CONTACTS) {
  3. if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
  4. //todo
  5. } else {
  6. //todo
  7. }
  8. }
  9. }

如何开发一个App(Android)

前言
本篇博客从开发的角度来介绍如何开发一个Android App,需要说明一点是,这里只是提供一个如何开发一个app的思路,并不会介绍很多技术上的细节,从整个大局去把握如何去构思一个app的开发,让你对独立开发一款app的时候有个理解,如果有说的不对的地方,欢迎大家拍砖留言讨论。

开发环境
Android应用层使用的开发语言是Java,自然需要用到Java运行环境,无论你在Window是系统还是Mac系统都需要安装JDK,并且配置它的环境变量,不懂什么叫环境变量的或者不知道怎么配的,请利用好自己的百度技能自行学习。

%title插图%num

开发工具
开发工具,Android因为历史原因,前面很长一段时间使用的是Eclipse,我们要开发Android应用的时候需要另外安装Google为它开发的插件ADT,Eclipse这个开源软件很优秀,也很专业,但仅仅只是使用它来开发Android就显得有点鸡肋了,如果没有对Eclipse进行优化的话,会很卡很卡,后面Google实在不愿意寄人篱下,就专门为我们开发者开发了目前很受广大开发者推崇的Android Studio,现在2.0预览版也出来啦,大伙赶紧去下吧。

%title插图%num

模拟器
Android自带的模拟器一直广受诟病,实在是太卡太慢了,对比人家IOS模拟器是那么的流畅,Android开发者直喊好苦逼啊,不过还好后面出了第三方比原生流畅n倍的模拟器Genymotion,大家可以去下一个个人版的够你平时开发测试用了。*好的办法还是使用真机测试,毕竟真机才是真实的环境。

%title插图%num

Android核心基础
前面是准备工作,想开发一款Android app,你*起码要掌握Android的核心基础知识,针对初学Android的童鞋,想一口吃掉一个胖子是不可能的,还是得把基础打牢之后才能完成独立的开发,Android入门不难,学完基础你只是具备开发app的基本条件,想做一个优秀的app还有很长一段路要走,经验是靠不断实践堆出来的,找一套系统的课程去认真学习一下,在线教育的资源很多,比如慕课网,*客学院都有很多不错的学习资源,童鞋自己择优来学习即可。

推广:http://edu.csdn.net/course/detail/545(笔者的课程)

产品开发流程
正常的互联网开发app的流程大致如下:
– 产品规划,定产品方向
– 需求调研,产出需求文档
– 需求评审,修订需求文档
– 产品狗画app线框图提供给射鸡师
– 射鸡师根据线框图设计视觉稿
– 程序猿根据视觉稿搭建UI框架
– 程序猿根据需求文档开发功能
– 测试媛编写测试用例,根据排期进行测试
– 程序猿修复回归测试反馈的bug,提交beta版
– 测试通过,提交给运营喵发布到渠道上线

上面是笔者的经验总结,可能有不太准确的地方,但大致整个流程是这样,我们开发者要清楚自己在整个产品中充当的角色,明白自己的工作职责即可。

快速搭建项目
Android比没有想象那么难,只是刚开始要学习的东西多,一下子消化不了才会比较茫然,笔者写这篇博客就是想帮助大家整理一下开发思路。

快速搭建项目也算是一项技能,而这项技能只能由你自己来完善,刚开始做开发的时候一定会有很多重复性的工作,如果没有意识去提高自己的开发效率的话,可能你的十年工作经验代表的只是一年的经验用了十年而已。

那要怎么做,笔者提供一个自己总结的,仅供参考:
– 定开发规范
– 搭建UI框架
– 选用开发库集成(或自造轮子)
– 第三方服务集成(视情况而定)

定开发规范
一个项目一般不会只有一个人独立开发,就算是独立开发,我们也应该定一下开发规范,一般会有什么样的规范?
– 命名规范
– 代码规范
– 代码管理规范

命名规范
命名规范包括:
– 项目命名
– 包命名
– 类命名、成员变量命名
– 资源文件命名
我们做每一个产品都会有相应的命名,不要用中文来命名,*好用能表达中文意思的英文来表示,例如CSDN客户端,可以用CSDNClient作为命名,我们创建项目的时候可以以它作为Application name。
可以看看以前写的这篇文章:
http://blog.csdn.net/wwj_748/article/details/42347283

代码规范
代码规范视语言而定,开发android使用的是Java,自然要按照Java的编码规范进行编码,比如命名采用驼峰命名法,编码的时候也要符合Android的开发规范,比如UI线程不做任何耗时的操作,像网络请求、数据库操作都需要放到子线程中去做,只有UI的刷新才在UI线程中做,像这些规范,可以根据自身在项目遇到的问题来定,定规范的好处就是减少踩坑的几率,提高开发的质量。

代码管理
对于一个经常更新迭代的产品,不可能由头到尾不变,这个时候我们需要对代码进行分支管理,*好使用git代码仓库对代码进行管理,作为一个合格的开发者连git都不用实在说不过去,还用svn的童鞋赶紧放弃它投入git的怀抱,它的好处是不言而喻的,自己去体会。

搭建UI框架
搭建UI框架需要我们根据产品的导航模式来设计,市场上常用的导航模式有如下图几种:

%title插图%num

我们的app如果不出意外一定是其中的一种导航模式,一般线框图出来我们就应该知道即将要开发的app长什么样子,开发者不必等视觉稿和素材出来才开始动工,我们先大致搭个架子,等视觉稿出来之后我们再做调整。

选用开发库
一般我们app涉及到的库会有:
– UI框架(比如下拉刷新PullToRefresh、侧滑菜单Slidingmenu)
– 网络请求库(比如okhtttp、AndroidAsyncHttp、Volley)
– 数据操作库(比如GreenDao、Ormlite)
– 图片缓存框架(比如Universal-Imageloader)
– 数据解析库(比如Gson)

之所以要选用这些库,肯定是为了避免重复造轮子,在开发效率的角度来说,选用优秀的开源库能大大缩短开发周期和提高开发效率,但从个人提升角度来看的话,我们可能就成了一个只会用API的程序猿了,如果想提升的话,造轮子或者分析这些优秀的源代码是一个不错的途径。

第三方服务集成
我们开发app的时候,肯定会遇到一些需求,比如推送的需求、自动升级、数据统计、社会化分享、用户反馈等等,然而对于一个刚起步的企业或者个人开发者的话,全都要自己去开发的话,那岂不是累死,像推送这种有一定的技术门槛,能做好都能成立一家公司了,所以选用一些第三方服务是一个可选之举。如果说你以后做大了,用第三方怕不好控制,那就自己做呗,有钱任性招兵买马就自己做,谁叫咱有钱呢。

前面这些东西开发一个app够了,开发出来能不能用还得有靠谱的测试,有没有crash,操作流不流畅,体验好不好才会有用户去用。这里不从产品的角度去评判一个app的好与坏,程序员要考虑的是从代码层面、性能层面去让我们的app变得更好。

云测
我们开发完毕之后,需要给测试工程师进行基本的功能需求测试,他们传统的做法就是根据事先写好的测试用例来做回归测试,再把测试出来的bug反馈给工程师,工程师再去修bug,但这样实在是太不靠谱了,有时候我们太在意功能而忽略了一些更重要的东西,那就是体验,给用户*直接的感受就是你这个app够不够惊艳,够不够流畅,用户可能根本就不在乎你这个功能做的有多牛逼。所以我们更应该从非功能性方向去做测试,我们的目的是让用户用的爽,而不是加一些乱七八糟的功能。那怎么测非功能性的一些因素,这里就要提到『云测』这个东西,因为现在设备太多了,如果公司要买一堆设备来做测试,那得多少成本,况且设备更新得太快,你根本就跟不上,所以就有了云测这个东西,它是一个云测试平台服务,提供了一大批主流机型,我们就直接省去购买设备的成本,还能得到完善的测试报告。

再来说一下它的好处:
– 终端云,省去测试设备购买租赁成本
– 高效率 节省测试人员成本及时间
– 包含兼容性测试、性能测试、功能测试
– 操作简单、详细测试报告生成

这么多好处,你在缺少测试工程师的时候,不去尝试那实在说不过去。

打包上线
前面的开发环节、测试环节都没问题之后,你离实现一个app的完整开发就不远了,正常的互联网公司,会把签名打包的apk给到运营,交给他们去写文案,上传到应用渠道,让渠道给我们去首发和推广。如果是个人开发者,这些工作就得我们自己做了。

总结
本篇博客从整个app开发流程上去给大家梳理了一遍,没有讲太多技术上的东西,但理解app流程对自己把握产品很有益处,虽然我们只是一个小小的开发者,但如果你有追求,哪天轮到你去负责一个产品的时候,你就应该对开发的每一个环节都了如指掌,因为出了问题,你也知道怎么针对性的去解决。笔者虽然只是一个小小的开发者,但也乐于思考,很多时候不太愿意被别人牵着鼻子走,因为我们的人生就应该把握在自己手里。

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