如何实现apk安装后自启动

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何实现apk安装后自启动相关的知识,希望对你有一定的参考价值。

安装自启动:
要做这个功能有一个前提,那就是用户的机器上已经装过相应应用,也就是说只有升级APK的时候才可以这么干,因为要执行的功能需要程序的配合。
具体步骤如下:
首先要知道程序已经安装完成,所以需要在程序中注册一个广播监听(必须是静态的,你懂的)apk安装完成的action:"android.intent.action.PACKAGE_ADDED",在这个广播的onReceive方法中监听action,并通过intent.getDataString()方法判断安装程序的包名是否属于自己的包名,如果是做下一步操作;
通过Intent显式或者隐式的启动你自己的程序。
建议:
  最好不要这样干,你要考虑一下用户的感受,特别是那种自动安装不需要点确认的时候,正在玩游戏、看视频、看小说、用微信你自动打开一个应用

开机自启动
android实现开机自启动可能是移动操作系统中最简单的了,只需要监听一个开机启动的Broadcast(广播)即可。首先写一个Receiver(即广播监听器),继承BroadcastReceiver。

如下所示:
public class BootReceiver extends BroadcastReceiver
private PendingIntent mAlarmSender;
@Override
public void onReceive(Context context, Intent intent)
// 在这里干你想干的事(启动一个Service,Activity等),本例是启动一个定时调度程序,每30分钟启动一个Service去更新数据
mAlarmSender = PendingIntent.getService(context, 0, new Intent(context,
RefreshDataService.class), 0);
long firstTime = SystemClock.elapsedRealtime();
AlarmManager am = (AlarmManager) context
.getSystemService(Activity.ALARM_SERVICE);
am.cancel(mAlarmSender);
am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, firstTime,
30 * 60 * 1000, mAlarmSender);



接下来,只需要在应用程序配置文件AndroidManifest.xml中注册这个Receiver来监听系统启动事件即可

如下所示:
<receiver android:name=".service.BootReceiver">
<intent-filter>
<!-- 系统启动完成后会调用-->
<action android:name="android.intent.action.BOOT_COMPLETED">
</action>
</intent-filter>
</receiver>
参考技术A 通过BroadcastReceiver来接受消息不过一般都是开机自启动,然后在这个receview中运行你的软件,
希望我的回答对你有所帮助,如果满意请设置为最佳答案,谢谢

Android动态部署三:如何从插件apk中启动Activity(-)

转载请注明出处:http://blog.csdn.net/ximsfei/article/details/50926406

github地址:https://github.com/ximsfei/DynamicDeploymentApk

在上一篇文章:APK安装及AndroidManifest.xml解析流程分析中分析了如何通过adb 命令安装apk,以及源码中apk解析的流程,根据这部分源码,我们可以自己去解析AndroidManifest.xml或者直接porting这部分源码,这样解析出来的信息会更完整,对后续的开发更有帮助;

接下来我们就来分析一下从Activity中startActivity的流程(Fragment等调用startActivity方法的流程类似,读者可以对照着这篇文章自己去看),并且我会重点描述一下,在实现动态部署框架时所需要关注的点。

Activity启动流程

startActivity(new Intent(this, TargetActivity.class));

在Activity中,很简单的一行代码,就可以跳转到TargetActivity了,下图就是调用这行代码后的时序图:

其中红色标注的是在看代码的过程中需要关注的。

public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) 
    ...
    Instrumentation.ActivityResult ar =
        mInstrumentation.execStartActivity(
            this, mMainThread.getApplicationThread(), mToken, this,
            intent, requestCode, options);
    ...

首先我们来看,调用startActivity后,会根据不同参数调用startActivityForResult方法,在startActivityForResult方法中又会调用mInstrumentation的execStartActivity方法,在Activity的源码中我们看到mInstrumentation是在attach方法中初始化的

final void attach(Context context, ActivityThread aThread,
       Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor) 
    ...
    mInstrumentation = instr;
    ...

Instrumentation.java

public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options, UserHandle user) 
    IApplicationThread whoThread = (IApplicationThread) contextThread;
    ...
    try 
        ...
        int result = ActivityManagerNative.getDefault()
            .startActivity(whoThread, who.getBasePackageName(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()),
                    token, target != null ? target.mEmbeddedID : null,
                    requestCode, 0, null, options);
        checkStartActivityResult(result, intent);
     catch (RemoteException e) 
        throw new RuntimeException("Failure from system", e);
    
    return null;

在execStartActivity中会通过binder机制调用到AMS中的startActivity方法,这里的whoThread可以简单的理解为ActivityThread$ApplicationThread对象,以及intent中携带了TargetActivity的信息。

ActivityManagerService.java

@Override
public final int startActivity(IApplicationThread caller, String callingPackage,                                                                                                             
        Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
        int startFlags, ProfilerInfo profilerInfo, Bundle options) 
    return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
        resultWho, requestCode, startFlags, profilerInfo, options,
        UserHandle.getCallingUserId());


@Override
public final int startActivityAsUser(...) 
    ...
    return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
            resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
            profilerInfo, null, null, options, false, userId, null, null);

ActivityStackSupervisor.java

final int startActivityMayWait(IApplicationThread caller, int callingUid,
        String callingPackage, Intent intent, String resolvedType,
        IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
        IBinder resultTo, String resultWho, int requestCode, int startFlags,
        ProfilerInfo profilerInfo, WaitResult outResult, Configuration config,
        Bundle options, boolean ignoreTargetSecurity, int userId,                                                                                                                            
        IActivityContainer iContainer, TaskRecord inTask) 
    ...
    intent = new Intent(intent);

    ActivityInfo aInfo =
            resolveActivity(intent, resolvedType, startFlags, profilerInfo, userId);
    ...
    startActivityLocked(caller, intent, resolvedType, aInfo,
                voiceSession, voiceInteractor, resultTo, resultWho,
                requestCode, callingPid, callingUid, callingPackage,
                realCallingPid, realCallingUid, startFlags, options, ignoreTargetSecurity,
                componentSpecified, null, container, inTask);
    ...
}

final boolean realStartActivityLocked(ActivityRecord r,
        ProcessRecord app, boolean andResume, boolean checkConfig)
        throws RemoteException 
    ...
    app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
                System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
                new Configuration(stack.mOverrideConfig), r.compat, r.launchedFromPackage,
                task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,
                newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);
    ...

在startActivityMayWait方法中首先会根据intent中携带的信息,获得TargetActivity的信息:ActivityInfo;最终会调用该类中的realStartActivityLocked方法,并在该方法中调用ActivityThread.ApplicationThread中的scheduleLaunchActivity方法。
ActivityThread.ApplicationThread

@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
        ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
        CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
        int procState, Bundle state, PersistableBundle persistentState,
        List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
        boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) 
    ...
    sendMessage(H.LAUNCH_ACTIVITY, r);

ActivityThread

public void handleMessage(Message msg) 
    switch (msg.what) 
        case LAUNCH_ACTIVITY:                                                                                                                                                           
            ...
            handleLaunchActivity(r, null);
         break;
        ...
    
    ...


private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) 
    ...
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) 
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed);
        ....
    
    ...


private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) 
    ActivityInfo aInfo = r.activityInfo;
    ...
    Activity activity = null;
    try 
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        ...
     catch (Exception e) 
        ...
    
    try 
        activity.attach(appContext, this, getInstrumentation(), r.token,
                    r.ident, app, r.intent, r.activityInfo, title, r.parent,                                                                                                                 
                    r.embeddedID, r.lastNonConfigurationInstances, config,
                    r.referrer, r.voiceInteractor);
            ...
            int theme = r.activityInfo.getThemeResource();
            if (theme != 0) 
                activity.setTheme(theme);
            

            activity.mCalled = false;
            if (r.isPersistable()) 
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
             else 
                mInstrumentation.callActivityOnCreate(activity, r.state);
            
            ...
            if (!r.activity.mFinished) 
                activity.performStart();
                r.stopped = false;
            
            if (!r.activity.mFinished) 
                if (r.isPersistable()) 
                    if (r.state != null || r.persistentState != null) 
                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                r.persistentState);
                    
                 else if (r.state != null) 
                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                
            
     catch (Exception e) 
        ....
    
    return activity;


public Instrumentation getInstrumentation()                                                                                                                                                  

    return mInstrumentation;

Instrumentation.java

public Activity newActivity(ClassLoader cl, String className,                                                                                                                                
        Intent intent)
        throws InstantiationException, IllegalAccessException,
        ClassNotFoundException 
    return (Activity)cl.loadClass(className).newInstance();


public void callActivityOnCreate(Activity activity, Bundle icicle) 
    prePerformCreate(activity);
    activity.performCreate(icicle);
    postPerformCreate(activity);

从上面的代码我们可以看到,在进入scheduleLaunchActivity方法后,最终会调用ActivityThread中的performLaunchActivity方法,在该方法中首先会获取当前apk的ClassLoader对象,调用mInstrumentation的newActivity方法,并传入ClassLoader方法,在newActivity方法中加载TargetActivity类,实例化。
之后会调用newActivity返回的activity的attach方法,文章开头我们知道Activity的mIntrumentation成员变量是通过attach方法传入的,所以Activity中的mIntrumentation就是ActivityThread中的mIntrumentation。在attach方法中还会传入appContext, app,activityInfo,title等其他信息。之后会为activity设置主题:setTheme,在这些信息设置完后会调用mIntrumentation的callActivityOnCreate方法,之后就会调用activity的performCreate, performStart方法,在这些方法中分别会调用onCreate和onStart方法,再会返回到handleLaunchActivity方法中调用handleResumeActivity方法,也就是activity的onResume方法。

至此TargetActivity已经启动,startActivity的流程也结束了。

实现动态部署所需要关注的点

首先我们要知道动态部署要怎么做,我们要做的事是通过ClassLoader以及类名调用非安装的apk中的 Activity, Service, BroadcastReceiver, ContentProvider,从而实现插件化的效果,因为Android的四大组件大部分都需要注册到AndroidManifest.xml中才能正常使用,所以从前一篇文章:APK安装及AndroidManifest.xml解析流程分析中,我们已经获得了插件apk的所有我们想要知道的信息。我们知道了所有Activity,Service等类的类名,以及apk的路径,当我们开始做的时候,又遇到了新的问题,在TargetActivity启动的过程中会在ActivityStackSupervisor.java的startActivityMayWait方法中获取Activity信息,而这些信息必须要注册到宿主apk的AndroidManifest.xml中才行,插件apk如何在不注册的情况下还能被查询到呢,如何启动呢,如何通过ClassLoader加载类呢。带着这些疑问我们接着往下看:

DexClassLoader classLoader = new DexClassLoader(apkFile.getAbsolutePath(),
           optimizedDexDir, optimizedLibDir, ClassLoader.getSystemClassLoader().getParent());

如何加载比较简单,我们知道了类名,只要实例化一个属于插件apk的DexClassLoader就可以了;
如何在不注册的情况下还能被查询到呢,不妨想想Activity的标准启动模式,理论上可以无限实例化,无限启动,那么我们想到这发现,可以在宿主apk中,注册一个Stub Activity,这样每次启动的是Stub Activity,在通过了startActivityMayWait方法中的查询之后,将Activity的信息替换成TargetActivity的信息。

那么在startActivity之后如何伪装成StubActivity呢,在通过检查之后又如何变回真正的TargetActivity呢?
还有我们需要替换哪些StubActivity的信息呢,在什么时候替换呢?

我们再来看Activity的启动流程
上面的代码中我们可以看到原生AOSP的启动流程中,Activity是在Instrumentation的newActivity方法中实例化的,而且在startActivity后会最先调用Instrumentation的execStartActivity方法,在activity实例化之后会通过调用Instrumentation中的callActivityOnCreate来调用activity的onCreate方法,这个类在启动TargetActivity的过程中扮演了很重要的角色,而且这些方法都是在startActivityMayWait之后调用的。
在调用startActivity之后,startActivityMayWait之前,会先调用Instrumentation中的execStartActivity方法,在这里我们可以重写intent中的信息,将TargetActivity伪装成StubActivity;
从上面startActivity的时序图中我们可以看到,在performLaunchActivity方法中会先调用Instrumentation的newActivity方法,在该方法中实例化activity,那么我们可以在这里从intent中获取信息,将StubActivity变回TargetActivity;

那么我们来看一下,成员变量mInstrumentation是在什么时候实例化的呢?

ActivityThread.java

public static ActivityThread currentActivityThread()                                                                                                                                        
   return sCurrentActivityThread;


private void attach(boolean system) 
    sCurrentActivityThread = this;
    ...
    if (!system) 
        ...
     else 
        try 
            mInstrumentation = new Instrumentation();
            ContextImpl context = ContextImpl.createAppContext(
                    this, getSystemContext().mPackageInfo);
            mInitialApplication = context.mPackageInfo.makeApplication(true, null);
            mInitialApplication.onCreate();
         catch (Exception e) 
            throw new RuntimeException(
                    "Unable to instantiate Application():" + e.toString(), e);
        
    }
}

public static void main(String[] args) 
    ...
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    ...

在ActivityThread的main方法中实例化ActivityThread同时会调用attch方法,所以在有ActivityThread对象之后可以说同时就有mInstrumentation了,众所周知,ActivityThread是每个应用最先初始化的类,是主线程,且只会初始化一次,至此,我们想到了曾今在window开发中用过的钩子技术,在这我们可以重写Instrumentation类,在ActivityThread启动后将mInstrumentation替换成我们重写的类,这样,我们就可以很容易的拦截execStartActivity,newActivity等方法,从而干涉Activity的启动流程了。我们可以通过java的反射机制来完成这项工作,反射机制在这里不做过多解释。我们可以反射currentActivityThread方法获取应用的ActivityThread对象,再反射私有成员变量mInstrumentation将其替换成我们重写的类(DynamicInstrumentation.java)。

Instrumentation instrumentation = DynamicActivityThread.getInstance().getInstrumentation();
if (!(instrumentation instanceof DynamicInstrumentation)) 
    DynamicInstrumentation dynamicInstrumentation = new DynamicInstrumentation(instrumentation);
    DynamicActivityThread.getInstance().setInstrumentation(dynamicInstrumentation);

至此我们已经可以启动插件apk中的不使用布局文件(可以在Activity中使用java代码动态创建View)的Activity了。
赶快动手尝试一下吧!

以下是在performLaunchActivity中调用newActivity方法后的时序图:

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor) 
    ...

在activity实例化后,会先调用attach setTheme方法设置Activity信息,从时序图以及代码来看,我猜想:我们需要替换Context,Resources,AssetManager,Theme,Title, Application等信息。
读者可以尝试着实现一下,下一篇文章我们会接着讲Activity的启动流程,其中就会讲到如何还原TargetActivity的这些信息了。

以上是关于如何实现apk安装后自启动的主要内容,如果未能解决你的问题,请参考以下文章

android如何实现静默安装哦

不安装APK直接启动应用

APK瘦身记,如何实现高达53%的压缩效果

APK瘦身记,如何实现高达53%的压缩效果

android怎么实现apk的静默安装

如何实现android静默安装