日期: 2021 年 5 月 15 日

检测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);
        }
    }
}

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

小米、华为Android10开机自己启动你的应用,需要手动改配置

App开机自启动:
一、AndroidManifest:

<?xml version=”1.0″ encoding=”utf-8″?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”com.safe.test”
android:installLocation=”internalOnly”>
<uses-permission android:name=”android.permission.CAMERA” /> <!– 在SDCard中创建与删除文件权限 –>
<uses-permission android:name=”android.permission.READ_PHONE_STATE” />
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE” /> <!– 从SDCard读取数据权限 –>
<uses-permission android:name=”android.permission.READ_EXTERNAL_STORAGE” />
<uses-permission android:name=”android.permission.CHANGE_WIFI_STATE” />
<uses-permission android:name=”android.permission.ACCESS_WIFI_STATE” />
<uses-permission android:name=”android.permission.ACCESS_COARSE_LOCATION” />
<uses-permission android:name=”android.permission.INTERNET”/>
<uses-permission android:name=”android.permission.READ_CALENDAR” />
<uses-permission android:name=”android.permission.WRITE_CALENDAR” />
<uses-permission android:name=”android.permission.RECEIVE_BOOT_COMPLETED”/>
<uses-permission android:name=”android.permission.SYSTEM_ALERT_WINDOW”/>
<uses-permission android:name=”android.permission.SYSTEM_OVERLAY_WINDOW”/>
<application
android:allowBackup=”true”
android:icon=”@mipmap/ic_launcher”
android:label=”@string/app_name”
android:roundIcon=”@mipmap/ic_launcher_round”
android:supportsRtl=”true”
android:theme=”@style/AppTheme”>
<activity
android:name=”.ui.login.LoginActivity”
android:label=”@string/app_name”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
<!–下面这个包名要与com.safe.test.ui.login.MyReceiver这个包名与你应用中要一致–>
<receiver
android:name=”com.safe.test.ui.login.MyReceiver”
android:enabled=”true”
android:exported=”true”>
<intent-filter android:priority=”1000″>
<action android:name=”android.intent.action.BOOT_COMPLETED”></action>
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</receiver>
</application>
</manifest>
二、MyReceiver.java类,放在与Activity相同路径下,android:name=”com.safe.test.ui.login.MyReceiver”

 

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

public class MyReceiver extends BroadcastReceiver
{
public MyReceiver()
{
}

@Override
public void onReceive(Context context, Intent intent)
{
Log.e(“MyReceiver”,”onReceive:”+intent.getAction().toString());
if (intent.getAction().equals(“android.intent.action.BOOT_COMPLETED”))
{

Intent thisIntent = new Intent(context, LoginActivity.class);//设置要启动的app
thisIntent.setAction(“android.intent.action.MAIN”);
thisIntent.addCategory(“android.intent.category.LAUNCHER”);
thisIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(thisIntent);
Log.e(“MyReceiver”,”startActivity:”+LoginActivity.class.toString());

}
}
}
三、代码已经添加完成,生下的就是手动启动开机自动运行权限了,

对于小米10来说 设置-》应用设置-》授权管理-》自启动管理 中打开你的应用

%title插图%num

然后,还有设置-》应用设置-》授权管理-》应用权限管理中

%title插图%num
后台弹出界面要打开

%title插图%num

然后,重启动系统,输入解锁屏幕密码后就可以看到应用自动运行了。

 

对于华为android 10来说,在设置中搜 “启动”关键字,进入启动管理

选择你的应用,设为手机,把下面三个开关开开即可。重启后就能自动运行了。

%title插图%num

Android10开机启动和网络

Android10开机启动和网络 参数设置 华为EMUI11

开机启动

1、需要请求忽略电池优化

<uses-permission android:name=”android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS”/>
@RequiresApi(api = Build.VERSION_CODES.M)
public void ignoreBatteryOptimization() {
try{
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
boolean hasIgnored = powerManager.isIgnoringBatteryOptimizations(this.getPackageName());
if(!hasIgnored) {
Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
intent.setData(Uri.parse(“package:”+getPackageName()));
startActivity(intent);
}
}catch(Exception e){
//TODO :handle exception
}
}
2、在 设置里面,打开《应用后台启动、等信息 全部开启》

%title插图%num

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

3、添加开机启动权限

<uses-permission android:name=”android.permission.RECEIVE_BOOT_COMPLETED” />

<receiver android:name=”.ppBroadcastReceiver”>
<intent-filter android:priority=”2147483647″>
<action android:name=”android.intent.action.BOOT_COMPLETED” />
</intent-filter>
</receiver>
请求网络服务

需要在工程路径res/xml路径存储  network_security_config.xml  文件 配合下面代码段观看

%title插图%num

<?xml version=”1.0″ encoding=”utf-8″?>
<network-security-config>
<base-config cleartextTrafficPermitted=”true” />
</network-security-config>
 

下面是详细配置的XML

 

<?xml version=”1.0″ encoding=”utf-8″?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”com.***.***.***”>

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

<application
android:allowBackup=”true”
android:icon=”@mipmap/ic_launcher_round”
android:label=”@string/app_name”
android:roundIcon=”@mipmap/ic_launcher_round”
android:supportsRtl=”true”
android:requestLegacyExternalStorage=”true”
android:networkSecurityConfig=”@xml/network_security_config”
android:theme=”@style/Theme.**”>
<activity android:name=”.MainActivity”>
<intent-filter>
<action android:name=”android.intent.action.MAIN” />

<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>

<receiver android:name=”.ppBroadcastReceiver”>
<intent-filter android:priority=”2147483647″>
<action android:name=”android.intent.action.BOOT_COMPLETED” />
</intent-filter>
</receiver>

<service android:name=”.ppService”>
</service>

</application>

</manifest>

Android* 的唤醒锁

Android 电源管理 — 唤醒锁全新上市

大多数人可能都遭遇过手机的电池续航时间较短带来的尴尬。 这*其令人讨厌。 没电的手机和一块水泥砖没什么差别。 一般而言,如果用户的手机电池无法持续一整天,他们会感到非常不满。而且,当手机充电时用户无法使用手机,这同样会带来*大的不便。

传统上需要使用笔记本电脑或 PC 处理的任务,现在借助全新的改进软件,即使未在桌旁也可使用智能手机进行处理。 但是相比笔记本电脑,智能手机的小外形很大地限制了可容纳的电池尺寸。 既要求手机具备笔记本电脑的功能,同时又要求其具备全天候的电池续航能力是难以实现的。

通过采用出色的电源管理,Android 和其他移动操作系统实现了耐久的电池续航时间。 停止使用手机后不久,显示器便会关闭,CPU 会进入深度节能状态,因此在不使用它时仅会消耗*少的电源。 这就是电话在使用时充一次电便能持续使用多日的原因。 借助 Android 的电源管理器,正常计划是显示器关闭时 CPU 也关闭。

但是,Android 开发人员能够(并有权限)阻止 Android 设备进入睡眠模式。 他们可能希望让 CPU 处于活动状态 — 即使显示器关闭。 或者可能他们希望在执行某项活动时阻止显示器自动关闭。 出于此原因,Google* 在其 PowerManager API 中增加了唤醒锁。 阻止设备进入睡眠模式的应用可以使用唤醒锁。 只要系统上有活动的唤醒锁,设备便无法进入挂起模式,除非释放唤醒锁。 使用唤醒锁时,一定要了解到当您不需要唤醒锁时,必须将其正确释放,因为未释放的唤醒锁无法进入默认状态以节能,从而很快便会将设备的电池耗尽。

本文将会为您介绍一些在 Android 4.0 中默认使用唤醒锁的 Android 应用(和使用模式),以便帮助您了解何时使用此项技术。 然后,将会介绍一个 SDPSamples 集中的示例应用“Wakelocks”,以展示如何编写唤醒锁的代码。

唤醒锁应用使用情况

借助 Android, 有一种方法可以查看哪些服务启用了唤醒锁,阻止系统进入低功耗状态。 设备上的 /proc/wakelocks 文件列出了定义使用唤醒锁的服务和驱动程序。 通过监控 /sys/power/wake_lock 文件的内容(需要根访问),您可以了解 CPU 资源何时启用了唤醒锁,以及哪种服务启用了 wakelock2。 我捕获了几种使用案例,其中运行 Android 4.0 的 Galaxy Nexus 上启用了唤醒锁,如下所示:

使用的应用 执行的操作 使用了唤醒锁的服务 运行状况
任意 按下 UI Widget(如点击按钮或 ListView 项) PowerManagerService 启用并在 5 秒钟后释放锁定
地图/导航 启用地图或进入导航 gps-lock 启用锁定并使用 GPS
YouTube 观看流视频 PowerManagerService 在视频播放的整个过程中一直启用唤醒锁
Music 听音乐 PowerManagerService 在音乐播放的过程中一直启用唤醒锁

表格:一些默认的 Android 应用演示唤醒锁的使用

YouTube 和 Music 应用能够很好地展示不同级别的唤醒锁。 用户播放视频时,YouTube 应用将会启用唤醒锁。 在播放视频的整个过程中,显示器会保持开启状态(忽略系统的显示设置)。 但是,如果用户在播放过程中按下了电源按钮,设备将会挂起,这会导致显示器关闭以及音频/视频停止播放。 Music 应用在播放音频时使用不同的唤醒锁。 显示设置无法更改,因此设备的屏幕将会根据用户的显示设置来关闭。 显示器关闭后,唤醒锁会让 CPU 保持活动状态以便音频能够继续播放 — 即使用户按下了电源按钮。

选择唤醒锁(在执行前)

在了解如何编写唤醒锁之前,一定要了解唤醒锁的种类,以便为您的应用挑选*适合的唤醒锁。 Android PowerManager API 介绍了多种用于更改设备电源状态的唤醒锁标记:

标记值 CPU 屏幕 键盘
PARTIAL_WAKE_LOCK 开启 关闭 关闭
SCREEN_DIM_WAKE_LOCK 开启 调暗(Dim) 关闭
SCREEN_BRIGHT_WAKE_LOCK 开启 调亮(Bright) 关闭
FULL_WAKE_LOCK 开启 调亮(Bright) 调亮(Bright)

表格: 源自 Android PowerManager API。

该 API 突出强调了唤醒锁会显著缩短 Android 设备的电池续航时间,因此如果可以避免应尽量减少使用它们。 如果使用,也应尽快将其释放。

使用唤醒锁的应用必须申请特别许可才可执行。 这可通过应用清单文件中的 android.permission.WAKE_LOCK 许可来实现。 也就是说,当用户通过 Google Play 安装使用唤醒锁的应用时,系统会提醒他们该应用包含的特性可能会“Prevent phone from sleeping(阻止手机进入睡眠状态)”。 如果开发人员希望阻止某个应用的显示器在特定使用情况下变暗,可采用 Google 另外提供的一种方法,这种方法无需特别许可。 WindowManager 包括一个 FLAG_KEEP_SCREEN_ON 变量,当应用的视图要阻止界面关闭时可对其进行设置。 建议在显示控制上使用这种方法,因为其影响在应用内相互独立。 用户任务切换到其他应用上之后,WindowManager 将会立刻释放唤醒锁。

让显示器保持开启状态(源自 SDPSamples)

SDPSamples 集的唤醒锁应用演示了(包括代码)应用如何使用 Window Manager 而非通过编写唤醒锁代码让显示器保持开启状态。 启用唤醒锁应用后,选择“Win Man Screen On”列表项。

 

 

只要按钮的状态显示“Screen is LOCKED”,界面就会保持开启状态。 按钮的状态更改为“Screen is UNLOCKED”后,如果超过 5 秒钟不操作,显示器将会关闭。

在代码中,每次按下按钮且状态发生改变时,通过设置和释放当前窗口的 FLAG_KEEP_SCREEN_ON 变量可在 WakeLockActivity.java 中使用 screenLockUpdateState() 函数实现该操作。

public void screenLockUpdateState() {

    if (mIsDisplayLocked)
     {
        ...
        // update display state
      getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    } else 
{
        ...
        // update display state
          getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
    }
} 

 

编写唤醒锁(来自 SDPSamples)

SDPSamples 集中的唤醒锁应用还包括执行不同唤醒锁的代码。 启动唤醒锁应用后,从下列四种唤醒锁中选择: Power Wake Lock Full、Power Wake Lock Bright、Power Wake Lock Dim 和 Power Wake Lock Partial。 这 4 个列表项对应着 PowerManager API 中介绍的 4 种唤醒锁标记。 每一项将会分别演示 5 秒内尝试关闭屏幕时设备如何响应。

 

通过监控 /sys/power/wake_lock 文件的内容(需要根访问),您可以看到按下电源按钮后 PARTIAL_WAKE_LOCK 是唯一仍然有效的唤醒锁。 其他的唤醒锁通过多种级别的亮度阻止显示器关闭。

编写唤醒锁的*步是申请许可,之后才可使用清单 AndroidManifest.xml 内的唤醒锁:

 

这一步完成后,便可以创建唤醒锁对象,包括控制唤醒锁的获取()函数和释放()函数。 WakeLockActivity.java 文件内的编码很好地展示了该情况:

public void onCreate(Bundle savedInstanceState) {
    ...
    mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
    ...
    mWakeLock = mPowerManager.newWakeLock(mWakeLockState,
                        "UMSE PowerTest");
    if (mWakeLock != null) {
        mWakeLock.acquire();
    ... 
    }
}

protected void onDestroy() {
    if (mWakeLock != null) {
        mWakeLock.release();
        mWakeLock = null;
    }
    ... 
}

 

结论

唤醒锁是 Android 中一款强大的概念,可支持开发人员修改设备的默认电源状态。 在应用中使用唤醒锁的风险:它会减少设备的电池续航时间。 使用唤醒锁的明显优点可在 Google 提供的多种默认应用中看得到,如道路导航和音乐/视频播放。 因此,开发人员应自行确定他们的应用模型能否从使用唤醒锁中得益。

关于作者

Christopher Bird 于 2007 年进入英特尔的 SSG,致力于构建支持凌动设备(手机和平板电脑)的生态系统

参考

1 Android 参考: http://developer.android.com/reference/android/os/PowerManager.html

2 LWN – “唤醒锁和嵌入式问题”: http://lwn.net/Articles/318611/

怎么让 Android 程序一直后台运行

怎么让 Android 程序一直后台运行,像 QQ 一样不被杀死?

强烈建议不要这么做,不仅仅从用户角度考虑,作为Android开发者也有责任去维护Android的生态环境。现在很多Android开发工程师,主力机居然是iPhone而不是Android设备,感到相当悲哀。

从技术角度概括一下现在普遍的防杀方法

  1. Service设置成START_STICKY,kill 后会被重启(等待5秒左右),重传Intent,保持与重启前一样
  2. 通过 startForeground将进程设置为前台进程,做前台服务,优先级和前台应用一个级别,除非在系统内存非常缺,否则此进程不会被 kill
  3. 双进程Service:让2个进程互相保护,其中一个Service被清理后,另外没被清理的进程可以立即重启进程
  4. QQ黑科技:在应用退到后台后,另起一个只有 1 像素的页面停留在桌面上,让自己保持前台状态,保护自己不被后台清理工具杀死
  5. 在已经root的设备下,修改相应的权限文件,将App伪装成系统级的应用(Android4.0系列的一个漏洞,已经确认可行)
  6. Android系统中当前进程(Process)fork出来的子进程,被系统认为是两个不同的进程。当父进程被杀死的时候,子进程仍然可以存活,并不受影响。鉴于目前提到的在Android-Service层做双守护都会失败,我们可以fork出c进程,多进程守护。死循环在那检查是否还存在,具体的思路如下(Android5.0以下可行)
    1. 用C编写守护进程(即子进程),守护进程做的事情就是循环检查目标进程是否存在,不存在则启动它。
    2. 在NDK环境中将1中编写的C代码编译打包成可执行文件(BUILD_EXECUTABLE)。
    3. 主进程启动时将守护进程放入私有目录下,赋予可执行权限,启动它即可。
  7. 联系厂商,加入白名单

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—-点击应用启动管理—-关闭自动管理—-打开允许后台活动

Android 性能优化之String篇

关于String相关知识都是老掉牙的东西了,但我们经常可能在不经意的String 字符串拼接的情况下浪费内存,影响性能,也常常会成为触发内存OOM的*后一步。
所以本文对String字符串进行深度解析,有助于我们日常开发中提高程序的性能,解决因String 而导致的性能问题。

首先我们先回顾一下String类型的本质

String类型的本质
先看一下String的头部源码

/** Strings are constant; their values cannot be changed after they
* are created. String buffers support mutable strings.
* Because String objects are immutable they can be shared.
* @see StringBuffer
* @see StringBuilder
* @see Charset
* @since 1.0
*/
public final class String implements Serializable, Comparable<String>, CharSequence {

private static final long serialVersionUID = -6849794470754667710L;

private static final char REPLACEMENT_CHAR = (char) 0xfffd;

打开String的源码,类注释中有这么一段话“Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings.Because String objects are immutable they can be shared.”。

这句话总结归纳了String的一个*重要的特点:

String是值不可变(immutable)的常量,是线程安全的(can be shared)。
接下来,String类使用了final修饰符,表明了String类的第二个特点:String类是不可继承的。

String类表示字符串。java程序中的所有字符串,如“ABC”,是实现这个类的实例

字符串是常量,它们的值不能被创建后改变。支持可变字符串字符串缓冲区。因为字符串对象是不可改变的,所以它们可以被共享。例如:

String str = “abc”;

相当于

String s = new String(“abc”);

这里实际上创建了两个String对象,一个是”abc”对象,存储在常量空间中,一个是使用new关键字为对象s申请的空间,存储引用地址。

在执行到双引号包含字符串的语句时,JVM会先到常量池里查找,如果有的话返回常量池里的这个实例的引用,否则的话创建一个新实例并置入常量池里,如上面所示,str 和 s 指向同一个引用.

String的定义方法归纳起来总共为以下四种方式:

直接使用”“引号创建;
使用new String()创建;
使用new String(“abcd”)创建以及其他的一些重载构造函数创建;
使用重载的字符串连接操作符+创建。
常量池
在讨论String的一些本质,先了解一下常量池的概念java中的常量池(constant pool)技术,是为了方便快捷地创建某些对象而出现的,当需要一个对象时,就可以从池中取一个出来(如果池中没有则创建一个),则在需要重复重复创建相等变量时节省了很多时间。常量池其实也就是一个内存空间,不同于使用new关键字创建的对象所在的堆空间。
在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。常量池还具备动态性(java.lang.String.intern()),运行期间可以将新的常量放入池中。

常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。

java中基本类型的包装类的大部分都实现了常量池技术,
即Byte,Short,Integer,Long,Character,Boolean;

Java String对象和字符串常量的关系?
JAVA中所有的对象都存放在堆里面,包括String对象。字符串常量保存在JAVA的.class文件的常量池中,在编译期就确定好了。

比如我们通过以下代码块:

String s = new String( “myString” );

其中字符串常量是”myString”,在编译时被存储在常量池的某个位置。在运行阶段,虚拟机发现字符串常量”myString”,它会在一个内部字符串常量列表中查找,如果没有找到,那么会在堆里面创建一个包含字符序列[myString]的String对象s1,然后把这个字符序列和对应的String对象作为名值对( [myString], s1 )保存到内部字符串常量列表中。如下图所示:

%title插图%num

如果虚拟机后面又发现了一个相同的字符串常量myString,它会在这个内部字符串常量列表内找到相同的字符序列,然后返回对应的String对象的引用。维护这个内部列表的关键是任何特定的字符序列在这个列表上只出现一次。

例如,String s2 = “myString”,运行时s2会从内部字符串常量列表内得到s1的返回值,所以s2和s1都指向同一个String对象。但是String对象s在堆里的一个不同位置,所以和s1不相同。

JAVA中的字符串常量可以作为String对象使用,字符串常量的字符序列本身是存放在常量池中,在字符串内部列表中每个字符串常量的字符序列对应一个String对象,实际使用的就是这个对象。

这个目前网上阐述的*多关于这个String对象和字符串常量的关系,网上各有说法,但是这个猜想也是有问题的
引自感谢博主
http://blog.csdn.net/sureyonder/article/details/5569366

String 在 JVM 的存储结构
String 在 JVM 的存储结构
一般而言,Java 对象在虚拟机的结构如下:
对象头(object header):8 个字节
Java 原始类型数据:如 int, float, char 等类型的数据,各类型数据占内存如 表 1. Java 各数据类型所占内存.
引用(reference):4 个字节
填充符(padding)

如果对于 String(JDK 6)的成员变量声明如下:

private final char value[];
private final int offset;
private final int count;
private int hash;

JDK6字符串内存占用的计算方式:

首先计算一个空的 char 数组所占空间,在 Java 里数组也是对象,因而数组也有对象头,故一个数组所占的空间为对象头所占的空间加上数组长度,即 8 + 4 = 12 字节 , 经过填充后为 16 字节。

那么一个空 String 所占空间为:

对象头(8 字节)+ char 数组(16 字节)+ 3 个 int(3 × 4 = 12 字节)+1 个 char 数组的引用 (4 字节 ) = 40 字节。

因此一个实际的 String 所占空间的计算公式如下:

8*( ( 8+12+2*n+4+12)+7 ) / 8 = 8*(int) ( ( ( (n) *2 )+43) /8 )

其中,n 为字符串长度。

String 方法很多时候我们移动客户端常用于文本分析及大量字符串处理,
比如高频率的拼接字符串,Log日志输出,会对内存性能造成一些影响。可能导致内存占用太大甚至OOM。
频繁的字符串拼接,使用StringBuffer或者StringBuilder代替String,可以在一定程度上避免OOM和内存抖动。

String 一些提高性能方法
String的contact()方法
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
char buf[] = new char[count + otherLen];
getChars(0, count, buf, 0);
str.getChars(0, otherLen, buf, count);
return new String(0, count + otherLen, buf);
}

这是concat()的源码,它看上去就是一个数字拷贝形式,我们知道数组的处理速度是非常快的,但是由于该方法*后是这样的:return new String(0, count + otherLen, buf);这同样也创建了10W个字符串对象,这是它变慢的根本原因。

String的intern()方法
当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。

例如:

“abc”.intern()方法的返回值还是字符串”abc”,表面上看起来好像这个方法没什么用处。但实际上,它做了个小动作:
检查字符串池里是否存在”abc”这么一个字符串,如果存在,就返回池里的字符串;如果不存在,该方法会把”abc”添加到字符串池中,然后再返回它的引用。

String s1 = new String(“111”);
String s2 = “sss111”;
String s3 = “sss” + “111”;
String s4 = “sss” + s1;
System.out.println(s2 == s3); //true
System.out.println(s2 == s4); //false
System.out.println(s2 == s4.intern()); //true

过多得使用 intern()将导致 PermGen 过度增长而*后返回 OutOfMemoryError,因为垃圾收集器不会对被缓存的 String 做垃圾回收,所以如果使用不当会造成内存泄露。

关于截取字符串方法的性能比较
对于从大文本中截取少量字符串的应用,String.substring()将会导致内存的过度浪费。
对于从一般文本中截取一定数量的字符串,截取的字符串长度总和与原始文本长度相差不大,现有的 String.substring()设计恰好可以共享原始文本从而达到节省内存的目的。
更多详细比较请查看这篇博文
http://blog.csdn.net/songylwq/article/details/9016609

使用StringBuilder 提高性能
在拼接动态字符串时,尽量用 StringBuffer 或 StringBuilder的 append,这样可以减少构造过多的临时 String 对象。但是如何正确的使用StringBuilder呢?

初始合适的长度
StringBuilder继承AbstractStringBuilder,打开AbstractStringBuilder的源码

/**
* A modifiable {@link CharSequence sequence of characters} for use in creating
* and modifying Strings. This class is intended as a base class for
* {@link StringBuffer} and {@link StringBuilder}.
*
* @see StringBuffer
* @see StringBuilder
* @since 1.5
*/
abstract class AbstractStringBuilder {

static final int INITIAL_CAPACITY = 16;

private char[] value;

private int count;

private boolean shared;
}
我们可以看到
StringBuilder的内部有一个char[], 不断的append()就是不断的往char[]里填东西的过程。
new StringBuilder(),并且 时char[]的默认长度是16,

private void enlargeBuffer(int min) {
int newCount = ((value.length >> 1) + value.length) + 2;
char[] newData = new char[min > newCount ? min : newCount];
System.arraycopy(value, 0, newData, 0, count);
value = newData;
shared = false;
}

然后如果StringBuilder的剩余容量,无法添加全部内容,如果要append第17个字符,怎么办?可以看到enlargeBuffer函数,用System.arraycopy成倍复制扩容!导致内存的消耗,增加GC的压力。
这要是在高频率的回调或循环下,对内存和性能影响非常大,或者引发OOM。

同时StringBuilder的toString方法,也会造成char数组的浪费。

public String toString() {
// Create a copy, don’t share the array
return new String(value, 0, count);
}

我们的优化方法是StringBuilder在append()的时候,不是直接往char[]里塞东西,而是先拿一个String[]把它们都存起来,到了*后才把所有String的length加起来,构造一个合理长度的StringBuilder。

重用的StringBuilder
/**
* 参考BigDecimal, 可重用的StringBuilder, 节约StringBuilder内部的char[]
*
* 参考下面的示例代码将其保存为ThreadLocal.
*
* <pre>
* private static final ThreadLocal<StringBuilderHelper> threadLocalStringBuilderHolder = new ThreadLocal<StringBuilderHelper>() {
*  @Override
*  protected StringBuilderHelper initialValue() {
*      return new StringBuilderHelper(256);
*  }
* };
*
* StringBuilder sb = threadLocalStringBuilderHolder.get().resetAndGetStringBuilder();
*
* </pre>
*/
public class StringBuilderHolder {

private final StringBuilder sb;

public StringBuilderHolder(int capacity) {
sb = new StringBuilder(capacity);
}

/**
* 重置StringBuilder内部的writerIndex, 而char[]保留不动.
*/
public StringBuilder resetAndGetStringBuilder() {
sb.setLength(0);
return sb;
}
}

这个做法来源于JDK里的BigDecimal类

Log真正需要时候做拼接
对于那些需要高频率拼接打印Log的场景,封装一个LogUtil,来控制日志在真正需要输出时候才去做拼接。比如:

public void log(String  msg ){
if (BuildConfig.DEBUG){
Log.e(“TAG”,”Explicit concurrent mark sweep ” +
“GC freed 10477(686KB) AllocSpace objects, 0(0B) ” +
“LOS objects, 39% free, 9MB/15MB, paused 915us total 28.320ms”+msg);
}
}

总结几个简单题目
String s1 = new String(“s1”) ;
String s2 = new String(“s1”) ;

上面创建了几个String对象?

答案:3个 ,编译期Constant Pool中创建1个,运行期heap中创建2个.

String s1 = “s1”;
String s2 = s1;
s2 = “s2”;

s1指向的对象中的字符串是什么?

答案: “s1”

总结
关于String 性能优化,了解String 在 JVM 中的存储结构,String 的 API 使用可能造成的性能问题以及解决方法,就总结到这。若有错漏,欢迎补充。

Mac将本地文件上传到服务器上

Mac将本地文件上传到服务器上

scp -P 22 /Java/program.war root@123.456.789.987:/usr/server/tomcat7/webapps/

上端口大写P 为参数,22 表示更改SSH端口后的端口,如果没有更改SSH端口可以不用添加该参数。 /Java/program.war表示本地上准备上传文件的路径和文件名。root@123.456.789.987 表示使用root用户登录远程服务器www.vpser.NET,:/usr/server/tomcat7/webapps/ 表示保存在远程服务器上目录和文件名。

以上是本人实践,以下是教程指令

Linux(MAC) SSH远程文件/目录传输命令scp
相 信各位VPSer在使用VPS时会经常在不同VPS间互相备份数据或者转移数据,大部分情况下VPS上都已经安装了Nginx或者类似的web server,直接将要传输的文件放到web server的目录,然后在目标机器上执行:wget http://www.vpser.net/testfile.zip 就行了。当VPS上没有安装web server和ftp server的时候或感觉上面的方法比较麻烦,那么用scp命令就会排上用场。

一、scp是什么?

scp是secure copy的简写,用于在linux下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进行拷贝不能跨服务器,而且scp传输是加密的。可能会稍微影响一下速度。

二、scp有什么用?

1、我们需要获得远程服务器上的某个文件,远程服务器既没有配置ftp服务器,没有开启web服务器,也没有做共享,无法通过常规途径获得文件时,只需要通过scp命令便可轻松的达到目的。

2、我们需要将本机上的文件上传到远程服务器上,远程服务器没有开启ftp服务器或共享,无法通过常规途径上传是,只需要通过scp命令便可以轻松的达到目的。

三、scp使用方法

1、获取远程服务器上的文件

scp -P 2222 root@www.vpser.net:/root/lnmp0.4.tar.gz /home/lnmp0.4.tar.gz

上 端口大写P 为参数,2222 表示更改SSH端口后的端口,如果没有更改SSH端口可以不用添加该参数。 root@www.vpser.Net 表示使用root用户登录远程服务器www.vpser.net,:/root/lnmp0.4.tar.gz 表示远程服务器上的文件,*后面的/home/lnmp0.4.tar.gz表示保存在本地上的路径和文件名。

2、获取远程服务器上的目录

scp -P 2222 -r root@www.vpser.net:/root/lnmp0.4/ /home/lnmp0.4/

上 端口大写P 为参数,2222 表示更改SSH端口后的端口,如果没有更改SSH端口可以不用添加该参数。-r 参数表示递归复制(即复制该目录下面的文件和目录);root@www.vpser.net 表示使用root用户登录远程服务器www.vpser.net,:/root/lnmp0.4/ 表示远程服务器上的目录,*后面的/home/lnmp0.4/表示保存在本地上的路径。

3、将本地文件上传到服务器上

scp -P 2222 /home/lnmp0.4.tar.gz root@www.vpser.net:/root/lnmp0.4.tar.gz

上 端口大写P 为参数,2222 表示更改SSH端口后的端口,如果没有更改SSH端口可以不用添加该参数。 /home/lnmp0.4.tar.gz表示本地上准备上传文件的路径和文件名。root@www.vpser.net 表示使用root用户登录远程服务器www.vpser.net,:/root/lnmp0.4.tar.gz 表示保存在远程服务器上目录和文件名。

4、将本地目录上传到服务器上

scp -P 2222 -r /home/lnmp0.4/ root@www.vpser.net:/root/lnmp0.4/

上 端口大写P 为参数,2222 表示更改SSH端口后的端口,如果没有更改SSH端口可以不用添加该参数。-r 参数表示递归复制(即复制该目录下面的文件和目录);/home/lnmp0.4/表示准备要上传的目录,root@www.vpser.net 表示使用root用户登录远程服务器www.vpser.net,:/root/lnmp0.4/ 表示保存在远程服务器上的目录位置。

5、可能有用的几个参数 :

-v 和大多数 linux 命令中的 -v 意思一样 , 用来显示进度 . 可以用来查看连接 , 认证 , 或是配置错误 .

-C 使能压缩选项 .

-4 强行使用 IPV4 地址 .

-6 强行使用 IPV6 地址 .

Mac上传文件到远程服务器

Mac上传文件到远程服务器,可以利用mac自带的终端来进行上传,使用sftp方式

1.终端shell菜单-> 新建远程连接

%title插图%num
2 ,sftp方式,输入远程服务器ip

%title插图%num

3. 然后连接,连的时候会让输入密码,输入完成后,连上了会是下面这个样子

%title插图%num
ps1:如果还没有设置过密码,可以在远程服务器上先用命令设置下密码:

sudo passwd

ps2:如果遇到不能连接sftp被拒*了

错误:

Permission denied (publickey,gssapi-keyex,gssapi-with-mic)

那么可能是服务器的校验设置没对:

cat /etc/ssh/sshd_config

查看其中PasswordAuthentication值,比如:

PasswordAuthentication no

如果其为no,将其改为yes或者注释掉(默认值为 yes。),再重启ssh服务即可

/bin/systemctl restart sshd.service

4.往远程传文件
put 本地文件路径 远程服务器存储文件路径

%title插图%num
ps:传的是文件,不是文件夹,如果是文件夹可以压缩一下再传

5. 传完文件效果
终端远程连接服务器后查看前面传文件指定目录下,就能看到指定文件了:

%title插图%num

Mac 终端中上传文件到CentOS

操作示例
Mac将本地文件上传到CentOS服务器上:

scp /Users/xiao/Documents/Image/favicon.ico root@192.168.191.32:~
1
默认使用ssh的22端口进行上传的。

上传:

服务器上显示上传后的结果:

scp是什么?
scp是secure copy的简写,用于在Linux下进行远程拷贝文件的命令,和它类似的命令有cp,不过cp只是在本机进行拷贝不能跨服务器,而且scp传输是加密的。

scp有什么用?
1、我们需要获得远程服务器上的某个文件,远程服务器既没有配置ftp服务器,没有开启web服务器,也没有做共享,无法通过常规途径获得文件时,只需要通过scp命令便可轻松的达到目的。

2、我们需要将本机上的文件上传到远程服务器上,远程服务器没有开启ftp服务器或共享,无法通过常规途径上传是,只需要通过scp命令便可以轻松的达到目的。

scp使用方法
端口大写-P 为参数,2222 表示更改SSH端口后的端口,如果没有更改SSH端口可以不用添加该参数。默认会使用SSH的22端口。

1.获取远程服务器上的文件
scp -P 2222 root@192.168.191.32:/home/favicon.ico /Users/xiao/Documents/favicon.ico
1
root@192.168.191.32 表示使用root用户登录远程服务器192.168.191.32,

/home/favicon.ico 表示远程服务器上的文件,

/Users/xiao/Documents/favicon.ico 表示保存在本地上的路径和文件名。

2.获取远程服务器上的目录
scp -P 2222 -r root@192.168.191.32:/home/ /Users/xiao/Documents/
1
-r :参数表示递归复制(即复制该目录下面的文件和目录)

root@192.168.191.32 表示使用root用户登录远程服务器192.168.191.32,

/home/ 表示远程服务器上的目录,

/Users/xiao/Documents/ 表示保存在本地上的目录路径。

3.将本地文件上传到服务器上
scp -P 2222 /Users/xiao/Documents/favicon.ico root@192.168.191.32:/home/favicon.ico
1
/Users/xiao/Documents/favicon.ico 表示保存在本地上的路径和文件名。

root@192.168.191.32 表示使用root用户登录远程服务器192.168.191.32,

/home/favicon.ico 表示远程服务器上的文件,

4.将本地目录上传到服务器上
scp -P 2222 -r /Users/xiao/Documents/ root@192.168.191.32:/home/
1
-r :参数表示递归复制(即复制该目录下面的文件和目录)

root@192.168.191.32 表示使用root用户登录远程服务器192.168.191.32,

/home/ 表示远程服务器上的目录,

/Users/xiao/Documents/ 表示保存在本地上的目录路径。

5.可能有用的几个参数 :
-v : 和大多数 linux 命令中的 -v 意思一样 , 用来显示进度 . 可以用来查看连接, 认证 , 或是配置错误 .

-4 : 强行使用 IPV4 地址 .

-6 : 强行使用 IPV6 地址 .

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